From 1ed5704ca8775530ac5ef1ca6d648a4029112823 Mon Sep 17 00:00:00 2001 From: patthoyts Date: Sat, 1 Nov 2008 15:34:23 +0000 Subject: Implemented the themed spinbox widget ttk::spinbox. --- ChangeLog | 17 ++ doc/ttk_spinbox.n | 88 +++++++++ generic/ttk/ttkEntry.c | 418 ++++++++++++++++++++++++++++++++++++------- library/ttk/altTheme.tcl | 7 +- library/ttk/clamTheme.tcl | 10 +- library/ttk/classicTheme.tcl | 6 +- library/ttk/defaults.tcl | 7 +- library/ttk/entry.tcl | 4 +- library/ttk/spinbox.tcl | 121 +++++++++++++ library/ttk/ttk.tcl | 3 +- library/ttk/winTheme.tcl | 4 +- library/ttk/xpTheme.tcl | 22 ++- tests/ttk/spinbox.test | 239 +++++++++++++++++++++++++ win/ttkWinTheme.c | 22 ++- win/ttkWinXPTheme.c | 48 ++++- 15 files changed, 933 insertions(+), 83 deletions(-) create mode 100644 doc/ttk_spinbox.n create mode 100644 library/ttk/spinbox.tcl create mode 100644 tests/ttk/spinbox.test diff --git a/ChangeLog b/ChangeLog index c403c4d..ce74f34 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,20 @@ +2008-11-01 Pat Thoyts + + * generic/ttk/ttkEntry.c: Implemented the themed spinbox + * library/ttk/altTheme.tcl: widget. + * library/ttk/clamTheme.tcl: + * library/ttk/classicTheme.tcl: + * library/ttk/defaults.tcl: + * library/ttk/entry.tcl: + * library/ttk/ttk.tcl: + * library/ttk/winTheme.tcl: + * library/ttk/xpTheme.tcl: + * library/ttk/spinbox.tcl: + * win/ttkWinTheme.c: + * win/ttkWinXPTheme.c: + * doc/ttk_spinbox.n: + * tests/ttk/spinbox.test: + 2008-10-31 Joe English * generic/widget.c: Temporary workaround for [Bug 2207435] diff --git a/doc/ttk_spinbox.n b/doc/ttk_spinbox.n new file mode 100644 index 0000000..350cfed --- /dev/null +++ b/doc/ttk_spinbox.n @@ -0,0 +1,88 @@ +'\" +'\" Copyright (c) 2008 Pat Thoyts +'\" +'\" See the file "license.terms" for information on usage and redistribution +'\" of this file, and for a DISCLAIMER OF ALL WARRANTIES. +'\" +'\" RCS: @(#) $Id: ttk_spinbox.n,v 1.1 2008/11/01 15:34:24 patthoyts Exp $ +'\" +.so man.macros +.TH ttk::spinbox n 8.5 Tk "Tk Themed Widget" +.BS +.SH NAME +ttk::spinbox \- Selecting text field widget +.SH SYNOPSIS +\fBttk::spinbox\fR \fIpathName \fR?\fIoptions\fR? +.BE +.SH DESCRIPTION +.PP +A \fBttk::spinbox\fR widget is a \fBttk::entry\fR widget with built-in +up and down buttons that are used to either modify a numeric value or +to select among a set of values. The widget implements all the features +of the \fBttk::entry\fR widget including support of the +\fB\-textvariable\fR option to link the value displayed by the widget +to a Tcl variable. +.SO ttk_widget +\-class \-cursor \-style +\-takefocus \-xscrollcommand +.SE +.SO ttk::entry +\-validate \-validatecommand +.SE +.SH "WIDGET-SPECIFIC OPTIONS" +.OP \-from from From +A floating\-point value specifying the lowest value for the spinbox. This is +used in conjunction with \fI\-to\fR and \fI\-increment\fR to set a numerical +range. +.OP \-to to To +A floating\-point value specifying the highest permissible value for the +widget. See also \fI\-from\fR and \fI\-increment\fR. +range. +.OP \-increment increment Increment +A floating\-point value specifying the change in value to be applied each +time one of the widget spin buttons is pressed. The up button applies a +positive increment, the down button applies a negative increment. +.OP \-values values Values +This must be a Tcl list of values. If this option is set then this will +override any range set using the \fI\-from\fR, \fI\-to\fR and +\fI\-increment\fR options. The widget will instead use the values +specified beginning with the first value. +.OP \-wrap wrap Wrap +Must be a proper boolean value. If on, the spinbox will wrap around the +values of data in the widget. +.OP \-format format Format +Specifies an alternate format to use when setting the string value +when using the \fB\-from\fR and \fB\-to\fR range. +This must be a format specifier of the form \fB%.f\fR, +as it will format a floating-point number. +.OP \-command command Command +Specifies a Tcl command to be invoked whenever a spinbutton is invoked. +.SH "INDICES" +.PP +See the \fBttk::entry\fR manual for information about indexing characters. +.SH "VALIDATION" +.PP +See the \fBttk::entry\fR manual for information about using the +\fI\-validate\fR and \fI\-validatecommand\fR options. +.SH "WIDGET COMMAND" +.PP +The following subcommands are possible for spinbox widgets in addition to +the commands described for the \fBttk::entry\fR widget: +.TP +\fIpathName \fBcurrent \fIindex\fR +.TP +\fIpathName \fBget\fR +Returns the spinbox's current value. +.TP +\fIpathName \fBset \fIvalue\fR +Set the spinbox string to \fIvalue\fR. If a \fI\-format\fR option has +been configured then this format will be applied. If formatting fails +or is not set or the \fI\-values\fR option has been used then the value +is set directly. +.SH "SEE ALSO" +ttk::widget(n), ttk::entry(n), spinbox(n) +.SH KEYWORDS +entry, spinbox, widget, text field +'\" Local Variables: +'\" mode: nroff +'\" End: diff --git a/generic/ttk/ttkEntry.c b/generic/ttk/ttkEntry.c index d41010d..7397dfb 100644 --- a/generic/ttk/ttkEntry.c +++ b/generic/ttk/ttkEntry.c @@ -1,5 +1,5 @@ /* - * $Id: ttkEntry.c,v 1.9 2007/05/18 21:46:11 jenglish Exp $ + * $Id: ttkEntry.c,v 1.10 2008/11/01 15:34:24 patthoyts Exp $ * * DERIVED FROM: tk/generic/tkEntry.c r1.35. * @@ -13,6 +13,7 @@ #include #include #include +#include #include "ttkTheme.h" #include "ttkWidget.h" @@ -26,6 +27,12 @@ #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 { @@ -144,9 +151,12 @@ typedef struct /* * Extra mask bits for Tk_SetOptions() */ -#define STATE_CHANGED (0x100) /* -state option changed */ -#define TEXTVAR_CHANGED (0x200) /* -textvariable option changed */ -#define SCROLLCMD_CHANGED (0x400) /* -xscrollcommand option changed */ +#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 */ /* * Default option values: @@ -1679,19 +1689,127 @@ 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. + */ + +typedef struct { + Tcl_Obj *valuesObj; + int currentIndex; +} ValuesPart; + +typedef struct { + WidgetCore core; + EntryPart entry; + ValuesPart values; +} Values; + +#define ENTRY_VALUES_OPTION \ + {TK_OPTION_STRING, "-values", "values", "Values", \ + "", Tk_Offset(Values, values.valuesObj), -1, 0, 0, VALUES_CHANGED} + +static int +ValuesInitialize(Tcl_Interp *interp, void *recordPtr) +{ + Values *valPtr = recordPtr; + valPtr->values.currentIndex = -1; + return TCL_OK; +} + +static int +ValuesValidate(Tcl_Interp *interp, void *recordPtr, int *indexPtr) +{ + Values *valPtr = recordPtr; + int currentIndex = valPtr->values.currentIndex; + const char *currentValue = valPtr->entry.string; + int eltc; + Tcl_Obj **eltv; + + + if (Tcl_ListObjLength(interp,valPtr->values.valuesObj,&eltc) != 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; +} + +/* $widget 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( + Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], void *recordPtr) +{ + Values *valPtr = recordPtr; + int currentIndex = valPtr->values.currentIndex; + const char *currentValue = valPtr->entry.string; + int nValues; + Tcl_Obj **values; + + Tcl_ListObjGetElements(interp,valPtr->values.valuesObj,&nValues,&values); + + if (objc == 2) { + if (ValuesValidate(interp, recordPtr, ¤tIndex) != TCL_OK) + return TCL_ERROR; + valPtr->values.currentIndex = currentIndex; + Tcl_SetObjResult(interp, Tcl_NewIntObj(currentIndex)); + } else if (objc == 3) { + if (Tcl_GetIntFromObj(interp, objv[2], ¤tIndex) != TCL_OK) { + return TCL_ERROR; + } + if (currentIndex < 0 || currentIndex >= nValues) { + Tcl_AppendResult(interp, + "Index ", Tcl_GetString(objv[2]), " out of range", + NULL); + return TCL_ERROR; + } + + valPtr->values.currentIndex = currentIndex; + + return EntrySetValue(recordPtr, Tcl_GetString(values[currentIndex])); + } else { + Tcl_WrongNumArgs(interp, 2, objv, "?newIndex?"); + return TCL_ERROR; + } + return TCL_OK; +} + +/*------------------------------------------------------------------------ * +++ Combobox widget record. */ typedef struct { Tcl_Obj *postCommandObj; - Tcl_Obj *valuesObj; Tcl_Obj *heightObj; - int currentIndex; } ComboboxPart; typedef struct { WidgetCore core; EntryPart entry; + ValuesPart values; ComboboxPart combobox; } Combobox; @@ -1703,9 +1821,7 @@ static Tk_OptionSpec ComboboxOptionSpecs[] = {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 }, + ENTRY_VALUES_OPTION, WIDGET_INHERIT_OPTIONS(EntryOptionSpecs) }; @@ -1716,8 +1832,8 @@ static int ComboboxInitialize(Tcl_Interp *interp, void *recordPtr) { Combobox *cb = recordPtr; - cb->combobox.currentIndex = -1; TtkTrackElementState(&cb->core); + ValuesInitialize(interp, recordPtr); return EntryInitialize(interp, recordPtr); } @@ -1728,86 +1844,241 @@ static int ComboboxConfigure(Tcl_Interp *interp, void *recordPtr, int mask) { Combobox *cbPtr = recordPtr; - int unused; + int currentIndex = 0; /* Make sure -values is a valid list: */ - if (Tcl_ListObjLength(interp,cbPtr->combobox.valuesObj,&unused) != TCL_OK) - return TCL_ERROR; + if (mask & VALUES_CHANGED) { + if (ValuesValidate(interp, recordPtr, ¤tIndex) != TCL_OK) + return TCL_ERROR; + cbPtr->values.currentIndex = currentIndex; + } return EntryConfigure(interp, recordPtr, mask); } -/* $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 +/*------------------------------------------------------------------------ + * +++ Combobox widget definition. */ -static int ComboboxCurrentCommand( - Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], void *recordPtr) +static WidgetCommandSpec ComboboxCommands[] = { - Combobox *cbPtr = recordPtr; - int currentIndex = cbPtr->combobox.currentIndex; - const char *currentValue = cbPtr->entry.string; - int nValues; - Tcl_Obj **values; + { "bbox", EntryBBoxCommand }, + { "cget", TtkWidgetCgetCommand }, + { "configure", TtkWidgetConfigureCommand }, + { "current", ValuesCurrentCommand }, + { "delete", EntryDeleteCommand }, + { "get", EntryGetCommand }, + { "icursor", EntryICursorCommand }, + { "identify", TtkWidgetIdentifyCommand }, + { "index", EntryIndexCommand }, + { "insert", EntryInsertCommand }, + { "instate", TtkWidgetInstateCommand }, + { "selection", EntrySelectionCommand }, + { "state", TtkWidgetStateCommand }, + { "set", EntrySetCommand }, + { "xview", EntryXViewCommand }, + {0,0} +}; - Tcl_ListObjGetElements(interp,cbPtr->combobox.valuesObj,&nValues,&values); +static WidgetSpec ComboboxWidgetSpec = +{ + "TCombobox", /* className */ + sizeof(Combobox), /* recordSize */ + ComboboxOptionSpecs, /* optionSpecs */ + ComboboxCommands, /* subcommands */ + ComboboxInitialize, /* initializeProc */ + EntryCleanup, /* cleanupProc */ + ComboboxConfigure, /* configureProc */ + EntryPostConfigure, /* postConfigureProc */ + TtkWidgetGetLayout, /* getLayoutProc */ + TtkWidgetSize, /* sizeProc */ + EntryDoLayout, /* layoutProc */ + EntryDisplay /* displayProc */ +}; - if (objc == 2) { - /* 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; - } +/*------------------------------------------------------------------------ + * +++ Spinbox widget record. + */ + +typedef struct { + Tcl_Obj *commandObj; + double fromValue; + double toValue; + int valueCount; + Tcl_Obj *formatObj; + Tcl_Obj *incrementObj; + Tcl_Obj *wrapObj; + char formatBuffer[TCL_INTEGER_SPACE + 4]; +} SpinboxPart; + +typedef struct { + WidgetCore core; + EntryPart entry; + ValuesPart values; + SpinboxPart spinbox; +} Spinbox; + +static Tk_OptionSpec SpinboxOptionSpecs[] = +{ + {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, + WIDGET_INHERIT_OPTIONS(EntryOptionSpecs) +}; + +/* + * SpinboxInitialize -- + * Initialization hook for spinbox widgets. + */ + +static int +SpinboxInitialize(Tcl_Interp *interp, void *recordPtr) +{ + Spinbox *sbPtr = recordPtr; + sbPtr->spinbox.valueCount = 0; + TtkTrackElementState(&sbPtr->core); + ValuesInitialize(interp, recordPtr); + return 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 (currentIndex >= nValues) { - /* Not found */ - currentIndex = -1; + if (d > sbPtr->spinbox.toValue) { + d = sbPtr->spinbox.toValue; + needsUpdate = 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) { + } + + /* Make sure -values is a valid list: + */ + if (mask & VALUES_CHANGED) { + if (ValuesValidate(interp, recordPtr, ¤tIndex) != TCL_OK) return TCL_ERROR; - } - if (currentIndex < 0 || currentIndex >= nValues) { - Tcl_AppendResult(interp, - "Index ", Tcl_GetString(objv[2]), " out of range", - NULL); + 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; } + } - cbPtr->combobox.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); + } + } + + return EntryConfigure(interp, recordPtr, mask); +} - return EntrySetValue(recordPtr, Tcl_GetString(values[currentIndex])); - } else { - Tcl_WrongNumArgs(interp, 2, objv, "?newIndex?"); +/* $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; } /*------------------------------------------------------------------------ - * +++ Combobox widget definition. + * +++ Spinbox widget definition. */ -static WidgetCommandSpec ComboboxCommands[] = + +static WidgetCommandSpec SpinboxCommands[] = { { "bbox", EntryBBoxCommand }, { "cget", TtkWidgetCgetCommand }, { "configure", TtkWidgetConfigureCommand }, - { "current", ComboboxCurrentCommand }, + { "current", ValuesCurrentCommand }, { "delete", EntryDeleteCommand }, { "get", EntryGetCommand }, { "icursor", EntryICursorCommand }, @@ -1817,20 +2088,21 @@ static WidgetCommandSpec ComboboxCommands[] = { "instate", TtkWidgetInstateCommand }, { "selection", EntrySelectionCommand }, { "state", TtkWidgetStateCommand }, - { "set", EntrySetCommand }, + { "set", SpinboxSetCommand }, + { "validate", EntryValidateCommand }, { "xview", EntryXViewCommand }, {0,0} }; -static WidgetSpec ComboboxWidgetSpec = +static WidgetSpec SpinboxWidgetSpec = { - "TCombobox", /* className */ - sizeof(Combobox), /* recordSize */ - ComboboxOptionSpecs, /* optionSpecs */ - ComboboxCommands, /* subcommands */ - ComboboxInitialize, /* initializeProc */ + "TSpinbox", /* className */ + sizeof(Spinbox), /* recordSize */ + SpinboxOptionSpecs, /* optionSpecs */ + SpinboxCommands, /* subcommands */ + SpinboxInitialize, /* initializeProc */ EntryCleanup, /* cleanupProc */ - ComboboxConfigure, /* configureProc */ + SpinboxConfigure, /* configureProc */ EntryPostConfigure, /* postConfigureProc */ TtkWidgetGetLayout, /* getLayoutProc */ TtkWidgetSize, /* sizeProc */ @@ -1902,6 +2174,14 @@ TTK_BEGIN_LAYOUT(ComboboxLayout) TTK_NODE("Combobox.textarea", TTK_FILL_BOTH))) TTK_END_LAYOUT +TTK_BEGIN_LAYOUT(SpinboxLayout) + TTK_GROUP("Spinbox.field", TTK_FILL_BOTH, + 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_END_LAYOUT + /*------------------------------------------------------------------------ * +++ Initialization. */ @@ -1915,9 +2195,11 @@ void TtkEntry_Init(Tcl_Interp *interp) Ttk_RegisterLayout(themePtr, "TEntry", EntryLayout); Ttk_RegisterLayout(themePtr, "TCombobox", ComboboxLayout); + Ttk_RegisterLayout(themePtr, "TSpinbox", SpinboxLayout); RegisterWidget(interp, "ttk::entry", &EntryWidgetSpec); RegisterWidget(interp, "ttk::combobox", &ComboboxWidgetSpec); + RegisterWidget(interp, "ttk::spinbox", &SpinboxWidgetSpec); } /*EOF*/ diff --git a/library/ttk/altTheme.tcl b/library/ttk/altTheme.tcl index e604373..0a4ce05 100644 --- a/library/ttk/altTheme.tcl +++ b/library/ttk/altTheme.tcl @@ -1,5 +1,5 @@ # -# $Id: altTheme.tcl,v 1.7 2008/05/23 20:20:05 jenglish Exp $ +# $Id: altTheme.tcl,v 1.8 2008/11/01 15:34:24 patthoyts Exp $ # # Ttk widget set: Alternate theme # @@ -60,6 +60,11 @@ namespace eval ttk::theme::alt { ttk::style map TCombobox -fieldbackground \ [list readonly $colors(-frame) disabled $colors(-frame)] + ttk::style configure TSpinbox -arrowsize 10 -padding {2 0 10 0} + ttk::style map TSpinbox -fieldbackground \ + [list readonly $colors(-frame) disabled $colors(-frame)] \ + -arrowcolor [list disabled $colors(-disabledfg)] + ttk::style configure Toolbutton -relief flat -padding 2 ttk::style map Toolbutton -relief \ {disabled flat selected sunken pressed sunken active raised} diff --git a/library/ttk/clamTheme.tcl b/library/ttk/clamTheme.tcl index 799b6ae..8498f1e 100644 --- a/library/ttk/clamTheme.tcl +++ b/library/ttk/clamTheme.tcl @@ -1,5 +1,5 @@ # -# $Id: clamTheme.tcl,v 1.7 2008/05/23 20:20:06 jenglish Exp $ +# $Id: clamTheme.tcl,v 1.8 2008/11/01 15:34:24 patthoyts Exp $ # # "Clam" theme. # @@ -106,6 +106,14 @@ namespace eval ttk::theme::clam { -foreground [list {readonly focus} $colors(-selectfg)] \ ; + 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} ttk::style map TNotebook.Tab \ -padding [list selected {6 4 6 2}] \ diff --git a/library/ttk/classicTheme.tcl b/library/ttk/classicTheme.tcl index 48c3df8..72473df 100644 --- a/library/ttk/classicTheme.tcl +++ b/library/ttk/classicTheme.tcl @@ -1,5 +1,5 @@ # -# $Id: classicTheme.tcl,v 1.7 2008/05/23 20:20:06 jenglish Exp $ +# $Id: classicTheme.tcl,v 1.8 2008/11/01 15:34:24 patthoyts Exp $ # # "classic" Tk theme. # @@ -71,6 +71,10 @@ namespace eval ttk::theme::classic { ttk::style map TCombobox -fieldbackground \ [list readonly $colors(-frame) disabled $colors(-frame)] + ttk::style configure TSpinbox -arrowsize 10 -padding {2 0 10 0} + ttk::style map TSpinbox -fieldbackground \ + [list readonly $colors(-frame) disabled $colors(-frame)] + ttk::style configure TLabelframe -borderwidth 2 -relief groove ttk::style configure TScrollbar -relief raised diff --git a/library/ttk/defaults.tcl b/library/ttk/defaults.tcl index dfb6654..98185ee5 100644 --- a/library/ttk/defaults.tcl +++ b/library/ttk/defaults.tcl @@ -1,5 +1,5 @@ # -# $Id: defaults.tcl,v 1.7 2008/05/23 20:20:06 jenglish Exp $ +# $Id: defaults.tcl,v 1.8 2008/11/01 15:34:24 patthoyts Exp $ # # Settings for default theme. # @@ -66,6 +66,11 @@ namespace eval ttk::theme::default { ttk::style map TCombobox -fieldbackground \ [list readonly $colors(-frame) disabled $colors(-frame)] + ttk::style configure TSpinbox -arrowsize 10 -padding {2 0 10 0} + ttk::style map TSpinbox -fieldbackground \ + [list readonly $colors(-frame) disabled $colors(-frame)] \ + -arrowcolor [list disabled $colors(-disabledfg)] + ttk::style configure TLabelframe \ -relief groove -borderwidth 2 diff --git a/library/ttk/entry.tcl b/library/ttk/entry.tcl index 360954e..d54328a 100644 --- a/library/ttk/entry.tcl +++ b/library/ttk/entry.tcl @@ -1,5 +1,5 @@ # -# $Id: entry.tcl,v 1.5 2008/10/28 20:02:03 jenglish Exp $ +# $Id: entry.tcl,v 1.6 2008/11/01 15:34:24 patthoyts Exp $ # # DERIVED FROM: tk/library/entry.tcl r1.22 # @@ -228,7 +228,7 @@ proc ttk::entry::See {w {index insert}} { # position following the next end-of-word position. # set ::ttk::entry::State(startNext) \ - [string equal $tcl_platform(platform) "windows"] + [string equal $::tcl_platform(platform) "windows"] proc ttk::entry::NextWord {w start} { variable State diff --git a/library/ttk/spinbox.tcl b/library/ttk/spinbox.tcl new file mode 100644 index 0000000..579a22c --- /dev/null +++ b/library/ttk/spinbox.tcl @@ -0,0 +1,121 @@ +# +# $Id: spinbox.tcl,v 1.1 2008/11/01 15:34:24 patthoyts Exp $ +# +# Tile widget set: spinbox bindings. +# +# + +namespace eval ttk::spinbox { + variable Values ;# Values($cb) is -listvariable of listbox widget + + variable State + set State(entryPress) 0 +} + +### Spinbox bindings. +# +# Duplicate the Entry bindings, override if needed: +# + +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::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} + + +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 + } + } + } +} + +proc ttk::spinbox::Release {w x y} { + variable State + unset -nocomplain State(xPress) State(yPress) + 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 + } + } + ::ttk::entry::Select $w 0 $units + + # Run -command callback: + # + 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] + } + } + return -code continue +} + +#*EOF* diff --git a/library/ttk/ttk.tcl b/library/ttk/ttk.tcl index c4d0ff1..8bc1478 100644 --- a/library/ttk/ttk.tcl +++ b/library/ttk/ttk.tcl @@ -1,5 +1,5 @@ # -# $Id: ttk.tcl,v 1.8 2007/12/13 15:27:08 dgp Exp $ +# $Id: ttk.tcl,v 1.9 2008/11/01 15:34:24 patthoyts Exp $ # # Ttk widget set initialization script. # @@ -105,6 +105,7 @@ source [file join $::ttk::library notebook.tcl] source [file join $::ttk::library panedwindow.tcl] source [file join $::ttk::library entry.tcl] source [file join $::ttk::library combobox.tcl] ;# dependency: entry.tcl +source [file join $::ttk::library spinbox.tcl] ;# dependency: entry.tcl source [file join $::ttk::library treeview.tcl] source [file join $::ttk::library sizegrip.tcl] diff --git a/library/ttk/winTheme.tcl b/library/ttk/winTheme.tcl index 03ca6d2..c27c45a 100644 --- a/library/ttk/winTheme.tcl +++ b/library/ttk/winTheme.tcl @@ -1,5 +1,5 @@ # -# $Id: winTheme.tcl,v 1.7 2008/05/23 20:20:06 jenglish Exp $ +# $Id: winTheme.tcl,v 1.8 2008/11/01 15:34:24 patthoyts Exp $ # # Settings for 'winnative' theme. # @@ -45,6 +45,8 @@ namespace eval ttk::theme::winnative { -focusfill [list {readonly focus} SystemHighlight] \ ; + ttk::style configure TSpinbox -padding {2 0 16 0} + ttk::style configure TLabelframe -borderwidth 2 -relief groove ttk::style configure Toolbutton -relief flat -padding {8 4} diff --git a/library/ttk/xpTheme.tcl b/library/ttk/xpTheme.tcl index 691dcea..c204233 100644 --- a/library/ttk/xpTheme.tcl +++ b/library/ttk/xpTheme.tcl @@ -1,5 +1,5 @@ # -# $Id: xpTheme.tcl,v 1.9 2008/05/23 20:20:06 jenglish Exp $ +# $Id: xpTheme.tcl,v 1.10 2008/11/01 15:34:24 patthoyts Exp $ # # Settings for 'xpnative' theme # @@ -52,6 +52,12 @@ namespace eval ttk::theme::xpnative { -focusfill [list {readonly focus} SystemHighlight] \ ; + ttk::style configure TSpinbox -padding {2 0 14 0} + ttk::style map TSpinbox \ + -selectbackground [list !focus SystemWindow] \ + -selectforeground [list !focus SystemWindowText] \ + ; + ttk::style configure Toolbutton -padding {4 4} # Vista requires some style modifications. There are some @@ -75,6 +81,20 @@ namespace eval ttk::theme::xpnative { } } + # EDIT EP_EDITBORDER_HVSCROLL + ttk::style configure TSpinbox -padding {2 0 15 1} + ttk::style element create Vista.Spinbox.field vsapi \ + EDIT 9 {disabled 4 focus 3 active 2 {} 1} \ + -padding {1 1 1 2} + ttk::style layout TSpinbox { + Vista.Spinbox.field -sticky nswe -children { + Spinbox.padding -sticky nswe -children { + Spinbox.textarea -expand 1 -sticky {} + } + Spinbox.uparrow -side top -sticky ens + Spinbox.downarrow -side bottom -sticky ens + } + } } } } diff --git a/tests/ttk/spinbox.test b/tests/ttk/spinbox.test new file mode 100644 index 0000000..66a7652 --- /dev/null +++ b/tests/ttk/spinbox.test @@ -0,0 +1,239 @@ +# +# ttk::spinbox widget tests +# + +package require Tk 8.5 +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 + +test spinbox-1.1 "Bad -values list" -setup { + ttk::spinbox .sb +} -body { + .sb configure -values "bad \{list" +} -cleanup { + 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 { + .sb set 50 + .sb get +} -cleanup { + destroy .sb +} -result 50 + +test spinbox-1.3.2 "get retrieves value" -setup { + ttk::spinbox .sb -from 0 -to 100 -values 55 +} -body { + .sb get +} -cleanup { + destroy .sb +} -result 55 + +test spinbox-1.4.1 "set changes value" -setup { + ttk::spinbox .sb -from 0 -to 100 +} -body { + .sb set 33 + .sb get +} -cleanup { + destroy .sb +} -result 33 + +test spinbox-1.4.2 "set changes value" -setup { + ttk::spinbox .sb -from 0 -to 100 -values 55 +} -body { + .sb set 33 + .sb get +} -cleanup { + 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 +} -body { + .sb set 5 + .sb insert 0 4 + .sb get +} -cleanup { + destroy .sb +} -result 45 + +test spinbox-1.6.2 "insert end" -setup { + ttk::spinbox .sb -from 0 -to 100 +} -body { + .sb set 5 + .sb insert end 4 + .sb get +} -cleanup { + destroy .sb +} -result 54 + +test spinbox-1.6.3 "insert invalid index" -setup { + ttk::spinbox .sb -from 0 -to 100 +} -body { + .sb set 5 + .sb insert 100 4 + .sb get +} -cleanup { + destroy .sb +} -result 54 + +test spinbox-1.7.1 "-command option: set doesnt fire" -setup { + ttk::spinbox .sb -from 0 -to 100 -command {set ::spinbox_test 1} +} -body { + set ::spinbox_test 0 + .sb set 50 + set ::spinbox_test +} -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 + set ::spinbox_test +} -cleanup { + destroy .sb +} -result 1 + +test spinbox-1.8.1 "option -validate" -setup { + ttk::spinbox .sb -from 0 -to 100 +} -body { + .sb configure -validate all + .sb cget -validate +} -cleanup { + destroy .sb +} -result {all} + +test spinbox-1.8.2 "option -validate" -setup { + ttk::spinbox .sb -from 0 -to 100 +} -body { + .sb configure -validate key + .sb configure -validate focus + .sb configure -validate focusin + .sb configure -validate focusout + .sb configure -validate none + .sb cget -validate +} -cleanup { + destroy .sb +} -result {none} + +test spinbox-1.8.3 "option -validate" -setup { + ttk::spinbox .sb -from 0 -to 100 +} -body { + .sb configure -validate bogus +} -cleanup { + destroy .sb +} -returnCodes error -result {bad validate "bogus": must be all, key, focus, focusin, focusout, or none} + +test spinbox-1.8.4 "-validate option: " -setup { + set ::spinbox_test {} + ttk::spinbox .sb -from 0 -to 100 +} -body { + .sb configure -validate all -validatecommand {lappend ::spinbox_test %P} + pack .sb + .sb set 50 + 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 { + ttk::spinbox .sb -values [list a b c d e a] +} -body { + .sb current +} -cleanup { + destroy .sb +} -result 0 + +test spinbox-2.1 "current command -- set index" -setup { + ttk::spinbox .sb -values [list a b c d e a] +} -body { + .sb current 5 + .sb get +} -cleanup { + destroy .sb +} -result a + +test spinbox-2.2 "current command -- change -values" -setup { + ttk::spinbox .sb -values [list a b c d e a] +} -body { + .sb current 5 + .sb configure -values [list c b a d e] + .sb current +} -cleanup { + destroy .sb +} -result 2 + +test spinbox-2.3 "current command -- change value" -setup { + ttk::spinbox .sb -values [list c b a d e] +} -body { + .sb current 2 + .sb set "b" + .sb current +} -cleanup { + destroy .sb +} -result 1 + +test spinbox-2.4 "current command -- value not in list" -setup { + ttk::spinbox .sb -values [list c b a d e] +} -body { + .sb current 2 + .sb set "z" + .sb current +} -cleanup { + destroy .sb +} -result -1 + +tcltest::cleanupTests + +# Local variables: +# mode: tcl +# End: diff --git a/win/ttkWinTheme.c b/win/ttkWinTheme.c index 749fe3f..e224f57 100644 --- a/win/ttkWinTheme.c +++ b/win/ttkWinTheme.c @@ -1,6 +1,6 @@ /* winTheme.c - Copyright (C) 2004 Pat Thoyts * - * $Id: ttkWinTheme.c,v 1.13 2008/01/08 17:01:00 jenglish Exp $ + * $Id: ttkWinTheme.c,v 1.14 2008/11/01 15:34:24 patthoyts Exp $ */ #ifdef _MSC_VER @@ -104,10 +104,12 @@ typedef struct { Ttk_Padding margins; /* additional placement padding */ } FrameControlElementData; -#define _FIXEDSIZE 0x8000 +#define _FIXEDSIZE 0x80000000L +#define _HALFMETRIC 0x40000000L #define FIXEDSIZE(id) (id|_FIXEDSIZE) +#define HALFMETRIC(id) (id|_HALFMETRIC) #define GETMETRIC(m) \ - ((m) & _FIXEDSIZE ? (m) & ~_FIXEDSIZE : GetSystemMetrics(m)) + ((m) & _FIXEDSIZE ? (m) & ~_FIXEDSIZE : GetSystemMetrics((m)&0x0fffffff)) static FrameControlElementData FrameControlElements[] = { { "Checkbutton.indicator", @@ -131,6 +133,12 @@ static FrameControlElementData FrameControlElements[] = { { "sizegrip", DFC_SCROLL, DFCS_SCROLLSIZEGRIP, SM_CXVSCROLL, SM_CYHSCROLL, arrow_statemap, {0,0,0,0} }, + { "Spinbox.uparrow", + DFC_SCROLL, DFCS_SCROLLUP, SM_CXVSCROLL, HALFMETRIC(SM_CYVSCROLL), + arrow_statemap, {0,0,0,0} }, + { "Spinbox.downarrow", + DFC_SCROLL, DFCS_SCROLLDOWN, SM_CXVSCROLL, HALFMETRIC(SM_CYVSCROLL), + arrow_statemap, {0,0,0,0} }, { 0,0,0,0,0,0, {0,0,0,0} } }; @@ -142,8 +150,12 @@ static void FrameControlElementSize( int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) { FrameControlElementData *p = clientData; - *widthPtr = GETMETRIC(p->cxId) + Ttk_PaddingWidth(p->margins); - *heightPtr = GETMETRIC(p->cyId) + Ttk_PaddingHeight(p->margins); + int cx = GETMETRIC(p->cxId); + int cy = GETMETRIC(p->cyId); + if (p->cxId & _HALFMETRIC) cx /= 2; + if (p->cyId & _HALFMETRIC) cy /= 2; + *widthPtr = cx + Ttk_PaddingWidth(p->margins); + *heightPtr = cy + Ttk_PaddingHeight(p->margins); } static void FrameControlElementDraw( diff --git a/win/ttkWinXPTheme.c b/win/ttkWinXPTheme.c index 03eaab9..27416e0 100644 --- a/win/ttkWinXPTheme.c +++ b/win/ttkWinXPTheme.c @@ -1,5 +1,5 @@ /* - * $Id: ttkWinXPTheme.c,v 1.20 2008/04/27 22:39:17 dkf Exp $ + * $Id: ttkWinXPTheme.c,v 1.21 2008/11/01 15:34:24 patthoyts Exp $ * * Tk theme engine which uses the Windows XP "Visual Styles" API * Adapted from Georgios Petasis' XP theme patch. @@ -316,6 +316,14 @@ static Ttk_StateTable rightarrow_statemap[] = { ABS_RIGHTNORMAL, 0, 0 } }; +static Ttk_StateTable spinbutton_statemap[] = +{ + { DNS_DISABLED, TTK_STATE_DISABLED, 0 }, + { DNS_PRESSED, TTK_STATE_PRESSED, 0 }, + { DNS_HOT, TTK_STATE_ACTIVE, 0 }, + { DNS_NORMAL, 0, 0 }, +}; + /* * Trackbar thumb: (Tk: "scale slider") */ @@ -586,6 +594,36 @@ static Ttk_ElementSpec GenericSizedElementSpec = { }; /*---------------------------------------------------------------------- + * +++ Spinbox arrow element. + * These are half-height scrollbar buttons. + */ + +static void +SpinboxArrowElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + ElementData *elementData = clientData; + + if (!InitElementData(elementData, tkwin, 0)) + return; + + GenericSizedElementSize(clientData, elementRecord, tkwin, + widthPtr, heightPtr, paddingPtr); + + /* force the arrow button height to half size */ + *heightPtr /= 2; +} + +static Ttk_ElementSpec SpinboxArrowElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + SpinboxArrowElementSize, + GenericElementDraw +}; + +/*---------------------------------------------------------------------- * +++ Scrollbar thumb element. * Same as a GenericElement, but don't draw in the disabled state. */ @@ -964,6 +1002,14 @@ static ElementInfo ElementInfoTable[] = { HP_HEADERITEM, header_statemap, PAD(4,0,4,0),0 }, { "sizegrip", &GenericElementSpec, L"STATUS", SP_GRIPPER, null_statemap, NOPAD,0 }, + { "Spinbox.field", &GenericElementSpec, L"EDIT", + EP_EDITTEXT, edittext_statemap, PAD(1, 1, 1, 1), 0 }, + { "Spinbox.uparrow", &SpinboxArrowElementSpec, L"SPIN", + SPNP_UP, spinbutton_statemap, NOPAD, + PAD_MARGINS | ((SM_CXVSCROLL << 8) | SM_CYVSCROLL) }, + { "Spinbox.downarrow", &SpinboxArrowElementSpec, L"SPIN", + SPNP_DOWN, spinbutton_statemap, NOPAD, + PAD_MARGINS | ((SM_CXVSCROLL << 8) | SM_CYVSCROLL) }, #if BROKEN_TEXT_ELEMENT { "Labelframe.text", &TextElementSpec, L"BUTTON", -- cgit v0.12