[ Previous | Next | Contents | Glossary | Home | Search ]
Motif 2.1 Programmer's Guide



SpinBox

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.

  1. For a numeric child, define the choices by passing in integers for a minimum value, a maximum value, and an incremental value. By setting the constraint resource, XmNdecimalPoints, you can display decimal values. The increment arrow increases the displayed SpinBox value by the given incremental value. The decrement arrow decreases the displayed value by the given incremental value.

  2. 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:

    XmNmodifyVerifyCallback
    Whenever an ArrowButton is pressed but before the position of the SpinBox changes, XmNmodifyVerifyCallback is invoked. This callback lets you control the next SpinBox position or perform other actions before the SpinBox value changes. The callback function can modify two members of the callback structure: doit and position. Set these as follows:

    1. To prevent the spinning action, set doit to False.

    2. To allow normal consecutive spinning, do not change doit or position.

    3. To spin to another value in the ring of choices, change position and leave doit set to True.

      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.

      XmNvalueChangedCallback
      Whenever an ArrowButton is pressed and the SpinBox position has just changed, XmNvalueChangedCallback is invoked. This callback lets you perform any action based on the change in the callback structure members, position and value. In addition to returning a reason, this callback indicates the event that triggered the callback, which child is affected, and the SpinBox position. For XmSTRING children, value contains the displayed string.

      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:

      XmNdefaultArrowSensitivity
      Specifies whether one or both ArrowButtons are sensitive (perform spinning) or insensitive (displayed as stippled and do not respond) by default. This allows you to turn off an arrow button; for example, you may want to stipple the increment arrow when the SpinBox reaches the last or maximum choice. The constraint resource XmNarrowSensitivity can override this resource setting for a particular child.

      XmNarrowLayout
      Sets the ArrowButton layout. Arrows can be displayed side by side, either to the left or to the right of a text child, or split at either end of the text child. They may also be displayed one on top of the other, before or after the children; or side by side, before or after the children. Arrow layout depends on XmNlayoutDirection. When layout direction is left to right, beginning arrows are positioned to the left. When layout direction is right to left, beginning arrows are positioned to the right. Figure 3 illustrates the arrow layouts when XmNlayoutDirection is left to right. The first two SpinBoxes (.0008 and March) show layout at the end of the child. The third SpinBox (3.27) shows a split layout. The last SpinBox (0) shows layout at the beginning of the child.

      Figure 3. SpinBox Arrow Layouts.




      View figure.

      XmNarrowOrientation
      Sets the orientation of the ArrowButtons: vertical or horizontal.

      XmNarrowSize
      Sets both the width and height of the two ArrowButtons.

      XmNinitialDelay
      Sets the length of time the mouse button can be held down before automatic spinning begins. This resource and the XmNrepeatDelay resource allow you to control the rate of spinning.

      XmNrepeatDelay
      Sets a delay between each change in the SpinBox choice position.

      The following constraints affect each SpinBox traversable child individually:

      XmNarrowSensitivity
      Sets the sensitivity of one or both ArrowButtons for a specific traversable child. This resource overrides the SpinBox XmNdefaultArrowSensitivity resource.

      XmNspinBoxChildType
      Specifies whether the ring of choices is of numeric or string type.

      XmNposition
      Specifies the current position in the range of valid numbers or in the string array. The interpretation of this resource depends on the XmNpositionType resource.

      XmNpositionType
      Specifies how the value in the XmNposition resource is to be interpreted: as its index or as the data value.

      XmNvalues

      Defines an array of strings for a SpinBox string type child.

      XmNnumValues
      The number of strings in the XmNvalues array.

      XmNminimumValue
      Sets the minimum value for a SpinBox numeric type child.

      XmNmaximumValue
      Sets the maximum value for a SpinBox numeric type child.

      XmNincrementValue
      Sets the value by which to raise or lower the displayed value in a SpinBox numeric type child.

      XmNdecimalPoints
      Specifies the number of decimal places to display for a SpinBox numeric 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.


    4. [ Previous | Next | Contents | Glossary | Home | Search ]