Motif Programming Manual (Volume 6A)

Previous Next Contents Document Index Motif Docs motifdeveloper.com Imperial Software Technology X-Designer

X-Designer - The Leading X/Motif GUI Builder - Click to download a FREE evaluation





In this chapter:



Chapter: 15

The SpinBox and SimpleSpinBox Widgets



This chapter describes two components which allow the user to chose from a group of values that occur in a predefined set: the SpinBox, and the SimpleSpinBox widgets1.

The SimpleSpinBox consists of a TextField, and two arrows. The programmer associates a set of values with the SimpleSpinBox; the TextField displays the current selection from the set, and the user can chose the next or previous value in the set by pressing on an arrow. Think of the widget as a kind of ComboBox which, when an arrow is pressed, automatically selects the next value for the user, rather than the user selecting from a popdown List of choices. Figure 15-1 displays a typical SimpleSpinBox. In this case, the widget is associated with the names of the months, and the user simply chooses the previous or next month by pressing the increment or decrement arrow.

Figure  15-1 A SimpleSpinBox widget

The SpinBox is similar to the SimpleSpinBox: it provides two arrows for rotating the current selection. The difference is that the SpinBox is a general purpose manager: the programmer can add multiple TextFields to the widget, and the arrows simply manipulate the set of values associated with the current TextField child which has the focus. Figure 15-2 display a SpinBox which contains three TextField children, used in this instance for displaying the day, month, and year components of a date.

Figure  15-2 A SpinBox widget with three TextField children

SpinBoxes, and SimpleSpinBoxes, assume that their TextField children come in two different flavors: numeric, and string. A numeric SpinBox TextField allows the user to select from a range: the programmer specifies the lower and upper bounds of the range, as well as the interval between selections. Clearly, a TextField in a SpinBox displaying days of the month would have a lower bound of 1, and an upper bound variously of 28, 29, 30, or 31, The interval would be 1, because you would expect that pressing the increment or decrement arrow should change the value to an adjacent day. A string SpinBox TextField, on the other hand, simply has an ordered array of strings from which the user can sequentially select.

In either flavor, when the last value in the set of choices is reached, and the user presses the increment arrow, the new current value is the first value in the range: the choice wraps around to the start of the set. This wrapping behavior is also true when going in the other direction: pressing the decrement arrow when the first value is current causes the last value to become the current choice. If the programmer does not want this kind of wrapping behavior, it can be configured through resources.

The differences between the SpinBox and SimpleSpinBox widgets as far as the user is concerned are minimal: each presents the current choice to the user in the same way through a TextField or TextFields, and each allows the user to modify the choice through increment and decrement arrows automatically provided for the purpose.

As far as programming the two widgets is concerned, there are subtle differences.

Firstly, the SimpleSpinBox comes with a built-in TextField ready-prepared to display the current choice: the programmer only needs to specify the valid set of values through appropriate resources. The general purpose SpinBox manager, on the other hand, comes unprepared: the programmer must add all the necessary TextField children herself, as well as specifying the value ranges. Although derived from SpinBox, the SimpleSpinBox is not meant to be used as a general purpose manager, and you should not put additional children into the widget: use the SpinBox instead if you need to modify the structure in this way.

The second and most important difference is the fact that the SpinBox widget is a constraint manager pure and simple: the set of values associated with the various TextField children is specified by setting constraints on each. By contrast, the SimpleSpinBox provides mirrored resources for the built-in TextField child; you do not have to specify the set of values by setting constraints on the TextField: instead you set the same constraint resources defined by the SpinBox directly onto the SimpleSpinBox widget itself. The SimpleSpinBox thereafter arranges to apply the values to the built-in child on your behalf. In other words, the set of resources you can use to program a SpinBox and a SimpleSpinBox is the same in each case; what is different is where you apply them.

The notion of a set is important when considering using a SpinBox or SimpleSpinBox. If it is not obvious what the "next" or "previous" value means in a given context, then the SpinBox is probably not the correct widget to use. For example, if the set consists of days of the week, it is fairly intuitive what "next" means if the current value is Monday. On the other hand, if the set consists of arbitrary data such as a list of color names, it is not necessarily obvious what "next" means if the current value is Orange. Unless there is an imposed order on the set, for example, an alphabetical listing, then this usage of the SpinBox could well be entirely inappropriate and confusing to the user, who would not know whether to increment or decrement to find the required new current value. A simple List or ComboBox presentation should therefore be preferred if the data is unordered, or if the next or previous choice is not obvious given an arbitrary current selection.

In passing, note that the increment and decrement arrows are drawn by the SpinBoxes; they are not real widgets, and thus there is little which can be done to manipulate their appearance other than through resources which are directly provided by the SpinBoxes themselves.

Creating a SimpleSpinBox

Creating a SimpleSpinBox is performed in precisely the same kind of way that other Motif widgets are instantiated. Applications must include the header file associated with the widget class, which in this case is <Xm/SSpinB.h>. A SimpleSpinBox can be created in one of the following ways, either using a Motif convenience routine, or the general purpose Xt methods:

Widget sspin_b = XmCreateSimpleSpinBox (parent, "name",
                                  resource-value-array, resource-value-count);
Widget sspin_b = XtCreateWidget ( "name", xmSimpleSpinBoxWidgetClass,
                                  parent, resource-value-list, NULL);
The parent can be a Shell or any manager widget. Since the geometry management involved in creating a SimpleSpinBox is minimal, involving as it does just a single TextField, the widget could be created managed using XtCreateManagedWidget() or similar without incurring a significant performance overhead. See Chapter 8, Manager Widgets, for a discussion of when manager widgets should be created in the managed or unmanaged state. The resource-value parameters control the behavior and visual effects of the SimpleSpinBox, as well as its built-in TextField child. The most important resource is XmNspinBoxChildType, which specifies whether the widget is numerical or string-based in behavior. The value of the resource is either XmNUMERIC, or XmSTRING.


The Numerical SimpleSpinBox

Numerical SimpleSpinBoxes are created by specifying the XmNspinBoxChildType resource as XmNUMERIC2. Once we have made our SimpleSpinBox numerical, we need to specify the range of values from which the user can chose. We do this by supplying a lower bound, an upper bound, and an increment value. Example 15-1 shows how to create a numerical SimpleSpinBox which lets the user select an even number which falls between zero and twenty.

Example  15-1 The numeric_simplespin.c program

/* numeric_simplespin.c -- demonstrate the simple spin box widget */

#include <Xm/Xm.h>
#include <Xm/SSpinB.h>

main (int argc, char *argv[])
{
    Widget         toplevel, simple_b;
    XtAppContext   app;
    int            n;
    Arg            args[8];

    XtSetLanguageProc (NULL, NULL, NULL);

    toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, 
                                    sessionShellWidgetClass, NULL);

    n = 0;
    XtSetArg (args[n], XmNspinBoxChildType, XmNUMERIC); n++;
    XtSetArg (args[n], XmNminimumValue, 0);             n++;
    XtSetArg (args[n], XmNmaximumValue, 20);            n++;
    XtSetArg (args[n], XmNincrementValue, 2);           n++;
    XtSetArg (args[n], XmNeditable, TRUE);              n++;
    XtSetArg (args[n], XmNcolumns, 10);                 n++;
    XtSetArg (args[n], XmNwrap, FALSE);                 n++;
    simple_b = XmCreateSimpleSpinBox (toplevel, "simple", args, n);

    XtManageChild (simple_b);
    XtRealizeWidget (toplevel);
    XtAppMainLoop (app);
}
We specify the minimum bounds to the range of selectable values through the XmNminimumValue resource. The upper bound is given by the XmNmaximumValue resource, and the gap between selectable values is specified using the XmNincrementValue resource. For the sake of example, we also explicitly allow the user to directly type into the built-in TextField by setting the XmNeditable resource to True. We also explicitly specify that the SimpleSpinBox is not to wrap around to the start (or end) of the range of values when the user increments at the end of the range (or decrements at the beginning) by forcing the XmNwrap resource to False. The user will either have to type the value in directly, or cycle right through the set of values in the opposite direction. This may or may not be anti-social programming in any given circumstance: you should think carefully before turning either or both XmNeditable and XmNwrap behavior off. In the example, it makes some kind of sense to turn XmNwrap off so that the user is more aware of the upper bound; if XmNwrap is False, the bell is automatically rung if the user attempts to exceed the bounds of the data set.

The numeric SimpleSpinBox is not confined to situations where the data is integral in character: the XmNdecimalPoints resource can be specified so that the widget appears to handle real numbers. Supposing that we wanted to allow the user to chose from within the range 2.75 through to 3.45, incrementing by 0.05 on each click of the arrow. The following code fragment shows the way to do this: since there are two decimal places, we need to multiply all our bound resources by 10 to the power of two. This is consistent with the way that the XmNdecimalPoints resource behaves in the Scale widget.

n = 0;
XtSetArg (args[n], XmNspinBoxChildType, XmNUMERIC);    n++;
XtSetArg (args[n], XmNminimumValue, 275);              n++;
XtSetArg (args[n], XmNmaximumValue, 345);              n++;
XtSetArg (args[n], XmNdecimalPoints, 2);               n++;
XtSetArg (args[n], XmNincrementValue, 5);              n++;
XtSetArg (args[n], XmNpositionType, XmPOSITION_VALUE); n++;
XtSetArg (args[n], XmNposition, 275);                  n++;
...
simple_b = XmCreateSimpleSpinBox (toplevel, "simple", args, n);
...
The XmNpositionType and XmNposition resources are used to initialize the current value of the SimpleSpinBox. XmNposition specifies the current value of the SimpleSpinBox; XmNpositionType specifies whether the XmNposition resource is interpreted as an absolute value, or as an index into the set of values. They will be further discussed later in this chapter.


The String SimpleSpinBox

A string-based SimpleSpinBox is created by specifying the XmNspinBoxChildType resource as XmSTRING3. The set of data associated with the string SimpleSpinBox is defined by the XmNvalues and XmNnumValues resources. The XmNvalues resource is internally represented by an array of compound strings; you are referred to Chapter 25, Compound Strings, if you require more details on the creation and manipulation of these objects.

Example 15-2 creates a string-based SimpleSpinBox, which allows the user to select from the names of the months.

Example  15-2 The string_simplespin.c program

/* string_simplespin.c -- demonstrate the simple spin box widget */

#include <Xm/Xm.h>
#include <Xm/SSpinB.h>

char *months[] = {   "January", "February", "March", "April", "May", "June",
                     "July", "August", "September", "October", "November", 
                     "December" };

main (int argc, char *argv[])
{
    Widget           toplevel, simple_b;
    XtAppContext     app;
    int              i, n = XtNumber (months);
    Arg              args[8];
    XmStringTable    str_list;

    XtSetLanguageProc (NULL, NULL, NULL);

    toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, 
                                    sessionShellWidgetClass, NULL);

    str_list = (XmStringTable) XtMalloc (n * sizeof (XmString *));

    for (i = 0; i < n; i++)
        str_list[i] = XmStringCreateLocalized (months[i]);

    i = 0;
    XtSetArg (args[n], XmNspinBoxChildType, XmSTRING); i++;
    XtSetArg (args[i], XmNeditable, FALSE);            i++;
    XtSetArg (args[i], XmNnumValues, n);               i++;
    XtSetArg (args[i], XmNvalues, str_list);           i++;
    XtSetArg (args[i], XmNwrap, TRUE);                 i++;

    simple_b = XmCreateSimpleSpinBox (toplevel, "simple", args, i);

    for (i = 0; i < n; i++)
        XmStringFree (str_list[i]);
    XtFree ((XtPointer) str_list);

    XtManageChild (simple_b);
    XtRealizeWidget (toplevel);
    XtAppMainLoop (app);
}
The output from the program is the same as that of Figure 15-1. We allocate an array of compound strings for use with the XmNvalues resource. We also specify XmNwrap to True, because the notion of a "next month" after December does make sense. We also set the XmNeditable resource False: allowing the user to directly type in an abbreviated or other style could give us some minor parsing problems, and we probably have better things to do with our time than writing verification callbacks for this.


The SimpleSpinBox Value

Now that we have created our SimpleSpinBox, we need to know how to explicitly set and fetch the current value. We could fetch the value using a simplistic algorithm: find the TextField which is built-in to the SimpleSpinBox, then retrieve the value using XmTextFieldGetString(). The name of the TextField is given by spin_TF, where spin is the name of the SimpleSpinBox parent. The following code fragment demonstrates this scheme:

char *FetchSimpleSpinValue (Widget simple_spin) {
    char   *pname = XtName (simple_spin);
    char   *tname = XtMalloc ((unsigned) strlen (pname) + 4);
    Widget textf;

    (void) sprintf (tname, "%s_TF", pname);

    textf = XtNameToWidget (simple_spin, tname);
    XtFree (tname);
    return XmTextFieldGetString (textf); /* Caller frees */
}
Whilst this would work, the method is somewhat heavy handed. Added to which, we would probably have to convert the fetched value into another form, particularly if the SimpleSpinBox is numeric in behavior. We most certainly could not use this kind of method for setting the value. The SimpleSpinBox internally keeps track of the selection through an index into the set of possible values, and directly writing the value into the TextField is not going to update the index correctly.

The correct way to fetch and store the current selection is to manipulate the XmNposition and XmNpositionType resources. XmNpositionType defines the way in which XmNposition is interpreted. If XmNpositionType is XmPOSITION_VALUE, the XmNposition resource represents an absolute value, bounded by the XmNmaximumValue and XmNminimumValue resource settings. If the position type is XmPOSITION_INDEX, then the XmNposition resource represents an index into the set of possible values. Clearly how we set the position type depends on the type of the SimpleSpinBox itself: if the widget is numeric, we probably want to fetch or set the current choice using an absolute value, but if the widget is string-based, we would prefer an index into the array of compound strings stored behind the XmNvalue resource. XmNpositionType is a create only-resource; by default, it is XmPOSITION_VALUE. This needs not concern us if we are using a string SimpleSpinBox: since the default value of XmNposition is zero, we set and fetch the resource as though the value of XmNposition is indeed an index into the array of compound strings. Example 15-3 creates a pair of SimpleSpinBoxes, one string based, the other numeric, and a pair of PushButtons. Pressing a PushButton prints out the current value of a SimpleSpinBox, and increments the value as a side effect.

Example  15-3 The simplespin_value.c program

/* simplespin_value.c -- demonstrate the simple spin box widget */

#include <Xm/Xm.h>
#include <Xm/RowColumn.h>
#include <Xm/PushB.h>
#include <Xm/SSpinB.h>

char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                   "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

/*
** Callback which prints out, then increments, 
** the SimpleSpinBox value
*/
void increment_spinbox (Widget w, XtPointer client_data,
                        XtPointer call_data)
{
    Widget           spin = (Widget) client_data;
    unsigned char    type;
    int              position;

    XtVaGetValues (spin, XmNspinBoxChildType, &type,
                            XmNposition, &position, NULL);

    printf ("type: %s current position: %d\n", 
                        (type == XmNUMERIC ? "numeric" : "string"),
                        position);

    if (type == XmNUMERIC) {
        int max;
        int min;

        XtVaGetValues (spin, XmNmaximumValue, &max,
                                        XmNminimumValue, &min, NULL);

        if (position == max) /* Wrap */
            position = min;
        else
            position++;

        XtVaSetValues (spin, XmNposition, position, NULL);
    }
    else {
        XmStringTable  values;
        int            count;

        XtVaGetValues (spin, XmNvalues, &values,
                                        XmNnumValues, &count, NULL);

        if (position == count - 1) /* Wrap */
            position = 0;
        else
            position++;

        XtVaSetValues (spin, XmNposition, position, NULL);
    }
}

main (int argc, char *argv[])
{
    Widget           toplevel, simple_b, push_b, rowcol, rc;
    XtAppContext     app;
    int              i, n = XtNumber (months);
    Arg              args[8];
    XmStringTable    str_list;

    XtSetLanguageProc (NULL, NULL, NULL);
    toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, 
                                    sessionShellWidgetClass, NULL);

    rowcol = XmCreateRowColumn (toplevel, "rowcol", NULL, 0);

    i = 0;
    XtSetArg (args[i], XmNorientation, XmHORIZONTAL); i++;
    rc = XmCreateRowColumn (rowcol, "rowcol", args, i);

    /* Create a numeric SimpleSpinBox */
    i = 0;
    XtSetArg (args[i], XmNmaximumValue, 31);               i++;
    XtSetArg (args[i], XmNminimumValue, 1);                i++;
    XtSetArg (args[i], XmNincrementValue, 1);              i++;
    XtSetArg (args[i], XmNpositionType, XmPOSITION_VALUE); i++;
    XtSetArg (args[i], XmNposition, 1);                    i++;
    XtSetArg (args[i], XmNspinBoxChildType, XmNUMERIC);    i++;
    simple_b = XmCreateSimpleSpinBox (rc, "simple", args, i);
    XtManageChild (simple_b);

    push_b = XmCreatePushButton (rc, "Push me", NULL, 0);
    XtAddCallback (push_b, XmNactivateCallback, increment_spinbox, 
                        (XtPointer) simple_b);
    XtManageChild (push_b);
    XtManageChild (rc);

    i = 0;
    XtSetArg (args[i], XmNorientation, XmHORIZONTAL); i++;
    rc = XmCreateRowColumn (rowcol, "rowcol", args, i);

    /* Create a string SimpleSpinBox */
    str_list = (XmStringTable) XtMalloc ((unsigned) n * sizeof (XmString *));

    for (i = 0; i < n; i++)
        str_list[i] = XmStringCreateLocalized (months[i]);

    i = 0;
    XtSetArg (args[i], XmNcolumns, 3);                     i++;
    XtSetArg (args[i], XmNeditable, FALSE);                i++;
    XtSetArg (args[i], XmNnumValues, n);                   i++;
    XtSetArg (args[i], XmNvalues, str_list);               i++;
    XtSetArg (args[i], XmNwrap, TRUE);                     i++;
    XtSetArg (args[i], XmNpositionType, XmPOSITION_INDEX); i++;
    XtSetArg (args[i], XmNspinBoxChildType, XmSTRING);     i++;
    simple_b = XmCreateSimpleSpinBox (rc, "simple", args, i);
    XtManageChild (simple_b);

    for (i = 0; i < n; i++)
        XmStringFree (str_list[i]);
    XtFree((char *) str_list);

    push_b = XmCreatePushButton (rc, "Push me", NULL, 0);
    XtAddCallback (push_b, XmNactivateCallback, increment_spinbox, 
                        (XtPointer) simple_b);
    XtManageChild (push_b);

    XtManageChild (rc);

    XtManageChild (rowcol);
    XtRealizeWidget (toplevel);
    XtAppMainLoop (app);
}
The output of the program is given in Figure 15-3.

Figure  15-3 Output of the simplespin_values program

Creating a SpinBox

Applications must include the header file associated with the SpinBox widget class, which is <Xm/SpinB.h>. A SpinBox can be created in one of the following ways, either using a Motif convenience routine, or the general purpose Xt methods:

Widget sspin_b = XmCreateSpinBox ( parent, "name", resource-value-array, 
                                   resource-value-count);
...
Widget sspin_b = XtCreateWidget ( "name", xmSpinBoxWidgetClass, parent, 
                                  resource-value-list, NULL);
The parent can be a Shell or any manager widget. The widget could probably be created managed using XtCreateManagedWidget() or similar without incurring a significant performance overhead since the layout which the widget performs for its children is very simplistic: it simply lays them out in a line. See Chapter 8, Manager Widgets, for a discussion of when manager widgets should be created in the managed or unmanaged state.

The SpinBox widget is a constraint manager. Unlike the SimpleSpinBox which provides a mirror for resources which are applied to the built-in TextField child, the SpinBox is programmed by directly applying resources to each textual child, which must be individually added by the programmer. The SpinBox behavior will work equally well with either Text or TextField children; in the examples which follow, we use a TextField throughout, but this should not be taken as in any way necessary. Example 15-4 creates a SpinBox which displays the time.

Example  15-4 The date_spinbox.c program

/* date_spinbox.c -- demonstrate the spin box widget */
#include <Xm/Xm.h>
#include <Xm/RowColumn.h>
#include <Xm/SpinB.h>
#include <Xm/TextF.h>
#include <Xm/Label.h>
#include <sys/types.h>
#include <sys/time.h>

Widget hours_t, mins_t, ampm_t;

main (int argc, char *argv[])
{
    Widget           toplevel, spin, child;
    XtAppContext     app;
    XmStringTable    ampm;
    Arg              args[8];
    int              n;
    /* Initialize the spinbox to the current time */
    long tick = time ((long *) 0);
    struct tm *tm = localtime (&tick);
    /* 12 hour clock */
    int hours = ((tm->tm_hour > 12) ? (tm->tm_hour - 12) : tm->tm_hour);

    XtSetLanguageProc (NULL, NULL, NULL);

    toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, 
                                    sessionShellWidgetClass, NULL);

    /* Create the SpinBox */
    spin = XmCreateSpinBox (toplevel, "spin", NULL, 0);

    /* Create the Hours field */
    n = 0;
    XtSetArg (args[n], XmNspinBoxChildType, XmNUMERIC); n++;
    XtSetArg (args[n], XmNcolumns, 2);                  n++;
    XtSetArg (args[n], XmNeditable, FALSE);             n++;
    XtSetArg (args[n], XmNminimumValue, 1);             n++;
    XtSetArg (args[n], XmNmaximumValue, 12);            n++; /* 12 hour clock */
    XtSetArg (args[n], XmNposition, hours);             n++;
    XtSetArg (args[n], XmNwrap, TRUE);                  n++;
    hours_t = XmCreateTextField (spin, "hourText", args, n);
    XtManageChild (hours_t);

    /* Hours/Minutes separator */
    child = XmCreateLabel (spin, ":", NULL, 0);
    XtManageChild (child);

    /* Create the Minutes field */
    n = 0;
    XtSetArg (args[n], XmNspinBoxChildType, XmNUMERIC); n++;
    XtSetArg (args[n], XmNcolumns, 2);                  n++;
    XtSetArg (args[n], XmNeditable, FALSE);             n++;
    XtSetArg (args[n], XmNminimumValue, 0);             n++;
    XtSetArg (args[n], XmNmaximumValue, 59);            n++;
    XtSetArg (args[n], XmNwrap, TRUE);                  n++;
    XtSetArg (args[n], XmNposition, tm->tm_min);        n++;
    mins_t = XmCreateTextField (spin, "minuteText", args, n);
    XtManageChild (mins_t);

    /* Create the am/pm indicator field */
    ampm = (XmStringTable) XtMalloc(2 * sizeof (XmString *));
    ampm[0] = XmStringCreateLocalized ("am");
    ampm[1] = XmStringCreateLocalized ("pm");

    n = 0;
    XtSetArg (args[n], XmNspinBoxChildType, XmSTRING); n++;
    XtSetArg (args[n], XmNcolumns, 2);                 n++;
    XtSetArg (args[n], XmNeditable, FALSE);            n++;
    XtSetArg (args[n], XmNnumValues, 2);               n++;
    XtSetArg (args[n], XmNvalues, ampm);               n++;
    XtSetArg (args[n], XmNwrap, TRUE);                 n++;
    XtSetArg (args[n], XmNposition, (tm->tm_hour > 12) ? 1 : 0); n++;
    ampm_t = XmCreateTextField (spin, "ampmText", args, n);
    XtManageChild (ampm_t);
    XmStringFree (ampm[0]);
    XmStringFree (ampm[1]);
    XtFree ((char *) ampm);

    XtManageChild (spin);
    XtRealizeWidget (toplevel);
    XtAppMainLoop (app);
}
The output of the date_spinbox.c program is given in Figure 15-4.

Figure  15-4 Output of the date_spinbox program

In the example, we create a SpinBox, and add four children: three TextFields to display the hours, minutes, and time-of-day, and a Label used to separate the hours and minutes fields. The SpinBox simply lays the children out horizontally in the order of creation. The widget is not sensitive to the XmNlayoutDirection resource in this respect: it is not possible to lay the children out vertically. The SpinBox itself is created very simply:

spin = XmCreateSpinBox (toplevel, "spin", NULL, 0);
The TextField to display the hours is programmed using SpinBox constraints: the XmNspinBoxChildType is set to XmNUMERIC, and the XmNmaximumValue and XmNminimumValue resources are set appropriately for a 12 hour clock. As a gloss, we initialize the value of the TextField from the current time, by using the XmNposition resource. The current time is fetched through the standard UNIX system calls time() and localtime(). You should refer to your system documentation if you are unfamiliar with these routines.

/* Initialize the spinbox to the current time */
long tick = time ((long *) 0);
struct tm *tm = localtime (&tick);
/* 12 hour clock */
int hours = ((tm->tm_hour > 12) ? (tm->tm_hour - 12) : tm->tm_hour);
...
n = 0;
XtSetArg (args[n], XmNspinBoxChildType, XmNUMERIC); n++;
XtSetArg (args[n], XmNcolumns, 2);                  n++;
XtSetArg (args[n], XmNeditable, FALSE);             n++;
XtSetArg (args[n], XmNminimumValue, 1);             n++;
XtSetArg (args[n], XmNmaximumValue, 12);            n++; /* 12 hour clock */
XtSetArg (args[n], XmNposition, hours);             n++; /* The current time */
XtSetArg (args[n], XmNwrap, TRUE);                  n++;
hours_t = XmCreateTextField (spin, "hourText", args, n);
XtManageChild (hours_t);
Although the SpinBox works by rotating the values of the TextField which currently has the input focus, the SpinBox will layout any widget class which is added as a child. In the example, we add a Label after the first TextField to act as a logical separator between the hours and minutes field.

/* Hours/Minutes separator */
child = XmCreateLabel (spin, ":", NULL, 0);
XtManageChild (child);
The TextField to display the minutes field is created in a similar fashion to the hours field; the XmNspinBoxChildType resource is set to XmNUMERIC, the XmNmaximumValue and XmNminimumValue constraint resources are set appropriately for the range of values to display, and the XmNposition resource is initialized to the current time.

The last TextField, used to display the morning or afternoon indicator, is created differently. In this case, the XmNspinBoxChildType constraint is set to XmSTRING, and the XmNvalues and XmNnumValues resources are used to specify an array of compound strings which contain the am/pm labels. Again, we initialize the TextField to display the current time using the XmNposition resource. Since the XmNspinBoxChildType is XmSTRING, the XmNposition resource in this case represents an index into the array of compound strings, rather than an absolute value.

/* Create the am/pm indicator field */
ampm = (XmStringTable) XtMalloc(2 * sizeof (XmString *));
ampm[0] = XmStringCreateLocalized ("am");
ampm[1] = XmStringCreateLocalized ("pm");

n = 0;
XtSetArg (args[n], XmNspinBoxChildType, XmSTRING); n++;
XtSetArg (args[n], XmNcolumns, 2);                 n++;
XtSetArg (args[n], XmNeditable, FALSE);            n++;
XtSetArg (args[n], XmNnumValues, 2);               n++;
XtSetArg (args[n], XmNvalues, ampm);               n++;
XtSetArg (args[n], XmNwrap, TRUE);                 n++;
/* Current time of day */
XtSetArg (args[n], XmNposition, (tm->tm_hour > 12) ? 1 : 0); n++;
ampm_t = XmCreateTextField (spin, "ampmText", args, n);
XtManageChild (ampm_t);
Clearly, this arrangement of widgets could trivially be used to create a simple clock. We would only need to add a looping timeout handler, and adjust the XmNposition resources of each TextField appropriately as the timer expires. A program to create a clock using the SpinBox is listed amongst the Exercises section at the end of the chapter.

SpinBox and SimpleSpinBox Resources

The SpinBox, and SimpleSpinBox, resources for configuring the range of values associated with TextField children have already been mentioned in previous sections and examples of this chapter. In the paragraphs which follow, SpinBox is to include SimpleSpinBox unless otherwise stated. For a SimpleSpinBox, resources which are specified as constraints should be applied to the SimpleSpinBox itself, rather than on the SimpleSpinBox built-in TextField.

To recap, TextField children of SpinBoxes are considered to come in two flavors: numeric, and string.

A numeric TextField child of the SpinBox is configured by specifying the constraint resource XmNspinBoxChildType as XmNUMERIC. The range of possible values is programmed by specifying the XmNmaximumValue and XmNminimumValue constraints, and the current value is specified by the XmNposition constraint.

A string SpinBox TextField is configured by specifying the constraint resource XmNspinBoxChildType as XmSTRING. The values associated with the SpinBox is controlled through the XmNvalues and XmNnumValues constraints, which specify an array of compound strings. The current value is given by the XmNposition constraint, which represents an index into the array of compound strings.

In addition to specifying the range of values, it is also possible to control the way in which the user can activate the arrows for spinning through these values. The constraint resource XmNarrowSensitivity controls which of the arrows is currently sensitive to user input. The value XmARROWS_DECREMENT_SENSITIVE makes it possible for the user to only decrement the current value of the SpinBox. Similarly, XmARROWS_INCREMENT_SENSITIVE only allows an increase in SpinBox value. If neither action is currently allowed, the value XmARROWS_INSENSITIVE can be used to turn off spinning altogether. The default value, XmARROWS_SENSITIVE, allows both increase and decrease actions. In addition, a SpinBox, but not a SimpleSpinBox, supports the resource XmNdefaultArrowSensitivity, which specifies a default layout for TextField children which do not have the XmNarrowSensitivity constraint explicitly set. XmNdefaultArrowSensitivity can hold the same values as the XmNarrowLayout resource, and as a normal non-constraint resource should be applied to the SpinBox and not the TextField children.

The SpinBox supports the notion of automatic spin. That is, if the user presses an arrow, and holds the mouse button down, the SpinBox will automatically rotate through the values as though the user clicked multiple times. Automatic spin is configured through the XmNinitialDelay and XmNrepeatDelay resources. XmNinitialDelay specifies a time interval which is to elapse since the mouse button was first held down before automatic spinning becomes operative. XmNrepeatDelay specifies the time interval between successive spins of the value. If XmNrepeatDelay is zero, automatic spinning is disabled.

The location of the arrows relative to the SpinBox TextFields can be configured through the XmNarrowLayout resource. The arrows can be placed either horizontally or vertically aligned, both before and after the TextField children. Possible values for the XmNarrowLayout resource are:

XmARROWS_BEGINNING            XmARROWS_END
XmARROWS_FLAT_BEGINNING       XmARROWS_FLAT_END         XmARROWS_SPLIT
Figure 15-5 shows the effect of the various XmNarrowLayout values.

Figure  15-5 The various settings of the XmNarrowLayout resource

SpinBox and SimpleSpinBox Callbacks

The callback routines associated with the SpinBox and SimpleSpinBox widgets give the programmer control both over the value to display, and whether to allow spinning of the value, in any given instant. The callback model for the SpinBox widgets is analogous to those for the Text and TextField widgets, which is not surprising since the SpinBoxes simply manage the contents of child Text components.

The XmNmodifyVerifyCallback of the SpinBox can be used to verify the contents of a SpinBox TextField child after the user has requested a new selection, but before the actual contents are changed in the TextField itself. In the callback, the programmer can accept, reject, or otherwise alter the new proposed choice in any way which she feels fit by modifying suitable elements in the structure passed to the callback.

The XmNvalueChangedCallback is called after any XmNmodifyVerifyCallback has completed, provided that the XmNmodifyVerifyCallback did not reject the new SpinBox TextField value.

These two callbacks are typically used in conjunction; the XmNmodifyVerifyCallback polices the validity of the user selection, and the XmNvalueChangedCallback blindly processes the new value, safe in the knowledge that the value has been validated.

Both XmNmodifyVerifyCallback and XmNvalueChangedCallback functions are passed a structure in the following form:

typedef struct
{
    int          reason;
    XEvent       *event;
    Widget       widget;
    Boolean      doit;
    int          position;
    XmString     value;
    Boolean      crossed_boundary;
} XmSpinBoxCallbackStruct;
The reason field specifies the action performed by the user. Table 15-1 lists the callback name, reason, and associated user action for all values of the reason field:


Table  15-1   Callback resources for the SpinBox Widget
Resource Name
Reason
Action

XmNmodifyVerifyCallback,

XmNvalueChangedCallback

XmCR_SPIN_FIRST

The SpinBox is at the lower end of the range of values

XmNmodifyVerifyCallback,

XmNvalueChangedCallback

XmCR_SPIN_LAST

The SpinBox is at the upper end of the range of values

XmNmodifyVerifyCallback,

XmNvalueChangedCallback

XmCR_SPIN_PRIOR

The user has armed the decrement arrow

XmNmodifyVerifyCallback,

XmNvalueChangedCallback

XmCR_SPIN_NEXT

The user has armed the increment arrow

XmNvalueChangedCallback

XmCR_OK

The user has changed the value

The widget element is set to whichever Text or TextField child of the SpinBox currently has the input focus.

The doit element is the key to performing validation of the user's action. If the programmer sets this field to False, the SpinBox action is cancelled, and no change takes place to the current selection.

The position element is the callback equivalent of the XmNposition resource. The SpinBox sets the element to what it believes should be the newly displayed value before calling any XmNmodifyVerifyCallback: the programmer can subsequently alter the element inside the callback if an alternative position is required.

The value field contains a compound string representation of the newly selected value. If the SpinBox TextField which is being modified is numeric in nature, the value field contains the position of the SpinBox converted into a temporary compound string allocated only for the duration of the callback. The conversion process takes into account the XmNdecimalPoints resource of the changed TextField. On the other hand, if the SpinBox TextField is string-based, then the value field is not allocated - it merely points directly into the array of compound strings associated with the XmNvalue resource. In either case, if the programmer wishes to store the value field for use outside the callback, she must allocate a separate copy. The value field should not be freed by the programmer under any circumstances.

The crossed_boundary field indicates whether the user action wraps around the set of values associated with the current TextField. For example, if the user in a numeric TextField selects the decrement action and the currently displayed position is equal to XmNminimum, or if in a string-based TextField the user selects the increment action and the currently displayed choice is the last item in the XmNvalues array.

Example 15-5 is a program which utilizes both the XmNmodifyVerifyCallback and this XmNvalueChangedCallback resources. It creates a SpinBox with three TextFields for day, month, year fields. The XmNmodifyVerifyCallback ensures that the date displayed is valid. For example, if February is the current month then the day field cannot exceed 28 or 29, depending on whether the year field represents a leap year. The XmNvalueChangedCallback simply prints out the new current date as it changes.

Example  15-5 The date_spinbox_cb.c program

/* date_spinbox_cb.c -- demonstrate the spin box widget callbacks */

#include <Xm/Xm.h>
#include <Xm/RowColumn.h>
#include <Xm/SpinB.h>
#include <Xm/TextF.h>
#include <Xm/Label.h>

Widget day_t, month_t, year_t;

char *months[] = {   "January", "February", "March", "April", "May", "June",
                     "July", "August", "September", "October", "November", 
                     "December" };

int month_days[2][12] =  {
                    { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
                    { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
                        };

/* check_days: the XmNmodifyVerifyCallback for the SpinBox */
void check_days (Widget w, XtPointer client_data, XtPointer call_data)
{
    XmSpinBoxCallbackStruct *sb = (XmSpinBoxCallbackStruct *) call_data;
    int year;
    int month;
    int day;
    int leap;

    if (sb->widget == day_t) {
        /* Make sure that the new day never exceeds the maximum 
        ** for the current month 
        */
    XtVaGetValues (year_t, XmNposition, &year, 0);

    leap = (((year % 4) == 0) ? ((year % 400) == 0 ? 1 : 0) : 0);

    XtVaGetValues (month_t, XmNposition, &month, 0);

    if (sb->position > month_days[leap][month]) {
        if (sb->crossed_boundary)
            /* Going backwards */
            sb->position = month_days[leap][month];
        else
            /* Going forwards */
            sb->position = 1;
        }
    }
    else {
        /* The month or year has changed.
        ** Recheck the day field to ensure it does not exceed the 
        ** maximum for the new month or year.
        */
        if (sb->widget == month_t) {
            month = sb->position;

            XtVaGetValues (year_t, XmNposition, &year, 0);
        }
        else {
            year = sb->position;

            XtVaGetValues (month_t, XmNposition, &month, 0);
        }

        leap = (((year % 4) == 0) ? ((year % 400) == 0 ? 1 : 0) : 0);

        XtVaGetValues (day_t, XmNposition, &day, 0);

        if (day > month_days[leap][month]) {
            XtVaSetValues (day_t, XmNposition, month_days[leap][month], NULL);
        }
    }
}

/* print_date: the XmNvalueChangedCallback for the SpinBox */
void print_date (Widget w, XtPointer client_data, XtPointer call_data)
{
    XmSpinBoxCallbackStruct *sb = (XmSpinBoxCallbackStruct *) call_data;
    int day;
    int month;
    int year;

    if (sb->reason == XmCR_OK) {
        XtVaGetValues (day_t, XmNposition, &day, 0);
        XtVaGetValues (month_t, XmNposition, &month, 0);
        XtVaGetValues (year_t, XmNposition, &year, 0);

        printf ("New date: %d/%s/%d\n", day, months[month], year);
    }
}

main (int argc, char *argv[])
{
    Widget           toplevel, spin;
    XtAppContext     app;
    XmStringTable    ampm;
    Arg              args[8];
    int              i, n;
    XmStringTable    str_list;

    XtSetLanguageProc (NULL, NULL, NULL);

    toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, 
                                    sessionShellWidgetClass, NULL);

    /* Create the SpinBox */
    spin = XmCreateSpinBox (toplevel, "spin", NULL, 0);

    /* Create the Day field */
    n = 0;
    XtSetArg (args[n], XmNspinBoxChildType, XmNUMERIC); n++;
    XtSetArg (args[n], XmNcolumns, 2);                  n++;
    XtSetArg (args[n], XmNeditable, FALSE);             n++;
    XtSetArg (args[n], XmNminimumValue, 1);             n++;
    XtSetArg (args[n], XmNmaximumValue, 31);            n++; 
    XtSetArg (args[n], XmNposition, 1);                 n++;
    XtSetArg (args[n], XmNwrap, TRUE);                  n++;
    day_t = XmCreateTextField (spin, "dayText", args, n);
    XtManageChild (day_t);

    /* Create the Month field */
    n = XtNumber (months);
    str_list = (XmStringTable) XtMalloc (n * sizeof (XmString *));

    for (i = 0; i < n; i++)
        str_list[i] = XmStringCreateLocalized (months[i]);

    n = 0;
    XtSetArg (args[n], XmNspinBoxChildType, XmSTRING);   n++;
    XtSetArg (args[n], XmNeditable, FALSE);              n++;
    XtSetArg (args[n], XmNcolumns, 10);                  n++;
    XtSetArg (args[n], XmNwrap, TRUE);                   n++;
    XtSetArg (args[n], XmNvalues, str_list);             n++;
    XtSetArg (args[n], XmNnumValues, XtNumber (months)); n++;
    month_t = XmCreateTextField (spin, "monthText", args, n);
    XtManageChild (month_t);

    for (i = 0; i < XtNumber (months); i++)
        XmStringFree (str_list[i]);
    XtFree((XtPointer) str_list);

    /* Create the Year field */

    n = 0;
    XtSetArg (args[n], XmNspinBoxChildType, XmNUMERIC); n++;
    XtSetArg (args[n], XmNcolumns, 4);                  n++;
    XtSetArg (args[n], XmNeditable, FALSE);             n++;
    XtSetArg (args[n], XmNminimumValue, 1990);          n++;
    XtSetArg (args[n], XmNmaximumValue, 2010);          n++;
    XtSetArg (args[n], XmNposition, 2000);              n++;
    XtSetArg (args[n], XmNwrap, TRUE);                  n++;
    year_t = XmCreateTextField (spin, "yearText", args, n);
    XtManageChild (year_t);

    XtManageChild (spin);
    XtAddCallback (spin, XmNmodifyVerifyCallback, check_days, NULL);
    XtAddCallback (spin, XmNvalueChangedCallback, print_date, NULL);

    XtRealizeWidget (toplevel);
    XtAppMainLoop (app);
}
The output of the program is similar to Figure 15-2. The XmNmodifyVerifyCallback check_days is straightforward: if a field changes, we variously fetch the day, month and year field positions to ensure that the day number does not exceed the maximum allowed for the given date. If it is the day field that has changed, we only need to reset the position element of the callback structure to the new valid day, and we know if a day is invalid simply by comparing the callback position against the month_days array. Otherwise, if the month or year has changed, we must perform an explicit XtVaSetValues() call on the day_t field if we need to change the value. The XmNvalueChangedCallback is also straight forwards: format and print out the value of all three fields, but only if this callback is being called for the right reason and we know that any prior verification process has succeeded. This is the case if the reason field of the callback structure is set to XmCR_OK.

Summary

The SpinBox and SimpleSpinBox widgets can be used whenever there is a choice from a well-defined set of items. They are the natural choice of interface component if the data is in an ordered sequence. The next or previous item in the sequence of choices should be intuitive and obvious to the user at all times. Whenever the data meets these requirements, the SpinBox widgets provide a simple, compact, and natural method of selection. Remember that the user only sees the current value: if the next or previous selection is not entirely obvious, then the SpinBox or SimpleSpinBox is probably the wrong choice of interface component to present the choices to the user in the first place.

When to use a SpinBox, and when a SimpleSpinBox, can be a tricky choice. We could just as easily present the day/month/year components of a date through three SimpleSpinBoxes rather than using a SpinBox with three TextFields. Each component of the date would have individually associated increment and decrement arrows, which the user may, or may not, find more convenient than making sure the right SpinBox TextField child has the focus when using the arrows. On the other hand, the SpinBox manager method is slightly more compact, using a single set of arrows for all of the TextField children. It may well be that in any given situation, the choice of a SpinBox or multiple SimpleSpinBoxes makes little difference in terms of ease of use.

We recommend the SpinBox should be used in preference when you need to emphasize the fact that the components of the value are to be considered as parts of a single entity. This may well be the deciding factor: whether the data is, when considered logically, a single unit. In the example of a date, this is likely to be manipulated and stored as one, rather than each component being separated out and used in differing calculations, and thus here the SpinBox method is probably preferable to multiple SimpleSpinBoxes. Probably is an important word: we would not like to put hard and fast rules on when to use a SpinBox, and when to use multiple SimpleSpinBoxes; this is a judgement call the interface designer is going to have to make for herself, involving as it does all kinds of application-specific and human-computer interaction considerations.

Exercises

    1. Create a simple working clock using the SpinBox widget. The clock should be in 12-hour format, and should display hours, minutes, seconds, and the time-of-day indicator appropriately as the time changes. Hint: you will need to use the function XtAppAddTimeOut() to effect the ticking of the clock.
    2. Modify the date_spinbox_cb program so that if the user wraps round the day field, the month changes automatically, and similarly if the months field wraps round, the year changes. For example, if the current date is 31 December 1999, incrementing the day field should display 1 January 2000, and decrementing the day thereafter should revert to the original 31 December 1999 date.


1 The SpinBox is only available from Motif 2.0 onwards. The SimpleSpinBox is only available from Motif 2.1.

2 Note that this is really a SpinBox constraint resource, and we are in reality making the SimpleSpinBox built-in TextField numerical, rather than making the SimpleSpinBox numerical. We can apply it to the SimpleSpinBox because of the way it applies constraints on our behalf.

3 Again, note that this is really a SpinBox constraint resource, and we are in reality making the SimpleSpinBox built-in TextField string-based, rather than making the SimpleSpinBox string-based.






X-Designer - The Leading X/Motif GUI Builder - Click to download a FREE evaluation

Previous Next Contents Document Index Motif Docs motifdeveloper.com Imperial Software Technology X-Designer




Copyright © 1991, 1994, 2000, 2001, 2002 O'Reilly & Associates, Inc., Antony Fountain and Jeremy Huxtable. All Rights Reserved.