A SpinBox lets a user select one choice from a set of mutually exclusive options. A SpinBox contains an increment and a decrement ArrowButton, which allow the user to change the displayed choice. You might think of a SpinBox as a "ring" of choices that operates similar to a digital clock. It displays choices consecutively, and, at the last item, wraps around to the first (or, going in the other direction, wraps from the first item to the last). As long as the user holds down an ArrowButton, the current position in the ring of choices changes continuously, with a "spinning" effect.
To control when spinning begins, set the XmNinitialDelay resource to specify the time, in milliseconds, that elapses before the displayed value changes. To control the rate of spinning, set the XmNrepeatDelay resource to specify the time, in milliseconds, that elapses between subsequent changes in the displayed value.
A SpinBox may include traversable text children, which display the choices, and label and separator children, which are used for informative or decorative purposes only. There are two types of traversable text children allowed in a SpinBox: numeric and string.
For a string child, provide an array of compound strings, using XmStringTable. The increment arrow displays the next string, moving toward the end of the array. The decrement arrow displays the previous string, moving toward the beginning of the array.
To create a SpinBox with one ring of choices, use XmCreateSpinBox and create a child (either numeric or string) using XmCreateText or XmCreateTextField.
The SpinBox, however, may manage multiple traversable text children, as illustrated by the SpinBox in Figure 2. Three traversable text children display months, days, and years. A decoration child (the comma) separates the displayed day and year. The two ArrowButtons are part of every SpinBox.
Figure 2. A SpinBox with Multiple Children.
View figure. |
Because the text children are traversable, the user can tab from one field to the next to change focus. Only one child at a time can take focus. The ArrowButtons spin only choices in the text field with focus. It is possible to "chain" the choices so that spinning choices in one text field affects what is displayed in other text fields. For example, when a user spins the day child in Figure 2 up to 31, XmNvalueChangedCallback is called for each change in position. When the user spins from 31 to 1, the XmNvalueChangedCallback function can use XtSetValues to change the XmNposition resource for the month child from 1 to 2 so that February is displayed.
February has fewer days than January. Therefore, when the month changes from January to February, the XmNmodifyVerifyCallback procedure must change the XmNmaximumValue of the day child from 31 to 28 (or 29 if leap year). If the year rolls the calendar backward from February to January, then the XmNmodifyVerifyCallback procedure must reset the XmNmaximumValue of the day child back to 31.
SpinBox provides two callbacks:
When doit is False, the SpinBox position and value do not change. You would set doit to False if you wanted the SpinBox to stop at the first or last choice without wrapping around the ring of choices. When doit is True, spinning continues to the position returned in the callback structure, and XmNvalueChangedCallback is invoked.
The SpinBox widget also includes translations to arm and disarm the SpinBox, increase and decrease the SpinBox position, and go to the first and last position.
The following SpinBox resources control arrow geometry, sensitivity and response:
Figure 3. SpinBox Arrow Layouts.
View figure. |
The following constraints affect each SpinBox traversable child individually:
Defines an array of strings for a SpinBox string type child.
If the Core resources XmNwidth and XmNheight are not specified, the SpinBox widget attempts to grow to accommodate large arrows or long text fields. It does not shrink if arrows or children are very small.
The following code creates a SpinBox with one string type child, as shown in Figure 4. The choices are values from January to December. This code invokes only the XmNvalueChangedCallback, which is called after the displayed value has changed.
Figure 4. A SpinBox with One Child.
View figure. |
... /***** Create SpinBox parent *****/ n = 0; XtSetArg(argList[n], XmNy, nextY); n++; spin0 = XmCreateSpinBox(parent, "spin0", argList, n); /***** Create XmString array of month names *****/ setMonths(); /***** Create TextField child *****/ n = 0; XtSetArg(argList[n], XmNvalues, monthValues); n++; XtSetArg(argList[n], XmNnumValues, 12); n++; XtSetArg(argList[n], XmNspinBoxChildType, XmSTRING); n++; XtSetArg(argList[n], XmNselectionPolicy, XmSINGLE_SELECT); n++; XtSetArg(argList[n], XmNeditable, False); n++; spin0_text = XmCreateTextField(spin0, "spin0_text", argList, n); /***** Manage SpinBox *****/ XtManageChild(spin0); /***** Call changedSpin0 AFTER displayed value has changed *****/ XtAddCallback(spin0, XmNvalueChangedCallback, changedSpin0, (XtPointer) 0); /***** Manage SpinBox child *****/ XtManageChild(spin0_text);
The setMonths routine creates an XmString array of month names; setMonths consists of the following code:
void setMonths(void) { XmString tempString; int monthLoop; for (monthLoop = 0; monthLoop < NUM_MONTHS; monthLoop++) { tempString = XmStringCreate(months[monthLoop], XmFONTLIST_DEFAULT_TAG); monthValues[monthLoop] = tempString; } }
The following callback code for XmNvalueChangedCallback modifies the variable thisYear whenever the boundary is crossed.
void changedSpin0(Widget w, XtPointer client, XtPointer call) { XmSpinBoxCallbackStruct *user; static int thisYear = 1994; user = (XmSpinBoxCallbackStruct *)call; if (user->crossed_boundary) { if (user->reason == XmCR_SPIN_NEXT) thisYear++; else thisYear--; } }
The code for the SpinBox shown in Figure 5 includes an XmModifyVerifyCallback to change resources and prevent wrapping below the minimum value.
Figure 5. A SpinBox that Does Not Wrap.
View figure. |
... /***** Create SpinBox parent *****/ n = 0; XtSetArg(argList[n], XmNarrowSize, 35); n++; XtSetArg(argList[n], XmNrepeatDelay, 250); n++; XtSetArg(argList[n], XmNinitialDelay, 500); n++; XtSetArg(argList[n], XmNarrowLayout, XmARROWS_SPLIT); n++; spin4 = XmCreateSpinBox(parent, "spin4", argList, n); n = 0; XtSetArg(argList[n], XmNmaximumValue, 4); n++; XtSetArg(argList[n], XmNspinBoxChildType, XmNUMERIC); n++; spin4_text = XmCreateTextField(spin4, "spin4_text", argList, n); /***** Manage SpinBox *****/ XtManageChild(spin4); /***** Call modifySpin4 BEFORE displayed value is changed *****/ XtAddCallback(spin4, XmNmodifyVerifyCallback, modifySpin4, (XtPointer) 0); /***** Manage SpinBox child *****/ XtManageChild(spin4_text);
The callback code for the previous call to XmNmodifyVerifyCallback uses doit to stop the SpinBox from wrapping at the minimum value:
void modifySpin4(Widget w, XtPointer client, XtPointer call) { XmSpinBoxCallbackStruct *user; int newHigh; Cardinal n; Arg argList[5]; user = (XmSpinBoxCallbackStruct *)call; if (user->crossed_boundary) { if (user->reason == XmCR_SPIN_NEXT) { n = 0; XtSetArg(argList[n], XmNmaximumValue, &newHigh); n++; XtGetValues(user->widget, argList, n); newHigh++; n = 0; XtSetArg(argList[n], XmNmaximumValue, newHigh); n++; XtSetValues(user->widget, argList, n); user->position = 0; } else if (user->reason == XmCR_SPIN_PRIOR) user->doit = False; } }
The SpinBox shown in Figure 6 has multiple children, including two decoration children. In addition, this SpinBox includes 'chaining', the process where a change in one child causes values to change in the child and on or more other children. Chaining is performed by the valueChanged callback.
The code for this SpinBox is as follows.
Figure 6. A SpinBox with Multiple, Chained Children.
View figure. |
... /***** Create SpinBox parent *****/ n = 0; XtSetArg(argList[n], XmNy, nextY); n++; XtSetArg(argList[n], XmNinitialDelay, 0); n++; XtSetArg(argList[n], XmNrepeatDelay, 150); n++; spin6 = XmCreateSpinBox( parent, "spin6", argList, n ); /***** Increment Y position for next SpinBox *****/ nextY += Y_OFFSET; /***** Create SpinBox child *****/ n = 0; XtSetArg(argList[n], XmNwidth, 30); n++; XtSetArg(argList[n], XmNposition, thisMM - 1); n++; XtSetArg(argList[n], XmNminimumValue, 1); n++; XtSetArg(argList[n], XmNmaximumValue, 12); n++; XtSetArg(argList[n], XmNspinBoxChildType, XmNUMERIC); n++; spin6_text1 = XmCreateTextField( spin6, "spin6_text1", argList, n ); /***** Create SpinBox decoration child *****/ n = 0; decoString = XmStringCreateLtoR("/", XmSTRING_DEFAULT_CHARSET); XtSetArg(argList[n], XmNlabelString, decoString); n++; spin6_deco1 = XmCreateLabel(spin6, "spin6_deco1", argList, n); /***** Create SpinBox child *****/ n = 0; XtSetArg(argList[n], XmNwidth, 30); n++; XtSetArg(argList[n], XmNposition, thisDD - 1); n++; XtSetArg(argList[n], XmNminimumValue, 1); n++; XtSetArg(argList[n], XmNmaximumValue, 31); n++; XtSetArg(argList[n], XmNspinBoxChildType, XmNUMERIC); n++; spin6_text2 = XmCreateTextField( spin6, "spin6_text2", argList, n ); /***** Create SpinBox decoration child *****/ n = 0; decoString = XmStringCreateLtoR("/", XmSTRING_DEFAULT_CHARSET); XtSetArg(argList[n], XmNlabelString, decoString); n++; spin6_deco2 = XmCreateLabel( spin6, "spin6_deco2", argList, n ); XmStringFree(decoString); /***** Create SpinBox child *****/ n = 0; XtSetArg(argList[n], XmNwidth, 30); n++; XtSetArg(argList[n], XmNposition, thisYY); n++; XtSetArg(argList[n], XmNmaximumValue, 99); n++; XtSetArg(argList[n], XmNspinBoxChildType, XmNUMERIC); n++; spin6_text3 = XmCreateTextField(spin6, "spin6_text3", argList, n); /***** Manage SpinBox *****/ XtManageChild(spin6); /***** Call changedSpin6 AFTER displayed value has changed *****/ XtAddCallback(spin6, XmNvalueChangedCallback, changedSpin6, (XtPointer) 0); /***** Manage SpinBox children *****/ XtManageChild(spin6_text1); XtManageChild(spin6_deco1); XtManageChild(spin6_text2); XtManageChild(spin6_deco2); XtManageChild(spin6_text3);
The callback code for the previous call to XmNvalueChangedCallback uses XtSetValues calls to chain the day and the month children, and the month and the year children. It also changes XmNmaximumValue for the day child whenever the month child changes, as follows:
... void changedSpin6(Widget w, XtPointer client, XtPointer call) { XmSpinBoxCallbackStruct *user; Cardinal n; Arg argList[5]; int saveMM; int saveDD; int saveYY; user = (XmSpinBoxCallbackStruct *)call; if (user->widget == spin6_text1) { thisMM = user->position + 1; if (thisDD <=3) setMaxDay(spin6_text2, thisMM -1); else if (thisDD > 27) setMaxDay(spin6_text2, thisMM); saveYY = thisYY; if (user->crossed_boundary) if (user->reason == XmCR_SPIN_NEXT) thisYY++; else thisYY--; if (thisYY != saveYY) { if (thisYY < 0) thisYY = 99; else if (thisYY > 99) thisYY %= 100; n = 0; XtSetArg(argList[n], XmNposition, thisYY); n++; XtSetValues(spin6_text3, argList, n); } } else if (user->widget == spin6_text2) { thisDD = user->position + 1; if (thisDD <=3 && user->reason == XmCR_SPIN_PRIOR) setMaxDay(spin6_text2, thisMM -1); else if (thisDD > 27 && user->reason == XmCR_SPIN_NEXT) setMaxDay(spin6_text2, thisMM); saveMM = thisMM; saveYY = thisYY; if (user->crossed_boundary) if (user->reason == XmCR_SPIN_NEXT) thisMM++; else thisMM--; if (thisMM != saveMM) { if (thisMM < 1) { thisMM = 12; thisYY--; } else if (thisMM > 12) { thisMM = 1; thisYY++; } n = 0; XtSetArg(argList[n], XmNposition, thisMM - 1); n++; XtSetValues(spin6_text1, argList, n); if (thisYY != saveYY) { n = 0; XtSetArg(argList[n], XmNposition, thisYY); n++; XtSetValues(spin6_text3, argList, n); } } } else if (user->widget == spin6_text3) { thisYY = user->position; if (user->reason == XmCR_OK) { if (thisDD <= 3) setMaxDay(spin6_text2, thisMM - 1); else if (thisDD > 27) setMaxDay(spin6_text2, thisMM); } } }
The XmSpinBoxValidatePosition function is available for use by applications that need to implement a policy for tracking user modifications to editable SpinBox children of type XmNUMERIC. It is up to the application to specify when and how the user modifications take effect.