From abcf7507cc5ab608bb75d22bbb408ee6bc5f0b1a Mon Sep 17 00:00:00 2001 From: hobbs Date: Mon, 29 May 2000 01:43:13 +0000 Subject: * 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 --- doc/spinbox.n | 582 ++++++++++++++++ generic/tkEntry.c | 1917 +++++++++++++++++++++++++++++++++++++++++++++------ generic/tkInt.h | 5 +- generic/tkWindow.c | 3 +- library/entry.tcl | 6 +- library/spinbox.tcl | 746 ++++++++++++++++++++ library/tk.tcl | 3 +- tests/entry.test | 26 +- tests/spinbox.test | 1577 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 4635 insertions(+), 230 deletions(-) create mode 100644 doc/spinbox.n create mode 100644 library/spinbox.tcl create mode 100644 tests/spinbox.test 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%.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); } - /* - * Draw the border and focus highlight last, so they will overwrite - * any text that extends past the viewable part of the window. - */ - - 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, - entryPtr->borderWidth, entryPtr->relief); - } - if (entryPtr->highlightWidth != 0) { - GC fgGC, bgGC; + if (entryPtr->type == TK_SPINBOX) { + int startx, height, inset, pad, tHeight, xWidth; + Spinbox *sbPtr = (Spinbox *) entryPtr; - bgGC = Tk_GCForColor(entryPtr->highlightBgColorPtr, pixmap); + /* + * 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, xBound, xBound, + Tk_Width(tkwin) - 2 * xBound, + Tk_Height(tkwin) - 2 * xBound, + entryPtr->borderWidth, entryPtr->relief); + } + 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,60 +3539,85 @@ 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; - 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 entry */ - 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; + 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; } - 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); @@ -3069,3 +3629,944 @@ ExpandPercents(entryPtr, before, change, new, index, type, dsPtr) 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 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 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 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 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 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 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 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; + } + + 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; + } + + 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 <> { + 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 <> { + if {![catch {::tk::spinbox::GetSelection %W} tkPriv(data)]} { + clipboard clear -displayof %W + clipboard append -displayof %W $tkPriv(data) + unset tkPriv(data) + } +} +bind Spinbox <> { + 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 <> { + %W delete sel.first sel.last +} +bind Spinbox <> { + if {!$tkPriv(mouseMoved) || $tk_strictMotif} { + ::tk::spinbox::Paste %W %x + } +} + +# Standard Motif bindings: + +bind Spinbox <1> { + ::tk::spinbox::ButtonDown %W %x %y +} +bind Spinbox { + ::tk::spinbox::Motion %W %x %y +} +bind Spinbox { + set tkPriv(selectMode) word + ::tk::spinbox::MouseSelect %W %x sel.first +} +bind Spinbox { + set tkPriv(selectMode) line + ::tk::spinbox::MouseSelect %W %x 0 +} +bind Spinbox { + set tkPriv(selectMode) char + %W selection adjust @%x +} +bind Spinbox { + set tkPriv(selectMode) word + ::tk::spinbox::MouseSelect %W %x +} +bind Spinbox { + set tkPriv(selectMode) line + ::tk::spinbox::MouseSelect %W %x +} +bind Spinbox { + set tkPriv(x) %x + ::tk::spinbox::AutoScan %W +} +bind Spinbox { + tkCancelRepeat +} +bind Spinbox { + ::tk::spinbox::ButtonUp %W %x %y +} +bind Spinbox { + %W icursor @%x +} + +bind Spinbox { + %W invoke buttonup +} +bind Spinbox { + %W invoke buttondown +} + +bind Spinbox { + ::tk::spinbox::SetCursor %W [expr {[%W index insert] - 1}] +} +bind Spinbox { + ::tk::spinbox::SetCursor %W [expr {[%W index insert] + 1}] +} +bind Spinbox { + ::tk::spinbox::KeySelect %W [expr {[%W index insert] - 1}] + ::tk::spinbox::SeeInsert %W +} +bind Spinbox { + ::tk::spinbox::KeySelect %W [expr {[%W index insert] + 1}] + ::tk::spinbox::SeeInsert %W +} +bind Spinbox { + ::tk::spinbox::SetCursor %W [::tk::spinbox::PreviousWord %W insert] +} +bind Spinbox { + ::tk::spinbox::SetCursor %W [::tk::spinbox::NextWord %W insert] +} +bind Spinbox { + ::tk::spinbox::KeySelect %W [::tk::spinbox::PreviousWord %W insert] + ::tk::spinbox::SeeInsert %W +} +bind Spinbox { + ::tk::spinbox::KeySelect %W [::tk::spinbox::NextWord %W insert] + ::tk::spinbox::SeeInsert %W +} +bind Spinbox { + ::tk::spinbox::SetCursor %W 0 +} +bind Spinbox { + ::tk::spinbox::KeySelect %W 0 + ::tk::spinbox::SeeInsert %W +} +bind Spinbox { + ::tk::spinbox::SetCursor %W end +} +bind Spinbox { + ::tk::spinbox::KeySelect %W end + ::tk::spinbox::SeeInsert %W +} + +bind Spinbox { + if {[%W selection present]} { + %W delete sel.first sel.last + } else { + %W delete insert + } +} +bind Spinbox { + ::tk::spinbox::Backspace %W +} + +bind Spinbox { + %W selection from insert +} +bind Spinbox