diff options
author | hobbs <hobbs> | 2000-05-29 01:43:13 (GMT) |
---|---|---|
committer | hobbs <hobbs> | 2000-05-29 01:43:13 (GMT) |
commit | abcf7507cc5ab608bb75d22bbb408ee6bc5f0b1a (patch) | |
tree | 8e72919b463211e30307b0e8eb87f7a28e578327 | |
parent | f78e5fa2c3172a6d7f3ee6e4d77819d9c16b48d1 (diff) | |
download | tk-abcf7507cc5ab608bb75d22bbb408ee6bc5f0b1a.zip tk-abcf7507cc5ab608bb75d22bbb408ee6bc5f0b1a.tar.gz tk-abcf7507cc5ab608bb75d22bbb408ee6bc5f0b1a.tar.bz2 |
* doc/spinbox.n: (new file) docs for spinbox widget
* generic/tkInt.h: added Tk_SpinboxObjCmd declaration
* generic/tkEntry.c: added 'spinbox' widget - an extension of the
entry widget type.
* generic/tkWindow.c: added 'spinbox' to core Tk commands
* library/spinbox.tcl: (new file) binding and helper procs for spinbox
* library/tk.tcl: added spinbox.tcl to list of files to source
* tests/entry.test: updated changed error messages
* tests/spinbox.test: (new file) test suite for spinbox
-rw-r--r-- | doc/spinbox.n | 582 | ||||
-rw-r--r-- | generic/tkEntry.c | 1893 | ||||
-rw-r--r-- | generic/tkInt.h | 5 | ||||
-rw-r--r-- | generic/tkWindow.c | 3 | ||||
-rw-r--r-- | library/entry.tcl | 6 | ||||
-rw-r--r-- | library/spinbox.tcl | 746 | ||||
-rw-r--r-- | library/tk.tcl | 3 | ||||
-rw-r--r-- | tests/entry.test | 26 | ||||
-rw-r--r-- | tests/spinbox.test | 1577 |
9 files changed, 4623 insertions, 218 deletions
diff --git a/doc/spinbox.n b/doc/spinbox.n new file mode 100644 index 0000000..5cf39e6 --- /dev/null +++ b/doc/spinbox.n @@ -0,0 +1,582 @@ +'\" +'\" Copyright (c) 2000 Jeffrey Hobbs. +'\" Copyright (c) 2000 Ajuba Solutions. +'\" +'\" See the file "license.terms" for information on usage and redistribution +'\" of this file, and for a DISCLAIMER OF ALL WARRANTIES. +'\" +'\" RCS: @(#) $Id: spinbox.n,v 1.1 2000/05/29 01:43:13 hobbs Exp $ +'\" +.so man.macros +.TH spinbox n 8.4 Tk "Tk Built-In Commands" +.BS +'\" Note: do not modify the .SH NAME line immediately below! +.SH NAME +spinbox \- Create and manipulate spinbox widgets +.SH SYNOPSIS +\fBspinbox\fR \fIpathName \fR?\fIoptions\fR? +.SO +\-activebackground \-background \-borderwidth +\-cursor \-exportselection \-font +\-foreground \-highlightbackground \-highlightcolor +\-highlightthickness \-insertbackground \-insertborderwidth +\-insertontime \-insertwidth \-insertofftime +\-justify \-relief \-repeatdelay +\-repeatinterval \-selectbackground \-selectborderwidth +\-selectforeground \-takefocus \-textvariable +\-xscrollcommand +.SE +.SH "WIDGET-SPECIFIC OPTIONS" +.OP \-buttonbackground buttonBackground Background +The background color to be used for the spin buttons. +.OP \-buttoncursor buttonCursor Cursor +The cursor to be used when over the spin buttons. If this is empty +(the default), a default cursor will be used. +.OP \-buttondownrelief buttonDownRelief Relief +The relief to be used for the upper spin button. +.OP \-buttonuprelief buttonUpRelief Relief +The relief to be used for the lower spin button. +.OP \-command command Command +Specifies a Tcl command to invoke whenever a spinbutton is invoked. +The command recognizes several percent substitutions: \fB%W\fR for +the widget path, \fB%s\fR for the current value of the widget, and +\fB%d\fR for the direction of the button pressed (\fBup\fR or \fBdown\fR). +.OP \-disabledbackground disabledBackground DisabledBackground +Specifies the background color to use when the spinbox is disabled. If +this option is the empty string, the normal background color is used. +.OP \-disabledforeground disabledForeground DisabledForeground +Specifies the foreground color to use when the spinbox is disabled. If +this option is the empty string, the normal foreground color is used. +.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%<pad>.<pad>f\fR, +as it will format a floating-point number. +.OP \-from from From +A floating-point value corresponding to the lowest value for a spinbox, to +be used in conjunction with \fB\-to\fR and \fB\-increment\fR. When all +are specified correctly, the spinbox will use these values to control its +contents. This value must be less than the \fB\-to\fR option. +If \fB\-values\fR is specified, it supercedes this option. +.OP "\-invalidcommand or \-invcmd" invalidCommand InvalidCommand +Specifies a script to eval when \fBvalidateCommand\fR returns 0. Setting +it to an empty string disables this feature (the default). The best use of +this option is to set it to \fIbell\fR. See \fBValidation\fR below for +more information. +.OP \-increment increment Increment +A floating-point value specifying the increment. When used with +\fB\-from\fR and \fB\-to\fR, the value in the widget will be adjusted by +\fB\-increment\fR when a spin button is pressed (up adds the value, +down subtracts the value). +.OP \-readonlybackground readonlyBackground ReadonlyBackground +Specifies the background color to use when the spinbox is readonly. If +this option is the empty string, the normal background color is used. +.OP \-state state State +Specifies one of three states for the spinbox: \fBnormal\fR, +\fBdisabled\fR, or \fBreadonly\fR. If the spinbox is readonly, then the +value may not be changed using widget commands and no insertion cursor +will be displayed, even if the input focus is in the widget; the +contents of the widget may still be selected. If the spinbox is +disabled, the value may not be changed, no insertion cursor will be +displayed, the contents will not be selectable, and the spinbox may +be displayed in a different color, depending on the values of the +\fB-disabledforeground\fR and \fB-disabledbackground\fR options. +.OP \-to to To +A floating-point value corresponding to the highest value for the spinbox, +to be used in conjunction with \fB\-from\fR and \fB\-increment\fR. When +all are specified correctly, the spinbox will use these values to control +its contents. This value must be greater than the \fB\-from\fR option. +If \fB\-values\fR is specified, it supercedes this option. +.OP \-validate validate Validate +Specifies the mode in which validation should operate: \fBnone\fR, +\fBfocus\fR, \fBfocusin\fR, \fBfocusout\fR, \fBkey\fR, or \fBall\fR. +It defaults to \fBnone\fR. When you want validation, you must explicitly +state which mode you wish to use. See \fBValidation\fR below for more. +.OP "\-validatecommand or \-vcmd" validateCommand ValidateCommand +Specifies a script to evaluate when you want to validate the input in the +widget. Setting it to an empty string disables this feature (the default). +Validation occurs according to the value of \fB\-validate\fR. +This command must return a valid Tcl boolean value. If it returns 0 (or +the valid Tcl boolean equivalent) then the value of the widget will not +change and the \fBinvalidCommand\fR will be evaluated if it is set. If it +returns 1, then value will be changed. +See \fBValidation\fR below for more information. +.OP \-values values Values +Must be a proper list value. If specified, the spinbox will use these +values as to control its contents, starting with the first value. This +option has precedence over the \fB\-from\fR and \fB\-to\fR range. +.OP \-width width Width +Specifies an integer value indicating the desired width of the spinbox window, +in average-size characters of the widget's font. +If the value is less than or equal to zero, the widget picks a +size just large enough to hold its current text. +.OP \-wrap wrap wrap +Must be a proper boolean value. If on, the spinbox will wrap around the +values of data in the widget. +.BE + +.SH DESCRIPTION +.PP +The \fBspinbox\fR command creates a new window (given by the +\fIpathName\fR argument) and makes it into a spinbox widget. +Additional options, described above, may be specified on the +command line or in the option database +to configure aspects of the spinbox such as its colors, font, +and relief. The \fBspinbox\fR command returns its +\fIpathName\fR argument. At the time this command is invoked, +there must not exist a window named \fIpathName\fR, but +\fIpathName\fR's parent must exist. +.PP +A \fBspinbox\fR is an extended \fBentry\fR widget that allows he user +to move, or spin, through a fixed set of ascending or descending values +such as times or dates in addition to editing the value as in an +\fBentry\fR. When first created, a spinbox's string is empty. +A portion of the spinbox may be selected as described below. +If a spinbox is exporting its selection (see the \fBexportSelection\fR +option), then it will observe the standard protocols for handling the +selection; spinbox selections are available as type \fBSTRING\fR. +Spinboxes also observe the standard Tk rules for dealing with the +input focus. When a spinbox has the input focus it displays an +\fIinsertion cursor\fR to indicate where new characters will be +inserted. +.PP +Spinboxes are capable of displaying strings that are too long to +fit entirely within the widget's window. In this case, only a +portion of the string will be displayed; commands described below +may be used to change the view in the window. Spinboxes use +the standard \fBxScrollCommand\fR mechanism for interacting with +scrollbars (see the description of the \fBxScrollCommand\fR option +for details). They also support scanning, as described below. + +.SH VALIDATION +.PP +Validation works by setting the \fBvalidateCommand\fR +option to a script which will be evaluated according to the \fBvalidate\fR +option as follows: +.PP +.IP \fBnone\fR 10 +Default. This means no validation will occur. +.IP \fBfocus\fR 10 +\fBvalidateCommand\fR will be called when the spinbox receives or +loses focus. +.IP \fBfocusin\fR 10 +\fBvalidateCommand\fR will be called when the spinbox receives focus. +.IP \fBfocusout\fR 10 +\fBvalidateCommand\fR will be called when the spinbox loses focus. +.IP \fBkey\fR 10 +\fBvalidateCommand\fR will be called when the spinbox is edited. +.IP \fBall\fR 10 +\fBvalidateCommand\fR will be called for all above conditions. +.PP +It is posible to perform percent substitutions on the \fBvalidateCommand\fR +and \fBinvalidCommand\fR, just as you would in a \fBbind\fR script. The +following substitutions are recognized: +.PP +.IP \fB%d\fR 5 +Type of action: 1 for \fBinsert\fR, 0 for \fBdelete\fR, +or -1 for focus, forced or textvariable validation. +.IP \fB%i\fR 5 +Index of char string to be inserted/deleted, if any, otherwise -1. +.IP \fB%P\fR 5 +The value of the spinbox should edition occur. If you are configuring the +spinbox widget to have a new textvariable, this will be the value of that +textvariable. +.IP \fB%s\fR 5 +The current value of spinbox before edition. +.IP \fB%S\fR 5 +The text string being inserted/deleted, if any. +Otherwise it is an empty string. +.IP \fB%v\fR 5 +The type of validation currently set. +.IP \fB%V\fR 5 +The type of validation that triggered the callback +(key, focusin, focusout, forced). +.IP \fB%W\fR 5 +The name of the spinbox widget. +.PP +In general, the \fBtextVariable\fR and \fBvalidateCommand\fR can be +dangerous to mix. Any problems have been overcome so that using the +\fBvalidateCommand\fR will not interfere with the traditional behavior of +the spinbox widget. Using the \fBtextVariable\fR for read-only purposes will +never cause problems. The danger comes when you try set the +\fBtextVariable\fR to something that the \fBvalidateCommand\fR would not +accept, which causes \fBvalidate\fR to become \fInone\fR (the +\fBinvalidCommand\fR will not be triggered). The same happens +when an error occurs evaluating the \fBvalidateCommand\fR. +.PP +Primarily, an error will occur when the \fBvalidateCommand\fR or +\fBinvalidCommand\fR encounters an error in its script while evaluating or +\fBvalidateCommand\fR does not return a valid Tcl boolean value. The +\fBvalidate\fR option will also set itself to \fBnone\fR when you edit the +spinbox widget from within either the \fBvalidateCommand\fR or the +\fBinvalidCommand\fR. Such editions will override the one that was being +validated. If you wish to edit the value of the widget +during validation and still have the \fBvalidate\fR option set, you should +include the command +.CS + \fI%W config -validate %v\fR +.CE +in the \fBvalidateCommand\fR or \fBinvalidCommand\fR (whichever one you +were editing the spinbox widget from). It is also recommended to not set an +associated \fBtextVariable\fR during validation, as that can cause the +spinbox widget to become out of sync with the \fBtextVariable\fR. + +.SH "WIDGET COMMAND" +.PP +The \fBspinbox\fR command creates a new Tcl command whose +name is \fIpathName\fR. This command may be used to invoke various +operations on the widget. It has the following general form: +.CS +\fIpathName option \fR?\fIarg arg ...\fR? +.CE +\fIOption\fR and the \fIarg\fRs +determine the exact behavior of the command. +.PP +Many of the widget commands for spinboxes take one or more indices as +arguments. An index specifies a particular character in the spinbox's +string, in any of the following ways: +.TP 12 +\fInumber\fR +Specifies the character as a numerical index, where 0 corresponds +to the first character in the string. +.TP 12 +\fBanchor\fR +Indicates the anchor point for the selection, which is set with the +\fBselect from\fR and \fBselect adjust\fR widget commands. +.TP 12 +\fBend\fR +Indicates the character just after the last one in the spinbox's string. +This is equivalent to specifying a numerical index equal to the length +of the spinbox's string. +.TP 12 +\fBinsert\fR +Indicates the character adjacent to and immediately following the +insertion cursor. +.TP 12 +\fBsel.first\fR +Indicates the first character in the selection. It is an error to +use this form if the selection isn't in the spinbox window. +.TP 12 +\fBsel.last\fR +Indicates the character just after the last one in the selection. +It is an error to use this form if the selection isn't in the +spinbox window. +.TP 12 +\fB@\fInumber\fR +In this form, \fInumber\fR is treated as an x-coordinate in the +spinbox's window; the character spanning that x-coordinate is used. +For example, ``\fB@0\fR'' indicates the left-most character in the +window. +.LP +Abbreviations may be used for any of the forms above, e.g. ``\fBe\fR'' +or ``\fBsel.f\fR''. In general, out-of-range indices are automatically +rounded to the nearest legal value. +.PP +The following commands are possible for spinbox widgets: +.TP +\fIpathName \fBbbox \fIindex\fR +Returns a list of four numbers describing the bounding box of the +character given by \fIindex\fR. +The first two elements of the list give the x and y coordinates of +the upper-left corner of the screen area covered by the character +(in pixels relative to the widget) and the last two elements give +the width and height of the character, in pixels. +The bounding box may refer to a region outside the visible area +of the window. +.TP +\fIpathName \fBcget\fR \fIoption\fR +Returns the current value of the configuration option given +by \fIoption\fR. +\fIOption\fR may have any of the values accepted by the \fBspinbox\fR +command. +.TP +\fIpathName \fBconfigure\fR ?\fIoption\fR? ?\fIvalue option value ...\fR? +Query or modify the configuration options of the widget. +If no \fIoption\fR is specified, returns a list describing all of +the available options for \fIpathName\fR (see \fBTk_ConfigureInfo\fR for +information on the format of this list). If \fIoption\fR is specified +with no \fIvalue\fR, then the command returns a list describing the +one named option (this list will be identical to the corresponding +sublist of the value returned if no \fIoption\fR is specified). If +one or more \fIoption\-value\fR pairs are specified, then the command +modifies the given widget option(s) to have the given value(s); in +this case the command returns an empty string. +\fIOption\fR may have any of the values accepted by the \fBspinbox\fR +command. +.TP +\fIpathName \fBdelete \fIfirst \fR?\fIlast\fR? +Delete one or more elements of the spinbox. +\fIFirst\fR is the index of the first character to delete, and +\fIlast\fR is the index of the character just after the last +one to delete. +If \fIlast\fR isn't specified it defaults to \fIfirst\fR+1, +i.e. a single character is deleted. +This command returns an empty string. +.TP +\fIpathName \fBget\fR +Returns the spinbox's string. +.TP +\fIpathName \fBicursor \fIindex\fR +Arrange for the insertion cursor to be displayed just before the character +given by \fIindex\fR. Returns an empty string. +.TP +\fIpathName \fBidentify\fI x y\fR +Returns the name of the window element corresponding to coordinates +\fIx\fR and \fIy\fR in the spinbox. Return value is one of: +\fBnone\fR, \fBspindown\fR, \fBspinup\fR, \fBentry\fR. +.TP +\fIpathName \fBindex\fI index\fR +Returns the numerical index corresponding to \fIindex\fR. +.TP +\fIpathName \fBinsert \fIindex string\fR +Insert the characters of \fIstring\fR just before the character +indicated by \fIindex\fR. Returns an empty string. +.TP +\fIpathName \fBinvoke\fI element\fR +Causes the specified element, either \fBspindown\fR or \fBspinup\fR, +to be invoked, triggering the action associated with it. +.TP +\fIpathName \fBscan\fR \fIoption args\fR +This command is used to implement scanning on spinboxes. It has +two forms, depending on \fIoption\fR: +.RS +.TP +\fIpathName \fBscan mark \fIx\fR +Records \fIx\fR and the current view in the spinbox window; used in +conjunction with later \fBscan dragto\fR commands. Typically this +command is associated with a mouse button press in the widget. It +returns an empty string. +.TP +\fIpathName \fBscan dragto \fIx\fR +This command computes the difference between its \fIx\fR argument +and the \fIx\fR argument to the last \fBscan mark\fR command for +the widget. It then adjusts the view left or right by 10 times the +difference in x-coordinates. This command is typically associated +with mouse motion events in the widget, to produce the effect of +dragging the spinbox at high speed through the window. The return +value is an empty string. +.RE +.TP +\fIpathName \fBselection \fIoption arg\fR +This command is used to adjust the selection within a spinbox. It +has several forms, depending on \fIoption\fR: +.RS +.TP +\fIpathName \fBselection adjust \fIindex\fR +Locate the end of the selection nearest to the character given by +\fIindex\fR, and adjust that end of the selection to be at \fIindex\fR +(i.e including but not going beyond \fIindex\fR). The other +end of the selection is made the anchor point for future +\fBselect to\fR commands. If the selection +isn't currently in the spinbox, then a new selection is created to +include the characters between \fIindex\fR and the most recent +selection anchor point, inclusive. +Returns an empty string. +.TP +\fIpathName \fBselection clear\fR +Clear the selection if it is currently in this widget. If the +selection isn't in this widget then the command has no effect. +Returns an empty string. +.TP +\fIpathName \fBselection element\fR ?\fIelement\fR? +Sets or gets the currently selected element. If a spinbutton element +is specified, it will be displayed depressed. +.TP +\fIpathName \fBselection from \fIindex\fR +Set the selection anchor point to just before the character +given by \fIindex\fR. Doesn't change the selection. +Returns an empty string. +.TP +\fIpathName \fBselection present\fR +Returns 1 if there is are characters selected in the spinbox, +0 if nothing is selected. +.TP +\fIpathName \fBselection range \fIstart\fR \fIend\fR +Sets the selection to include the characters starting with +the one indexed by \fIstart\fR and ending with the one just +before \fIend\fR. +If \fIend\fR refers to the same character as \fIstart\fR or an +earlier one, then the spinbox's selection is cleared. +.TP +\fIpathName \fBselection to \fIindex\fR +If \fIindex\fR is before the anchor point, set the selection +to the characters from \fIindex\fR up to but not including +the anchor point. +If \fIindex\fR is the same as the anchor point, do nothing. +If \fIindex\fR is after the anchor point, set the selection +to the characters from the anchor point up to but not including +\fIindex\fR. +The anchor point is determined by the most recent \fBselect from\fR +or \fBselect adjust\fR command in this widget. +If the selection isn't in this widget then a new selection is +created using the most recent anchor point specified for the widget. +Returns an empty string. +.RE +.TP +\fIpathName \fBset\fR ?\fIstring\fR? +If \fIstring\fR is specified, the spinbox will try and set it to this +value, otherwise it just returns the spinbox's string. +If validation is on, it will occur when setting the string. +.TP +\fIpathName \fBvalidate\fR +This command is used to force an evaluation of the \fBvalidateCommand\fR +independent of the conditions specified by the \fBvalidate\fR option. +This is done by temporarily setting the \fBvalidate\fR option to \fBall\fR. +It returns 0 or 1. +.TP +\fIpathName \fBxview \fIargs\fR +This command is used to query and change the horizontal position of the +text in the widget's window. It can take any of the following +forms: +.RS +.TP +\fIpathName \fBxview\fR +Returns a list containing two elements. +Each element is a real fraction between 0 and 1; together they describe +the horizontal span that is visible in the window. +For example, if the first element is .2 and the second element is .6, +20% of the spinbox's text is off-screen to the left, the middle 40% is visible +in the window, and 40% of the text is off-screen to the right. +These are the same values passed to scrollbars via the \fB\-xscrollcommand\fR +option. +.TP +\fIpathName \fBxview\fR \fIindex\fR +Adjusts the view in the window so that the character given by \fIindex\fR +is displayed at the left edge of the window. +.TP +\fIpathName \fBxview moveto\fI fraction\fR +Adjusts the view in the window so that the character \fIfraction\fR of the +way through the text appears at the left edge of the window. +\fIFraction\fR must be a fraction between 0 and 1. +.TP +\fIpathName \fBxview scroll \fInumber what\fR +This command shifts the view in the window left or right according to +\fInumber\fR and \fIwhat\fR. +\fINumber\fR must be an integer. +\fIWhat\fR must be either \fBunits\fR or \fBpages\fR or an abbreviation +of one of these. +If \fIwhat\fR is \fBunits\fR, the view adjusts left or right by +\fInumber\fR average-width characters on the display; if it is +\fBpages\fR then the view adjusts by \fInumber\fR screenfuls. +If \fInumber\fR is negative then characters farther to the left +become visible; if it is positive then characters farther to the right +become visible. +.RE + +.SH "DEFAULT BINDINGS" +.PP +Tk automatically creates class bindings for spinboxes that give them +the following default behavior. +In the descriptions below, ``word'' refers to a contiguous group +of letters, digits, or ``_'' characters, or any single character +other than these. +.IP [1] +Clicking mouse button 1 positions the insertion cursor +just before the character underneath the mouse cursor, sets the +input focus to this widget, and clears any selection in the widget. +Dragging with mouse button 1 strokes out a selection between +the insertion cursor and the character under the mouse. +.IP [2] +Double-clicking with mouse button 1 selects the word under the mouse +and positions the insertion cursor at the beginning of the word. +Dragging after a double click will stroke out a selection consisting +of whole words. +.IP [3] +Triple-clicking with mouse button 1 selects all of the text in the +spinbox and positions the insertion cursor before the first character. +.IP [4] +The ends of the selection can be adjusted by dragging with mouse +button 1 while the Shift key is down; this will adjust the end +of the selection that was nearest to the mouse cursor when button +1 was pressed. +If the button is double-clicked before dragging then the selection +will be adjusted in units of whole words. +.IP [5] +Clicking mouse button 1 with the Control key down will position the +insertion cursor in the spinbox without affecting the selection. +.IP [6] +If any normal printing characters are typed in a spinbox, they are +inserted at the point of the insertion cursor. +.IP [7] +The view in the spinbox can be adjusted by dragging with mouse button 2. +If mouse button 2 is clicked without moving the mouse, the selection +is copied into the spinbox at the position of the mouse cursor. +.IP [8] +If the mouse is dragged out of the spinbox on the left or right sides +while button 1 is pressed, the spinbox will automatically scroll to +make more text visible (if there is more text off-screen on the side +where the mouse left the window). +.IP [9] +The Left and Right keys move the insertion cursor one character to the +left or right; they also clear any selection in the spinbox and set +the selection anchor. +If Left or Right is typed with the Shift key down, then the insertion +cursor moves and the selection is extended to include the new character. +Control-Left and Control-Right move the insertion cursor by words, and +Control-Shift-Left and Control-Shift-Right move the insertion cursor +by words and also extend the selection. +Control-b and Control-f behave the same as Left and Right, respectively. +Meta-b and Meta-f behave the same as Control-Left and Control-Right, +respectively. +.IP [10] +The Home key, or Control-a, will move the insertion cursor to the +beginning of the spinbox and clear any selection in the spinbox. +Shift-Home moves the insertion cursor to the beginning of the spinbox +and also extends the selection to that point. +.IP [11] +The End key, or Control-e, will move the insertion cursor to the +end of the spinbox and clear any selection in the spinbox. +Shift-End moves the cursor to the end and extends the selection +to that point. +.IP [12] +The Select key and Control-Space set the selection anchor to the position +of the insertion cursor. They don't affect the current selection. +Shift-Select and Control-Shift-Space adjust the selection to the +current position of the insertion cursor, selecting from the anchor +to the insertion cursor if there was not any selection previously. +.IP [13] +Control-/ selects all the text in the spinbox. +.IP [14] +Control-\e clears any selection in the spinbox. +.IP [15] +The F16 key (labelled Copy on many Sun workstations) or Meta-w +copies the selection in the widget to the clipboard, if there is a selection. +.IP [16] +The F20 key (labelled Cut on many Sun workstations) or Control-w +copies the selection in the widget to the clipboard and deletes +the selection. +If there is no selection in the widget then these keys have no effect. +.IP [17] +The F18 key (labelled Paste on many Sun workstations) or Control-y +inserts the contents of the clipboard at the position of the +insertion cursor. +.IP [18] +The Delete key deletes the selection, if there is one in the spinbox. +If there is no selection, it deletes the character to the right of +the insertion cursor. +.IP [19] +The BackSpace key and Control-h delete the selection, if there is one +in the spinbox. +If there is no selection, it deletes the character to the left of +the insertion cursor. +.IP [20] +Control-d deletes the character to the right of the insertion cursor. +.IP [21] +Meta-d deletes the word to the right of the insertion cursor. +.IP [22] +Control-k deletes all the characters to the right of the insertion +cursor. +.IP [23] +Control-t reverses the order of the two characters to the right of +the insertion cursor. +.PP +If the spinbox is disabled using the \fB\-state\fR option, then the spinbox's +view can still be adjusted and text in the spinbox can still be selected, +but no insertion cursor will be displayed and no text modifications will +take place. +.PP +The behavior of spinboxes can be changed by defining new bindings for +individual widgets or by redefining the class bindings. + +.SH KEYWORDS +spinbox, entry, widget diff --git a/generic/tkEntry.c b/generic/tkEntry.c index 2271c49..f9031b9 100644 --- a/generic/tkEntry.c +++ b/generic/tkEntry.c @@ -1,5 +1,5 @@ /* - * tkEntry.c -- + * Entry.c -- * * This module implements entry widgets for the Tk * toolkit. An entry displays a string and allows @@ -7,18 +7,23 @@ * * Copyright (c) 1990-1994 The Regents of the University of California. * Copyright (c) 1994-1997 Sun Microsystems, Inc. + * Copyright (c) 2000 Ajuba Solutions. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkEntry.c,v 1.16 2000/05/17 22:23:25 ericm Exp $ + * RCS: @(#) $Id: tkEntry.c,v 1.17 2000/05/29 01:43:14 hobbs Exp $ */ #include "tkInt.h" #include "default.h" +enum EntryType { + TK_ENTRY, TK_SPINBOX +}; + /* - * A data structure of the following type is kept for each entry + * A data structure of the following type is kept for each Entry * widget managed by this file: */ @@ -34,7 +39,7 @@ typedef struct { Tcl_Command widgetCmd; /* Token for entry's widget command. */ Tk_OptionTable optionTable; /* Table that defines configuration options * available for this widget. */ - + enum EntryType type; /* Specialized type of Entry widget */ /* * Fields that are set by widget commands other than "configure". @@ -106,9 +111,6 @@ typedef struct { * characters. */ int selBorderWidth; /* Width of border around selection. */ XColor *selFgColorPtr; /* Foreground color for selected text. */ - char *showChar; /* Value of -show option. If non-NULL, first - * character is used for displaying all - * characters in entry. Malloc'ed. */ int state; /* Normal or disabled. Entry is read-only * when disabled. */ char *textVarName; /* Name of variable (malloc'ed) or NULL. @@ -122,23 +124,27 @@ typedef struct { char *scrollCmd; /* Command prefix for communicating with * scrollbar(s). Malloc'ed. NULL means * no command to issue. */ + char *showChar; /* Value of -show option. If non-NULL, first + * character is used for displaying all + * characters in entry. Malloc'ed. + * This is only used by the Entry widget. */ /* * Fields whose values are derived from the current values of the * configuration settings above. */ + char *displayString; /* String to use when displaying. This may + * be a pointer to string, or a pointer to + * malloced memory with the same character + * length as string but whose characters + * are all equal to showChar. */ int numBytes; /* Length of string in bytes. */ int numChars; /* Length of string in characters. Both * string and displayString have the same * character length, but may have different * byte lengths due to being made from * different UTF-8 characters. */ - char *displayString; /* String to use when displaying. This may - * be a pointer to string, or a pointer to - * malloced memory with the same character - * length as string but whose characters - * are all equal to showChar. */ int numDisplayBytes; /* Length of displayString in bytes. */ int inset; /* Number of pixels on the left and right * sides that are taken up by XPAD, borderWidth @@ -156,19 +162,78 @@ typedef struct { GC selTextGC; /* For drawing selected text. */ GC highlightGC; /* For drawing traversal highlight. */ int avgWidth; /* Width of average character. */ + int xWidth; /* Extra width to reserve for widget. + * Used by spinboxes for button space. */ int flags; /* Miscellaneous flags; see below for * definitions. */ - Tk_TSOffset tsoffset; + int validate; /* Non-zero means try to validate */ char *validateCmd; /* Command prefix to use when invoking * validate command. NULL means don't * invoke commands. Malloc'ed. */ - int validate; /* Non-zero means try to validate */ char *invalidCmd; /* Command called when a validation returns 0 * (successfully fails), defaults to {}. */ + } Entry; /* + * A data structure of the following type is kept for each spinbox + * widget managed by this file: + */ + +typedef struct { + Entry entry; /* A pointer to the generic entry structure. + * This must be the first element of the + * Spinbox. */ + + /* + * Spinbox specific configuration settings. + */ + + Tk_3DBorder activeBorder; /* Used for drawing border around active + * buttons. */ + Tk_3DBorder buttonBorder; /* Used for drawing border around buttons. */ + Tk_Cursor bCursor; /* cursor for buttons, or None. */ + int bdRelief; /* 3-D effect: TK_RELIEF_RAISED, etc. */ + int buRelief; /* 3-D effect: TK_RELIEF_RAISED, etc. */ + char *command; /* Command to invoke for spin buttons. + * NULL means no command to issue. */ + + /* + * Spinbox specific fields for use with configuration settings above. + */ + + int wrap; /* whether to wrap around when spinning */ + + int selElement; /* currently selected control */ + int curElement; /* currently mouseover control */ + + int repeatDelay; /* repeat delay */ + int repeatInterval; /* repeat interval */ + + double fromValue; /* Value corresponding to left/top of dial */ + double toValue; /* Value corresponding to right/bottom + * of dial */ + double increment; /* If > 0, all values are rounded to an + * even multiple of this value. */ + char *formatBuf; /* string into which to format value. + * Malloc'ed. */ + char *reqFormat; /* Sprintf conversion specifier used for the + * value that the users requests. Malloc'ed. */ + char *valueFormat; /* Sprintf conversion specifier used for + * the value. */ + char digitFormat[10]; /* Sprintf conversion specifier computed from + * digits and other information; used for + * the value. */ + + char *valueStr; /* Values List. Malloc'ed. */ + Tcl_Obj *listObj; /* Pointer to the list object being used */ + int eIndex; /* Holds the current index into elements */ + int nElements; /* Holds the current count of elements */ + +} Spinbox; + +/* * Assigned bits of "flags" fields of Entry structures, and what those * bits mean: * @@ -212,6 +277,12 @@ typedef struct { #define YPAD 1 /* + * A comparison function for double values. For Spinboxes. + */ +#define MIN_DBL_VAL 1E-9 +#define DOUBLES_EQ(d1, d2) (fabs((d1) - (d2)) < MIN_DBL_VAL) + +/* * The following enum is used to define a type for the -state option * of the Entry widget. These values are used as indices into the * string table below. @@ -238,16 +309,16 @@ enum validateType { /* * These extra enums are for use with EntryValidateChange */ - VALIDATE_FORCED, VALIDATE_DELETE, VALIDATE_INSERT + VALIDATE_FORCED, VALIDATE_DELETE, VALIDATE_INSERT, VALIDATE_BUTTON }; #define DEF_ENTRY_VALIDATE "none" #define DEF_ENTRY_INVALIDCMD "" /* - * Information used for argv parsing. + * Information used for Entry objv parsing. */ -static Tk_OptionSpec optionSpecs[] = { +static Tk_OptionSpec entryOptSpec[] = { {TK_OPTION_BORDER, "-background", "background", "Background", DEF_ENTRY_BG_COLOR, -1, Tk_Offset(Entry, normalBorder), 0, (ClientData) DEF_ENTRY_BG_MONO, 0}, @@ -359,11 +430,166 @@ static Tk_OptionSpec optionSpecs[] = { }; /* - * Flags for GetEntryIndex procedure: + * Information used for Spinbox objv parsing. */ -#define ZERO_OK 1 -#define LAST_PLUS_ONE_OK 2 +#define DEF_SPINBOX_REPEAT_DELAY "400" +#define DEF_SPINBOX_REPEAT_INTERVAL "100" + +#define DEF_SPINBOX_CMD "" + +#define DEF_SPINBOX_FROM "0" +#define DEF_SPINBOX_TO "0" +#define DEF_SPINBOX_INCREMENT "1" +#define DEF_SPINBOX_FORMAT "" + +#define DEF_SPINBOX_VALUES "" +#define DEF_SPINBOX_WRAP "0" + +static Tk_OptionSpec sbOptSpec[] = { + {TK_OPTION_BORDER, "-activebackground", "activeBackground", "Background", + DEF_BUTTON_ACTIVE_BG_COLOR, -1, Tk_Offset(Spinbox, activeBorder), + 0, (ClientData) DEF_BUTTON_ACTIVE_BG_MONO, 0}, + {TK_OPTION_BORDER, "-background", "background", "Background", + DEF_ENTRY_BG_COLOR, -1, Tk_Offset(Entry, normalBorder), + 0, (ClientData) DEF_ENTRY_BG_MONO, 0}, + {TK_OPTION_SYNONYM, "-bd", (char *) NULL, (char *) NULL, + (char *) NULL, 0, -1, 0, (ClientData) "-borderwidth", 0}, + {TK_OPTION_SYNONYM, "-bg", (char *) NULL, (char *) NULL, + (char *) NULL, 0, -1, 0, (ClientData) "-background", 0}, + {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", + DEF_ENTRY_BORDER_WIDTH, -1, Tk_Offset(Entry, borderWidth), + 0, 0, 0}, + {TK_OPTION_BORDER, "-buttonbackground", "Button.background", "Background", + DEF_BUTTON_BG_COLOR, -1, Tk_Offset(Spinbox, buttonBorder), + 0, (ClientData) DEF_BUTTON_BG_MONO, 0}, + {TK_OPTION_CURSOR, "-buttoncursor", "Button.cursor", "Cursor", + DEF_BUTTON_CURSOR, -1, Tk_Offset(Spinbox, bCursor), + TK_OPTION_NULL_OK, 0, 0}, + {TK_OPTION_RELIEF, "-buttondownrelief", "Button.relief", "Relief", + DEF_BUTTON_RELIEF, -1, Tk_Offset(Spinbox, bdRelief), + 0, 0, 0}, + {TK_OPTION_RELIEF, "-buttonuprelief", "Button.relief", "Relief", + DEF_BUTTON_RELIEF, -1, Tk_Offset(Spinbox, buRelief), + 0, 0, 0}, + {TK_OPTION_STRING, "-command", "command", "Command", + DEF_SPINBOX_CMD, -1, Tk_Offset(Spinbox, command), + TK_OPTION_NULL_OK, 0, 0}, + {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor", + DEF_ENTRY_CURSOR, -1, Tk_Offset(Entry, cursor), + TK_OPTION_NULL_OK, 0, 0}, + {TK_OPTION_BORDER, "-disabledbackground", "disabledBackground", + "DisabledBackground", DEF_ENTRY_DISABLED_BG_COLOR, -1, + Tk_Offset(Entry, disabledBorder), TK_OPTION_NULL_OK, + (ClientData) DEF_ENTRY_DISABLED_BG_MONO, 0}, + {TK_OPTION_COLOR, "-disabledforeground", "disabledForeground", + "DisabledForeground", DEF_ENTRY_DISABLED_FG, -1, + Tk_Offset(Entry, dfgColorPtr), TK_OPTION_NULL_OK, 0, 0}, + {TK_OPTION_BOOLEAN, "-exportselection", "exportSelection", + "ExportSelection", DEF_ENTRY_EXPORT_SELECTION, -1, + Tk_Offset(Entry, exportSelection), 0, 0, 0}, + {TK_OPTION_SYNONYM, "-fg", "foreground", (char *) NULL, + (char *) NULL, 0, -1, 0, (ClientData) "-foreground", 0}, + {TK_OPTION_FONT, "-font", "font", "Font", + DEF_ENTRY_FONT, -1, Tk_Offset(Entry, tkfont), 0, 0, 0}, + {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground", + DEF_ENTRY_FG, -1, Tk_Offset(Entry, fgColorPtr), 0, + 0, 0}, + {TK_OPTION_STRING, "-format", "format", "Format", + DEF_SPINBOX_FORMAT, -1, Tk_Offset(Spinbox, reqFormat), + TK_OPTION_NULL_OK, 0, 0}, + {TK_OPTION_DOUBLE, "-from", "from", "From", + DEF_SPINBOX_FROM, -1, Tk_Offset(Spinbox, fromValue), 0, 0, 0}, + {TK_OPTION_COLOR, "-highlightbackground", "highlightBackground", + "HighlightBackground", DEF_ENTRY_HIGHLIGHT_BG, + -1, Tk_Offset(Entry, highlightBgColorPtr), + 0, 0, 0}, + {TK_OPTION_COLOR, "-highlightcolor", "highlightColor", "HighlightColor", + DEF_ENTRY_HIGHLIGHT, -1, Tk_Offset(Entry, highlightColorPtr), + 0, 0, 0}, + {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness", + "HighlightThickness", DEF_ENTRY_HIGHLIGHT_WIDTH, -1, + Tk_Offset(Entry, highlightWidth), 0, 0, 0}, + {TK_OPTION_DOUBLE, "-increment", "increment", "Increment", + DEF_SPINBOX_INCREMENT, -1, Tk_Offset(Spinbox, increment), 0, 0, 0}, + {TK_OPTION_BORDER, "-insertbackground", "insertBackground", "Foreground", + DEF_ENTRY_INSERT_BG, -1, Tk_Offset(Entry, insertBorder), + 0, 0, 0}, + {TK_OPTION_PIXELS, "-insertborderwidth", "insertBorderWidth", + "BorderWidth", DEF_ENTRY_INSERT_BD_COLOR, -1, + Tk_Offset(Entry, insertBorderWidth), 0, + (ClientData) DEF_ENTRY_INSERT_BD_MONO, 0}, + {TK_OPTION_INT, "-insertofftime", "insertOffTime", "OffTime", + DEF_ENTRY_INSERT_OFF_TIME, -1, Tk_Offset(Entry, insertOffTime), + 0, 0, 0}, + {TK_OPTION_INT, "-insertontime", "insertOnTime", "OnTime", + DEF_ENTRY_INSERT_ON_TIME, -1, Tk_Offset(Entry, insertOnTime), + 0, 0, 0}, + {TK_OPTION_PIXELS, "-insertwidth", "insertWidth", "InsertWidth", + DEF_ENTRY_INSERT_WIDTH, -1, Tk_Offset(Entry, insertWidth), + 0, 0, 0}, + {TK_OPTION_STRING, "-invalidcommand", "invalidCommand", "InvalidCommand", + DEF_ENTRY_INVALIDCMD, -1, Tk_Offset(Entry, invalidCmd), + TK_OPTION_NULL_OK, 0, 0}, + {TK_OPTION_SYNONYM, "-invcmd", (char *) NULL, (char *) NULL, + (char *) NULL, 0, -1, 0, (ClientData) "-invalidcommand", 0}, + {TK_OPTION_JUSTIFY, "-justify", "justify", "Justify", + DEF_ENTRY_JUSTIFY, -1, Tk_Offset(Entry, justify), 0, 0, 0}, + {TK_OPTION_RELIEF, "-relief", "relief", "Relief", + DEF_ENTRY_RELIEF, -1, Tk_Offset(Entry, relief), + 0, 0, 0}, + {TK_OPTION_BORDER, "-readonlybackground", "readonlyBackground", + "ReadonlyBackground", DEF_ENTRY_READONLY_BG_COLOR, -1, + Tk_Offset(Entry, readonlyBorder), TK_OPTION_NULL_OK, + (ClientData) DEF_ENTRY_READONLY_BG_MONO, 0}, + {TK_OPTION_INT, "-repeatdelay", "repeatDelay", "RepeatDelay", + DEF_SPINBOX_REPEAT_DELAY, -1, Tk_Offset(Spinbox, repeatDelay), + 0, 0, 0}, + {TK_OPTION_INT, "-repeatinterval", "repeatInterval", "RepeatInterval", + DEF_SPINBOX_REPEAT_INTERVAL, -1, Tk_Offset(Spinbox, repeatInterval), + 0, 0, 0}, + {TK_OPTION_BORDER, "-selectbackground", "selectBackground", "Foreground", + DEF_ENTRY_SELECT_COLOR, -1, Tk_Offset(Entry, selBorder), + 0, (ClientData) DEF_ENTRY_SELECT_MONO, 0}, + {TK_OPTION_PIXELS, "-selectborderwidth", "selectBorderWidth", + "BorderWidth", DEF_ENTRY_SELECT_BD_COLOR, -1, + Tk_Offset(Entry, selBorderWidth), + 0, (ClientData) DEF_ENTRY_SELECT_BD_MONO, 0}, + {TK_OPTION_COLOR, "-selectforeground", "selectForeground", "Background", + DEF_ENTRY_SELECT_FG_COLOR, -1, Tk_Offset(Entry, selFgColorPtr), + 0, (ClientData) DEF_ENTRY_SELECT_FG_MONO, 0}, + {TK_OPTION_STRING_TABLE, "-state", "state", "State", + DEF_ENTRY_STATE, -1, Tk_Offset(Entry, state), + 0, (ClientData) stateStrings, 0}, + {TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus", + DEF_ENTRY_TAKE_FOCUS, -1, Tk_Offset(Entry, takeFocus), + TK_CONFIG_NULL_OK, 0, 0}, + {TK_OPTION_STRING, "-textvariable", "textVariable", "Variable", + DEF_ENTRY_TEXT_VARIABLE, -1, Tk_Offset(Entry, textVarName), + TK_CONFIG_NULL_OK, 0, 0}, + {TK_OPTION_DOUBLE, "-to", "to", "To", + DEF_SPINBOX_TO, -1, Tk_Offset(Spinbox, toValue), 0, 0, 0}, + {TK_OPTION_STRING_TABLE, "-validate", "validate", "Validate", + DEF_ENTRY_VALIDATE, -1, Tk_Offset(Entry, validate), + 0, (ClientData) validateStrings, 0}, + {TK_OPTION_STRING, "-validatecommand", "validateCommand", "ValidateCommand", + (char *) NULL, -1, Tk_Offset(Entry, validateCmd), + TK_CONFIG_NULL_OK, 0, 0}, + {TK_OPTION_STRING, "-values", "values", "Values", + DEF_SPINBOX_VALUES, -1, Tk_Offset(Spinbox, valueStr), + TK_OPTION_NULL_OK, 0, 0}, + {TK_OPTION_SYNONYM, "-vcmd", (char *) NULL, (char *) NULL, + (char *) NULL, 0, -1, 0, (ClientData) "-validatecommand", 0}, + {TK_OPTION_INT, "-width", "width", "Width", + DEF_ENTRY_WIDTH, -1, Tk_Offset(Entry, prefWidth), 0, 0, 0}, + {TK_OPTION_BOOLEAN, "-wrap", "wrap", "Wrap", + DEF_SPINBOX_WRAP, -1, Tk_Offset(Spinbox, wrap), 0, 0, 0}, + {TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand", + DEF_ENTRY_SCROLL_COMMAND, -1, Tk_Offset(Entry, scrollCmd), + TK_CONFIG_NULL_OK, 0, 0}, + {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL, + (char *) NULL, 0, -1, 0, 0, 0} +}; /* * The following tables define the entry widget commands (and sub- @@ -371,27 +597,74 @@ static Tk_OptionSpec optionSpecs[] = { * enumerated types used to dispatch the entry widget command. */ -static char *commandNames[] = { +static char *entryCmdNames[] = { "bbox", "cget", "configure", "delete", "get", "icursor", "index", "insert", "scan", "selection", "validate", "xview", (char *) NULL }; -enum command { +enum entryCmd { COMMAND_BBOX, COMMAND_CGET, COMMAND_CONFIGURE, COMMAND_DELETE, COMMAND_GET, COMMAND_ICURSOR, COMMAND_INDEX, COMMAND_INSERT, COMMAND_SCAN, COMMAND_SELECTION, COMMAND_VALIDATE, COMMAND_XVIEW }; -static char *selCommandNames[] = { +static char *selCmdNames[] = { "adjust", "clear", "from", "present", "range", "to", (char *) NULL }; -enum selcommand { +enum selCmd { SELECTION_ADJUST, SELECTION_CLEAR, SELECTION_FROM, SELECTION_PRESENT, SELECTION_RANGE, SELECTION_TO }; /* + * The following tables define the spinbox widget commands (and sub- + * commands) and map the indexes into the string tables into + * enumerated types used to dispatch the spinbox widget command. + */ + +static char *sbCmdNames[] = { + "bbox", "cget", "configure", "delete", "get", "icursor", "identify", + "index", "insert", "invoke", "scan", "selection", "set", + "validate", "xview", (char *) NULL +}; + +enum sbCmd { + SB_CMD_BBOX, SB_CMD_CGET, SB_CMD_CONFIGURE, SB_CMD_DELETE, + SB_CMD_GET, SB_CMD_ICURSOR, SB_CMD_IDENTIFY, SB_CMD_INDEX, + SB_CMD_INSERT, SB_CMD_INVOKE, SB_CMD_SCAN, SB_CMD_SELECTION, + SB_CMD_SET, SB_CMD_VALIDATE, SB_CMD_XVIEW +}; + +static char *sbSelCmdNames[] = { + "adjust", "clear", "element", "from", "present", "range", "to", + (char *) NULL +}; + +enum sbselCmd { + SB_SEL_ADJUST, SB_SEL_CLEAR, SB_SEL_ELEMENT, SB_SEL_FROM, + SB_SEL_PRESENT, SB_SEL_RANGE, SB_SEL_TO +}; + +/* + * Extra for selection of elements + */ + +static char *selElementNames[] = { + "none", "buttondown", "buttonup", (char *) NULL, "entry" +}; +enum selelement { + SEL_NONE, SEL_BUTTONDOWN, SEL_BUTTONUP, SEL_NULL, SEL_ENTRY +}; + +/* + * Flags for GetEntryIndex procedure: + */ + +#define ZERO_OK 1 +#define LAST_PLUS_ONE_OK 2 + +/* * Forward declarations for procedures defined later in this file: */ @@ -431,7 +704,8 @@ static int EntryValidateChange _ANSI_ARGS_((Entry *entryPtr, static void ExpandPercents _ANSI_ARGS_((Entry *entryPtr, char *before, char *change, char *new, int index, int type, Tcl_DString *dsPtr)); -static void EntryValueChanged _ANSI_ARGS_((Entry *entryPtr)); +static void EntryValueChanged _ANSI_ARGS_((Entry *entryPtr, + char *newValue)); static void EntryVisibleRange _ANSI_ARGS_((Entry *entryPtr, double *firstPtr, double *lastPtr)); static int EntryWidgetObjCmd _ANSI_ARGS_((ClientData clientData, @@ -445,7 +719,20 @@ static void InsertChars _ANSI_ARGS_((Entry *entryPtr, int index, char *string)); /* - * The structure below defines entry class behavior by means of procedures + * These forward declarations are the spinbox specific ones: + */ + +static int SpinboxWidgetObjCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, + Tcl_Obj *CONST objv[])); +static int GetSpinboxElement _ANSI_ARGS_((Spinbox *sbPtr, + int x, int y)); +static int SpinboxInvoke _ANSI_ARGS_((Tcl_Interp *interp, + Spinbox *sbPtr, int element)); +static int ComputeFormat _ANSI_ARGS_((Spinbox *sbPtr)); + +/* + * The structure below defines widget class behavior by means of procedures * that can be invoked from generic window code. */ @@ -497,7 +784,7 @@ Tk_EntryObjCmd(clientData, interp, objc, objv) * invocations of the command. */ - optionTable = Tk_CreateOptionTable(interp, optionSpecs); + optionTable = Tk_CreateOptionTable(interp, entryOptSpec); name = Tcl_GetString(objv[0]); Tcl_GetCommandInfo(interp, name, &info); info.objClientData = (ClientData) optionTable; @@ -518,10 +805,13 @@ Tk_EntryObjCmd(clientData, interp, objc, objv) /* * Initialize the fields of the structure that won't be initialized * by ConfigureEntry, or that ConfigureEntry requires to be - * initialized already (e.g. resource pointers). + * initialized already (e.g. resource pointers). Only the non-NULL/0 + * data must be initialized as memset covers the rest. */ entryPtr = (Entry *) ckalloc(sizeof(Entry)); + memset((VOID *) entryPtr, 0, sizeof(Entry)); + entryPtr->tkwin = tkwin; entryPtr->display = Tk_Display(tkwin); entryPtr->interp = interp; @@ -529,62 +819,24 @@ Tk_EntryObjCmd(clientData, interp, objc, objv) Tk_PathName(entryPtr->tkwin), EntryWidgetObjCmd, (ClientData) entryPtr, EntryCmdDeletedProc); entryPtr->optionTable = optionTable; + entryPtr->type = TK_ENTRY; entryPtr->string = (char *) ckalloc(1); entryPtr->string[0] = '\0'; - entryPtr->insertPos = 0; entryPtr->selectFirst = -1; entryPtr->selectLast = -1; - entryPtr->selectAnchor = 0; - entryPtr->scanMarkX = 0; - entryPtr->scanMarkIndex = 0; - - entryPtr->normalBorder = NULL; - entryPtr->disabledBorder = NULL; - entryPtr->readonlyBorder = NULL; - entryPtr->borderWidth = 0; + entryPtr->cursor = None; entryPtr->exportSelection = 1; - entryPtr->tkfont = NULL; - entryPtr->fgColorPtr = NULL; - entryPtr->dfgColorPtr = NULL; - entryPtr->highlightBgColorPtr = NULL; - entryPtr->highlightColorPtr = NULL; - entryPtr->highlightWidth = 0; - entryPtr->insertBorder = NULL; - entryPtr->insertBorderWidth = 0; - entryPtr->insertOffTime = 0; - entryPtr->insertOnTime = 0; - entryPtr->insertWidth = 0; entryPtr->justify = TK_JUSTIFY_LEFT; entryPtr->relief = TK_RELIEF_FLAT; - entryPtr->selBorder = NULL; - entryPtr->selBorderWidth = 0; - entryPtr->selFgColorPtr = NULL; - entryPtr->showChar = NULL; entryPtr->state = STATE_NORMAL; - entryPtr->textVarName = NULL; - entryPtr->takeFocus = NULL; - entryPtr->prefWidth = 0; - entryPtr->scrollCmd = NULL; - entryPtr->numBytes = 0; - entryPtr->numChars = 0; entryPtr->displayString = entryPtr->string; - entryPtr->numDisplayBytes = 0; entryPtr->inset = XPAD; - entryPtr->textLayout = NULL; - entryPtr->layoutX = 0; - entryPtr->layoutY = 0; - entryPtr->leftX = 0; - entryPtr->leftIndex = 0; - entryPtr->insertBlinkHandler = (Tcl_TimerToken) NULL; entryPtr->textGC = None; entryPtr->selTextGC = None; entryPtr->highlightGC = None; entryPtr->avgWidth = 1; - entryPtr->flags = 0; - entryPtr->validateCmd = NULL; entryPtr->validate = VALIDATE_NONE; - entryPtr->invalidCmd = NULL; Tk_SetClass(entryPtr->tkwin, "Entry"); TkSetClassProcs(entryPtr->tkwin, &entryClass, (ClientData) entryPtr); @@ -645,13 +897,13 @@ EntryWidgetObjCmd(clientData, interp, objc, objv) * the list of valid command names. */ - result = Tcl_GetIndexFromObj(interp, objv[1], commandNames, + result = Tcl_GetIndexFromObj(interp, objv[1], entryCmdNames, "option", 0, &cmdIndex); if (result != TCL_OK) { return result; } - switch (cmdIndex) { + switch ((enum entryCmd) cmdIndex) { case COMMAND_BBOX: { int index, x, y, width, height; char buf[TCL_INTEGER_SPACE * 4]; @@ -826,15 +1078,15 @@ EntryWidgetObjCmd(clientData, interp, objc, objv) /* * Parse the selection sub-command, using the command - * table "selCommandNames" defined above. + * table "selCmdNames" defined above. */ - result = Tcl_GetIndexFromObj(interp, objv[2], selCommandNames, + result = Tcl_GetIndexFromObj(interp, objv[2], selCmdNames, "selection option", 0, &selIndex); if (result != TCL_OK) { goto error; } - + /* * Disabled entries don't allow the selection to be modified. */ @@ -842,7 +1094,7 @@ EntryWidgetObjCmd(clientData, interp, objc, objv) if (entryPtr->state == STATE_DISABLED) { goto done; } - + switch(selIndex) { case SELECTION_ADJUST: { if (objc != 4) { @@ -1076,7 +1328,7 @@ DestroyEntry(memPtr) Tcl_DeleteCommandFromToken(entryPtr->interp, entryPtr->widgetCmd); if (entryPtr->flags & REDRAW_PENDING) { - Tcl_CancelIdleCall(DisplayEntry, (ClientData) entryPtr); + Tcl_CancelIdleCall(DisplayEntry, (ClientData) entryPtr); } /* @@ -1101,6 +1353,17 @@ DestroyEntry(memPtr) if (entryPtr->displayString != entryPtr->string) { ckfree(entryPtr->displayString); } + if (entryPtr->type == TK_SPINBOX) { + Spinbox *sbPtr = (Spinbox *) entryPtr; + + if (sbPtr->listObj != NULL) { + Tcl_DecrRefCount(sbPtr->listObj); + sbPtr->listObj = NULL; + } + if (sbPtr->formatBuf) { + ckfree(sbPtr->formatBuf); + } + } Tk_FreeTextLayout(entryPtr->textLayout); Tk_FreeConfigOptions((char *) entryPtr, entryPtr->optionTable, entryPtr->tkwin); @@ -1139,10 +1402,17 @@ ConfigureEntry(interp, entryPtr, objc, objv, flags) int flags; /* Flags to pass to Tk_ConfigureWidget. */ { Tk_SavedOptions savedOptions; - Tcl_Obj *errorResult = NULL; Tk_3DBorder border; + Tcl_Obj *errorResult = NULL; + Spinbox *sbPtr = (Spinbox *) entryPtr; /* Only used when this widget + * is of type TK_SPINBOX */ + char *oldValues = NULL; /* lint initialization */ + char *oldFormat = NULL; /* lint initialization */ int error; - int oldExport; + int oldExport = 0; /* lint initialization */ + int valuesChanged = 0; /* lint initialization */ + double oldFrom = 0.0; /* lint initialization */ + double oldTo = 0.0; /* lint initialization */ /* * Eliminate any existing trace on a variable monitored by the entry. @@ -1154,7 +1424,17 @@ ConfigureEntry(interp, entryPtr, objc, objv, flags) EntryTextVarProc, (ClientData) entryPtr); } - oldExport = entryPtr->exportSelection; + /* + * Store old values that we need to effect certain behavior if + * they change value + */ + oldExport = entryPtr->exportSelection; + if (entryPtr->type == TK_SPINBOX) { + oldValues = sbPtr->valueStr; + oldFormat = sbPtr->reqFormat; + oldFrom = sbPtr->fromValue; + oldTo = sbPtr->toValue; + } for (error = 0; error <= 1; error++) { if (!error) { @@ -1182,11 +1462,11 @@ ConfigureEntry(interp, entryPtr, objc, objv, flags) * the geometry and setting the background from a 3-D border. */ - if (entryPtr->state == STATE_DISABLED && - entryPtr->disabledBorder != NULL) { + if ((entryPtr->state == STATE_DISABLED) && + (entryPtr->disabledBorder != NULL)) { border = entryPtr->disabledBorder; - } else if (entryPtr->state == STATE_READONLY && - entryPtr->readonlyBorder != NULL) { + } else if ((entryPtr->state == STATE_READONLY) && + (entryPtr->readonlyBorder != NULL)) { border = entryPtr->readonlyBorder; } else { border = entryPtr->normalBorder; @@ -1200,6 +1480,80 @@ ConfigureEntry(interp, entryPtr, objc, objv, flags) entryPtr->insertBorderWidth = entryPtr->insertWidth/2; } + if (entryPtr->type == TK_SPINBOX) { + if (sbPtr->fromValue > sbPtr->toValue) { + Tcl_SetResult(interp, + "-to value must be greater than -from value", + TCL_VOLATILE); + continue; + } + + if (sbPtr->reqFormat && (oldFormat != sbPtr->reqFormat)) { + /* + * Make sure that the given format is somewhat correct, and + * calculate the minimum space we'll need for the values as + * strings. + */ + int min, max; + size_t formatLen, formatSpace = TCL_DOUBLE_SPACE; + char fbuf[4], *fmt = sbPtr->reqFormat; + + formatLen = strlen(fmt); + if ((fmt[0] != '%') || (fmt[formatLen-1] != 'f')) { + badFormatOpt: + Tcl_AppendResult(interp, "bad spinbox format specifier \"", + sbPtr->reqFormat, "\"", (char *) NULL); + continue; + } + if ((sscanf(fmt, "%%%d.%d%[f]", &min, &max, fbuf) == 3) + && (max >= 0)) { + formatSpace = min + max + 1; + } else if (((sscanf(fmt, "%%.%d%[f]", &min, fbuf) == 2) + || (sscanf(fmt, "%%%d%[f]", &min, fbuf) == 2) + || (sscanf(fmt, "%%%d.%[f]", &min, fbuf) == 2)) + && (min >= 0)) { + formatSpace = min + 1; + } else { + goto badFormatOpt; + } + if (formatSpace < TCL_DOUBLE_SPACE) { + formatSpace = TCL_DOUBLE_SPACE; + } + sbPtr->formatBuf = ckrealloc(sbPtr->formatBuf, formatSpace); + /* + * We perturb the value of oldFrom to allow us to go into + * the branch below that will reformat the displayed value. + */ + oldFrom = sbPtr->fromValue - 1; + } + + /* + * See if we have to rearrange our listObj data + */ + if (oldValues != sbPtr->valueStr) { + if (sbPtr->listObj != NULL) { + Tcl_DecrRefCount(sbPtr->listObj); + } + sbPtr->listObj = NULL; + if (sbPtr->valueStr != NULL) { + Tcl_Obj *newObjPtr; + int nelems; + + newObjPtr = Tcl_NewStringObj(sbPtr->valueStr, -1); + if (Tcl_ListObjLength(interp, newObjPtr, &nelems) + != TCL_OK) { + valuesChanged = -1; + continue; + } + sbPtr->listObj = newObjPtr; + Tcl_IncrRefCount(sbPtr->listObj); + sbPtr->nElements = nelems; + sbPtr->eIndex = 0; + valuesChanged++; + } + } + } + /* * Restart the cursor timing sequence in case the on-time or * off-time just changed. Set validate temporarily to none, @@ -1243,6 +1597,46 @@ ConfigureEntry(interp, entryPtr, objc, objv, flags) Tk_FreeSavedOptions(&savedOptions); } + if (entryPtr->type == TK_SPINBOX) { + ComputeFormat(sbPtr); + + if (valuesChanged > 0) { + Tcl_Obj *objPtr; + + /* + * No check for error return, because there shouldn't be one + * given the check for valid list above + */ + Tcl_ListObjIndex(interp, sbPtr->listObj, 0, &objPtr); + EntryValueChanged(entryPtr, Tcl_GetString(objPtr)); + } else if ((sbPtr->valueStr == NULL) + && !DOUBLES_EQ(sbPtr->fromValue, sbPtr->toValue) + && (!DOUBLES_EQ(sbPtr->fromValue, oldFrom) + || !DOUBLES_EQ(sbPtr->toValue, oldTo))) { + /* + * If the valueStr is empty and -from && -to are specified, check + * to see if the current string is within the range. If not, + * it will be constrained to the nearest edge. If the current + * string isn't a double value, we set it to -from. + */ + int code; + double dvalue; + + code = Tcl_GetDouble(NULL, entryPtr->string, &dvalue); + if (code != TCL_OK) { + dvalue = sbPtr->fromValue; + } else { + if (dvalue > sbPtr->toValue) { + dvalue = sbPtr->toValue; + } else if (dvalue < sbPtr->fromValue) { + dvalue = sbPtr->fromValue; + } + } + sprintf(sbPtr->formatBuf, sbPtr->valueFormat, dvalue); + EntryValueChanged(entryPtr, sbPtr->formatBuf); + } + } + /* * If the entry is tied to the value of a variable, then set up * a trace on the variable's value, create the variable if it doesn't @@ -1254,7 +1648,7 @@ ConfigureEntry(interp, entryPtr, objc, objv, flags) value = Tcl_GetVar(interp, entryPtr->textVarName, TCL_GLOBAL_ONLY); if (value == NULL) { - EntryValueChanged(entryPtr); + EntryValueChanged(entryPtr, NULL); } else { EntrySetValue(entryPtr, value); } @@ -1298,23 +1692,32 @@ EntryWorldChanged(instanceData) XGCValues gcValues; GC gc = None; unsigned long mask; - Entry *entryPtr; Tk_3DBorder border; XColor *colorPtr; - - entryPtr = (Entry *) instanceData; + Entry *entryPtr = (Entry *) instanceData; entryPtr->avgWidth = Tk_TextWidth(entryPtr->tkfont, "0", 1); if (entryPtr->avgWidth == 0) { entryPtr->avgWidth = 1; } + if (entryPtr->type == TK_SPINBOX) { + /* + * Compute the button width for a spinbox + */ + + entryPtr->xWidth = entryPtr->avgWidth + 2 * (1+XPAD); + if (entryPtr->xWidth < 11) { + entryPtr->xWidth = 11; /* we want a min visible size */ + } + } + /* * Default background and foreground are from the normal state. * In a disabled state, both of those may be overridden; in the readonly * state, the background may be overridden. */ - + border = entryPtr->normalBorder; colorPtr = entryPtr->fgColorPtr; switch (entryPtr->state) { @@ -1332,7 +1735,7 @@ EntryWorldChanged(instanceData) } break; } - + Tk_SetBackgroundFromBorder(entryPtr->tkwin, border); gcValues.foreground = colorPtr->pixel; gcValues.font = Tk_FontId(entryPtr->tkfont); @@ -1386,12 +1789,11 @@ DisplayEntry(clientData) Entry *entryPtr = (Entry *) clientData; Tk_Window tkwin = entryPtr->tkwin; int baseY, selStartX, selEndX, cursorX; - int xBound; + int showSelection, xBound; Tk_FontMetrics fm; Pixmap pixmap; - int showSelection; Tk_3DBorder border; - + entryPtr->flags &= ~REDRAW_PENDING; if ((entryPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) { return; @@ -1423,7 +1825,7 @@ DisplayEntry(clientData) * one, plus vertical position of baseline of text. */ - xBound = Tk_Width(tkwin) - entryPtr->inset; + xBound = Tk_Width(tkwin) - entryPtr->inset - entryPtr->xWidth; baseY = (Tk_Height(tkwin) + fm.ascent - fm.descent) / 2; /* @@ -1443,11 +1845,11 @@ DisplayEntry(clientData) * insertion cursor background. */ - if (entryPtr->state == STATE_DISABLED && - entryPtr->disabledBorder != NULL) { + if ((entryPtr->state == STATE_DISABLED) && + (entryPtr->disabledBorder != NULL)) { border = entryPtr->disabledBorder; - } else if (entryPtr->state == STATE_READONLY && - entryPtr->readonlyBorder != NULL) { + } else if ((entryPtr->state == STATE_READONLY) && + (entryPtr->readonlyBorder != NULL)) { border = entryPtr->readonlyBorder; } else { border = entryPtr->normalBorder; @@ -1455,7 +1857,7 @@ DisplayEntry(clientData) Tk_Fill3DRectangle(tkwin, pixmap, border, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT); - if (showSelection && entryPtr->state != STATE_DISABLED + if (showSelection && (entryPtr->state != STATE_DISABLED) && (entryPtr->selectLast > entryPtr->leftIndex)) { if (entryPtr->selectFirst <= entryPtr->leftIndex) { selStartX = entryPtr->leftX; @@ -1516,7 +1918,7 @@ DisplayEntry(clientData) entryPtr->textLayout, entryPtr->layoutX, entryPtr->layoutY, entryPtr->leftIndex, entryPtr->numChars); - if (showSelection && entryPtr->state != STATE_DISABLED + if (showSelection && (entryPtr->state != STATE_DISABLED) && (entryPtr->selTextGC != entryPtr->textGC) && (entryPtr->selectFirst < entryPtr->selectLast)) { int selFirst; @@ -1531,29 +1933,106 @@ DisplayEntry(clientData) selFirst, entryPtr->selectLast); } + if (entryPtr->type == TK_SPINBOX) { + int startx, height, inset, pad, tHeight, xWidth; + Spinbox *sbPtr = (Spinbox *) entryPtr; + + /* + * Draw the spin button controls. + */ + xWidth = entryPtr->xWidth; + pad = XPAD + 1; + inset = entryPtr->inset - XPAD; + startx = Tk_Width(tkwin) - (xWidth + inset); + height = (Tk_Height(tkwin) - 2*inset)/2; +#if 0 + Tk_Fill3DRectangle(tkwin, pixmap, sbPtr->buttonBorder, + startx, inset, xWidth, height, 1, sbPtr->buRelief); + Tk_Fill3DRectangle(tkwin, pixmap, sbPtr->buttonBorder, + startx, inset+height, xWidth, height, 1, sbPtr->bdRelief); +#else + Tk_Fill3DRectangle(tkwin, pixmap, sbPtr->buttonBorder, + startx, inset, xWidth, height, 1, + (sbPtr->selElement == SEL_BUTTONUP) ? + TK_RELIEF_SUNKEN : TK_RELIEF_RAISED); + Tk_Fill3DRectangle(tkwin, pixmap, sbPtr->buttonBorder, + startx, inset+height, xWidth, height, 1, + (sbPtr->selElement == SEL_BUTTONDOWN) ? + TK_RELIEF_SUNKEN : TK_RELIEF_RAISED); +#endif + + xWidth -= 2*pad; + /* + * Only draw the triangles if we have enough display space + */ + if ((xWidth > 1)) { + XPoint points[3]; + int starty, space, offset; + + space = height - 2*pad; + /* + * Ensure width of triangle is odd to guarantee a sharp tip + */ + if (!(xWidth % 2)) { + xWidth++; + } + tHeight = (xWidth + 1) / 2; + if (tHeight > space) { + tHeight = space; + } + space = (space - tHeight) / 2; + startx += pad; + starty = inset + height - pad - space; + offset = (sbPtr->selElement == SEL_BUTTONUP); + /* + * The points are slightly different for the up and down arrows + * because (for *.x), we need to account for a bug in the way + * XFillPolygon draws triangles, and we want to shift + * the arrows differently when allowing for depressed behavior. + */ + points[0].x = startx + offset; + points[0].y = starty + (offset ? 0 : -1); + points[1].x = startx + xWidth/2 + offset; + points[1].y = starty - tHeight + (offset ? 0 : -1); + points[2].x = startx + xWidth + offset; + points[2].y = points[0].y; + XFillPolygon(entryPtr->display, pixmap, entryPtr->textGC, + points, 3, Convex, CoordModeOrigin); + + starty = inset + height + pad + space; + offset = (sbPtr->selElement == SEL_BUTTONDOWN); + points[0].x = startx + 1 + offset; + points[0].y = starty + (offset ? 1 : 0); + points[1].x = startx + xWidth/2 + offset; + points[1].y = starty + tHeight + (offset ? 0 : -1); + points[2].x = startx - 1 + xWidth + offset; + points[2].y = points[0].y; + XFillPolygon(entryPtr->display, pixmap, entryPtr->textGC, + points, 3, Convex, CoordModeOrigin); + } + } + /* * Draw the border and focus highlight last, so they will overwrite * any text that extends past the viewable part of the window. */ + xBound = entryPtr->highlightWidth; if (entryPtr->relief != TK_RELIEF_FLAT) { - Tk_Draw3DRectangle(tkwin, pixmap, border, - entryPtr->highlightWidth, entryPtr->highlightWidth, - Tk_Width(tkwin) - 2 * entryPtr->highlightWidth, - Tk_Height(tkwin) - 2 * entryPtr->highlightWidth, + Tk_Draw3DRectangle(tkwin, pixmap, border, xBound, xBound, + Tk_Width(tkwin) - 2 * xBound, + Tk_Height(tkwin) - 2 * xBound, entryPtr->borderWidth, entryPtr->relief); } - if (entryPtr->highlightWidth != 0) { + if (xBound > 0) { GC fgGC, bgGC; bgGC = Tk_GCForColor(entryPtr->highlightBgColorPtr, pixmap); if (entryPtr->flags & GOT_FOCUS) { fgGC = Tk_GCForColor(entryPtr->highlightColorPtr, pixmap); - TkpDrawHighlightBorder(tkwin, fgGC, bgGC, - entryPtr->highlightWidth, pixmap); + TkpDrawHighlightBorder(tkwin, fgGC, bgGC, xBound, pixmap); } else { - TkpDrawHighlightBorder(tkwin, bgGC, bgGC, - entryPtr->highlightWidth, pixmap); + TkpDrawHighlightBorder(tkwin, bgGC, bgGC, xBound, pixmap); } } @@ -1634,6 +2113,7 @@ EntryComputeGeometry(entryPtr) } *p = '\0'; } + Tk_FreeTextLayout(entryPtr->textLayout); entryPtr->textLayout = Tk_ComputeTextLayout(entryPtr->tkfont, entryPtr->displayString, entryPtr->numChars, 0, @@ -1648,16 +2128,18 @@ EntryComputeGeometry(entryPtr) * window unless the entire window is full. */ - overflow = totalLength - (Tk_Width(entryPtr->tkwin) - 2*entryPtr->inset); + overflow = totalLength - + (Tk_Width(entryPtr->tkwin) - 2*entryPtr->inset - entryPtr->xWidth); if (overflow <= 0) { entryPtr->leftIndex = 0; if (entryPtr->justify == TK_JUSTIFY_LEFT) { entryPtr->leftX = entryPtr->inset; } else if (entryPtr->justify == TK_JUSTIFY_RIGHT) { entryPtr->leftX = Tk_Width(entryPtr->tkwin) - entryPtr->inset - - totalLength; + - entryPtr->xWidth - totalLength; } else { - entryPtr->leftX = (Tk_Width(entryPtr->tkwin) - totalLength)/2; + entryPtr->leftX = (Tk_Width(entryPtr->tkwin) + - entryPtr->xWidth - totalLength)/2; } entryPtr->layoutX = entryPtr->leftX; } else { @@ -1694,6 +2176,12 @@ EntryComputeGeometry(entryPtr) width = totalLength + 2*entryPtr->inset; } } + + /* + * Add one extra length for the spin buttons + */ + width += entryPtr->xWidth; + Tk_GeometryRequest(entryPtr->tkwin, width, height); } @@ -1793,7 +2281,7 @@ InsertChars(entryPtr, index, value) if (entryPtr->insertPos >= index) { entryPtr->insertPos += charsAdded; } - EntryValueChanged(entryPtr); + EntryValueChanged(entryPtr, NULL); } /* @@ -1907,7 +2395,7 @@ DeleteChars(entryPtr, index, count) entryPtr->insertPos = index; } } - EntryValueChanged(entryPtr); + EntryValueChanged(entryPtr, NULL); } /* @@ -1930,10 +2418,14 @@ DeleteChars(entryPtr, index, count) */ static void -EntryValueChanged(entryPtr) +EntryValueChanged(entryPtr, newValue) Entry *entryPtr; /* Entry whose value just changed. */ + char *newValue; /* If this value is not NULL, we first + * force the value of the entry to this */ { - char *newValue; + if (newValue != NULL) { + EntrySetValue(entryPtr, newValue); + } if (entryPtr->textVarName == NULL) { newValue = NULL; @@ -2073,7 +2565,7 @@ EntrySetValue(entryPtr, value) * EntryEventProc -- * * This procedure is invoked by the Tk dispatcher for various - * events on entryes. + * events on entries. * * Results: * None. @@ -2091,26 +2583,54 @@ EntryEventProc(clientData, eventPtr) XEvent *eventPtr; /* Information about event. */ { Entry *entryPtr = (Entry *) clientData; - if (eventPtr->type == Expose) { - EventuallyRedraw(entryPtr); - entryPtr->flags |= BORDER_NEEDED; - } else if (eventPtr->type == DestroyNotify) { - DestroyEntry((char *) clientData); - } else if (eventPtr->type == ConfigureNotify) { - Tcl_Preserve((ClientData) entryPtr); - entryPtr->flags |= UPDATE_SCROLLBAR; - EntryComputeGeometry(entryPtr); - EventuallyRedraw(entryPtr); - Tcl_Release((ClientData) entryPtr); - } else if (eventPtr->type == FocusIn) { - if (eventPtr->xfocus.detail != NotifyInferior) { - EntryFocusProc(entryPtr, 1); - } - } else if (eventPtr->type == FocusOut) { - if (eventPtr->xfocus.detail != NotifyInferior) { - EntryFocusProc(entryPtr, 0); + + if ((entryPtr->type == TK_SPINBOX) && (eventPtr->type == MotionNotify)) { + Spinbox *sbPtr = (Spinbox *) clientData; + int elem; + + elem = GetSpinboxElement(sbPtr, eventPtr->xmotion.x, + eventPtr->xmotion.y); + if (elem != sbPtr->curElement) { + Tk_Cursor cursor; + + sbPtr->curElement = elem; + if (elem == SEL_ENTRY) { + cursor = entryPtr->cursor; + } else if ((elem == SEL_BUTTONDOWN) || (elem == SEL_BUTTONUP)) { + cursor = sbPtr->bCursor; + } else { + cursor = None; + } + if (cursor != None) { + Tk_DefineCursor(entryPtr->tkwin, cursor); + } else { + Tk_UndefineCursor(entryPtr->tkwin); + } } } + + switch (eventPtr->type) { + case Expose: + EventuallyRedraw(entryPtr); + entryPtr->flags |= BORDER_NEEDED; + break; + case DestroyNotify: + DestroyEntry((char *) clientData); + break; + case ConfigureNotify: + Tcl_Preserve((ClientData) entryPtr); + entryPtr->flags |= UPDATE_SCROLLBAR; + EntryComputeGeometry(entryPtr); + EventuallyRedraw(entryPtr); + Tcl_Release((ClientData) entryPtr); + break; + case FocusIn: + case FocusOut: + if (eventPtr->xfocus.detail != NotifyInferior) { + EntryFocusProc(entryPtr, (eventPtr->type == FocusIn)); + } + break; + } } /* @@ -2195,8 +2715,9 @@ GetEntryIndex(interp, entryPtr, string, indexPtr) */ Tcl_SetResult(interp, (char *) NULL, TCL_STATIC); - Tcl_AppendResult(interp, "bad entry index \"", string, - "\"", (char *) NULL); + Tcl_AppendResult(interp, "bad ", + (entryPtr->type == TK_ENTRY) ? "entry" : "spinbox", + " index \"", string, "\"", (char *) NULL); return TCL_ERROR; } } else if (string[0] == 'e') { @@ -2213,7 +2734,9 @@ GetEntryIndex(interp, entryPtr, string, indexPtr) } } else if (string[0] == 's') { if (entryPtr->selectFirst < 0) { - Tcl_SetResult(interp, "selection isn't in entry", TCL_STATIC); + Tcl_SetResult(interp, (char *) NULL, TCL_STATIC); + Tcl_AppendResult(interp, "selection isn't in widget ", + Tk_PathName(entryPtr->tkwin), (char *) NULL); return TCL_ERROR; } if (length < 5) { @@ -2227,7 +2750,7 @@ GetEntryIndex(interp, entryPtr, string, indexPtr) goto badIndex; } } else if (string[0] == '@') { - int x, roundUp; + int x, roundUp, maxWidth; if (Tcl_GetInt(interp, string + 1, &x) != TCL_OK) { goto badIndex; @@ -2236,8 +2759,10 @@ GetEntryIndex(interp, entryPtr, string, indexPtr) x = entryPtr->inset; } roundUp = 0; - if (x >= (Tk_Width(entryPtr->tkwin) - entryPtr->inset)) { - x = Tk_Width(entryPtr->tkwin) - entryPtr->inset - 1; + maxWidth = Tk_Width(entryPtr->tkwin) - entryPtr->inset + - entryPtr->xWidth - 1; + if (x > maxWidth) { + x = maxWidth; roundUp = 1; } *indexPtr = Tk_PointToChar(entryPtr->textLayout, @@ -2554,7 +3079,7 @@ EntryVisibleRange(entryPtr, firstPtr, lastPtr) } else { charsInWindow = Tk_PointToChar(entryPtr->textLayout, Tk_Width(entryPtr->tkwin) - entryPtr->inset - - entryPtr->layoutX - 1, 0); + - entryPtr->xWidth - entryPtr->layoutX - 1, 0); if (charsInWindow < entryPtr->numChars) { charsInWindow++; } @@ -2609,7 +3134,9 @@ EntryUpdateScrollbar(entryPtr) code = Tcl_VarEval(interp, entryPtr->scrollCmd, args, (char *) NULL); if (code != TCL_OK) { Tcl_AddErrorInfo(interp, - "\n (horizontal scrolling command executed by entry)"); + "\n (horizontal scrolling command executed by "); + Tcl_AddErrorInfo(interp, Tk_PathName(entryPtr->tkwin)); + Tcl_AddErrorInfo(interp, ")"); Tcl_BackgroundError(interp); } Tcl_SetResult(interp, (char *) NULL, TCL_STATIC); @@ -2648,11 +3175,11 @@ EntryBlinkProc(clientData) if (entryPtr->flags & CURSOR_ON) { entryPtr->flags &= ~CURSOR_ON; entryPtr->insertBlinkHandler = Tcl_CreateTimerHandler( - entryPtr->insertOffTime, EntryBlinkProc, (ClientData) entryPtr); + entryPtr->insertOffTime, EntryBlinkProc, (ClientData) entryPtr); } else { entryPtr->flags |= CURSOR_ON; entryPtr->insertBlinkHandler = Tcl_CreateTimerHandler( - entryPtr->insertOnTime, EntryBlinkProc, (ClientData) entryPtr); + entryPtr->insertOnTime, EntryBlinkProc, (ClientData) entryPtr); } EventuallyRedraw(entryPtr); } @@ -2800,13 +3327,21 @@ EntryValidate(entryPtr, cmd) code = Tcl_EvalEx(interp, cmd, -1, TCL_EVAL_GLOBAL | TCL_EVAL_DIRECT); + /* + * We accept TCL_OK and TCL_RETURN as valid return codes from the + * command callback. + */ if (code != TCL_OK && code != TCL_RETURN) { - Tcl_AddErrorInfo(interp, - "\n\t(in validation command executed by entry)"); + Tcl_AddErrorInfo(interp, "\n\t(in validation command executed by "); + Tcl_AddErrorInfo(interp, Tk_PathName(entryPtr->tkwin)); + Tcl_AddErrorInfo(interp, ")"); Tcl_BackgroundError(interp); return TCL_ERROR; } + /* + * The command callback should return an acceptable Tcl boolean. + */ if (Tcl_GetBooleanFromObj(interp, Tcl_GetObjResult(interp), &bool) != TCL_OK) { Tcl_AddErrorInfo(interp, @@ -3004,68 +3539,1034 @@ ExpandPercents(entryPtr, before, change, new, index, type, dsPtr) } else { ch = '%'; } - switch (ch) { - case 'd': /* Type of call that caused validation */ - switch (type) { - case VALIDATE_INSERT: - number = 1; - break; - case VALIDATE_DELETE: - number = 0; - break; - default: - number = -1; - break; - } - sprintf(numStorage, "%d", number); - string = numStorage; + if (type == VALIDATE_BUTTON) { + /* + * -command %-substitution + */ + switch (ch) { + case 's': /* Current string value of spinbox */ + string = entryPtr->string; + break; + case 'd': /* direction, up or down */ + string = change; + break; + case 'W': /* widget name */ + string = Tk_PathName(entryPtr->tkwin); + break; + default: + length = Tcl_UniCharToUtf(ch, numStorage); + numStorage[length] = '\0'; + string = numStorage; + break; + } + } else { + /* + * -validatecommand / -invalidcommand %-substitution + */ + switch (ch) { + case 'd': /* Type of call that caused validation */ + switch (type) { + case VALIDATE_INSERT: + number = 1; + break; + case VALIDATE_DELETE: + number = 0; + break; + default: + number = -1; + break; + } + sprintf(numStorage, "%d", number); + string = numStorage; + break; + case 'i': /* index of insert/delete */ + sprintf(numStorage, "%d", index); + string = numStorage; + break; + case 'P': /* 'Peeked' new value of the string */ + string = new; + break; + case 's': /* Current string value of spinbox */ + string = entryPtr->string; + break; + case 'S': /* string to be inserted/deleted, if any */ + string = change; + break; + case 'v': /* type of validation currently set */ + string = validateStrings[entryPtr->validate]; + break; + case 'V': /* type of validation in effect */ + switch (type) { + case VALIDATE_INSERT: + case VALIDATE_DELETE: + string = validateStrings[VALIDATE_KEY]; + break; + case VALIDATE_FORCED: + string = "forced"; + break; + default: + string = validateStrings[type]; + break; + } + break; + case 'W': /* widget name */ + string = Tk_PathName(entryPtr->tkwin); + break; + default: + length = Tcl_UniCharToUtf(ch, numStorage); + numStorage[length] = '\0'; + string = numStorage; + break; + } + } + + spaceNeeded = Tcl_ScanCountedElement(string, -1, &cvtFlags); + length = Tcl_DStringLength(dsPtr); + Tcl_DStringSetLength(dsPtr, length + spaceNeeded); + spaceNeeded = Tcl_ConvertCountedElement(string, -1, + Tcl_DStringValue(dsPtr) + length, + cvtFlags | TCL_DONT_USE_BRACES); + Tcl_DStringSetLength(dsPtr, length + spaceNeeded); + } +} + +/* + *-------------------------------------------------------------- + * + * Tk_SpinboxObjCmd -- + * + * This procedure is invoked to process the "spinbox" Tcl + * command. See the user documentation for details on what + * it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *-------------------------------------------------------------- + */ + +int +Tk_SpinboxObjCmd(clientData, interp, objc, objv) + ClientData clientData; /* Either NULL or pointer to option table. */ + Tcl_Interp *interp; /* Current interpreter. */ + int objc; /* Number of arguments. */ + Tcl_Obj *CONST objv[]; /* Argument objects. */ +{ + register Entry *entryPtr; + register Spinbox *sbPtr; + Tk_OptionTable optionTable; + Tk_Window tkwin; + + optionTable = (Tk_OptionTable) clientData; + if (optionTable == NULL) { + Tcl_CmdInfo info; + char *name; + + /* + * We haven't created the option table for this widget class + * yet. Do it now and save the table as the clientData for + * the command, so we'll have access to it in future + * invocations of the command. + */ + + optionTable = Tk_CreateOptionTable(interp, sbOptSpec); + name = Tcl_GetString(objv[0]); + Tcl_GetCommandInfo(interp, name, &info); + info.objClientData = (ClientData) optionTable; + Tcl_SetCommandInfo(interp, name, &info); + } + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?"); + return TCL_ERROR; + } + + tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp), + Tcl_GetString(objv[1]), (char *) NULL); + if (tkwin == NULL) { + return TCL_ERROR; + } + + /* + * Initialize the fields of the structure that won't be initialized + * by ConfigureEntry, or that ConfigureEntry requires to be + * initialized already (e.g. resource pointers). Only the non-NULL/0 + * data must be initialized as memset covers the rest. + */ + + sbPtr = (Spinbox *) ckalloc(sizeof(Spinbox)); + entryPtr = (Entry *) sbPtr; + memset((VOID *) sbPtr, 0, sizeof(Spinbox)); + + entryPtr->tkwin = tkwin; + entryPtr->display = Tk_Display(tkwin); + entryPtr->interp = interp; + entryPtr->widgetCmd = Tcl_CreateObjCommand(interp, + Tk_PathName(entryPtr->tkwin), SpinboxWidgetObjCmd, + (ClientData) sbPtr, EntryCmdDeletedProc); + entryPtr->optionTable = optionTable; + entryPtr->type = TK_SPINBOX; + entryPtr->string = (char *) ckalloc(1); + entryPtr->string[0] = '\0'; + entryPtr->selectFirst = -1; + entryPtr->selectLast = -1; + + entryPtr->cursor = None; + entryPtr->exportSelection = 1; + entryPtr->justify = TK_JUSTIFY_LEFT; + entryPtr->relief = TK_RELIEF_FLAT; + entryPtr->state = STATE_NORMAL; + entryPtr->displayString = entryPtr->string; + entryPtr->inset = XPAD; + entryPtr->textGC = None; + entryPtr->selTextGC = None; + entryPtr->highlightGC = None; + entryPtr->avgWidth = 1; + entryPtr->validate = VALIDATE_NONE; + + sbPtr->selElement = SEL_NONE; + sbPtr->curElement = SEL_NONE; + sbPtr->bCursor = None; + sbPtr->repeatDelay = 400; + sbPtr->repeatInterval = 100; + sbPtr->fromValue = 0.0; + sbPtr->toValue = 100.0; + sbPtr->increment = 1.0; + sbPtr->formatBuf = (char *) ckalloc(TCL_DOUBLE_SPACE); + sbPtr->bdRelief = TK_RELIEF_FLAT; + sbPtr->buRelief = TK_RELIEF_FLAT; + + Tk_SetClass(entryPtr->tkwin, "Spinbox"); + TkSetClassProcs(entryPtr->tkwin, &entryClass, (ClientData) entryPtr); + Tk_CreateEventHandler(entryPtr->tkwin, + PointerMotionMask|ExposureMask|StructureNotifyMask|FocusChangeMask, + EntryEventProc, (ClientData) entryPtr); + Tk_CreateSelHandler(entryPtr->tkwin, XA_PRIMARY, XA_STRING, + EntryFetchSelection, (ClientData) entryPtr, XA_STRING); + + if (Tk_InitOptions(interp, (char *) sbPtr, optionTable, tkwin) + != TCL_OK) { + Tk_DestroyWindow(entryPtr->tkwin); + return TCL_ERROR; + } + if (ConfigureEntry(interp, entryPtr, objc-2, objv+2, 0) != TCL_OK) { + goto error; + } + + Tcl_SetResult(interp, Tk_PathName(entryPtr->tkwin), TCL_STATIC); + return TCL_OK; + + error: + Tk_DestroyWindow(entryPtr->tkwin); + return TCL_ERROR; +} + +/* + *-------------------------------------------------------------- + * + * SpinboxWidgetObjCmd -- + * + * This procedure is invoked to process the Tcl command + * that corresponds to a widget managed by this module. + * See the user documentation for details on what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *-------------------------------------------------------------- + */ + +static int +SpinboxWidgetObjCmd(clientData, interp, objc, objv) + ClientData clientData; /* Information about spinbox widget. */ + Tcl_Interp *interp; /* Current interpreter. */ + int objc; /* Number of arguments. */ + Tcl_Obj *CONST objv[]; /* Argument objects. */ +{ + Entry *entryPtr = (Entry *) clientData; + Spinbox *sbPtr = (Spinbox *) clientData; + int cmdIndex, selIndex, result; + Tcl_Obj *objPtr; + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?"); + return TCL_ERROR; + } + Tcl_Preserve((ClientData) entryPtr); + + /* + * Parse the widget command by looking up the second token in + * the list of valid command names. + */ + + result = Tcl_GetIndexFromObj(interp, objv[1], sbCmdNames, + "option", 0, &cmdIndex); + if (result != TCL_OK) { + return result; + } + + switch ((enum sbCmd) cmdIndex) { + case SB_CMD_BBOX: { + int index, x, y, width, height; + char buf[TCL_INTEGER_SPACE * 4]; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "index"); + goto error; + } + if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]), + &index) != TCL_OK) { + goto error; + } + if ((index == entryPtr->numChars) && (index > 0)) { + index--; + } + Tk_CharBbox(entryPtr->textLayout, index, &x, &y, + &width, &height); + sprintf(buf, "%d %d %d %d", x + entryPtr->layoutX, + y + entryPtr->layoutY, width, height); + Tcl_SetResult(interp, buf, TCL_VOLATILE); break; - case 'i': /* index of insert/delete */ - sprintf(numStorage, "%d", index); - string = numStorage; + } + + case SB_CMD_CGET: { + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "option"); + goto error; + } + + objPtr = Tk_GetOptionValue(interp, (char *) entryPtr, + entryPtr->optionTable, objv[2], entryPtr->tkwin); + if (objPtr == NULL) { + goto error; + } else { + Tcl_SetObjResult(interp, objPtr); + } break; - case 'P': /* 'Peeked' new value of the string */ - string = new; + } + + case SB_CMD_CONFIGURE: { + if (objc <= 3) { + objPtr = Tk_GetOptionInfo(interp, (char *) entryPtr, + entryPtr->optionTable, + (objc == 3) ? objv[2] : (Tcl_Obj *) NULL, + entryPtr->tkwin); + if (objPtr == NULL) { + goto error; + } else { + Tcl_SetObjResult(interp, objPtr); + } + } else { + result = ConfigureEntry(interp, entryPtr, objc-2, objv+2, 0); + } break; - case 's': /* Current string value of entry */ - string = entryPtr->string; + } + + case SB_CMD_DELETE: { + int first, last; + + if ((objc < 3) || (objc > 4)) { + Tcl_WrongNumArgs(interp, 2, objv, "firstIndex ?lastIndex?"); + goto error; + } + if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]), + &first) != TCL_OK) { + goto error; + } + if (objc == 3) { + last = first + 1; + } else { + if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[3]), + &last) != TCL_OK) { + goto error; + } + } + if ((last >= first) && (entryPtr->state == STATE_NORMAL)) { + DeleteChars(entryPtr, first, last - first); + } break; - case 'S': /* string to be inserted/deleted, if any */ - string = change; + } + + case SB_CMD_GET: { + if (objc != 2) { + Tcl_WrongNumArgs(interp, 2, objv, (char *) NULL); + goto error; + } + Tcl_SetResult(interp, entryPtr->string, TCL_STATIC); + break; + } + + case SB_CMD_ICURSOR: { + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "pos"); + goto error; + } + if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]), + &entryPtr->insertPos) != TCL_OK) { + goto error; + } + EventuallyRedraw(entryPtr); break; - case 'v': /* type of validation currently set */ - string = validateStrings[entryPtr->validate]; + } + + case SB_CMD_IDENTIFY: { + int x, y, elem; + + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "x y"); + goto error; + } + if ((Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK) || + (Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK)) { + goto error; + } + elem = GetSpinboxElement(sbPtr, x, y); + if (elem != SEL_NONE) { + Tcl_SetResult(interp, selElementNames[elem], TCL_VOLATILE); + } break; - case 'V': /* type of validation in effect */ - switch (type) { - case VALIDATE_INSERT: - case VALIDATE_DELETE: - string = validateStrings[VALIDATE_KEY]; - break; - case VALIDATE_FORCED: - string = "forced"; - break; - default: - string = validateStrings[type]; - break; + } + + case SB_CMD_INDEX: { + int index; + + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "string"); + goto error; + } + if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]), + &index) != TCL_OK) { + goto error; } + Tcl_SetObjResult(interp, Tcl_NewIntObj(index)); break; - case 'W': /* widget name */ - string = Tk_PathName(entryPtr->tkwin); + } + + case SB_CMD_INSERT: { + int index; + + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "index text"); + goto error; + } + if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]), + &index) != TCL_OK) { + goto error; + } + if (entryPtr->state == STATE_NORMAL) { + InsertChars(entryPtr, index, Tcl_GetString(objv[3])); + } break; - default: - length = Tcl_UniCharToUtf(ch, numStorage); - numStorage[length] = '\0'; - string = numStorage; + } + + case SB_CMD_INVOKE: { + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "elemName"); + goto error; + } + result = Tcl_GetIndexFromObj(interp, objv[2], + selElementNames, "element", 0, &cmdIndex); + if (result != TCL_OK) { + goto error; + } + if (entryPtr->state != STATE_DISABLED) { + if (SpinboxInvoke(interp, sbPtr, cmdIndex) != TCL_OK) { + goto error; + } + } break; } - spaceNeeded = Tcl_ScanCountedElement(string, -1, &cvtFlags); - length = Tcl_DStringLength(dsPtr); - Tcl_DStringSetLength(dsPtr, length + spaceNeeded); - spaceNeeded = Tcl_ConvertCountedElement(string, -1, - Tcl_DStringValue(dsPtr) + length, - cvtFlags | TCL_DONT_USE_BRACES); - Tcl_DStringSetLength(dsPtr, length + spaceNeeded); + case SB_CMD_SCAN: { + int x; + char *minorCmd; + + if (objc != 4) { + Tcl_WrongNumArgs(interp, 2, objv, "mark|dragto x"); + goto error; + } + if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK) { + goto error; + } + + minorCmd = Tcl_GetString(objv[2]); + if (minorCmd[0] == 'm' + && (strncmp(minorCmd, "mark", strlen(minorCmd)) == 0)) { + entryPtr->scanMarkX = x; + entryPtr->scanMarkIndex = entryPtr->leftIndex; + } else if ((minorCmd[0] == 'd') + && (strncmp(minorCmd, "dragto", strlen(minorCmd)) == 0)) { + EntryScanTo(entryPtr, x); + } else { + Tcl_AppendResult(interp, "bad scan option \"", + Tcl_GetString(objv[2]), "\": must be mark or dragto", + (char *) NULL); + goto error; + } + break; + } + + case SB_CMD_SELECTION: { + int index, index2; + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "option ?index?"); + goto error; + } + + /* + * Parse the selection sub-command, using the command + * table "sbSelCmdNames" defined above. + */ + + result = Tcl_GetIndexFromObj(interp, objv[2], sbSelCmdNames, + "selection option", 0, &selIndex); + if (result != TCL_OK) { + goto error; + } + + /* + * Disabled entries don't allow the selection to be modified. + */ + + if (entryPtr->state == STATE_DISABLED) { + goto done; + } + + switch(selIndex) { + case SB_SEL_ADJUST: { + if (objc != 4) { + Tcl_WrongNumArgs(interp, 3, objv, "index"); + goto error; + } + if (GetEntryIndex(interp, entryPtr, + Tcl_GetString(objv[3]), &index) != TCL_OK) { + goto error; + } + if (entryPtr->selectFirst >= 0) { + int half1, half2; + + half1 = (entryPtr->selectFirst + + entryPtr->selectLast)/2; + half2 = (entryPtr->selectFirst + + entryPtr->selectLast + 1)/2; + if (index < half1) { + entryPtr->selectAnchor = entryPtr->selectLast; + } else if (index > half2) { + entryPtr->selectAnchor = entryPtr->selectFirst; + } else { + /* + * We're at about the halfway point in the + * selection; just keep the existing anchor. + */ + } + } + EntrySelectTo(entryPtr, index); + break; + } + + case SB_SEL_CLEAR: { + if (objc != 3) { + Tcl_WrongNumArgs(interp, 3, objv, (char *) NULL); + goto error; + } + if (entryPtr->selectFirst >= 0) { + entryPtr->selectFirst = -1; + entryPtr->selectLast = -1; + EventuallyRedraw(entryPtr); + } + goto done; + } + + case SB_SEL_FROM: { + if (objc != 4) { + Tcl_WrongNumArgs(interp, 3, objv, "index"); + goto error; + } + if (GetEntryIndex(interp, entryPtr, + Tcl_GetString(objv[3]), &index) != TCL_OK) { + goto error; + } + entryPtr->selectAnchor = index; + break; + } + + case SB_SEL_PRESENT: { + if (objc != 3) { + Tcl_WrongNumArgs(interp, 3, objv, (char *) NULL); + goto error; + } + Tcl_SetObjResult(interp, + Tcl_NewBooleanObj((entryPtr->selectFirst >= 0))); + goto done; + } + + case SB_SEL_RANGE: { + if (objc != 5) { + Tcl_WrongNumArgs(interp, 3, objv, "start end"); + goto error; + } + if (GetEntryIndex(interp, entryPtr, + Tcl_GetString(objv[3]), &index) != TCL_OK) { + goto error; + } + if (GetEntryIndex(interp, entryPtr, + Tcl_GetString(objv[4]),& index2) != TCL_OK) { + goto error; + } + if (index >= index2) { + entryPtr->selectFirst = -1; + entryPtr->selectLast = -1; + } else { + entryPtr->selectFirst = index; + entryPtr->selectLast = index2; + } + if (!(entryPtr->flags & GOT_SELECTION) + && (entryPtr->exportSelection)) { + Tk_OwnSelection(entryPtr->tkwin, XA_PRIMARY, + EntryLostSelection, (ClientData) entryPtr); + entryPtr->flags |= GOT_SELECTION; + } + EventuallyRedraw(entryPtr); + break; + } + + case SB_SEL_TO: { + if (objc != 4) { + Tcl_WrongNumArgs(interp, 3, objv, "index"); + goto error; + } + if (GetEntryIndex(interp, entryPtr, + Tcl_GetString(objv[3]), &index) != TCL_OK) { + goto error; + } + EntrySelectTo(entryPtr, index); + break; + } + + case SB_SEL_ELEMENT: { + if ((objc < 3) || (objc > 4)) { + Tcl_WrongNumArgs(interp, 3, objv, "?elemName?"); + goto error; + } + if (objc == 3) { + Tcl_SetResult(interp, + selElementNames[sbPtr->selElement], + TCL_VOLATILE); + } else { + int lastElement = sbPtr->selElement; + + result = Tcl_GetIndexFromObj(interp, objv[3], + selElementNames, "selection element", 0, + &(sbPtr->selElement)); + if (result != TCL_OK) { + goto error; + } + if (lastElement != sbPtr->selElement) { + EventuallyRedraw(entryPtr); + } + } + break; + } + } + break; + } + + case SB_CMD_SET: { + if (objc > 3) { + Tcl_WrongNumArgs(interp, 2, objv, "?string?"); + goto error; + } + if (objc == 3) { + EntryValueChanged(entryPtr, Tcl_GetString(objv[2])); + } + Tcl_SetResult(interp, entryPtr->string, TCL_STATIC); + break; + } + + case SB_CMD_VALIDATE: { + int code; + + if (objc != 2) { + Tcl_WrongNumArgs(interp, 2, objv, (char *) NULL); + goto error; + } + selIndex = entryPtr->validate; + entryPtr->validate = VALIDATE_ALL; + code = EntryValidateChange(entryPtr, (char *) NULL, + entryPtr->string, -1, VALIDATE_FORCED); + if (entryPtr->validate != VALIDATE_NONE) { + entryPtr->validate = selIndex; + } + Tcl_SetObjResult(interp, Tcl_NewBooleanObj((code == TCL_OK))); + break; + } + + case SB_CMD_XVIEW: { + int index; + + if (objc == 2) { + double first, last; + char buf[TCL_DOUBLE_SPACE * 2]; + + EntryVisibleRange(entryPtr, &first, &last); + sprintf(buf, "%g %g", first, last); + Tcl_SetResult(interp, buf, TCL_VOLATILE); + goto done; + } else if (objc == 3) { + if (GetEntryIndex(interp, entryPtr, Tcl_GetString(objv[2]), + &index) != TCL_OK) { + goto error; + } + } else { + double fraction; + int count; + + index = entryPtr->leftIndex; + switch (Tk_GetScrollInfoObj(interp, objc, objv, &fraction, + &count)) { + case TK_SCROLL_ERROR: { + goto error; + } + case TK_SCROLL_MOVETO: { + index = (int) ((fraction * entryPtr->numChars) + 0.5); + break; + } + case TK_SCROLL_PAGES: { + int charsPerPage; + + charsPerPage = ((Tk_Width(entryPtr->tkwin) + - 2 * entryPtr->inset - entryPtr->xWidth) + / entryPtr->avgWidth) - 2; + if (charsPerPage < 1) { + charsPerPage = 1; + } + index += count * charsPerPage; + break; + } + case TK_SCROLL_UNITS: { + index += count; + break; + } + } + } + if (index >= entryPtr->numChars) { + index = entryPtr->numChars - 1; + } + if (index < 0) { + index = 0; + } + entryPtr->leftIndex = index; + entryPtr->flags |= UPDATE_SCROLLBAR; + EntryComputeGeometry(entryPtr); + EventuallyRedraw(entryPtr); + break; + } + } + + done: + Tcl_Release((ClientData) entryPtr); + return result; + + error: + Tcl_Release((ClientData) entryPtr); + return TCL_ERROR; +} + +/* + *--------------------------------------------------------------------------- + * + * GetSpinboxElement -- + * + * Return the element associated with an x,y coord. + * + * Results: + * Element type as enum selelement. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +static int +GetSpinboxElement(sbPtr, x, y) + Spinbox *sbPtr; /* Spinbox for which the index is being + * specified. */ + int x; /* x coord */ + int y; /* y coord */ +{ + Entry *entryPtr = (Entry *) sbPtr; + + if ((x < 0) || (y < 0) || (y > Tk_Height(entryPtr->tkwin)) + || (x > Tk_Width(entryPtr->tkwin))) { + return SEL_NONE; + } + + if (x > (Tk_Width(entryPtr->tkwin) - entryPtr->inset - entryPtr->xWidth)) { + if (y > (Tk_Height(entryPtr->tkwin) / 2)) { + return SEL_BUTTONDOWN; + } else { + return SEL_BUTTONUP; + } + } else { + return SEL_ENTRY; } + return SEL_ENTRY; +} + +/* + *-------------------------------------------------------------- + * + * SpinboxInvoke -- + * + * This procedure is invoked when the invoke method for the + * widget is called. + * + * Results: + * TCL_OK. + * + * Side effects: + * An background error condition may arise when invoking the + * callback. The widget value may change. + * + *-------------------------------------------------------------- + */ + +static int +SpinboxInvoke(interp, sbPtr, element) + register Tcl_Interp *interp; /* Current interpreter. */ + register Spinbox *sbPtr; /* Spinbox to invoke. */ + int element; /* element to invoke, either the "up" + * or "down" button. */ +{ + Entry *entryPtr = (Entry *) sbPtr; + char *type; + int code, up; + Tcl_DString script; + + switch (element) { + case SEL_BUTTONUP: + type = "up"; + up = 1; + break; + case SEL_BUTTONDOWN: + type = "down"; + up = 0; + break; + default: + return TCL_OK; + } + + if (fabs(sbPtr->increment) > MIN_DBL_VAL) { + if (sbPtr->listObj != NULL) { + Tcl_Obj *objPtr; + + Tcl_ListObjIndex(interp, sbPtr->listObj, sbPtr->eIndex, &objPtr); + if (strcmp(Tcl_GetString(objPtr), entryPtr->string)) { + /* + * Somehow the string changed from what we expected, + * so let's do a search on the list to see if the current + * value is there. If not, move to the first element of + * the list. + */ + int i, listc, elemLen, length = entryPtr->numChars; + char *bytes; + Tcl_Obj **listv; + + Tcl_ListObjGetElements(interp, sbPtr->listObj, &listc, &listv); + for (i = 0; i < listc; i++) { + bytes = Tcl_GetStringFromObj(listv[i], &elemLen); + if ((length == elemLen) && + (memcmp(bytes, entryPtr->string, + (size_t) length) == 0)) { + sbPtr->eIndex = i; + break; + } + } + } + if (up) { + if (++sbPtr->eIndex >= sbPtr->nElements) { + if (sbPtr->wrap) { + sbPtr->eIndex = 0; + } else { + sbPtr->eIndex = sbPtr->nElements-1; + } + } + } else { + if (--sbPtr->eIndex < 0) { + if (sbPtr->wrap) { + sbPtr->eIndex = sbPtr->nElements-1; + } else { + sbPtr->eIndex = 0; + } + } + } + Tcl_ListObjIndex(interp, sbPtr->listObj, sbPtr->eIndex, &objPtr); + EntryValueChanged(entryPtr, Tcl_GetString(objPtr)); + } else if (!DOUBLES_EQ(sbPtr->fromValue, sbPtr->toValue)) { + double dvalue; + + if (Tcl_GetDouble(NULL, entryPtr->string, &dvalue) != TCL_OK) { + /* + * If the string is empty, or isn't a valid double value, + * just use the -from value + */ + dvalue = sbPtr->fromValue; + } else { + if (up) { + dvalue += sbPtr->increment; + if (dvalue > sbPtr->toValue) { + if (sbPtr->wrap) { + dvalue = sbPtr->fromValue; + } else { + dvalue = sbPtr->toValue; + } + } else if (dvalue < sbPtr->fromValue) { + /* + * It's possible that when pressing up, we are + * still less than the fromValue, because the + * user may have manipulated the value by hand. + */ + dvalue = sbPtr->fromValue; + } + } else { + dvalue -= sbPtr->increment; + if (dvalue < sbPtr->fromValue) { + if (sbPtr->wrap) { + dvalue = sbPtr->toValue; + } else { + dvalue = sbPtr->fromValue; + } + } else if (dvalue > sbPtr->toValue) { + /* + * It's possible that when pressing down, we are + * still greater than the toValue, because the + * user may have manipulated the value by hand. + */ + dvalue = sbPtr->toValue; + } + } + } + sprintf(sbPtr->formatBuf, sbPtr->valueFormat, dvalue); + EntryValueChanged(entryPtr, sbPtr->formatBuf); + } + } + + if (sbPtr->command != NULL) { + Tcl_DStringInit(&script); + ExpandPercents(entryPtr, sbPtr->command, type, "", 0, + VALIDATE_BUTTON, &script); + Tcl_DStringAppend(&script, "", 1); + + code = Tcl_EvalEx(interp, Tcl_DStringValue(&script), -1, + TCL_EVAL_GLOBAL | TCL_EVAL_DIRECT); + Tcl_DStringFree(&script); + + if (code != TCL_OK) { + Tcl_AddErrorInfo(interp, "\n\t(in command executed by spinbox)"); + Tcl_BackgroundError(interp); + /* + * Yes, it's an error, but a bg one, so we return OK + */ + return TCL_OK; + } + + Tcl_SetResult(interp, NULL, 0); + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * ComputeFormat -- + * + * This procedure is invoked to recompute the "format" fields + * of a spinbox's widget record, which determines how the value + * of the dial is converted to a string. + * + * Results: + * Tcl result code. + * + * Side effects: + * The format fields of the spinbox are modified. + * + *---------------------------------------------------------------------- + */ +static int +ComputeFormat(sbPtr) + Spinbox *sbPtr; /* Information about dial widget. */ +{ + double maxValue, x; + int mostSigDigit, numDigits, leastSigDigit, afterDecimal; + int eDigits, fDigits; + + /* + * Compute the displacement from the decimal of the most significant + * digit required for any number in the dial's range. + */ + + if (sbPtr->reqFormat) { + sbPtr->valueFormat = sbPtr->reqFormat; + return TCL_OK; + } + + maxValue = fabs(sbPtr->fromValue); + x = fabs(sbPtr->toValue); + if (x > maxValue) { + maxValue = x; + } + if (maxValue == 0) { + maxValue = 1; + } + mostSigDigit = (int) floor(log10(maxValue)); + + if (fabs(sbPtr->increment) > MIN_DBL_VAL) { + /* + * A increment was specified, so use it. + */ + leastSigDigit = (int) floor(log10(sbPtr->increment)); + } else { + leastSigDigit = 0; + } + numDigits = mostSigDigit - leastSigDigit + 1; + if (numDigits < 1) { + numDigits = 1; + } + + /* + * Compute the number of characters required using "e" format and + * "f" format, and then choose whichever one takes fewer characters. + */ + + eDigits = numDigits + 4; + if (numDigits > 1) { + eDigits++; /* Decimal point. */ + } + afterDecimal = numDigits - mostSigDigit - 1; + if (afterDecimal < 0) { + afterDecimal = 0; + } + fDigits = (mostSigDigit >= 0) ? mostSigDigit + afterDecimal : afterDecimal; + if (afterDecimal > 0) { + fDigits++; /* Decimal point. */ + } + if (mostSigDigit < 0) { + fDigits++; /* Zero to left of decimal point. */ + } + if (fDigits <= eDigits) { + sprintf(sbPtr->digitFormat, "%%.%df", afterDecimal); + } else { + sprintf(sbPtr->digitFormat, "%%.%de", numDigits-1); + } + sbPtr->valueFormat = sbPtr->digitFormat; + return TCL_OK; } diff --git a/generic/tkInt.h b/generic/tkInt.h index a944e38..bf85dcb 100644 --- a/generic/tkInt.h +++ b/generic/tkInt.h @@ -11,7 +11,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: $Id: tkInt.h,v 1.23 2000/04/19 01:06:51 ericm Exp $ + * RCS: $Id: tkInt.h,v 1.24 2000/05/29 01:43:14 hobbs Exp $ */ #ifndef _TKINT @@ -990,6 +990,9 @@ EXTERN int Tk_SendCmd _ANSI_ARGS_((ClientData clientData, EXTERN int Tk_SendObjCmd _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +EXTERN int Tk_SpinboxObjCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, int objc, + Tcl_Obj *CONST objv[])); EXTERN int Tk_TextCmd _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, int argc, char **argv)); EXTERN int Tk_TkObjCmd _ANSI_ARGS_((ClientData clientData, diff --git a/generic/tkWindow.c b/generic/tkWindow.c index 7fa867f..d1238ac 100644 --- a/generic/tkWindow.c +++ b/generic/tkWindow.c @@ -12,7 +12,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkWindow.c,v 1.17 2000/04/28 00:46:04 ericm Exp $ + * RCS: @(#) $Id: tkWindow.c,v 1.18 2000/05/29 01:43:14 hobbs Exp $ */ #include "tkPort.h" @@ -145,6 +145,7 @@ static TkCmd commands[] = { {"radiobutton", NULL, Tk_RadiobuttonObjCmd, 1, 0}, {"scale", NULL, Tk_ScaleObjCmd, 1, 0}, {"scrollbar", Tk_ScrollbarCmd, NULL, 1, 1}, + {"spinbox", NULL, Tk_SpinboxObjCmd, 1, 0}, {"text", Tk_TextCmd, NULL, 1, 1}, {"toplevel", NULL, Tk_ToplevelObjCmd, 0, 1}, diff --git a/library/entry.tcl b/library/entry.tcl index a46561e..b44eaad 100644 --- a/library/entry.tcl +++ b/library/entry.tcl @@ -3,7 +3,7 @@ # This file defines the default bindings for Tk entry widgets and provides # procedures that help in implementing those bindings. # -# RCS: @(#) $Id: entry.tcl,v 1.12 2000/05/14 20:45:38 ericm Exp $ +# RCS: @(#) $Id: entry.tcl,v 1.13 2000/05/29 01:43:14 hobbs Exp $ # # Copyright (c) 1992-1994 The Regents of the University of California. # Copyright (c) 1994-1997 Sun Microsystems, Inc. @@ -336,7 +336,7 @@ proc tkEntryButton1 {w x} { set tkPriv(pressX) $x $w icursor [tkEntryClosestGap $w $x] $w selection from insert - if {[string compare [$w cget -state] "disabled"]} {focus $w} + if {[string compare "disabled" [$w cget -state]]} {focus $w} } # tkEntryMouseSelect -- @@ -406,7 +406,7 @@ proc tkEntryPaste {w x} { $w icursor [tkEntryClosestGap $w $x] catch {$w insert insert [selection get -displayof $w]} - if {[string equal [$w cget -state] "normal"]} {focus $w} + if {[string compare "disabled" [$w cget -state]]} {focus $w} } # tkEntryAutoScan -- diff --git a/library/spinbox.tcl b/library/spinbox.tcl new file mode 100644 index 0000000..b93479b --- /dev/null +++ b/library/spinbox.tcl @@ -0,0 +1,746 @@ +# spinbox.tcl -- +# +# This file defines the default bindings for Tk spinbox widgets and provides +# procedures that help in implementing those bindings. +# +# RCS: @(#) $Id: spinbox.tcl,v 1.1 2000/05/29 01:43:15 hobbs Exp $ +# +# Copyright (c) 1992-1994 The Regents of the University of California. +# Copyright (c) 1994-1997 Sun Microsystems, Inc. +# Copyright (c) 1999-2000 Jeffrey Hobbs +# Copyright (c) 2000 Ajuba Solutions +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# + +#------------------------------------------------------------------------- +# Elements of tkPriv that are used in this file: +# +# afterId - If non-null, it means that auto-scanning is underway +# and it gives the "after" id for the next auto-scan +# command to be executed. +# mouseMoved - Non-zero means the mouse has moved a significant +# amount since the button went down (so, for example, +# start dragging out a selection). +# pressX - X-coordinate at which the mouse button was pressed. +# selectMode - The style of selection currently underway: +# char, word, or line. +# x, y - Last known mouse coordinates for scanning +# and auto-scanning. +# data - Used for Cut and Copy +#------------------------------------------------------------------------- + +# Initialize namespace +namespace eval ::tk::spinbox {} + +#------------------------------------------------------------------------- +# The code below creates the default class bindings for entries. +#------------------------------------------------------------------------- +bind Spinbox <<Cut>> { + if {![catch {::tk::spinbox::GetSelection %W} tkPriv(data)]} { + clipboard clear -displayof %W + clipboard append -displayof %W $tkPriv(data) + %W delete sel.first sel.last + unset tkPriv(data) + } +} +bind Spinbox <<Copy>> { + if {![catch {::tk::spinbox::GetSelection %W} tkPriv(data)]} { + clipboard clear -displayof %W + clipboard append -displayof %W $tkPriv(data) + unset tkPriv(data) + } +} +bind Spinbox <<Paste>> { + global tcl_platform + catch { + if {[string compare $tcl_platform(platform) "unix"]} { + catch { + %W delete sel.first sel.last + } + } + %W insert insert [selection get -displayof %W -selection CLIPBOARD] + ::tk::spinbox::SeeInsert %W + } +} +bind Spinbox <<Clear>> { + %W delete sel.first sel.last +} +bind Spinbox <<PasteSelection>> { + if {!$tkPriv(mouseMoved) || $tk_strictMotif} { + ::tk::spinbox::Paste %W %x + } +} + +# Standard Motif bindings: + +bind Spinbox <1> { + ::tk::spinbox::ButtonDown %W %x %y +} +bind Spinbox <B1-Motion> { + ::tk::spinbox::Motion %W %x %y +} +bind Spinbox <Double-1> { + set tkPriv(selectMode) word + ::tk::spinbox::MouseSelect %W %x sel.first +} +bind Spinbox <Triple-1> { + set tkPriv(selectMode) line + ::tk::spinbox::MouseSelect %W %x 0 +} +bind Spinbox <Shift-1> { + set tkPriv(selectMode) char + %W selection adjust @%x +} +bind Spinbox <Double-Shift-1> { + set tkPriv(selectMode) word + ::tk::spinbox::MouseSelect %W %x +} +bind Spinbox <Triple-Shift-1> { + set tkPriv(selectMode) line + ::tk::spinbox::MouseSelect %W %x +} +bind Spinbox <B1-Leave> { + set tkPriv(x) %x + ::tk::spinbox::AutoScan %W +} +bind Spinbox <B1-Enter> { + tkCancelRepeat +} +bind Spinbox <ButtonRelease-1> { + ::tk::spinbox::ButtonUp %W %x %y +} +bind Spinbox <Control-1> { + %W icursor @%x +} + +bind Spinbox <Up> { + %W invoke buttonup +} +bind Spinbox <Down> { + %W invoke buttondown +} + +bind Spinbox <Left> { + ::tk::spinbox::SetCursor %W [expr {[%W index insert] - 1}] +} +bind Spinbox <Right> { + ::tk::spinbox::SetCursor %W [expr {[%W index insert] + 1}] +} +bind Spinbox <Shift-Left> { + ::tk::spinbox::KeySelect %W [expr {[%W index insert] - 1}] + ::tk::spinbox::SeeInsert %W +} +bind Spinbox <Shift-Right> { + ::tk::spinbox::KeySelect %W [expr {[%W index insert] + 1}] + ::tk::spinbox::SeeInsert %W +} +bind Spinbox <Control-Left> { + ::tk::spinbox::SetCursor %W [::tk::spinbox::PreviousWord %W insert] +} +bind Spinbox <Control-Right> { + ::tk::spinbox::SetCursor %W [::tk::spinbox::NextWord %W insert] +} +bind Spinbox <Shift-Control-Left> { + ::tk::spinbox::KeySelect %W [::tk::spinbox::PreviousWord %W insert] + ::tk::spinbox::SeeInsert %W +} +bind Spinbox <Shift-Control-Right> { + ::tk::spinbox::KeySelect %W [::tk::spinbox::NextWord %W insert] + ::tk::spinbox::SeeInsert %W +} +bind Spinbox <Home> { + ::tk::spinbox::SetCursor %W 0 +} +bind Spinbox <Shift-Home> { + ::tk::spinbox::KeySelect %W 0 + ::tk::spinbox::SeeInsert %W +} +bind Spinbox <End> { + ::tk::spinbox::SetCursor %W end +} +bind Spinbox <Shift-End> { + ::tk::spinbox::KeySelect %W end + ::tk::spinbox::SeeInsert %W +} + +bind Spinbox <Delete> { + if {[%W selection present]} { + %W delete sel.first sel.last + } else { + %W delete insert + } +} +bind Spinbox <BackSpace> { + ::tk::spinbox::Backspace %W +} + +bind Spinbox <Control-space> { + %W selection from insert +} +bind Spinbox <Select> { + %W selection from insert +} +bind Spinbox <Control-Shift-space> { + %W selection adjust insert +} +bind Spinbox <Shift-Select> { + %W selection adjust insert +} +bind Spinbox <Control-slash> { + %W selection range 0 end +} +bind Spinbox <Control-backslash> { + %W selection clear +} +bind Spinbox <KeyPress> { + ::tk::spinbox::Insert %W %A +} + +# Ignore all Alt, Meta, and Control keypresses unless explicitly bound. +# Otherwise, if a widget binding for one of these is defined, the +# <KeyPress> class binding will also fire and insert the character, +# which is wrong. Ditto for Escape, Return, and Tab. + +bind Spinbox <Alt-KeyPress> {# nothing} +bind Spinbox <Meta-KeyPress> {# nothing} +bind Spinbox <Control-KeyPress> {# nothing} +bind Spinbox <Escape> {# nothing} +bind Spinbox <Return> {# nothing} +bind Spinbox <KP_Enter> {# nothing} +bind Spinbox <Tab> {# nothing} +if {[string equal $tcl_platform(platform) "macintosh"]} { + bind Spinbox <Command-KeyPress> {# nothing} +} + +# On Windows, paste is done using Shift-Insert. Shift-Insert already +# generates the <<Paste>> event, so we don't need to do anything here. +if {[string compare $tcl_platform(platform) "windows"]} { + bind Spinbox <Insert> { + catch {::tk::spinbox::Insert %W [selection get -displayof %W]} + } +} + +# Additional emacs-like bindings: + +bind Spinbox <Control-a> { + if {!$tk_strictMotif} { + ::tk::spinbox::SetCursor %W 0 + } +} +bind Spinbox <Control-b> { + if {!$tk_strictMotif} { + ::tk::spinbox::SetCursor %W [expr {[%W index insert] - 1}] + } +} +bind Spinbox <Control-d> { + if {!$tk_strictMotif} { + %W delete insert + } +} +bind Spinbox <Control-e> { + if {!$tk_strictMotif} { + ::tk::spinbox::SetCursor %W end + } +} +bind Spinbox <Control-f> { + if {!$tk_strictMotif} { + ::tk::spinbox::SetCursor %W [expr {[%W index insert] + 1}] + } +} +bind Spinbox <Control-h> { + if {!$tk_strictMotif} { + ::tk::spinbox::Backspace %W + } +} +bind Spinbox <Control-k> { + if {!$tk_strictMotif} { + %W delete insert end + } +} +bind Spinbox <Control-t> { + if {!$tk_strictMotif} { + ::tk::spinbox::Transpose %W + } +} +bind Spinbox <Meta-b> { + if {!$tk_strictMotif} { + ::tk::spinbox::SetCursor %W [::tk::spinbox::PreviousWord %W insert] + } +} +bind Spinbox <Meta-d> { + if {!$tk_strictMotif} { + %W delete insert [::tk::spinbox::NextWord %W insert] + } +} +bind Spinbox <Meta-f> { + if {!$tk_strictMotif} { + ::tk::spinbox::SetCursor %W [::tk::spinbox::NextWord %W insert] + } +} +bind Spinbox <Meta-BackSpace> { + if {!$tk_strictMotif} { + %W delete [::tk::spinbox::PreviousWord %W insert] insert + } +} +bind Spinbox <Meta-Delete> { + if {!$tk_strictMotif} { + %W delete [::tk::spinbox::PreviousWord %W insert] insert + } +} + +# A few additional bindings of my own. + +bind Spinbox <2> { + if {!$tk_strictMotif} { + %W scan mark %x + set tkPriv(x) %x + set tkPriv(y) %y + set tkPriv(mouseMoved) 0 + } +} +bind Spinbox <B2-Motion> { + if {!$tk_strictMotif} { + if {abs(%x-$tkPriv(x)) > 2} { + set tkPriv(mouseMoved) 1 + } + %W scan dragto %x + } +} + +# ::tk::spinbox::Invoke -- +# Invoke an element of the spinbox +# +# Arguments: +# w - The spinbox window. +# elem - Element to invoke + +proc ::tk::spinbox::Invoke {w elem} { + global tkPriv + + if {![info exists tkPriv(outsideElement)]} { + $w invoke $elem + incr tkPriv(repeated) + } + set delay [$w cget -repeatinterval] + if {$delay > 0} { + set tkPriv(afterId) [after $delay \ + [list ::tk::spinbox::Invoke $w $elem]] + } +} + +# ::tk::spinbox::ClosestGap -- +# Given x and y coordinates, this procedure finds the closest boundary +# between characters to the given coordinates and returns the index +# of the character just after the boundary. +# +# Arguments: +# w - The spinbox window. +# x - X-coordinate within the window. + +proc ::tk::spinbox::ClosestGap {w x} { + set pos [$w index @$x] + set bbox [$w bbox $pos] + if {($x - [lindex $bbox 0]) < ([lindex $bbox 2]/2)} { + return $pos + } + incr pos +} + +# ::tk::spinbox::ButtonDown -- +# This procedure is invoked to handle button-1 presses in spinbox +# widgets. It moves the insertion cursor, sets the selection anchor, +# and claims the input focus. +# +# Arguments: +# w - The spinbox window in which the button was pressed. +# x - The x-coordinate of the button press. + +proc ::tk::spinbox::ButtonDown {w x y} { + global tkPriv + + # Get the element that was clicked in. If we are not directly over + # the spinbox, default to entry. This is necessary for spinbox grabs. + # + set tkPriv(element) [$w identify $x $y] + if {$tkPriv(element) eq ""} { + set tkPriv(element) "entry" + } + + switch -exact $tkPriv(element) { + "buttonup" - "buttondown" { + if {[string compare "disabled" [$w cget -state]]} { + $w selection element $tkPriv(element) + set tkPriv(repeated) 0 + set tkPriv(relief) [$w cget -$tkPriv(element)relief] + after cancel $tkPriv(afterId) + set delay [$w cget -repeatdelay] + if {$delay > 0} { + set tkPriv(afterId) [after $delay \ + [list ::tk::spinbox::Invoke $w $tkPriv(element)]] + } + if {[info exists tkPriv(outsideElement)]} { + unset tkPriv(outsideElement) + } + } + } + "entry" { + set tkPriv(selectMode) char + set tkPriv(mouseMoved) 0 + set tkPriv(pressX) $x + $w icursor [::tk::spinbox::ClosestGap $w $x] + $w selection from insert + if {[string compare "disabled" [$w cget -state]]} {focus $w} + $w selection clear + } + default { + return -code error "unknown spinbox element \"$tkPriv(element)\"" + } + } +} + +# ::tk::spinbox::ButtonUp -- +# This procedure is invoked to handle button-1 releases in spinbox +# widgets. +# +# Arguments: +# w - The spinbox window in which the button was pressed. +# x - The x-coordinate of the button press. + +proc ::tk::spinbox::ButtonUp {w x y} { + global tkPriv + + tkCancelRepeat + + # tkPriv(relief) may not exist if the ButtonUp is not paired with + # a preceding ButtonDown + if {[info exists tkPriv(element)] && [info exists tkPriv(relief)] && \ + [string match "button*" $tkPriv(element)]} { + if {[info exists tkPriv(repeated)] && !$tkPriv(repeated)} { + $w invoke $tkPriv(element) + } + $w configure -$tkPriv(element)relief $tkPriv(relief) + $w selection element none + } +} + +# ::tk::spinbox::MouseSelect -- +# This procedure is invoked when dragging out a selection with +# the mouse. Depending on the selection mode (character, word, +# line) it selects in different-sized units. This procedure +# ignores mouse motions initially until the mouse has moved from +# one character to another or until there have been multiple clicks. +# +# Arguments: +# w - The spinbox window in which the button was pressed. +# x - The x-coordinate of the mouse. +# cursor - optional place to set cursor. + +proc ::tk::spinbox::MouseSelect {w x {cursor {}}} { + global tkPriv + + if {[string compare "entry" $tkPriv(element)]} { + if {[string compare "none" $tkPriv(element)] && \ + [string compare "ignore" $cursor]} { + $w selection element none + $w invoke $tkPriv(element) + $w selection element $tkPriv(element) + } + return + } + set cur [::tk::spinbox::ClosestGap $w $x] + set anchor [$w index anchor] + if {($cur != $anchor) || (abs($tkPriv(pressX) - $x) >= 3)} { + set tkPriv(mouseMoved) 1 + } + switch $tkPriv(selectMode) { + char { + if {$tkPriv(mouseMoved)} { + if {$cur < $anchor} { + $w selection range $cur $anchor + } elseif {$cur > $anchor} { + $w selection range $anchor $cur + } else { + $w selection clear + } + } + } + word { + if {$cur < [$w index anchor]} { + set before [tcl_wordBreakBefore [$w get] $cur] + set after [tcl_wordBreakAfter [$w get] [expr {$anchor-1}]] + } else { + set before [tcl_wordBreakBefore [$w get] $anchor] + set after [tcl_wordBreakAfter [$w get] [expr {$cur - 1}]] + } + if {$before < 0} { + set before 0 + } + if {$after < 0} { + set after end + } + $w selection range $before $after + } + line { + $w selection range 0 end + } + } + if {[string compare $cursor {}] && [string compare $cursor "ignore"]} { + catch {$w icursor $cursor} + } + update idletasks +} + +# ::tk::spinbox::Paste -- +# This procedure sets the insertion cursor to the current mouse position, +# pastes the selection there, and sets the focus to the window. +# +# Arguments: +# w - The spinbox window. +# x - X position of the mouse. + +proc ::tk::spinbox::Paste {w x} { + global tkPriv + + $w icursor [::tk::spinbox::ClosestGap $w $x] + catch {$w insert insert [selection get -displayof $w]} + if {[string equal "disabled" [$w cget -state]]} {focus $w} +} + +# ::tk::spinbox::Motion -- +# This procedure is invoked when the mouse moves in a spinbox window +# with button 1 down. +# +# Arguments: +# w - The spinbox window. + +proc ::tk::spinbox::Motion {w x y} { + global tkPriv + + if {![info exists tkPriv(element)]} { + set tkPriv(element) [$w identify $x $y] + } + + set tkPriv(x) $x + if {[string equal "entry" $tkPriv(element)]} { + ::tk::spinbox::MouseSelect $w $x ignore + } elseif {[string compare [$w identify $x $y] $tkPriv(element)]} { + if {![info exists tkPriv(outsideElement)]} { + # We've wandered out of the spin button + # setting outside element will cause ::tk::spinbox::Invoke to + # loop without doing anything + set tkPriv(outsideElement) "" + $w selection element none + } + } elseif {[info exists tkPriv(outsideElement)]} { + unset tkPriv(outsideElement) + $w selection element $tkPriv(element) + } +} + +# ::tk::spinbox::AutoScan -- +# This procedure is invoked when the mouse leaves an spinbox window +# with button 1 down. It scrolls the window left or right, +# depending on where the mouse is, and reschedules itself as an +# "after" command so that the window continues to scroll until the +# mouse moves back into the window or the mouse button is released. +# +# Arguments: +# w - The spinbox window. + +proc ::tk::spinbox::AutoScan {w} { + global tkPriv + + set x $tkPriv(x) + if {$x >= [winfo width $w]} { + $w xview scroll 2 units + ::tk::spinbox::MouseSelect $w $x ignore + } elseif {$x < 0} { + $w xview scroll -2 units + ::tk::spinbox::MouseSelect $w $x ignore + } + set tkPriv(afterId) [after 50 [list ::tk::spinbox::AutoScan $w]] +} + +# ::tk::spinbox::KeySelect -- +# This procedure is invoked when stroking out selections using the +# keyboard. It moves the cursor to a new position, then extends +# the selection to that position. +# +# Arguments: +# w - The spinbox window. +# new - A new position for the insertion cursor (the cursor hasn't +# actually been moved to this position yet). + +proc ::tk::spinbox::KeySelect {w new} { + if {![$w selection present]} { + $w selection from insert + $w selection to $new + } else { + $w selection adjust $new + } + $w icursor $new +} + +# ::tk::spinbox::Insert -- +# Insert a string into an spinbox at the point of the insertion cursor. +# If there is a selection in the spinbox, and it covers the point of the +# insertion cursor, then delete the selection before inserting. +# +# Arguments: +# w - The spinbox window in which to insert the string +# s - The string to insert (usually just a single character) + +proc ::tk::spinbox::Insert {w s} { + if {$s == ""} { + return + } + catch { + set insert [$w index insert] + if {([$w index sel.first] <= $insert) \ + && ([$w index sel.last] >= $insert)} { + $w delete sel.first sel.last + } + } + $w insert insert $s + ::tk::spinbox::SeeInsert $w +} + +# ::tk::spinbox::Backspace -- +# Backspace over the character just before the insertion cursor. +# If backspacing would move the cursor off the left edge of the +# window, reposition the cursor at about the middle of the window. +# +# Arguments: +# w - The spinbox window in which to backspace. + +proc ::tk::spinbox::Backspace w { + if {[$w selection present]} { + $w delete sel.first sel.last + } else { + set x [expr {[$w index insert] - 1}] + if {$x >= 0} {$w delete $x} + if {[$w index @0] >= [$w index insert]} { + set range [$w xview] + set left [lindex $range 0] + set right [lindex $range 1] + $w xview moveto [expr {$left - ($right - $left)/2.0}] + } + } +} + +# ::tk::spinbox::SeeInsert -- +# Make sure that the insertion cursor is visible in the spinbox window. +# If not, adjust the view so that it is. +# +# Arguments: +# w - The spinbox window. + +proc ::tk::spinbox::SeeInsert w { + set c [$w index insert] + if {($c < [$w index @0]) || ($c > [$w index @[winfo width $w]])} { + $w xview $c + } +} + +# ::tk::spinbox::SetCursor - +# Move the insertion cursor to a given position in an spinbox. Also +# clears the selection, if there is one in the spinbox, and makes sure +# that the insertion cursor is visible. +# +# Arguments: +# w - The spinbox window. +# pos - The desired new position for the cursor in the window. + +proc ::tk::spinbox::SetCursor {w pos} { + $w icursor $pos + $w selection clear + ::tk::spinbox::SeeInsert $w +} + +# ::tk::spinbox::Transpose - +# This procedure implements the "transpose" function for spinbox widgets. +# It tranposes the characters on either side of the insertion cursor, +# unless the cursor is at the end of the line. In this case it +# transposes the two characters to the left of the cursor. In either +# case, the cursor ends up to the right of the transposed characters. +# +# Arguments: +# w - The spinbox window. + +proc ::tk::spinbox::Transpose w { + set i [$w index insert] + if {$i < [$w index end]} { + incr i + } + set first [expr {$i-2}] + if {$first < 0} { + return + } + set data [$w get] + set new [string index $data [expr {$i-1}]][string index $data $first] + $w delete $first $i + $w insert insert $new + ::tk::spinbox::SeeInsert $w +} + +# ::tk::spinbox::NextWord -- +# Returns the index of the next word position after a given position in the +# spinbox. The next word is platform dependent and may be either the next +# end-of-word position or the next start-of-word position after the next +# end-of-word position. +# +# Arguments: +# w - The spinbox window in which the cursor is to move. +# start - Position at which to start search. + +if {[string equal $tcl_platform(platform) "windows"]} { + proc ::tk::spinbox::NextWord {w start} { + set pos [tcl_endOfWord [$w get] [$w index $start]] + if {$pos >= 0} { + set pos [tcl_startOfNextWord [$w get] $pos] + } + if {$pos < 0} { + return end + } + return $pos + } +} else { + proc ::tk::spinbox::NextWord {w start} { + set pos [tcl_endOfWord [$w get] [$w index $start]] + if {$pos < 0} { + return end + } + return $pos + } +} + +# ::tk::spinbox::PreviousWord -- +# +# Returns the index of the previous word position before a given +# position in the spinbox. +# +# Arguments: +# w - The spinbox window in which the cursor is to move. +# start - Position at which to start search. + +proc ::tk::spinbox::PreviousWord {w start} { + set pos [tcl_startOfPreviousWord [$w get] [$w index $start]] + if {$pos < 0} { + return 0 + } + return $pos +} + +# ::tk::spinbox::GetSelection -- +# +# Returns the selected text of the spinbox. +# +# Arguments: +# w - The spinbox window from which the text to get + +proc ::tk::spinbox::GetSelection {w} { + return [string range [$w get] [$w index sel.first] \ + [expr {[$w index sel.last] - 1}]] +} diff --git a/library/tk.tcl b/library/tk.tcl index cdf52ac..12ac1ba 100644 --- a/library/tk.tcl +++ b/library/tk.tcl @@ -3,7 +3,7 @@ # Initialization script normally executed in the interpreter for each # Tk-based application. Arranges class bindings for widgets. # -# RCS: @(#) $Id: tk.tcl,v 1.21 2000/05/03 00:18:36 hobbs Exp $ +# RCS: @(#) $Id: tk.tcl,v 1.22 2000/05/29 01:43:15 hobbs Exp $ # # Copyright (c) 1992-1994 The Regents of the University of California. # Copyright (c) 1994-1996 Sun Microsystems, Inc. @@ -313,6 +313,7 @@ if {[string compare $tcl_platform(platform) "macintosh"] && \ source [file join $tk_library menu.tcl] source [file join $tk_library scale.tcl] source [file join $tk_library scrlbar.tcl] + source [file join $tk_library spinbox.tcl] source [file join $tk_library text.tcl] } diff --git a/tests/entry.test b/tests/entry.test index a44d1d4..ecaf4f3 100644 --- a/tests/entry.test +++ b/tests/entry.test @@ -6,20 +6,12 @@ # Copyright (c) 1998-1999 by Scriptics Corporation. # All rights reserved. # -# RCS: @(#) $Id: entry.test,v 1.9 2000/05/17 22:23:26 ericm Exp $ +# RCS: @(#) $Id: entry.test,v 1.10 2000/05/29 01:43:15 hobbs Exp $ if {[lsearch [namespace children] ::tcltest] == -1} { source [file join [pwd] [file dirname [info script]] defs.tcl] } -if {[lsearch [image types] test] < 0} { - puts "This application hasn't been compiled with the \"test\"" - puts "image, so I can't run this test. Are you sure you're using" - puts "tktest instead of wish?" - ::tcltest::cleanupTests - return -} - foreach i [winfo children .] { destroy $i } @@ -78,6 +70,8 @@ foreach test { {-insertborderwidth 1.3 1 2.6x {bad screen distance "2.6x"}} {-insertofftime 100 100 3.2 {expected integer but got "3.2"}} {-insertontime 100 100 3.2 {expected integer but got "3.2"}} + {-invalidcommand "any string" "any string" {} {}} + {-invcmd "any string" "any string" {} {}} {-justify right right bogus {bad justification "bogus": must be left, right, or center}} {-readonlybackground green green non-existent {unknown color name "non-existent"}} @@ -444,7 +438,7 @@ test entry-3.62 {EntryWidgetCmd procedure, "selection range" widget command} { .e select to 5 .e select range 4 4 list [catch {.e index sel.first} msg] $msg -} {1 {selection isn't in entry}} +} {1 {selection isn't in widget .e}} test entry-3.63 {EntryWidgetCmd procedure, "selection range" widget command} { .e delete 0 end .e insert end 0123456789 @@ -952,7 +946,7 @@ test entry-8.7 {DeleteChars procedure} { .e select to 8 .e delete 1 8 list [catch {.e index sel.first} msg] $msg -} {1 {selection isn't in entry}} +} {1 {selection isn't in widget .e}} test entry-8.8 {DeleteChars procedure} { .e delete 0 end .e insert 0 0123456789abcde @@ -971,7 +965,7 @@ test entry-8.9 {DeleteChars procedure} { .e select to 8 .e delete 3 8 list [catch {.e index sel.first} msg] $msg -} {1 {selection isn't in entry}} +} {1 {selection isn't in widget .e}} test entry-8.10 {DeleteChars procedure} { .e delete 0 end .e insert 0 0123456789abcde @@ -1080,7 +1074,7 @@ test entry-10.2 {EntrySetValue procedure, updating selection} { .e selection range 4 10 set x "a" list [catch {.e index sel.first} msg] $msg -} {1 {selection isn't in entry}} +} {1 {selection isn't in widget .e}} test entry-10.3 {EntrySetValue procedure, updating selection} { catch {destroy .e} entry .e -textvariable x @@ -1212,7 +1206,7 @@ test entry-13.10 {GetEntryIndex procedure} {unixOnly} { # selection range is reset. list [catch {.e index sel.first} msg] $msg -} {1 {selection isn't in entry}} +} {1 {selection isn't in widget .e}} test entry-13.11 {GetEntryIndex procedure} {macOrPc} { # On mac and pc, when selection is cleared, entry widget remembers # last selected range. When selection ownership is restored to @@ -1222,7 +1216,7 @@ test entry-13.11 {GetEntryIndex procedure} {macOrPc} { } {1 1} test entry-13.12 {GetEntryIndex procedure} {unixOnly} { list [catch {.e index sbogus} msg] $msg -} {1 {selection isn't in entry}} +} {1 {selection isn't in widget .e}} test entry-13.13 {GetEntryIndex procedure} {macOrPc} { list [catch {.e index sbogus} msg] $msg } {1 {bad entry index "sbogus"}} @@ -1383,7 +1377,7 @@ test entry-17.4 {EntryUpdateScrollbar procedure} { } {{invalid command name "thisisnotacommand"} {invalid command name "thisisnotacommand" while executing "thisisnotacommand 0 1" - (horizontal scrolling command executed by entry)}} + (horizontal scrolling command executed by .e)}} set l [interp hidden] eval destroy [winfo children .] diff --git a/tests/spinbox.test b/tests/spinbox.test new file mode 100644 index 0000000..1a42d06 --- /dev/null +++ b/tests/spinbox.test @@ -0,0 +1,1577 @@ +# This file is a Tcl script to test spinbox widgets in Tk. It is +# organized in the standard fashion for Tcl tests. +# +# Copyright (c) 1998-2000 by Scriptics Corporation. +# All rights reserved. +# +# RCS: @(#) $Id: spinbox.test,v 1.1 2000/05/29 01:43:15 hobbs Exp $ + +if {[lsearch [namespace children] ::tcltest] == -1} { + source [file join [pwd] [file dirname [info script]] defs.tcl] +} + +foreach i [winfo children .] { + destroy $i +} +wm geometry . {} +raise . + +proc scroll args { + global scrollInfo + set scrollInfo $args +} + +# Create additional widget that's used to hold the selection at times. + +spinbox .sel +.sel insert end "This is some sample text" + +# Font names + +set big -adobe-helvetica-medium-r-normal--24-240-75-75-p-*-iso8859-1 +set fixed -adobe-courier-medium-r-normal--12-120-75-75-m-*-iso8859-1 + +# Create entries in the option database to be sure that geometry options +# like border width have predictable values. + +option add *Spinbox.borderWidth 2 +option add *Spinbox.highlightThickness 2 +option add *Spinbox.font {Helvetica -12} + +spinbox .e -bd 2 -relief sunken +pack .e +update + +set i 1 +foreach test { + {-activebackground #ff0000 #ff0000 non-existent + {unknown color name "non-existent"}} + {-background #ff0000 #ff0000 non-existent + {unknown color name "non-existent"}} + {-bd 4 4 badValue {bad screen distance "badValue"}} + {-bg #ff0000 #ff0000 non-existent {unknown color name "non-existent"}} + {-borderwidth 1.3 1 badValue {bad screen distance "badValue"}} + {-buttonbackground #ff0000 #ff0000 non-existent + {unknown color name "non-existent"}} + {-buttoncursor arrow arrow badValue {bad cursor spec "badValue"}} + {-command {a command} {a command} {} {}} + {-cursor arrow arrow badValue {bad cursor spec "badValue"}} + {-disabledbackground green green non-existent + {unknown color name "non-existent"}} + {-disabledforeground #110022 #110022 bogus {unknown color name "bogus"}} + {-exportselection yes 1 xyzzy {expected boolean value but got "xyzzy"}} + {-fg #110022 #110022 bogus {unknown color name "bogus"}} + {-font -Adobe-Helvetica-Medium-R-Normal--*-120-*-*-*-*-*-* + -Adobe-Helvetica-Medium-R-Normal--*-120-*-*-*-*-*-* {} + {font "" doesn't exist}} + {-foreground #110022 #110022 bogus {unknown color name "bogus"}} + {-format %0.5f %0.5f %d {bad spinbox format specifier "%d"}} + {-from -10 -10.0 bogus {expected floating-point number but got "bogus"}} + {-highlightbackground #123456 #123456 ugly {unknown color name "ugly"}} + {-highlightcolor #123456 #123456 bogus {unknown color name "bogus"}} + {-highlightthickness 6 6 bogus {bad screen distance "bogus"}} + {-highlightthickness -2 0 {} {}} + {-increment 1.0 1.0 bogus {expected floating-point number but got "bogus"}} + {-insertbackground #110022 #110022 bogus {unknown color name "bogus"}} + {-insertborderwidth 1.3 1 2.6x {bad screen distance "2.6x"}} + {-insertofftime 100 100 3.2 {expected integer but got "3.2"}} + {-insertontime 100 100 3.2 {expected integer but got "3.2"}} + {-invalidcommand "a command" "a command" {} {}} + {-invcmd "a command" "a command" {} {}} + {-justify right right bogus {bad justification "bogus": must be left, right, or center}} + {-readonlybackground green green non-existent + {unknown color name "non-existent"}} + {-relief groove groove 1.5 {bad relief "1.5": must be flat, groove, raised, ridge, solid, or sunken}} + {-repeatdelay 500 500 3p {expected integer but got "3p"}} + {-repeatinterval -500 -500 3p {expected integer but got "3p"}} + {-selectbackground #110022 #110022 bogus {unknown color name "bogus"}} + {-selectborderwidth 1.3 1 badValue {bad screen distance "badValue"}} + {-selectforeground #654321 #654321 bogus {unknown color name "bogus"}} + {-state n normal bogus {bad state "bogus": must be disabled, normal, or readonly}} + {-takefocus "any string" "any string" {} {}} + {-textvariable i i {} {}} + {-to 14.9 14.9 bogus {expected floating-point number but got "bogus"}} + {-validate "key" "key" "bogus" {bad validate "bogus": must be all, key, focus, focusin, focusout, or none}} + {-validatecommand "a command" "a command" {} {}} + {-values {mon tue wed thur} {mon tue wed thur} {bad {}list} {list element in braces followed by "list" instead of space}} + {-vcmd "a command" "a command" {} {}} + {-width 402 402 3p {expected integer but got "3p"}} + {-wrap yes 1 xyzzy {expected boolean value but got "xyzzy"}} + {-xscrollcommand {Some command} {Some command} {} {}} +} { + set name [lindex $test 0] + test spinbox-1.$i {configuration options} { + .e configure $name [lindex $test 1] + list [lindex [.e configure $name] 4] [.e cget $name] + } [list [lindex $test 2] [lindex $test 2]] + incr i + if {[lindex $test 3] != ""} { + test spinbox-1.$i {configuration options} { + list [catch {.e configure $name [lindex $test 3]} msg] $msg + } [list 1 [lindex $test 4]] + } + .e configure $name [lindex [.e configure $name] 3] + incr i +} + +test spinbox-2.1 {Tk_SpinboxCmd procedure} { + list [catch {spinbox} msg] $msg +} {1 {wrong # args: should be "spinbox pathName ?options?"}} +test spinbox-2.2 {Tk_SpinboxCmd procedure} { + list [catch {spinbox gorp} msg] $msg +} {1 {bad window path name "gorp"}} +test spinbox-2.3 {Tk_SpinboxCmd procedure} { + catch {destroy .e} + spinbox .e + list [winfo exists .e] [winfo class .e] [info commands .e] +} {1 Spinbox .e} +test spinbox-2.4 {Tk_SpinboxCmd procedure} { + catch {destroy .e} + list [catch {spinbox .e -gorp foo} msg] $msg [winfo exists .e] \ + [info commands .e] +} {1 {unknown option "-gorp"} 0 {}} +test spinbox-2.5 {Tk_SpinboxCmd procedure} { + catch {destroy .e} + spinbox .e +} {.e} + +catch {destroy .e} +spinbox .e -font $fixed +pack .e +update + +set cx [font measure $fixed a] +set cy [font metrics $fixed -linespace] +set ux [font measure $fixed \u4e4e] + +test spinbox-3.1 {SpinboxWidgetCmd procedure} { + list [catch {.e} msg] $msg +} {1 {wrong # args: should be ".e option ?arg arg ...?"}} +test spinbox-3.2 {SpinboxWidgetCmd procedure, "bbox" widget command} { + list [catch {.e bbox} msg] $msg +} {1 {wrong # args: should be ".e bbox index"}} +test spinbox-3.3 {SpinboxWidgetCmd procedure, "bbox" widget command} { + list [catch {.e bbox a b} msg] $msg +} {1 {wrong # args: should be ".e bbox index"}} +test spinbox-3.4 {SpinboxWidgetCmd procedure, "bbox" widget command} { + list [catch {.e bbox bogus} msg] $msg +} {1 {bad spinbox index "bogus"}} +test spinbox-3.5 {SpinboxWidgetCmd procedure, "bbox" widget command} { + .e delete 0 end + .e bbox 0 +} [list 5 5 0 $cy] +test spinbox-3.6 {SpinboxWidgetCmd procedure, "bbox" widget command} { + # Tcl_UtfAtIndex(): no utf chars + + .e delete 0 end + .e insert 0 "abc" + list [.e bbox 3] [.e bbox end] +} [list "[expr 5+2*$cx] 5 $cx $cy" "[expr 5+2*$cx] 5 $cx $cy"] +test spinbox-3.7 {SpinboxWidgetCmd procedure, "bbox" widget command} { + # Tcl_UtfAtIndex(): utf at end + .e delete 0 end + .e insert 0 "ab\u4e4e" + .e bbox end +} "[expr 5+2*$cx] 5 $ux $cy" +test spinbox-3.8 {SpinboxWidgetCmd procedure, "bbox" widget command} { + # Tcl_UtfAtIndex(): utf before index + .e delete 0 end + .e insert 0 "ab\u4e4ec" + .e bbox 3 +} "[expr 5+2*$cx+$ux] 5 $cx $cy" +test spinbox-3.9 {SpinboxWidgetCmd procedure, "bbox" widget command} { + # Tcl_UtfAtIndex(): no chars + .e delete 0 end + .e bbox end +} "5 5 0 $cy" +test spinbox-3.10 {SpinboxWidgetCmd procedure, "bbox" widget command} { + .e delete 0 end + .e insert 0 "abcdefghij\u4e4eklmnop" + list [.e bbox 0] [.e bbox 1] [.e bbox 10] [.e bbox end] +} [list "5 5 $cx $cy" "[expr 5+$cx] 5 $cx $cy" "[expr 5+10*$cx] 5 $ux $cy" "[expr 5+$ux+15*$cx] 5 $cx $cy"] +test spinbox-3.11 {SpinboxWidgetCmd procedure, "cget" widget command} { + list [catch {.e cget} msg] $msg +} {1 {wrong # args: should be ".e cget option"}} +test spinbox-3.12 {SpinboxWidgetCmd procedure, "cget" widget command} { + list [catch {.e cget a b} msg] $msg +} {1 {wrong # args: should be ".e cget option"}} +test spinbox-3.13 {SpinboxWidgetCmd procedure, "cget" widget command} { + list [catch {.e cget -gorp} msg] $msg +} {1 {unknown option "-gorp"}} +test spinbox-3.14 {SpinboxWidgetCmd procedure, "cget" widget command} { + .e configure -bd 4 + .e cget -bd +} {4} +test spinbox-3.15 {SpinboxWidgetCmd procedure, "configure" widget command} { + llength [.e configure] +} {49} +test spinbox-3.16 {SpinboxWidgetCmd procedure, "configure" widget command} { + list [catch {.e configure -foo} msg] $msg +} {1 {unknown option "-foo"}} +test spinbox-3.17 {SpinboxWidgetCmd procedure, "configure" widget command} { + .e configure -bd 4 + .e configure -bg #ffffff + lindex [.e configure -bd] 4 +} {4} +test spinbox-3.18 {SpinboxWidgetCmd procedure, "delete" widget command} { + list [catch {.e delete} msg] $msg +} {1 {wrong # args: should be ".e delete firstIndex ?lastIndex?"}} +test spinbox-3.19 {SpinboxWidgetCmd procedure, "delete" widget command} { + list [catch {.e delete a b c} msg] $msg +} {1 {wrong # args: should be ".e delete firstIndex ?lastIndex?"}} +test spinbox-3.20 {SpinboxWidgetCmd procedure, "delete" widget command} { + list [catch {.e delete foo} msg] $msg +} {1 {bad spinbox index "foo"}} +test spinbox-3.21 {SpinboxWidgetCmd procedure, "delete" widget command} { + list [catch {.e delete 0 bar} msg] $msg +} {1 {bad spinbox index "bar"}} +test spinbox-3.22 {SpinboxWidgetCmd procedure, "delete" widget command} { + .e delete 0 end + .e insert end "01234567890" + .e delete 2 4 + .e get +} {014567890} +test spinbox-3.23 {SpinboxWidgetCmd procedure, "delete" widget command} { + .e delete 0 end + .e insert end "01234567890" + .e delete 6 + .e get +} {0123457890} +test spinbox-3.24 {SpinboxWidgetCmd procedure, "delete" widget command} { + # UTF + set x {} + .e delete 0 end + .e insert end "01234\u4e4e67890" + .e delete 6 + lappend x [.e get] + .e delete 0 end + .e insert end "012345\u4e4e7890" + .e delete 6 + lappend x [.e get] + .e delete 0 end + .e insert end "0123456\u4e4e890" + .e delete 6 + lappend x [.e get] +} [list "01234\u4e4e7890" "0123457890" "012345\u4e4e890"] +test spinbox-3.25 {SpinboxWidgetCmd procedure, "delete" widget command} { + .e delete 0 end + .e insert end "01234567890" + .e delete 6 5 + .e get +} {01234567890} +test spinbox-3.26 {SpinboxWidgetCmd procedure, "delete" widget command} { + .e delete 0 end + .e insert end "01234567890" + .e configure -state disabled + .e delete 2 8 + .e configure -state normal + .e get +} {01234567890} +test spinbox-3.27 {SpinboxWidgetCmd procedure, "get" widget command} { + list [catch {.e get foo} msg] $msg +} {1 {wrong # args: should be ".e get"}} +test spinbox-3.28 {SpinboxWidgetCmd procedure, "icursor" widget command} { + list [catch {.e icursor} msg] $msg +} {1 {wrong # args: should be ".e icursor pos"}} +test spinbox-3.29 {SpinboxWidgetCmd procedure, "icursor" widget command} { + list [catch {.e icursor foo} msg] $msg +} {1 {bad spinbox index "foo"}} +test spinbox-3.30 {SpinboxWidgetCmd procedure, "icursor" widget command} { + .e delete 0 end + .e insert end "01234567890" + .e icursor 4 + .e index insert +} {4} +test spinbox-3.31 {SpinboxWidgetCmd procedure, "index" widget command} { + list [catch {.e in} msg] $msg +} {1 {ambiguous option "in": must be bbox, cget, configure, delete, get, icursor, identify, index, insert, invoke, scan, selection, set, validate, or xview}} +test spinbox-3.32 {SpinboxWidgetCmd procedure, "index" widget command} { + list [catch {.e index} msg] $msg +} {1 {wrong # args: should be ".e index string"}} +test spinbox-3.33 {SpinboxWidgetCmd procedure, "index" widget command} { + list [catch {.e index foo} msg] $msg +} {1 {bad spinbox index "foo"}} +test spinbox-3.34 {SpinboxWidgetCmd procedure, "index" widget command} { + list [catch {.e index 0} msg] $msg +} {0 0} +test spinbox-3.35 {SpinboxWidgetCmd procedure, "index" widget command} { + # UTF + .e delete 0 end + .e insert 0 abc\u4e4e\u0153def + list [.e index 3] [.e index 4] [.e index end] +} {3 4 8} +test spinbox-3.36 {SpinboxWidgetCmd procedure, "insert" widget command} { + list [catch {.e insert a} msg] $msg +} {1 {wrong # args: should be ".e insert index text"}} +test spinbox-3.37 {SpinboxWidgetCmd procedure, "insert" widget command} { + list [catch {.e insert a b c} msg] $msg +} {1 {wrong # args: should be ".e insert index text"}} +test spinbox-3.38 {SpinboxWidgetCmd procedure, "insert" widget command} { + list [catch {.e insert foo Text} msg] $msg +} {1 {bad spinbox index "foo"}} +test spinbox-3.39 {SpinboxWidgetCmd procedure, "insert" widget command} { + .e delete 0 end + .e insert end "01234567890" + .e insert 3 xxx + .e get +} {012xxx34567890} +test spinbox-3.40 {SpinboxWidgetCmd procedure, "insert" widget command} { + .e delete 0 end + .e insert end "01234567890" + .e configure -state disabled + .e insert 3 xxx + .e configure -state normal + .e get +} {01234567890} +test spinbox-3.41 {SpinboxWidgetCmd procedure, "insert" widget command} { + list [catch {.e insert a b c} msg] $msg +} {1 {wrong # args: should be ".e insert index text"}} +test spinbox-3.42 {SpinboxWidgetCmd procedure, "scan" widget command} { + list [catch {.e scan a} msg] $msg +} {1 {wrong # args: should be ".e scan mark|dragto x"}} +test spinbox-3.43 {SpinboxWidgetCmd procedure, "scan" widget command} { + list [catch {.e scan a b c} msg] $msg +} {1 {wrong # args: should be ".e scan mark|dragto x"}} +test spinbox-3.44 {SpinboxWidgetCmd procedure, "scan" widget command} { + list [catch {.e scan foobar 20} msg] $msg +} {1 {bad scan option "foobar": must be mark or dragto}} +test spinbox-3.45 {SpinboxWidgetCmd procedure, "scan" widget command} { + list [catch {.e scan mark 20.1} msg] $msg +} {1 {expected integer but got "20.1"}} +# This test is non-portable because character sizes vary. + +test spinbox-3.46 {SpinboxWidgetCmd procedure, "scan" widget command} {fonts} { + .e delete 0 end + update + .e insert end "This is quite a long string, in fact a " + .e insert end "very very long string" + .e scan mark 30 + .e scan dragto 28 + .e index @0 +} {2} +test spinbox-3.47 {SpinboxWidgetCmd procedure, "select" widget command} { + list [catch {.e select} msg] $msg +} {1 {wrong # args: should be ".e selection option ?index?"}} +test spinbox-3.48 {SpinboxWidgetCmd procedure, "select" widget command} { + list [catch {.e select foo} msg] $msg +} {1 {bad selection option "foo": must be adjust, clear, element, from, present, range, or to}} +test spinbox-3.49 {SpinboxWidgetCmd procedure, "select clear" widget command} { + list [catch {.e select clear gorp} msg] $msg +} {1 {wrong # args: should be ".e selection clear"}} +test spinbox-3.50 {SpinboxWidgetCmd procedure, "select clear" widget command} { + .e delete 0 end + .e insert end "0123456789" + .e select from 1 + .e select to 4 + update + .e select clear + list [catch {selection get} msg] $msg [selection own] +} {1 {PRIMARY selection doesn't exist or form "STRING" not defined} .e} +test spinbox-3.51 {SpinboxWidgetCmd procedure, "selection present" widget command} { + list [catch {.e selection present foo} msg] $msg +} {1 {wrong # args: should be ".e selection present"}} +test spinbox-3.52 {SpinboxWidgetCmd procedure, "selection present" widget command} { + .e delete 0 end + .e insert end 0123456789 + .e select from 3 + .e select to 6 + .e selection present +} {1} +test spinbox-3.53 {SpinboxWidgetCmd procedure, "selection present" widget command} { + .e delete 0 end + .e insert end 0123456789 + .e select from 3 + .e select to 6 + .e configure -exportselection false + .e selection present +} {1} +.e configure -exportselection true +test spinbox-3.54 {SpinboxWidgetCmd procedure, "selection present" widget command} { + .e delete 0 end + .e insert end 0123456789 + .e select from 3 + .e select to 6 + .e delete 0 end + .e selection present +} {0} +test spinbox-3.55 {SpinboxWidgetCmd procedure, "selection adjust" widget command} { + list [catch {.e select adjust x} msg] $msg +} {1 {bad spinbox index "x"}} +test spinbox-3.56 {SpinboxWidgetCmd procedure, "selection adjust" widget command} { + list [catch {.e select adjust 2 3} msg] $msg +} {1 {wrong # args: should be ".e selection adjust index"}} +test spinbox-3.57 {SpinboxWidgetCmd procedure, "selection adjust" widget command} { + .e delete 0 end + .e insert end "0123456789" + .e select from 1 + .e select to 5 + update + .e select adjust 4 + selection get +} {123} +test spinbox-3.58 {SpinboxWidgetCmd procedure, "selection adjust" widget command} { + .e delete 0 end + .e insert end "0123456789" + .e select from 1 + .e select to 5 + update + .e select adjust 2 + selection get +} {234} +test spinbox-3.59 {SpinboxWidgetCmd procedure, "selection from" widget command} { + list [catch {.e select from 2 3} msg] $msg +} {1 {wrong # args: should be ".e selection from index"}} +test spinbox-3.60 {SpinboxWidgetCmd procedure, "selection range" widget command} { + list [catch {.e select range 2} msg] $msg +} {1 {wrong # args: should be ".e selection range start end"}} +test spinbox-3.61 {SpinboxWidgetCmd procedure, "selection range" widget command} { + list [catch {.e selection range 2 3 4} msg] $msg +} {1 {wrong # args: should be ".e selection range start end"}} +test spinbox-3.62 {SpinboxWidgetCmd procedure, "selection range" widget command} { + .e delete 0 end + .e insert end 0123456789 + .e select from 1 + .e select to 5 + .e select range 4 4 + list [catch {.e index sel.first} msg] $msg +} {1 {selection isn't in widget .e}} +test spinbox-3.63 {SpinboxWidgetCmd procedure, "selection range" widget command} { + .e delete 0 end + .e insert end 0123456789 + .e select from 3 + .e select to 7 + .e select range 2 9 + list [.e index sel.first] [.e index sel.last] [.e index anchor] +} {2 9 3} +.e delete 0 end +.e insert end "This is quite a long text string, so long that it " +.e insert end "runs off the end of the window quite a bit." +test spinbox-3.64 {SpinboxWidgetCmd procedure, "selection to" widget command} { + list [catch {.e select to 2 3} msg] $msg +} {1 {wrong # args: should be ".e selection to index"}} +test spinbox-3.65 {SpinboxWidgetCmd procedure, "xview" widget command} { + .e xview 5 + .e xview +} {0.0537634 0.268817} +test spinbox-3.66 {SpinboxWidgetCmd procedure, "xview" widget command} { + list [catch {.e xview gorp} msg] $msg +} {1 {bad spinbox index "gorp"}} +test spinbox-3.67 {SpinboxWidgetCmd procedure, "xview" widget command} { + .e xview 0 + .e icursor 10 + .e xview insert + .e xview +} {0.107527 0.322581} +test spinbox-3.68 {SpinboxWidgetCmd procedure, "xview" widget command} { + list [catch {.e xview moveto foo bar} msg] $msg +} {1 {wrong # args: should be ".e xview moveto fraction"}} +test spinbox-3.69 {SpinboxWidgetCmd procedure, "xview" widget command} { + list [catch {.e xview moveto foo} msg] $msg +} {1 {expected floating-point number but got "foo"}} +test spinbox-3.70 {SpinboxWidgetCmd procedure, "xview" widget command} { + .e xview moveto 0.5 + .e xview +} {0.505376 0.72043} +test spinbox-3.71 {SpinboxWidgetCmd procedure, "xview" widget command} { + list [catch {.e xview scroll 24} msg] $msg +} {1 {wrong # args: should be ".e xview scroll number units|pages"}} +test spinbox-3.72 {SpinboxWidgetCmd procedure, "xview" widget command} { + list [catch {.e xview scroll gorp units} msg] $msg +} {1 {expected integer but got "gorp"}} +test spinbox-3.73 {SpinboxWidgetCmd procedure, "xview" widget command} { + .e xview moveto 0 + .e xview scroll 1 pages + .e xview +} {0.193548 0.408602} +test spinbox-3.74 {SpinboxWidgetCmd procedure, "xview" widget command} { + .e xview moveto .9 + update + .e xview scroll -2 p + .e xview +} {0.397849 0.612903} +test spinbox-3.75 {SpinboxWidgetCmd procedure, "xview" widget command} { + .e xview 30 + update + .e xview scroll 2 units + .e index @0 +} {32} +test spinbox-3.76 {SpinboxWidgetCmd procedure, "xview" widget command} { + .e xview 30 + update + .e xview scroll -1 units + .e index @0 +} {29} +test spinbox-3.77 {SpinboxWidgetCmd procedure, "xview" widget command} { + list [catch {.e xview scroll 23 foobars} msg] $msg +} {1 {bad argument "foobars": must be units or pages}} +test spinbox-3.78 {SpinboxWidgetCmd procedure, "xview" widget command} { + list [catch {.e xview eat 23 hamburgers} msg] $msg +} {1 {unknown option "eat": must be moveto or scroll}} +test spinbox-3.79 {SpinboxWidgetCmd procedure, "xview" widget command} { + .e xview 0 + update + .e xview -4 + .e index @0 +} {0} +test spinbox-3.80 {SpinboxWidgetCmd procedure, "xview" widget command} { + .e xview 300 + .e index @0 +} {73} +.e insert 10 \u4e4e +test spinbox-3.81 {SpinboxWidgetCmd procedure, "xview" widget command} { + # UTF + # If Tcl_NumUtfChars wasn't used, wrong answer would be: + # 0.106383 0.117021 0.117021 + + set x {} + .e xview moveto .1 + lappend x [lindex [.e xview] 0] + .e xview moveto .11 + lappend x [lindex [.e xview] 0] + .e xview moveto .12 + lappend x [lindex [.e xview] 0] +} {0.0957447 0.106383 0.117021} +test spinbox-3.82 {SpinboxWidgetCmd procedure} { + list [catch {.e gorp} msg] $msg +} {1 {bad option "gorp": must be bbox, cget, configure, delete, get, icursor, identify, index, insert, invoke, scan, selection, set, validate, or xview}} + +frame .f -width 200 -height 50 -relief raised -bd 2 +pack .f -side right +test spinbox-5.1 {ConfigureSpinbox procedure, -textvariable} { + catch {destroy .e} + set x 12345 + spinbox .e -textvariable x + .e get +} {12345} +test spinbox-5.2 {ConfigureSpinbox procedure, -textvariable} { + catch {destroy .e} + set x 12345 + spinbox .e -textvariable x + set y abcde + .e configure -textvariable y + set x 54321 + .e get +} {abcde} +test spinbox-5.3 {ConfigureSpinbox procedure, -textvariable} { + catch {destroy .e} + catch {unset x} + spinbox .e + .e insert 0 "Some text" + .e configure -textvariable x + set x +} {Some text} +test spinbox-5.4 {ConfigureSpinbox procedure, -textvariable} { + proc override args { + global x + set x 12345 + } + catch {destroy .e} + catch {unset x} + trace variable x w override + spinbox .e + .e insert 0 "Some text" + .e configure -textvariable x + set result [list $x [.e get]] + unset x; rename override {} + set result +} {12345 12345} +test spinbox-5.5 {ConfigureSpinbox procedure} { + catch {destroy .e} + spinbox .e -exportselection false + pack .e + .e insert end "0123456789" + .sel select from 0 + .sel select to 10 + set x {} + lappend x [selection get] + .e select from 1 + .e select to 5 + lappend x [selection get] + .e configure -exportselection 1 + lappend x [selection get] + set x +} {{This is so} {This is so} 1234} +test spinbox-5.6 {ConfigureSpinbox procedure} { + catch {destroy .e} + spinbox .e + pack .e + .e insert end "0123456789" + .e select from 1 + .e select to 5 + .e configure -exportselection 0 + list [catch {selection get} msg] $msg [.e index sel.first] \ + [.e index sel.last] +} {1 {PRIMARY selection doesn't exist or form "STRING" not defined} 1 5} +test spinbox-5.7 {ConfigureSpinbox procedure} { + catch {destroy .e} + spinbox .e -font $fixed -width 4 -xscrollcommand scroll + pack .e + .e insert end "01234567890" + update + .e configure -width 5 + set scrollInfo +} {0 0.363636} +test spinbox-5.8 {ConfigureSpinbox procedure} {fonts} { + catch {destroy .e} + spinbox .e -width 0 + pack .e + .e insert end "0123" + update + .e configure -font $big + update + winfo geom .e +} {79x37+0+0} +test spinbox-5.9 {ConfigureSpinbox procedure} {fonts} { + catch {destroy .e} + spinbox .e -font $fixed -bd 2 -relief raised + pack .e + .e insert end "0123" + update + list [.e index @10] [.e index @11] [.e index @12] [.e index @13] +} {0 0 1 1} +test spinbox-5.10 {ConfigureSpinbox procedure} {fonts} { + catch {destroy .e} + spinbox .e -font $fixed -bd 2 -relief flat + pack .e + .e insert end "0123" + update + list [.e index @10] [.e index @11] [.e index @12] [.e index @13] +} {0 0 1 1} +test spinbox-5.11 {ConfigureSpinbox procedure} { + # If "0" in selected font had 0 width, caused divide-by-zero error. + + catch {destroy .e} + pack [spinbox .e -font {{open look glyph}}] + .e scan dragto 30 + update +} {} + +# No tests for DisplaySpinbox. + +test spinbox-6.1 {SpinboxComputeGeometry procedure} {fonts} { + catch {destroy .e} + spinbox .e -font $fixed -bd 2 -relief raised -width 20 -highlightthickness 3 + pack .e + .e insert end 012\t45 + update + list [.e index @61] [.e index @62] +} {3 4} +test spinbox-6.2 {SpinboxComputeGeometry procedure} {fonts} { + catch {destroy .e} + spinbox .e -font $fixed -bd 2 -relief raised -width 20 -justify center \ + -highlightthickness 3 + pack .e + .e insert end 012\t45 + update + list [.e index @96] [.e index @97] +} {3 4} +test spinbox-6.3 {SpinboxComputeGeometry procedure} {fonts} { + catch {destroy .e} + spinbox .e -font $fixed -bd 2 -relief raised -width 20 -justify right \ + -highlightthickness 3 + pack .e + .e insert end 012\t45 + update + list [.e index @131] [.e index @132] +} {3 4} +test spinbox-6.4 {SpinboxComputeGeometry procedure} { + catch {destroy .e} + spinbox .e -font $fixed -bd 2 -relief raised -width 5 + pack .e + .e insert end "01234567890" + update + .e xview 6 + .e index @0 +} {6} +test spinbox-6.5 {SpinboxComputeGeometry procedure} { + catch {destroy .e} + spinbox .e -font $fixed -bd 2 -relief raised -width 5 + pack .e + .e insert end "01234567890" + update + .e xview 7 + .e index @0 +} {6} +test spinbox-6.6 {SpinboxComputeGeometry procedure} {fonts} { + catch {destroy .e} + spinbox .e -font $fixed -bd 2 -relief raised -width 10 + pack .e + .e insert end "01234\t67890" + update + .e xview 3 + list [.e index @39] [.e index @40] +} {5 6} +test spinbox-6.7 {SpinboxComputeGeometry procedure} {fonts} { + catch {destroy .e} + spinbox .e -font $big -bd 3 -relief raised -width 5 + pack .e + .e insert end "01234567" + update + list [winfo reqwidth .e] [winfo reqheight .e] +} {94 39} +test spinbox-6.8 {SpinboxComputeGeometry procedure} {fonts} { + catch {destroy .e} + spinbox .e -font $big -bd 3 -relief raised -width 0 + pack .e + .e insert end "01234567" + update + list [winfo reqwidth .e] [winfo reqheight .e] +} {133 39} +test spinbox-6.9 {SpinboxComputeGeometry procedure} {fonts} { + catch {destroy .e} + spinbox .e -font $big -bd 3 -relief raised -width 0 -highlightthickness 2 + pack .e + update + list [winfo reqwidth .e] [winfo reqheight .e] +} {42 39} + +catch {destroy .e} +spinbox .e -width 10 -font $fixed -textvariable contents -xscrollcommand scroll +pack .e +focus .e +test spinbox-7.1 {InsertChars procedure} { + .e delete 0 end + .e insert 0 abcde + .e insert 2 XXX + update + list [.e get] $contents $scrollInfo +} {abXXXcde abXXXcde {0 1}} +test spinbox-7.2 {InsertChars procedure} { + .e delete 0 end + .e insert 0 abcde + .e insert 500 XXX + update + list [.e get] $contents $scrollInfo +} {abcdeXXX abcdeXXX {0 1}} +test spinbox-7.3 {InsertChars procedure} { + .e delete 0 end + .e insert 0 0123456789 + .e select from 2 + .e select to 6 + .e insert 2 XXX + set x "[.e index sel.first] [.e index sel.last]" + .e select to 8 + lappend x [.e index sel.first] [.e index sel.last] +} {5 9 5 8} +test spinbox-7.4 {InsertChars procedure} { + .e delete 0 end + .e insert 0 0123456789 + .e select from 2 + .e select to 6 + .e insert 3 XXX + set x "[.e index sel.first] [.e index sel.last]" + .e select to 8 + lappend x [.e index sel.first] [.e index sel.last] +} {2 9 2 8} +test spinbox-7.5 {InsertChars procedure} { + .e delete 0 end + .e insert 0 0123456789 + .e select from 2 + .e select to 6 + .e insert 5 XXX + set x "[.e index sel.first] [.e index sel.last]" + .e select to 8 + lappend x [.e index sel.first] [.e index sel.last] +} {2 9 2 8} +test spinbox-7.6 {InsertChars procedure} { + .e delete 0 end + .e insert 0 0123456789 + .e select from 2 + .e select to 6 + .e insert 6 XXX + set x "[.e index sel.first] [.e index sel.last]" + .e select to 5 + lappend x [.e index sel.first] [.e index sel.last] +} {2 6 2 5} +test spinbox-7.7 {InsertChars procedure} { + .e delete 0 end + .e insert 0 0123456789 + .e icursor 4 + .e insert 4 XXX + .e index insert +} {7} +test spinbox-7.8 {InsertChars procedure} { + .e delete 0 end + .e insert 0 0123456789 + .e icursor 4 + .e insert 5 XXX + .e index insert +} {4} +test spinbox-7.9 {InsertChars procedure} { + .e delete 0 end + .e insert 0 "This is a very long string" + update + .e xview 4 + .e insert 3 XXX + .e index @0 +} {7} +test spinbox-7.10 {InsertChars procedure} { + .e delete 0 end + .e insert 0 "This is a very long string" + update + .e xview 4 + .e insert 4 XXX + .e index @0 +} {4} +.e configure -width 0 +test spinbox-7.11 {InsertChars procedure} {fonts} { + .e delete 0 end + .e insert 0 "xyzzy" + update + .e insert 2 00 + winfo reqwidth .e +} {70} + +.e configure -width 10 +test spinbox-8.1 {DeleteChars procedure} { + .e delete 0 end + .e insert 0 abcde + .e delete 2 4 + update + list [.e get] $contents $scrollInfo +} {abe abe {0 1}} +test spinbox-8.2 {DeleteChars procedure} { + .e delete 0 end + .e insert 0 abcde + .e delete -2 2 + update + list [.e get] $contents $scrollInfo +} {cde cde {0 1}} +test spinbox-8.3 {DeleteChars procedure} { + .e delete 0 end + .e insert 0 abcde + .e delete 3 1000 + update + list [.e get] $contents $scrollInfo +} {abc abc {0 1}} +test spinbox-8.4 {DeleteChars procedure} { + .e delete 0 end + .e insert 0 0123456789abcde + .e select from 3 + .e select to 8 + .e delete 1 3 + update + set x "[.e index sel.first] [.e index sel.last]" + .e select to 5 + lappend x [.e index sel.first] [.e index sel.last] +} {1 6 1 5} +test spinbox-8.5 {DeleteChars procedure} { + .e delete 0 end + .e insert 0 0123456789abcde + .e select from 3 + .e select to 8 + .e delete 1 4 + update + set x "[.e index sel.first] [.e index sel.last]" + .e select to 4 + lappend x [.e index sel.first] [.e index sel.last] +} {1 5 1 4} +test spinbox-8.6 {DeleteChars procedure} { + .e delete 0 end + .e insert 0 0123456789abcde + .e select from 3 + .e select to 8 + .e delete 1 7 + update + set x "[.e index sel.first] [.e index sel.last]" + .e select to 5 + lappend x [.e index sel.first] [.e index sel.last] +} {1 2 1 5} +test spinbox-8.7 {DeleteChars procedure} { + .e delete 0 end + .e insert 0 0123456789abcde + .e select from 3 + .e select to 8 + .e delete 1 8 + list [catch {.e index sel.first} msg] $msg +} {1 {selection isn't in widget .e}} +test spinbox-8.8 {DeleteChars procedure} { + .e delete 0 end + .e insert 0 0123456789abcde + .e select from 3 + .e select to 8 + .e delete 3 7 + update + set x "[.e index sel.first] [.e index sel.last]" + .e select to 8 + lappend x [.e index sel.first] [.e index sel.last] +} {3 4 3 8} +test spinbox-8.9 {DeleteChars procedure} { + .e delete 0 end + .e insert 0 0123456789abcde + .e select from 3 + .e select to 8 + .e delete 3 8 + list [catch {.e index sel.first} msg] $msg +} {1 {selection isn't in widget .e}} +test spinbox-8.10 {DeleteChars procedure} { + .e delete 0 end + .e insert 0 0123456789abcde + .e select from 8 + .e select to 3 + .e delete 5 8 + update + set x "[.e index sel.first] [.e index sel.last]" + .e select to 8 + lappend x [.e index sel.first] [.e index sel.last] +} {3 5 5 8} +test spinbox-8.11 {DeleteChars procedure} { + .e delete 0 end + .e insert 0 0123456789abcde + .e select from 8 + .e select to 3 + .e delete 8 10 + update + set x "[.e index sel.first] [.e index sel.last]" + .e select to 4 + lappend x [.e index sel.first] [.e index sel.last] +} {3 8 4 8} +test spinbox-8.12 {DeleteChars procedure} { + .e delete 0 end + .e insert 0 0123456789abcde + .e icursor 4 + .e delete 1 4 + .e index insert +} {1} +test spinbox-8.13 {DeleteChars procedure} { + .e delete 0 end + .e insert 0 0123456789abcde + .e icursor 4 + .e delete 1 5 + .e index insert +} {1} +test spinbox-8.14 {DeleteChars procedure} { + .e delete 0 end + .e insert 0 0123456789abcde + .e icursor 4 + .e delete 4 6 + .e index insert +} {4} +test spinbox-8.15 {DeleteChars procedure} { + .e delete 0 end + .e insert 0 "This is a very long string" + .e xview 4 + .e delete 1 4 + .e index @0 +} {1} +test spinbox-8.16 {DeleteChars procedure} { + .e delete 0 end + .e insert 0 "This is a very long string" + .e xview 4 + .e delete 1 5 + .e index @0 +} {1} +test spinbox-8.17 {DeleteChars procedure} { + .e delete 0 end + .e insert 0 "This is a very long string" + .e xview 4 + .e delete 4 6 + .e index @0 +} {4} +.e configure -width 0 +test spinbox-8.18 {DeleteChars procedure} {fonts} { + .e delete 0 end + .e insert 0 "xyzzy" + update + .e delete 2 4 + winfo reqwidth .e +} {42} + +test spinbox-9.1 {SpinboxValueChanged procedure} { + catch {destroy .e} + proc override args { + global x + set x 12345 + } + catch {unset x} + trace variable x w override + spinbox .e -textvariable x + .e insert 0 foo + set result [list $x [.e get]] + unset x; rename override {} + set result +} {12345 12345} + +catch {destroy .e} +spinbox .e +pack .e +.e configure -width 0 +test spinbox-10.1 {SpinboxSetValue procedure} {fonts} { + set x abcde + set y ab + .e configure -textvariable x + update + .e configure -textvariable y + update + list [.e get] [winfo reqwidth .e] +} {ab 35} +test spinbox-10.2 {SpinboxSetValue procedure, updating selection} { + catch {destroy .e} + spinbox .e -textvariable x + .e insert 0 "abcdefghjklmnopqrstu" + .e selection range 4 10 + set x "a" + list [catch {.e index sel.first} msg] $msg +} {1 {selection isn't in widget .e}} +test spinbox-10.3 {SpinboxSetValue procedure, updating selection} { + catch {destroy .e} + spinbox .e -textvariable x + .e insert 0 "abcdefghjklmnopqrstu" + .e selection range 4 10 + set x "abcdefg" + list [.e index sel.first] [.e index sel.last] +} {4 7} +test spinbox-10.4 {SpinboxSetValue procedure, updating selection} { + catch {destroy .e} + spinbox .e -textvariable x + .e insert 0 "abcdefghjklmnopqrstu" + .e selection range 4 10 + set x "abcdefghijklmn" + list [.e index sel.first] [.e index sel.last] +} {4 10} +test spinbox-10.5 {SpinboxSetValue procedure, updating display position} { + catch {destroy .e} + spinbox .e -width 10 -font $fixed -textvariable x + pack .e + .e insert 0 "abcdefghjklmnopqrstuvwxyz" + .e xview 10 + update + set x "abcdefg" + update + .e index @0 +} {0} +test spinbox-10.6 {SpinboxSetValue procedure, updating display position} { + catch {destroy .e} + spinbox .e -width 10 -font $fixed -textvariable x + pack .e + .e insert 0 "abcdefghjklmnopqrstuvwxyz" + .e xview 10 + update + set x "1234567890123456789012" + update + .e index @0 +} {10} +test spinbox-10.7 {SpinboxSetValue procedure, updating insertion cursor} { + catch {destroy .e} + spinbox .e -width 10 -font $fixed -textvariable x + pack .e + .e insert 0 "abcdefghjklmnopqrstuvwxyz" + .e icursor 5 + set x "123" + .e index insert +} {3} +test spinbox-10.8 {SpinboxSetValue procedure, updating insertion cursor} { + catch {destroy .e} + spinbox .e -width 10 -font $fixed -textvariable x + pack .e + .e insert 0 "abcdefghjklmnopqrstuvwxyz" + .e icursor 5 + set x "123456" + .e index insert +} {5} + +test spinbox-11.1 {SpinboxEventProc procedure} { + catch {destroy .e} + spinbox .e + .e insert 0 abcdefg + destroy .e + update +} {} +test spinbox-11.2 {SpinboxEventProc procedure} { + eval destroy [winfo children .] + spinbox .e1 -fg #112233 + rename .e1 .e2 + set x {} + lappend x [winfo children .] + lappend x [.e2 cget -fg] + destroy .e1 + lappend x [info command .e*] [winfo children .] +} {.e1 #112233 {} {}} + +test spinbox-12.1 {SpinboxCmdDeletedProc procedure} { + eval destroy [winfo children .] + button .e1 -text "xyz_123" + rename .e1 {} + list [info command .e*] [winfo children .] +} {{} {}} + +catch {destroy .e} +spinbox .e -font $fixed -width 5 -bd 2 -relief sunken +pack .e +.e insert 0 012345678901234567890 +.e xview 4 +update +test spinbox-13.1 {GetSpinboxIndex procedure} { + .e index end +} {21} +test spinbox-13.2 {GetSpinboxIndex procedure} { + list [catch {.e index abogus} msg] $msg +} {1 {bad spinbox index "abogus"}} +test spinbox-13.3 {GetSpinboxIndex procedure} { + .e select from 1 + .e select to 6 + .e index anchor +} {1} +test spinbox-13.4 {GetSpinboxIndex procedure} { + .e select from 4 + .e select to 1 + .e index anchor +} {4} +test spinbox-13.5 {GetSpinboxIndex procedure} { + .e select from 3 + .e select to 15 + .e select adjust 4 + .e index anchor +} {15} +test spinbox-13.6 {GetSpinboxIndex procedure} { + list [catch {.e index ebogus} msg] $msg +} {1 {bad spinbox index "ebogus"}} +test spinbox-13.7 {GetSpinboxIndex procedure} { + .e icursor 2 + .e index insert +} {2} +test spinbox-13.8 {GetSpinboxIndex procedure} { + list [catch {.e index ibogus} msg] $msg +} {1 {bad spinbox index "ibogus"}} +test spinbox-13.9 {GetSpinboxIndex procedure} { + .e select from 1 + .e select to 6 + list [.e index sel.first] [.e index sel.last] +} {1 6} +selection clear .e +test spinbox-13.10 {GetSpinboxIndex procedure} {unixOnly} { + # On unix, when selection is cleared, spinbox widget's internal + # selection range is reset. + + list [catch {.e index sel.first} msg] $msg +} {1 {selection isn't in widget .e}} +test spinbox-13.11 {GetSpinboxIndex procedure} {macOrPc} { + # On mac and pc, when selection is cleared, spinbox widget remembers + # last selected range. When selection ownership is restored to + # spinbox, the old range will be rehighlighted. + + list [catch {selection get}] [.e index sel.first] +} {1 1} +test spinbox-13.12 {GetSpinboxIndex procedure} {unixOnly} { + list [catch {.e index sbogus} msg] $msg +} {1 {selection isn't in widget .e}} +test spinbox-13.13 {GetSpinboxIndex procedure} {macOrPc} { + list [catch {.e index sbogus} msg] $msg +} {1 {bad spinbox index "sbogus"}} +test spinbox-13.14 {GetSpinboxIndex procedure} {macOrPc} { + list [catch {selection get}] [catch {.e index sbogus}] +} {1 1} +test spinbox-13.15 {GetSpinboxIndex procedure} { + list [catch {.e index @xyz} msg] $msg +} {1 {bad spinbox index "@xyz"}} +test spinbox-13.16 {GetSpinboxIndex procedure} {fonts} { + .e index @4 +} {4} +test spinbox-13.17 {GetSpinboxIndex procedure} {fonts} { + .e index @11 +} {4} +test spinbox-13.18 {GetSpinboxIndex procedure} {fonts} { + .e index @12 +} {5} +test spinbox-13.19 {GetSpinboxIndex procedure} {fonts} { + # 11 is the minimum button width + .e index @[expr [winfo width .e] - 6 - 11] +} {8} +test spinbox-13.20 {GetSpinboxIndex procedure} {fonts} { + .e index @[expr [winfo width .e] - 5] +} {9} +test spinbox-13.21 {GetSpinboxIndex procedure} { + .e index @1000 +} {9} +test spinbox-13.22 {GetSpinboxIndex procedure} { + list [catch {.e index 1xyz} msg] $msg +} {1 {bad spinbox index "1xyz"}} +test spinbox-13.23 {GetSpinboxIndex procedure} { + .e index -10 +} {0} +test spinbox-13.24 {GetSpinboxIndex procedure} { + .e index 12 +} {12} +test spinbox-13.25 {GetSpinboxIndex procedure} { + .e index 49 +} {21} + +# XXX Still need to write tests for SpinboxScanTo and SpinboxSelectTo. + +set x {} +for {set i 1} {$i <= 500} {incr i} { + append x "This is line $i, out of 500\n" +} +test spinbox-14.1 {SpinboxFetchSelection procedure} { + catch {destroy .e} + spinbox .e + .e insert end "This is a test string" + .e select from 1 + .e select to 18 + selection get +} {his is a test str} +test spinbox-14.3 {SpinboxFetchSelection procedure} { + catch {destroy .e} + spinbox .e + .e insert end $x + .e select from 0 + .e select to end + string compare [selection get] $x +} 0 + +test spinbox-15.1 {SpinboxLostSelection} { + catch {destroy .e} + spinbox .e + .e insert 0 "Text" + .e select from 0 + .e select to 4 + set result [selection get] + selection clear + .e select from 0 + .e select to 4 + lappend result [selection get] +} {Text Text} + +# No tests for EventuallyRedraw. + +catch {destroy .e} +spinbox .e -width 10 -xscrollcommand scroll +pack .e +update + +test spinbox-16.1 {SpinboxVisibleRange procedure} {fonts} { + .e delete 0 end + .e insert 0 ............................. + .e xview +} {0 0.827586} +test spinbox-15.4 {SpinboxVisibleRange procedure} { + .e delete 0 end + .e xview +} {0 1} + +catch {destroy .e} +spinbox .e -width 10 -xscrollcommand scroll -font $fixed +pack .e +update +test spinbox-17.1 {SpinboxUpdateScrollbar procedure} { + .e delete 0 end + .e insert 0 123 + update + set scrollInfo +} {0 1} +test spinbox-17.2 {SpinboxUpdateScrollbar procedure} { + .e delete 0 end + .e insert 0 0123456789abcdef + .e xview 3 + update + set scrollInfo +} {0.1875 0.8125} +test spinbox-17.3 {SpinboxUpdateScrollbar procedure} { + .e delete 0 end + .e insert 0 abcdefghijklmnopqrs + .e xview 6 + update + set scrollInfo +} {0.315789 0.842105} +test spinbox-17.4 {SpinboxUpdateScrollbar procedure} { + destroy .e + proc bgerror msg { + global x + set x $msg + } + spinbox .e -width 5 -xscrollcommand thisisnotacommand + pack .e + update + rename bgerror {} + list $x $errorInfo +} {{invalid command name "thisisnotacommand"} {invalid command name "thisisnotacommand" + while executing +"thisisnotacommand 0 1" + (horizontal scrolling command executed by .e)}} + +set l [interp hidden] +eval destroy [winfo children .] + +test spinbox-18.1 {Spinbox widget vs hiding} { + destroy .e + spinbox .e + interp hide {} .e + destroy .e + list [winfo children .] [interp hidden] +} [list {} $l] + +## +## Spinbox widget VALIDATION tests +## + +destroy .e +catch {unset ::e} +catch {unset ::vVals} +spinbox .e -validate all \ + -validatecommand [list doval %W %d %i %P %s %S %v %V] \ + -invalidcommand bell \ + -textvariable ::e \ + -background red -foreground white +pack .e +proc doval {W d i P s S v V} { + set ::vVals [list $W $d $i $P $s $S $v $V] + return 1 +} + +# The validation tests build each one upon the previous, so cascading +# failures aren't good +# +test spinbox-19.1 {spinbox widget validation} { + .e insert 0 a + set ::vVals +} {.e 1 0 a {} a all key} +test spinbox-19.2 {spinbox widget validation} { + .e insert 1 b + set ::vVals +} {.e 1 1 ab a b all key} +test spinbox-19.3 {spinbox widget validation} { + .e insert end c + set ::vVals +} {.e 1 2 abc ab c all key} +test spinbox-19.4 {spinbox widget validation} { + .e insert 1 123 + list $::vVals $::e +} {{.e 1 1 a123bc abc 123 all key} a123bc} +test spinbox-19.5 {spinbox widget validation} { + .e delete 2 + set ::vVals +} {.e 0 2 a13bc a123bc 2 all key} +test spinbox-19.6 {spinbox widget validation} { + .e configure -validate key + .e delete 1 3 + set ::vVals +} {.e 0 1 abc a13bc 13 key key} +test spinbox-19.7 {spinbox widget validation} { + set ::vVals {} + .e configure -validate focus + .e insert end d + set ::vVals +} {} +test spinbox-19.8 {spinbox widget validation} { + focus -force .e + # update necessary to process FocusIn event + update + set ::vVals +} {.e -1 -1 abcd abcd {} focus focusin} +test spinbox-19.9 {spinbox widget validation} { + focus -force . + # update necessary to process FocusOut event + update + set ::vVals +} {.e -1 -1 abcd abcd {} focus focusout} +.e configure -validate all +test spinbox-19.10 {spinbox widget validation} { + focus -force .e + # update necessary to process FocusIn event + update + set ::vVals +} {.e -1 -1 abcd abcd {} all focusin} +test spinbox-19.11 {spinbox widget validation} { + focus -force . + # update necessary to process FocusOut event + update + set ::vVals +} {.e -1 -1 abcd abcd {} all focusout} +.e configure -validate focusin +test spinbox-19.12 {spinbox widget validation} { + focus -force .e + # update necessary to process FocusIn event + update + set ::vVals +} {.e -1 -1 abcd abcd {} focusin focusin} +test spinbox-19.13 {spinbox widget validation} { + set ::vVals {} + focus -force . + # update necessary to process FocusOut event + update + set ::vVals +} {} +.e configure -validate focuso +test spinbox-19.14 {spinbox widget validation} { + focus -force .e + # update necessary to process FocusIn event + update + set ::vVals +} {} +test spinbox-19.15 {spinbox widget validation} { + focus -force . + # update necessary to process FocusOut event + update + set ::vVals +} {.e -1 -1 abcd abcd {} focusout focusout} +test spinbox-19.16 {spinbox widget validation} { + list [.e validate] $::vVals +} {1 {.e -1 -1 abcd abcd {} all forced}} +test spinbox-19.17 {spinbox widget validation} { + set ::e newdata + list [.e cget -validate] $::vVals +} {focusout {.e -1 -1 newdata abcd {} focusout forced}} + +proc doval {W d i P s S v V} { + set ::vVals [list $W $d $i $P $s $S $v $V] + return 0 +} +.e configure -validate all + +test spinbox-19.18 {spinbox widget validation} { + set ::e nextdata + list [.e cget -validate] $::vVals +} {none {.e -1 -1 nextdata newdata {} all forced}} + +proc doval {W d i P s S v V} { + set ::vVals [list $W $d $i $P $s $S $v $V] + set ::e mydata + return 1 +} +.e configure -validate all + +## This sets validate to none because it shows that we prevent a possible +## loop condition in the validation, when the spinbox textvar is also set +test spinbox-19.19 {spinbox widget validation} { + .e validate + list [.e cget -validate] [.e get] $::vVals +} {none mydata {.e -1 -1 nextdata nextdata {} all forced}} + +.e configure -validate all + +## This leaves validate alone because we trigger validation through the +## textvar (a write trace), and the write during validation triggers +## nothing (by definition of avoiding loops on var traces). This is +## one of those "dangerous" conditions where the user will have a +## different value in the spinbox widget shown as is in the textvar. +test spinbox-19.20 {spinbox widget validation} { + set ::e testdata + list [.e cget -validate] [.e get] $::e $::vVals +} {all testdata mydata {.e -1 -1 testdata mydata {} all forced}} + +# A format specifier is allowed to be of the form %[-+ 0]{0,1}\d.?\d?f +# +destroy .e +spinbox .e +test spinbox-20.1 {spinbox config, -format specifier} { + list [catch {.e config -format %2f} msg] $msg +} {0 {}} +test spinbox-20.2 {spinbox config, -format specifier} { + list [catch {.e config -format %2.2f} msg] $msg +} {0 {}} +test spinbox-20.3 {spinbox config, -format specifier} { + list [catch {.e config -format %.2f} msg] $msg +} {0 {}} +test spinbox-20.4 {spinbox config, -format specifier} { + list [catch {.e config -format %2.f} msg] $msg +} {0 {}} +test spinbox-20.5 {spinbox config, -format specifier} { + list [catch {.e config -format %2e-1f} msg] $msg +} {1 {bad spinbox format specifier "%2e-1f"}} +test spinbox-20.6 {spinbox config, -format specifier} { + list [catch {.e config -format 2.2} msg] $msg +} {1 {bad spinbox format specifier "2.2"}} +test spinbox-20.7 {spinbox config, -format specifier} { + list [catch {.e config -format %2.-2f} msg] $msg +} {1 {bad spinbox format specifier "%2.-2f"}} +test spinbox-20.8 {spinbox config, -format specifier} { + list [catch {.e config -format %-2.02f} msg] $msg +} {0 {}} +test spinbox-20.9 {spinbox config, -format specifier} { + list [catch {.e config -format "% 2.02f"} msg] $msg +} {0 {}} +test spinbox-20.10 {spinbox config, -format specifier} { + list [catch {.e config -format "% -2.200f"} msg] $msg +} {0 {}} +test spinbox-20.11 {spinbox config, -format specifier} { + list [catch {.e config -format "%09.200f"} msg] $msg +} {0 {}} +test spinbox-20.12 {spinbox config, -format specifier does something} { + set out {} + .e config -format "%02.f" + .e config -values {} -from 0 -to 10 -increment 1 + lappend out [.e set 0]; # set currently doesn't force format + .e invoke buttonup + lappend out [.e set]; # but after invoke it should be formatted + lappend out [.e set 3]; # set currently doesn't force format + .e config -format "%03.f" + lappend out [.e set]; # changing -format should cause formatting +} {0 01 3 003} + +test spinbox-21.1 {spinbox button, out of range checking} { + destroy .e + spinbox .e -from -10 -to 20 -increment 2 + set out {} + lappend out [.e get]; # -10 + .e delete 0 end + .e insert 0 25; # set outside of range + .e invoke buttondown; # should constrain + lappend out [.e get]; # 20 + .e delete 0 end + .e insert 0 25; # set outside of range + .e invoke buttonup; # should constrain + lappend out [.e get]; # 20 + .e delete 0 end + .e insert 0 -100; # set outside of range + .e invoke buttonup; # should constrain + lappend out [.e get]; # -10 + .e delete 0 end + .e insert 0 -100; # set outside of range + .e invoke buttondown; # should constrain + lappend out [.e get]; # -10 + .e delete 0 end + .e insert 0 bogus; # set to a bogus value + .e invoke buttondown; # should use fromValue + lappend out [.e get]; # -10 + .e delete 0 end + .e insert 0 19; # set just inside of range + .e invoke buttonup; # no wrap + lappend out [.e get]; # 20 + .e invoke buttonup; # no wrap + lappend out [.e get]; # 20 + .e invoke buttondown + lappend out [.e get]; # 18 + .e delete 0 end + .e insert 0 -9; # set just inside of range + .e invoke buttondown; # no wrap + lappend out [.e get]; # -10 + .e invoke buttondown; # no wrap + lappend out [.e get]; # -10 + .e invoke buttonup; # no wrap + lappend out [.e get]; # -8 + + .e configure -wrap 1 + .e delete 0 end + .e insert 0 19; # set just inside of range + .e invoke buttonup; # wrap + lappend out [.e get]; # -10 + .e invoke buttonup + lappend out [.e get]; # -8 + .e invoke buttondown + lappend out [.e get]; # -10 + .e delete 0 end + .e insert 0 -9; # set just inside of range + .e invoke buttondown; # wrap + lappend out [.e get]; # 20 + .e invoke buttondown + lappend out [.e get]; # 18 + .e invoke buttonup; # no wrap + lappend out [.e get]; # 20 + +} {-10 20 20 -10 -10 -10 20 20 18 -10 -10 -8 -10 -8 -10 20 18 20} + +destroy .e +catch {unset ::e ::vVals} + +## +## End validation tests +## + +# XXX Still need to write tests for SpinboxBlinkProc, SpinboxFocusProc, +# and SpinboxTextVarProc. + +option clear + +# cleanup +::tcltest::cleanupTests +return |