From 65e8ffb61167e8855e39549a9e4233b41cfa2344 Mon Sep 17 00:00:00 2001 From: jenglish Date: Sun, 7 Dec 2008 18:42:55 +0000 Subject: Add native aqua elements for ttk::spinbox [Bug 2219588]. Moved most spinbox "business logic" out of ttkEntry.c into Tcl bindings. Minor spinbox appearance improvements in clam theme. --- ChangeLog | 9 + generic/ttk/ttkEntry.c | 417 ++++++++++++++-------------------------------- library/ttk/clamTheme.tcl | 5 +- library/ttk/spinbox.tcl | 197 ++++++++++++---------- macosx/ttkMacOSXTheme.c | 62 ++++++- tests/ttk/spinbox.test | 139 ++++++++++------ 6 files changed, 396 insertions(+), 433 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3ead46a..c246a74 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2008-12-07 Joe English + + * macosx/ttkMacOSXTheme.c: Add native aqua elements for + ttk::spinbox [Bug 2219588] + * generic/ttk/ttkEntry.c, library/ttk/spinbox.tcl, + tests/ttk/spinbox.test: Moved most spinbox "business logic" + out of ttkEntry.c into Tcl bindings. + * library/ttk/clamTheme.tcl: Minor spinbox appearance improvements. + 2008-12-06 Donal K. Fellows TIP #197 IMPLEMENTATION diff --git a/generic/ttk/ttkEntry.c b/generic/ttk/ttkEntry.c index edf49a0..38133bb 100644 --- a/generic/ttk/ttkEntry.c +++ b/generic/ttk/ttkEntry.c @@ -1,5 +1,5 @@ /* - * $Id: ttkEntry.c,v 1.13 2008/11/16 17:14:16 jenglish Exp $ + * $Id: ttkEntry.c,v 1.14 2008/12/07 18:42:55 jenglish Exp $ * * DERIVED FROM: tk/generic/tkEntry.c r1.35. * @@ -13,7 +13,6 @@ #include #include #include -#include #include "ttkTheme.h" #include "ttkWidget.h" @@ -27,12 +26,6 @@ #define VALIDATION_SET_VALUE (WIDGET_USER_FLAG<<4) /* - * inline equality test for doubles - */ -#define MIN_DBL_VAL 1E-9 -#define DOUBLES_EQ(d1, d2) (fabs((d1) - (d2)) < MIN_DBL_VAL) - -/* * Definitions for -validate option values: */ typedef enum validateMode { @@ -80,8 +73,7 @@ static const char *validateReasonStrings[] = { /* Style parameters: */ -typedef struct -{ +typedef struct { Tcl_Obj *foregroundObj; /* Foreground color for normal text */ Tcl_Obj *backgroundObj; /* Entry widget background color */ Tcl_Obj *selBorderObj; /* Border and background for selection */ @@ -91,8 +83,7 @@ typedef struct Tcl_Obj *insertWidthObj; /* Insert cursor width */ } EntryStyleData; -typedef struct -{ +typedef struct { /* * Internal state: */ @@ -142,8 +133,7 @@ typedef struct } EntryPart; -typedef struct -{ +typedef struct { WidgetCore core; EntryPart entry; } Entry; @@ -151,12 +141,9 @@ typedef struct /* * Extra mask bits for Tk_SetOptions() */ -#define STATE_CHANGED (0x0100) /* -state option changed */ -#define TEXTVAR_CHANGED (0x0200) /* -textvariable option changed */ -#define SCROLLCMD_CHANGED (0x0400) /* -xscrollcommand option changed */ -#define VALUES_CHANGED (0x0800) /* -values option changed */ -#define FORMAT_CHANGED (0x1000) /* -format option changed (spinbox) */ -#define RANGE_CHANGED (0x2000) /* -from or -to option changed */ +#define STATE_CHANGED (0x100) /* -state option changed */ +#define TEXTVAR_CHANGED (0x200) /* -textvariable option changed */ +#define SCROLLCMD_CHANGED (0x400) /* -xscrollcommand option changed */ /* * Default option values: @@ -168,8 +155,7 @@ typedef struct #define DEF_ENTRY_FONT "TkTextFont" #define DEF_LIST_HEIGHT "10" -static Tk_OptionSpec EntryOptionSpecs[] = -{ +static Tk_OptionSpec EntryOptionSpecs[] = { WIDGET_TAKES_FOCUS, {TK_OPTION_BOOLEAN, "-exportselection", "exportSelection", @@ -533,7 +519,7 @@ static int RunValidationScript( ExpandPercents(entryPtr, template, new, index, count, reason, &script); code = Tcl_EvalEx(interp, Tcl_DStringValue(&script), Tcl_DStringLength(&script), - TCL_EVAL_GLOBAL | TCL_EVAL_DIRECT); + TCL_EVAL_GLOBAL); Tcl_DStringFree(&script); if (WidgetDestroyed(&entryPtr->core)) return TCL_ERROR; @@ -1647,8 +1633,7 @@ static int EntryXViewCommand( return TtkScrollviewCommand(interp, objc, objv, entryPtr->entry.xscrollHandle); } -static WidgetCommandSpec EntryCommands[] = -{ +static WidgetCommandSpec EntryCommands[] = { { "bbox", EntryBBoxCommand }, { "cget", TtkWidgetCgetCommand }, { "configure", TtkWidgetConfigureCommand }, @@ -1670,8 +1655,7 @@ static WidgetCommandSpec EntryCommands[] = * +++ Entry widget definition. */ -static WidgetSpec EntryWidgetSpec = -{ +static WidgetSpec EntryWidgetSpec = { "TEntry", /* className */ sizeof(Entry), /* recordSize */ EntryOptionSpecs, /* optionSpecs */ @@ -1687,91 +1671,105 @@ static WidgetSpec EntryWidgetSpec = }; /*------------------------------------------------------------------------ - * +++ Values entry widget widget record. - * - * This record and the command function are shared by the combobox and - * spinbox which both have support for a -values option and [current] - * command. + * +++ Combobox widget record. */ typedef struct { + Tcl_Obj *postCommandObj; Tcl_Obj *valuesObj; - int currentIndex; -} ValuesPart; + Tcl_Obj *heightObj; + int currentIndex; +} ComboboxPart; typedef struct { WidgetCore core; EntryPart entry; - ValuesPart values; -} Values; + ComboboxPart combobox; +} Combobox; -#define ENTRY_VALUES_OPTION \ - {TK_OPTION_STRING, "-values", "values", "Values", \ - "", Tk_Offset(Values, values.valuesObj), -1, 0, 0, VALUES_CHANGED} +static Tk_OptionSpec ComboboxOptionSpecs[] = { + {TK_OPTION_STRING, "-height", "height", "Height", + DEF_LIST_HEIGHT, Tk_Offset(Combobox, combobox.heightObj), -1, + 0,0,0 }, + {TK_OPTION_STRING, "-postcommand", "postCommand", "PostCommand", + "", Tk_Offset(Combobox, combobox.postCommandObj), -1, + 0,0,0 }, + {TK_OPTION_STRING, "-values", "values", "Values", + "", Tk_Offset(Combobox, combobox.valuesObj), -1, + 0,0,0 }, + WIDGET_INHERIT_OPTIONS(EntryOptionSpecs) +}; +/* ComboboxInitialize -- + * Initialization hook for combobox widgets. + */ static void -ValuesInitialize(Tcl_Interp *interp, void *recordPtr) +ComboboxInitialize(Tcl_Interp *interp, void *recordPtr) { - Values *valPtr = recordPtr; - valPtr->values.currentIndex = -1; + Combobox *cb = recordPtr; + + cb->combobox.currentIndex = -1; + TtkTrackElementState(&cb->core); + EntryInitialize(interp, recordPtr); } +/* ComboboxConfigure -- + * Configuration hook for combobox widgets. + */ static int -ValuesValidate(Tcl_Interp *interp, void *recordPtr, int *indexPtr) +ComboboxConfigure(Tcl_Interp *interp, void *recordPtr, int mask) { - Values *valPtr = recordPtr; - int currentIndex = valPtr->values.currentIndex; - const char *currentValue = valPtr->entry.string; - int eltc; - Tcl_Obj **eltv; - + Combobox *cbPtr = recordPtr; + int unused; - if (Tcl_ListObjLength(interp,valPtr->values.valuesObj,&eltc) != TCL_OK) + /* Make sure -values is a valid list: + */ + if (Tcl_ListObjLength(interp,cbPtr->combobox.valuesObj,&unused) != TCL_OK) return TCL_ERROR; - Tcl_ListObjGetElements(interp,valPtr->values.valuesObj,&eltc,&eltv); - - if ( currentIndex < 0 - || currentIndex >= eltc - || strcmp(currentValue, Tcl_GetString(eltv[currentIndex]))) { - /* - * Not valid. Check current value against each element in -values: - */ - for (currentIndex = 0; currentIndex < eltc; ++currentIndex) { - if (!strcmp(currentValue,Tcl_GetString(eltv[currentIndex]))) { - break; - } - } - if (currentIndex >= eltc) { - /* Not found */ - currentIndex = -1; - } - } - *indexPtr = currentIndex; - return TCL_OK; + return EntryConfigure(interp, recordPtr, mask); } -/* $widget current ?newIndex? -- get or set current index. +/* $cb current ?newIndex? -- get or set current index. * Setting the current index updates the combobox value, * but the value and -values may be changed independently * of the index. Instead of trying to keep currentIndex * in sync at all times, [$cb current] double-checks */ -static int ValuesCurrentCommand( +static int ComboboxCurrentCommand( Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], void *recordPtr) { - Values *valPtr = recordPtr; - int currentIndex = valPtr->values.currentIndex; + Combobox *cbPtr = recordPtr; + int currentIndex = cbPtr->combobox.currentIndex; + const char *currentValue = cbPtr->entry.string; int nValues; Tcl_Obj **values; - Tcl_ListObjGetElements(interp,valPtr->values.valuesObj,&nValues,&values); + Tcl_ListObjGetElements(interp,cbPtr->combobox.valuesObj,&nValues,&values); if (objc == 2) { - if (ValuesValidate(interp, recordPtr, ¤tIndex) != TCL_OK) - return TCL_ERROR; - valPtr->values.currentIndex = currentIndex; + /* Check if currentIndex still valid: + */ + if ( currentIndex < 0 + || currentIndex >= nValues + || strcmp(currentValue,Tcl_GetString(values[currentIndex])) + ) + { + /* Not valid. Check current value against each element in -values: + */ + for (currentIndex = 0; currentIndex < nValues; ++currentIndex) { + if (!strcmp(currentValue,Tcl_GetString(values[currentIndex]))) { + break; + } + } + if (currentIndex >= nValues) { + /* Not found */ + currentIndex = -1; + } + } + cbPtr->combobox.currentIndex = currentIndex; Tcl_SetObjResult(interp, Tcl_NewIntObj(currentIndex)); + return TCL_OK; } else if (objc == 3) { if (Tcl_GetIntFromObj(interp, objv[2], ¤tIndex) != TCL_OK) { return TCL_ERROR; @@ -1783,7 +1781,7 @@ static int ValuesCurrentCommand( return TCL_ERROR; } - valPtr->values.currentIndex = currentIndex; + cbPtr->combobox.currentIndex = currentIndex; return EntrySetValue(recordPtr, Tcl_GetString(values[currentIndex])); } else { @@ -1794,74 +1792,13 @@ static int ValuesCurrentCommand( } /*------------------------------------------------------------------------ - * +++ Combobox widget record. - */ - -typedef struct { - Tcl_Obj *postCommandObj; - Tcl_Obj *heightObj; -} ComboboxPart; - -typedef struct { - WidgetCore core; - EntryPart entry; - ValuesPart values; - ComboboxPart combobox; -} Combobox; - -static Tk_OptionSpec ComboboxOptionSpecs[] = -{ - {TK_OPTION_STRING, "-height", "height", "Height", - DEF_LIST_HEIGHT, Tk_Offset(Combobox, combobox.heightObj), -1, - 0,0,0 }, - {TK_OPTION_STRING, "-postcommand", "postCommand", "PostCommand", - "", Tk_Offset(Combobox, combobox.postCommandObj), -1, - 0,0,0 }, - ENTRY_VALUES_OPTION, - WIDGET_INHERIT_OPTIONS(EntryOptionSpecs) -}; - -/* ComboboxInitialize -- - * Initialization hook for combobox widgets. - */ -static void -ComboboxInitialize(Tcl_Interp *interp, void *recordPtr) -{ - Combobox *cb = recordPtr; - TtkTrackElementState(&cb->core); - ValuesInitialize(interp, recordPtr); - EntryInitialize(interp, recordPtr); -} - -/* ComboboxConfigure -- - * Configuration hook for combobox widgets. - */ -static int -ComboboxConfigure(Tcl_Interp *interp, void *recordPtr, int mask) -{ - Combobox *cbPtr = recordPtr; - int currentIndex = 0; - - /* Make sure -values is a valid list: - */ - if (mask & VALUES_CHANGED) { - if (ValuesValidate(interp, recordPtr, ¤tIndex) != TCL_OK) - return TCL_ERROR; - cbPtr->values.currentIndex = currentIndex; - } - - return EntryConfigure(interp, recordPtr, mask); -} - -/*------------------------------------------------------------------------ * +++ Combobox widget definition. */ -static WidgetCommandSpec ComboboxCommands[] = -{ +static WidgetCommandSpec ComboboxCommands[] = { { "bbox", EntryBBoxCommand }, { "cget", TtkWidgetCgetCommand }, { "configure", TtkWidgetConfigureCommand }, - { "current", ValuesCurrentCommand }, + { "current", ComboboxCurrentCommand }, { "delete", EntryDeleteCommand }, { "get", EntryGetCommand }, { "icursor", EntryICursorCommand }, @@ -1876,8 +1813,7 @@ static WidgetCommandSpec ComboboxCommands[] = {0,0} }; -static WidgetSpec ComboboxWidgetSpec = -{ +static WidgetSpec ComboboxWidgetSpec = { "TCombobox", /* className */ sizeof(Combobox), /* recordSize */ ComboboxOptionSpecs, /* optionSpecs */ @@ -1893,188 +1829,88 @@ static WidgetSpec ComboboxWidgetSpec = }; /*------------------------------------------------------------------------ - * +++ Spinbox widget record. + * +++ Spinbox widget. */ typedef struct { - Tcl_Obj *commandObj; - double fromValue; - double toValue; - int valueCount; - Tcl_Obj *formatObj; + Tcl_Obj *valuesObj; + + Tcl_Obj *fromObj; + Tcl_Obj *toObj; Tcl_Obj *incrementObj; + Tcl_Obj *formatObj; + Tcl_Obj *wrapObj; - char formatBuffer[TCL_INTEGER_SPACE + 4]; + Tcl_Obj *commandObj; } SpinboxPart; typedef struct { WidgetCore core; EntryPart entry; - ValuesPart values; SpinboxPart spinbox; } Spinbox; -static Tk_OptionSpec SpinboxOptionSpecs[] = -{ +static Tk_OptionSpec SpinboxOptionSpecs[] = { + {TK_OPTION_STRING, "-values", "values", "Values", + "", Tk_Offset(Spinbox, spinbox.valuesObj), -1, + 0,0,0 }, + + {TK_OPTION_DOUBLE, "-from", "from", "From", + "0", Tk_Offset(Spinbox,spinbox.fromObj), -1, + 0,0,0 }, + {TK_OPTION_DOUBLE, "-to", "to", "To", + "0", Tk_Offset(Spinbox,spinbox.toObj), -1, + 0,0,0 }, + {TK_OPTION_DOUBLE, "-increment", "increment", "Increment", + "1", Tk_Offset(Spinbox,spinbox.incrementObj), -1, + 0,0,0 }, + {TK_OPTION_STRING, "-format", "format", "Format", + "", Tk_Offset(Spinbox, spinbox.formatObj), -1, + 0,0,0 }, + {TK_OPTION_STRING, "-command", "command", "Command", "", Tk_Offset(Spinbox, spinbox.commandObj), -1, 0,0,0 }, - {TK_OPTION_STRING, "-format", "format", "Format", "", - Tk_Offset(Spinbox, spinbox.formatObj), -1, 0, 0, FORMAT_CHANGED }, - {TK_OPTION_DOUBLE, "-from", "from", "From", "0", - -1, Tk_Offset(Spinbox,spinbox.fromValue), 0, 0, RANGE_CHANGED }, - {TK_OPTION_DOUBLE, "-to", "to", "To", "0", - -1, Tk_Offset(Spinbox,spinbox.toValue), 0, 0, RANGE_CHANGED }, - {TK_OPTION_DOUBLE, "-increment", "increment", "Increment", "1", - Tk_Offset(Spinbox,spinbox.incrementObj), -1, 0, 0, 0}, - {TK_OPTION_BOOLEAN, "-wrap", "wrap", "Wrap", "0", - Tk_Offset(Spinbox,spinbox.wrapObj), -1, 0, 0, 0}, - ENTRY_VALUES_OPTION, + {TK_OPTION_BOOLEAN, "-wrap", "wrap", "Wrap", + "0", Tk_Offset(Spinbox,spinbox.wrapObj), -1, + 0,0,0 }, + WIDGET_INHERIT_OPTIONS(EntryOptionSpecs) }; -/* - * SpinboxInitialize -- +/* SpinboxInitialize -- * Initialization hook for spinbox widgets. */ - static void SpinboxInitialize(Tcl_Interp *interp, void *recordPtr) { - Spinbox *sbPtr = recordPtr; - sbPtr->spinbox.valueCount = 0; - TtkTrackElementState(&sbPtr->core); - ValuesInitialize(interp, recordPtr); + Spinbox *sb = recordPtr; + TtkTrackElementState(&sb->core); EntryInitialize(interp, recordPtr); } -/* - * If the format option has not been set manually then we calculate - * an appropriate format here based upon the -from and -to options - * If -values has been set this is not called. - */ - -static const char * -SpinboxCalculateFormat(Spinbox *sbPtr) -{ - const char *formatString = "%f"; - if (Tcl_GetCharLength(sbPtr->spinbox.formatObj) > 0) { - formatString = Tcl_GetString(sbPtr->spinbox.formatObj); - } else { - double increment = 0; - int len; - if (Tcl_GetDoubleFromObj(NULL, sbPtr->spinbox.incrementObj, &increment) != TCL_OK) - increment = 1; - len = (int)floor(fabs(log10(fabs(increment)))); - sprintf(sbPtr->spinbox.formatBuffer, "%%.%df", len); - formatString = sbPtr->spinbox.formatBuffer; - } - return formatString; -} - /* SpinboxConfigure -- * Configuration hook for spinbox widgets. */ static int SpinboxConfigure(Tcl_Interp *interp, void *recordPtr, int mask) { - Spinbox *sbPtr = recordPtr; - int currentIndex = 0, needsUpdate = 0; - double d = 0; - - needsUpdate = (mask & FORMAT_CHANGED); - - if (mask & RANGE_CHANGED) { - if (Tcl_GetDouble(NULL, sbPtr->entry.string, &d) != TCL_OK) { - d = sbPtr->spinbox.fromValue; - needsUpdate = 1; - } else { - if (d < sbPtr->spinbox.fromValue) { - d = sbPtr->spinbox.fromValue; - needsUpdate = 1; - } - if (d > sbPtr->spinbox.toValue) { - d = sbPtr->spinbox.toValue; - needsUpdate = 1; - } - } - } + Spinbox *sb = recordPtr; + int unused; /* Make sure -values is a valid list: */ - if (mask & VALUES_CHANGED) { - if (ValuesValidate(interp, recordPtr, ¤tIndex) != TCL_OK) - return TCL_ERROR; - if (Tcl_ListObjLength(interp,sbPtr->values.valuesObj, - &sbPtr->spinbox.valueCount) != TCL_OK) - return TCL_ERROR; - if (sbPtr->spinbox.valueCount > 0) { - if (sbPtr->values.currentIndex == -1 - || sbPtr->values.currentIndex != currentIndex) { - Tcl_Obj *valueObj; - if (currentIndex == -1) currentIndex = 0; - Tcl_ListObjIndex(interp, sbPtr->values.valuesObj, - currentIndex, &valueObj); - EntrySetValue(recordPtr, Tcl_GetString(valueObj)); - } - sbPtr->values.currentIndex = currentIndex; - } - } - - if (needsUpdate && sbPtr->spinbox.valueCount < 1) { - Tcl_Obj *strObj, *valueObj = NULL; - const char *formatString = SpinboxCalculateFormat(sbPtr); - strObj = Tcl_NewDoubleObj(d); - valueObj = Tcl_Format(interp, formatString, 1, &strObj); - if (valueObj) { - Tcl_IncrRefCount(valueObj); - EntrySetValue(recordPtr, Tcl_GetString(valueObj)); - Tcl_DecrRefCount(valueObj); - } - } + if (Tcl_ListObjLength(interp,sb->spinbox.valuesObj,&unused) != TCL_OK) + return TCL_ERROR; return EntryConfigure(interp, recordPtr, mask); } -/* $spinbox set $value - * Sets the value of a spinbox widget. - * We need to take account of any -format option. - */ -static int SpinboxSetCommand( - Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], void *recordPtr) -{ - Spinbox *sbPtr = recordPtr; - if (objc != 3) { - Tcl_WrongNumArgs(interp, 2, objv, "value"); - return TCL_ERROR; - } - if (sbPtr->spinbox.valueCount < 1) { - Tcl_Obj *valueObj = NULL; - const char *formatString = SpinboxCalculateFormat(sbPtr); - valueObj = Tcl_Format(interp, formatString, 1, &objv[2]); - if (valueObj) { - Tcl_IncrRefCount(valueObj); - EntrySetValue(recordPtr, Tcl_GetString(valueObj)); - Tcl_DecrRefCount(valueObj); - } else { - EntrySetValue(recordPtr, Tcl_GetString(objv[2])); - } - } else { - EntrySetValue(recordPtr, Tcl_GetString(objv[2])); - } - return TCL_OK; -} - -/*------------------------------------------------------------------------ - * +++ Spinbox widget definition. - */ - -static WidgetCommandSpec SpinboxCommands[] = -{ +static WidgetCommandSpec SpinboxCommands[] = { { "bbox", EntryBBoxCommand }, { "cget", TtkWidgetCgetCommand }, { "configure", TtkWidgetConfigureCommand }, - { "current", ValuesCurrentCommand }, +//X: { "current", ValuesCurrentCommand }, { "delete", EntryDeleteCommand }, { "get", EntryGetCommand }, { "icursor", EntryICursorCommand }, @@ -2084,14 +1920,13 @@ static WidgetCommandSpec SpinboxCommands[] = { "instate", TtkWidgetInstateCommand }, { "selection", EntrySelectionCommand }, { "state", TtkWidgetStateCommand }, - { "set", SpinboxSetCommand }, + { "set", EntrySetCommand }, { "validate", EntryValidateCommand }, { "xview", EntryXViewCommand }, {0,0} }; -static WidgetSpec SpinboxWidgetSpec = -{ +static WidgetSpec SpinboxWidgetSpec = { "TSpinbox", /* className */ sizeof(Spinbox), /* recordSize */ SpinboxOptionSpecs, /* optionSpecs */ @@ -2171,18 +2006,18 @@ TTK_BEGIN_LAYOUT(ComboboxLayout) TTK_END_LAYOUT TTK_BEGIN_LAYOUT(SpinboxLayout) - TTK_GROUP("Spinbox.field", TTK_FILL_BOTH, + TTK_GROUP("Spinbox.field", TTK_PACK_TOP|TTK_FILL_X, + TTK_GROUP("null", TTK_PACK_RIGHT, + TTK_NODE("Spinbox.uparrow", TTK_PACK_TOP|TTK_STICK_E) + TTK_NODE("Spinbox.downarrow", TTK_PACK_BOTTOM|TTK_STICK_E)) TTK_GROUP("Spinbox.padding", TTK_FILL_BOTH, - TTK_NODE("Spinbox.textarea", TTK_PACK_LEFT|TTK_EXPAND)) - TTK_NODE("Spinbox.uparrow", TTK_PACK_TOP|TTK_STICK_E) - TTK_NODE("Spinbox.downarrow", TTK_PACK_BOTTOM|TTK_STICK_E)) + TTK_NODE("Spinbox.textarea", TTK_FILL_BOTH))) TTK_END_LAYOUT /*------------------------------------------------------------------------ * +++ Initialization. */ -MODULE_SCOPE void TtkEntry_Init(Tcl_Interp *interp) { Ttk_Theme themePtr = Ttk_GetDefaultTheme(interp); diff --git a/library/ttk/clamTheme.tcl b/library/ttk/clamTheme.tcl index 1a07e3a..808c8a4 100644 --- a/library/ttk/clamTheme.tcl +++ b/library/ttk/clamTheme.tcl @@ -1,5 +1,5 @@ # -# $Id: clamTheme.tcl,v 1.9 2008/11/29 00:43:48 patthoyts Exp $ +# $Id: clamTheme.tcl,v 1.10 2008/12/07 18:42:55 jenglish Exp $ # # "Clam" theme. # @@ -111,9 +111,6 @@ namespace eval ttk::theme::clam { ttk::style configure TSpinbox -arrowsize 10 -padding {2 0 10 0} ttk::style map TSpinbox \ -background [list readonly $colors(-frame)] \ - -bordercolor [list focus $colors(-selectbg)] \ - -lightcolor [list focus "#6f9dc6"] \ - -darkcolor [list focus "#6f9dc6"] \ -arrowcolor [list disabled $colors(-disabledfg)] ttk::style configure TNotebook.Tab -padding {6 2 6 2} diff --git a/library/ttk/spinbox.tcl b/library/ttk/spinbox.tcl index 579a22c..9464b07 100644 --- a/library/ttk/spinbox.tcl +++ b/library/ttk/spinbox.tcl @@ -1,16 +1,10 @@ # -# $Id: spinbox.tcl,v 1.1 2008/11/01 15:34:24 patthoyts Exp $ +# $Id: spinbox.tcl,v 1.2 2008/12/07 18:42:55 jenglish Exp $ # -# Tile widget set: spinbox bindings. +# ttk::spinbox bindings # -# - -namespace eval ttk::spinbox { - variable Values ;# Values($cb) is -listvariable of listbox widget - variable State - set State(entryPress) 0 -} +namespace eval ttk::spinbox { } ### Spinbox bindings. # @@ -19,103 +13,132 @@ namespace eval ttk::spinbox { ttk::copyBindings TEntry TSpinbox -bind TSpinbox {ttk::spinbox::Select %W %x %y word} -bind TSpinbox {ttk::spinbox::Select %W %x %y line} +bind TSpinbox { ttk::spinbox::Motion %W %x %y } +bind TSpinbox { ttk::spinbox::Press %W %x %y } +bind TSpinbox { ttk::spinbox::Release %W } +bind TSpinbox { ttk::spinbox::DoubleClick %W %x %y } +bind TSpinbox {} ;# disable TEntry triple-click + +bind TSpinbox { ttk::spinbox::Spin %W +1 } +bind TSpinbox { ttk::spinbox::Spin %W -1 } -bind TSpinbox { ttk::spinbox::Press %W %x %y } -bind TSpinbox { ttk::spinbox::Release %W %x %y } -bind TSpinbox {ttk::spinbox::Change %W [expr {%D/-120}] line} -bind TSpinbox {ttk::spinbox::Change %W +[%W cget -increment] line} -bind TSpinbox {ttk::spinbox::Change %W -[%W cget -increment] line} +bind TSpinbox <> { ttk::spinbox::Spin %W +1 } +bind TSpinbox <> { ttk::spinbox::Spin %W -1 } +# @@@ x-plat +bind TSpinbox { ttk::spinbox::Spin %W [expr {%D/-120}] } + +## Motion -- +# Sets cursor. +# +proc ttk::spinbox::Motion {w x y} { + if { [$w identify $x $y] eq "textarea" + && [$w instate {!readonly !disabled}] + } { + ttk::setCursor $w text + } else { + ttk::setCursor $w "" + } +} + +## Press -- +# proc ttk::spinbox::Press {w x y} { if {[$w instate disabled]} { return } - variable State - set State(xPress) $x - set State(yPress) $y focus $w switch -glob -- [$w identify $x $y] { - *uparrow { - ttk::Repeatedly Change $w +[$w cget -increment] line - } - *downarrow { - ttk::Repeatedly Change $w -[$w cget -increment] line - } - *textarea { - set State(entryPress) [$w instate !readonly] - if {$State(entryPress)} { - ttk::entry::Press $w $x - } - } + *textarea { ttk::entry::Press $w $x } + *rightarrow - + *uparrow { ttk::Repeatedly event generate $w <> } + *leftarrow - + *downarrow { ttk::Repeatedly event generate $w <> } + *spinbutton { + if {$y * 2 >= [winfo height $w]} { + set event <> + } else { + set event <> + } + ttk::Repeatedly event generate $w $event + } } } -proc ttk::spinbox::Release {w x y} { - variable State - unset -nocomplain State(xPress) State(yPress) +## DoubleClick -- +# Select all if over the text area; otherwise same as Press. +# +proc ttk::spinbox::DoubleClick {w x y} { + if {[$w instate disabled]} { return } + + switch -glob -- [$w identify $x $y] { + *textarea { SelectAll $w } + * { Press $w $x $y } + } +} + +proc ttk::spinbox::Release {w} { ttk::CancelRepeat } -proc ttk::spinbox::Change {w n units} { - if {[set vlen [llength [$w cget -values]]] != 0} { - set index [expr {[$w current] + $n}] - if {[catch {$w current $index}]} { - if {[$w cget -wrap]} { - if {$index == -1} { - set index [llength [$w cget -values]] - incr index -1 - } else { - set index 0 - } - $w current $index - } - } - } else { - if {![catch {expr {[$w get] + $n}} v]} { - if {$v < [$w cget -from]} { - if {[$w cget -wrap]} { - set v [$w cget -to] - } else { - set v [$w cget -from] - } - } elseif {$v > [$w cget -to]} { - if {[$w cget -wrap]} { - set v [$w cget -from] - } else { - set v [$w cget -to] - } - } - $w set $v - } +proc ttk::spinbox::SelectAll {w} { + $w selection range 0 end + $w icursor end +} + +proc ttk::spinbox::Limit {v min max} { + if {$v < $min} { return $min } + if {$v > $max} { return $max } + return $v +} + +proc ttk::spinbox::Wrap {v min max} { + if {$v < $min} { return $max } + if {$v > $max} { return $min } + return $v +} + +proc ttk::spinbox::Adjust {w v min max} { + if {[$w cget -wrap]} { + return [Wrap $v $min $max] + } else { + return [Limit $v $min $max] } - ::ttk::entry::Select $w 0 $units +} - # Run -command callback: - # +proc ttk::spinbox::Spin {w dir} { + set nvalues [llength [set values [$w cget -values]]] + set value [$w get] + if {$nvalues} { + set current [lsearch -exact $values $value] + set index [Adjust $w [expr {$current + $dir}] 0 [expr {$nvalues - 1}]] + $w set [lindex $values $index] + } else { + if {[catch { + set v [expr {[scan [$w get] %f] + $dir * [$w cget -increment]}] + }]} { + set v [$w cget -from] + } + $w set [FormatValue $w [Adjust $w $v [$w cget -from] [$w cget -to]]] + } + SelectAll $w uplevel #0 [$w cget -command] - } -# Spinbox double-click on the arrows needs interception, otherwise -# pass to the TEntry handler -proc ttk::spinbox::Select {w x y mode} { - if {[$w instate disabled]} { return } - variable State - set State(xPress) $x - set State(yPress) $y - switch -glob -- [$w identify $x $y] { - *uparrow { - ttk::Repeatedly Change $w +[$w cget -increment] units - } - *downarrow { - ttk::Repeatedly Change $w -[$w cget -increment] units - } - *textarea { - return [::ttk::entry::Select $w $x $mode] - } +proc ttk::spinbox::FormatValue {w val} { + set fmt [$w cget -format] + if {$fmt eq ""} { + # Try to guess a suitable -format based on -increment. + set delta [expr {abs([$w cget -increment])}] + if {0 < $delta && $delta < 1} { + # NB: This guesses wrong if -increment has more than 1 + # significant digit itself, e.g., -increment 0.25 + set nsd [expr {int(ceil(-log10($delta)))}] + set fmt "%.${nsd}f" + } else { + set fmt "%.0f" + } } - return -code continue + return [format $fmt $val] } #*EOF* diff --git a/macosx/ttkMacOSXTheme.c b/macosx/ttkMacOSXTheme.c index bc9fabc..598abd9 100644 --- a/macosx/ttkMacOSXTheme.c +++ b/macosx/ttkMacOSXTheme.c @@ -27,7 +27,7 @@ * top-level window, not to the Tk_Window. BoxToRect() * accounts for this. * - * RCS: @(#) $Id: ttkMacOSXTheme.c,v 1.23 2008/12/07 16:29:38 das Exp $ + * RCS: @(#) $Id: ttkMacOSXTheme.c,v 1.24 2008/12/07 18:42:55 jenglish Exp $ */ #include "tkMacOSXPrivate.h" @@ -480,6 +480,56 @@ static Ttk_ElementSpec ComboboxElementSpec = { static Ttk_StateTable ThemeTrackEnableTable[] = { { kThemeTrackDisabled, TTK_STATE_DISABLED, 0 }, { kThemeTrackInactive, TTK_STATE_BACKGROUND, 0 }, + * +++ Spinbuttons. + * + * From Apple HIG, part III, section "Controls", "The Stepper Control": + * there should be 2 pixels of space between the stepper control + * (AKA IncDecButton, AKA "little arrows") and the text field it modifies. + */ + +static Ttk_Padding SpinbuttonMargins = {2,0,2,0}; +static void SpinButtonElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + SInt32 s; + GetThemeMetric(kThemeMetricLittleArrowsWidth, &s); *widthPtr = s; + GetThemeMetric(kThemeMetricLittleArrowsHeight, &s); *heightPtr = s; + *widthPtr += Ttk_PaddingWidth(SpinbuttonMargins); + *heightPtr += Ttk_PaddingHeight(SpinbuttonMargins); +} + +static void SpinButtonElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + Rect bounds = BoxToRect(d, Ttk_PadBox(b, SpinbuttonMargins)); + ThemeButtonDrawInfo info; + + /* @@@ can't currently distinguish PressedUp (== Pressed) from PressedDown; + * ignore this bit for now [see #2219588] + */ + info.state = Ttk_StateTableLookup(ThemeStateTable, + state & ~TTK_STATE_PRESSED); + info.value = Ttk_StateTableLookup(ButtonValueTable, state); + info.adornment = kThemeAdornmentNone; + + BEGIN_DRAWING(d) + DrawThemeButton( + &bounds, kThemeIncDecButton, &info, NULL, NULL/*DontErase*/, NULL, 0); + END_DRAWING +} + +static Ttk_ElementSpec SpinButtonElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + SpinButtonElementSize, + SpinButtonElementDraw +}; + + +/*---------------------------------------------------------------------- { kThemeTrackActive, 0, 0 } /* { kThemeTrackNothingToScroll, ?, ? }, */ }; @@ -977,6 +1027,11 @@ TTK_LAYOUT("Tab", TTK_NODE("Notebook.label", TTK_EXPAND|TTK_FILL_BOTH)))) /* Progress bars -- track only */ +TTK_LAYOUT("TSpinbox", + TTK_NODE("Spinbox.spinbutton", TTK_PACK_RIGHT|TTK_STICK_E) + TTK_GROUP("Spinbox.field", TTK_EXPAND|TTK_FILL_X, + TTK_NODE("Spinbox.textarea", TTK_EXPAND|TTK_FILL_X))) + TTK_LAYOUT("TProgressbar", TTK_NODE("Progressbar.track", TTK_EXPAND|TTK_FILL_BOTH)) @@ -986,7 +1041,7 @@ TTK_LAYOUT("Heading", TTK_NODE("Treeheading.image", TTK_PACK_RIGHT) TTK_NODE("Treeheading.text", 0)) -/* Tree items -- omit focus ring */ +/* Tree items -- omit focus ring */ TTK_LAYOUT("Item", TTK_GROUP("Treeitem.padding", TTK_FILL_BOTH, TTK_NODE("Treeitem.indicator", TTK_PACK_LEFT) @@ -1025,6 +1080,8 @@ static int AquaTheme_Init(Tcl_Interp *interp) &ButtonElementSpec, &BevelButtonParms); Ttk_RegisterElementSpec(themePtr, "Menubutton.button", &ButtonElementSpec, &PopupButtonParms); + Ttk_RegisterElementSpec(themePtr, "Spinbox.spinbutton", + &SpinButtonElementSpec, 0); Ttk_RegisterElementSpec(themePtr, "Combobox.button", &ComboboxElementSpec, 0); Ttk_RegisterElementSpec(themePtr, "Treeitem.indicator", @@ -1037,6 +1094,7 @@ static int AquaTheme_Init(Tcl_Interp *interp) Ttk_RegisterElementSpec(themePtr, "Labelframe.border",&GroupElementSpec,0); Ttk_RegisterElementSpec(themePtr, "Entry.field",&EntryElementSpec,0); + Ttk_RegisterElementSpec(themePtr, "Spinbox.field",&EntryElementSpec,0); Ttk_RegisterElementSpec(themePtr, "separator",&SeparatorElementSpec,0); Ttk_RegisterElementSpec(themePtr, "hseparator",&SeparatorElementSpec,0); diff --git a/tests/ttk/spinbox.test b/tests/ttk/spinbox.test index a1b8b1f..3397e37 100644 --- a/tests/ttk/spinbox.test +++ b/tests/ttk/spinbox.test @@ -2,12 +2,10 @@ # ttk::spinbox widget tests # -package require Tk 8.5 +package require Tk package require tcltest ; namespace import -force tcltest::* loadTestedCommands -set PI [expr {acos(-1)}] - test spinbox-1.0 "Spinbox tests -- setup" -body { ttk::spinbox .sb } -cleanup { destroy .sb } -result .sb @@ -20,22 +18,6 @@ test spinbox-1.1 "Bad -values list" -setup { destroy .sb } -returnCodes error -result "unmatched open brace in list" -test spinbox-1.2.1 "starts within range" -setup { - ttk::spinbox .sb -from 0 -to 100 -} -body { - .sb get -} -cleanup { - destroy .sb -} -result 0 - -test spinbox-1.2.2 "starts within range" -setup { - ttk::spinbox .sb -from 100 -to 110 -} -body { - .sb get -} -cleanup { - destroy .sb -} -result 100 - test spinbox-1.3.1 "get retrieves value" -setup { ttk::spinbox .sb -from 0 -to 100 } -body { @@ -48,6 +30,7 @@ test spinbox-1.3.1 "get retrieves value" -setup { test spinbox-1.3.2 "get retrieves value" -setup { ttk::spinbox .sb -from 0 -to 100 -values 55 } -body { + .sb set 55 .sb get } -cleanup { destroy .sb @@ -71,23 +54,6 @@ test spinbox-1.4.2 "set changes value" -setup { destroy .sb } -result 33 -test spinbox-1.5.1 "format accounts for -increment" -setup { - ttk::spinbox .sb -from 0 -to 10 -increment 0.1 -} -body { - ::ttk::spinbox::Change .sb [expr {acos(-1)}] line - .sb get -} -cleanup { - destroy .sb -} -result 3.1 - -test spinbox-1.5.2 "format accounts for -increment" -setup { - ttk::spinbox .sb -from 0 -to 10 -increment 0.1 -} -body { - .sb set $PI - .sb get -} -cleanup { - destroy .sb -} -result 3.1 test spinbox-1.6.1 "insert start" -setup { ttk::spinbox .sb -from 0 -to 100 @@ -128,20 +94,20 @@ test spinbox-1.7.1 "-command option: set doesnt fire" -setup { } -cleanup { destroy .sb } -result 0 - + test spinbox-1.7.2 "-command option: button handler will fire" -setup { ttk::spinbox .sb -from 0 -to 100 -command {set ::spinbox_test 1} } -body { set ::spinbox_test 0 .sb set 50 - ::ttk::spinbox::Change .sb [expr {acos(-1)}] line + event generate .sb <> set ::spinbox_test } -cleanup { destroy .sb } -result 1 test spinbox-1.8.1 "option -validate" -setup { - ttk::spinbox .sb -from 0 -to 100 + ttk::spinbox .sb -from 0 -to 100 } -body { .sb configure -validate all .sb cget -validate @@ -150,7 +116,7 @@ test spinbox-1.8.1 "option -validate" -setup { } -result {all} test spinbox-1.8.2 "option -validate" -setup { - ttk::spinbox .sb -from 0 -to 100 + ttk::spinbox .sb -from 0 -to 100 } -body { .sb configure -validate key .sb configure -validate focus @@ -163,7 +129,7 @@ test spinbox-1.8.2 "option -validate" -setup { } -result {none} test spinbox-1.8.3 "option -validate" -setup { - ttk::spinbox .sb -from 0 -to 100 + ttk::spinbox .sb -from 0 -to 100 } -body { .sb configure -validate bogus } -cleanup { @@ -177,23 +143,24 @@ test spinbox-1.8.4 "-validate option: " -setup { .sb configure -validate all -validatecommand {lappend ::spinbox_test %P} pack .sb .sb set 50 - focus -force .sb - update + focus .sb + after 100 {set ::spinbox_wait 1} ; vwait ::spinbox_wait set ::spinbox_test } -cleanup { destroy .sb } -result {50} - -test spinbox-2.0 "current command -- unset should be 0" -setup { + +test spinbox-2.0 "current command -- unset should be 0" -constraints nyi -setup { ttk::spinbox .sb -values [list a b c d e a] } -body { .sb current } -cleanup { destroy .sb } -result 0 +# @@@ for combobox, this is -1. -test spinbox-2.1 "current command -- set index" -setup { +test spinbox-2.1 "current command -- set index" -constraints nyi -setup { ttk::spinbox .sb -values [list a b c d e a] } -body { .sb current 5 @@ -202,7 +169,7 @@ test spinbox-2.1 "current command -- set index" -setup { destroy .sb } -result a -test spinbox-2.2 "current command -- change -values" -setup { +test spinbox-2.2 "current command -- change -values" -constraints nyi -setup { ttk::spinbox .sb -values [list a b c d e a] } -body { .sb current 5 @@ -212,7 +179,7 @@ test spinbox-2.2 "current command -- change -values" -setup { destroy .sb } -result 2 -test spinbox-2.3 "current command -- change value" -setup { +test spinbox-2.3 "current command -- change value" -constraints nyi -setup { ttk::spinbox .sb -values [list c b a d e] } -body { .sb current 2 @@ -222,7 +189,7 @@ test spinbox-2.3 "current command -- change value" -setup { destroy .sb } -result 1 -test spinbox-2.4 "current command -- value not in list" -setup { +test spinbox-2.4 "current command -- value not in list" -constraints nyi -setup { ttk::spinbox .sb -values [list c b a d e] } -body { .sb current 2 @@ -232,6 +199,80 @@ test spinbox-2.4 "current command -- value not in list" -setup { destroy .sb } -result -1 +# nostomp: NB intentional difference between ttk::spinbox and tk::spinbox; +# see also #1439266 +# +test spinbox-nostomp-1 "don't stomp on -variable (init; -from/to)" -body { + set SBV 55 + ttk::spinbox .sb -textvariable SBV -from 0 -to 100 -increment 5 + list $SBV [.sb get] +} -cleanup { + unset SBV + destroy .sb +} -result [list 55 55] + +test spinbox-nostomp-2 "don't stomp on -variable (init; -values)" -body { + set SBV Apr + ttk::spinbox .sb -textvariable SBV -values {Jan Feb Mar Apr May Jun Jul Aug} + list $SBV [.sb get] +} -cleanup { + unset SBV + destroy .sb +} -result [list Apr Apr] + +test spinbox-nostomp-3 "don't stomp on -variable (configure; -from/to)" -body { + set SBV 55 + ttk::spinbox .sb + .sb configure -textvariable SBV -from 0 -to 100 -increment 5 + list $SBV [.sb get] +} -cleanup { + unset SBV + destroy .sb +} -result [list 55 55] + +test spinbox-nostomp-4 "don't stomp on -variable (configure; -values)" -body { + set SBV Apr + ttk::spinbox .sb + .sb configure -textvariable SBV -values {Jan Feb Mar Apr May Jun Jul Aug} + list $SBV [.sb get] +} -cleanup { + unset SBV + destroy .sb +} -result [list Apr Apr] + +test spinbox-dieoctaldie-1 "Cope with leading zeros" -body { + # See SF#2358545 -- ttk::spinbox also affected + set secs 07 + ttk::spinbox .sb -from 0 -to 59 -format %02.0f -textvariable secs + + set result [list $secs] + event generate .sb <>; lappend result $secs + event generate .sb <>; lappend result $secs + event generate .sb <>; lappend result $secs + event generate .sb <>; lappend result $secs + + event generate .sb <>; lappend result $secs + event generate .sb <>; lappend result $secs + event generate .sb <>; lappend result $secs + event generate .sb <>; lappend result $secs + + set result +} -result [list 07 08 09 10 11 10 09 08 07] -cleanup { + destroy .sb + unset secs +} + +test spinbox-dieoctaldie-2 "Cope with general bad input" -body { + set result [list] + ttk::spinbox .sb -from 0 -to 100 -format %03.0f + .sb set asdfasdf ; lappend result [.sb get] + event generate .sb <> ; lappend result [.sb get] + .sb set asdfasdf ; lappend result [.sb get] + event generate .sb <> ; lappend result [.sb get] +} -result [list asdfasdf 000 asdfasdf 000] -cleanup { + destroy .sb +} + tcltest::cleanupTests # Local variables: -- cgit v0.12