Contents Previous Next

14 The Scale Widget


This chapter describes how to use the Scale widget to represent a range of values. The widget can be manipulated to change the value.

The Scale widget displays a numeric value that falls within upper and lower bounds. The widget allows the user to change that value interactively using a slider mechanism similar to that of a ScrollBar. This style of interface is useful when it is inconvenient or inappropriate to have the user change a value using the keyboard. The widget is also extremely intuitive to use; inexperienced users often understand how a Scale works when they first see one. the figure shows how a Scale can be used with other widgets in an application.

figs.eps/V6a.13.01.eps.png
A Scale widget in an application

A Scale can be oriented either horizontally or vertically. The values given to a Scale are stored as integers, but decimal representation of values is possible through the use of a resource that allows you to place a decimal point in the value. A Scale can be put in output-only mode, in which it is sometimes called a gauge. When a Scale is read-only, it implies that the value is controlled by another widget or that it is being used to report status information specific to the application. The standard way to create a read-only Scale is to specify that it is insensitive. Unfortunately, this technique has the side-effect of graying out the widget. One workaround is to create a Scale widget that is sensitive, but that has a null translation table.

14.1 Creating a Scale Widget

Applications that use the Scale widget must include the header file <Xm/Scale.h>. You can then create a Scale widget as follows:

   Widget scale;

   scale = XtVaCreateManagedWidget ("name",
       xmScaleWidgetClass, parent,
       resource-value-list,
       NULL);

Even though the Scale widget functions as a primitive widget, it is actually subclassed from the Manager widget. All the parts of a Scale are really other primitive widgets, but these subwidgets are not accessible through the Motif toolkit. The fact that the Scale is a Manager widget means that you can create widgets that are children of a Scale. The children are arranged so that they are evenly distributed along the vertical or horizontal axis parallel to the slider, depending on the orientation of the Scale. This technique is used primarily to provide "tick marks" for the Scale, as we'll describe later. In all other respects, a Scale can be treated just like other primitive widgets. the source code shows a program that creates some Scale widgets. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4.

   /* simple_scale.c -- demonstrate a few scale widgets. */

   #include <Xm/Scale.h>
   #include <Xm/RowColumn.h>

   main(argc, argv)
   int argc;
   char *argv[];
   {
       Widget        toplevel, rowcol, scale;
       XtAppContext  app;
       void          new_value(); /* callback for Scale widgets */

       XtSetLanguageProc (NULL, NULL, NULL);

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

       rowcol = XtVaCreateWidget ("rowcol",
           xmRowColumnWidgetClass, toplevel,
           XmNorientation, XmHORIZONTAL,
           NULL);

       scale = XtVaCreateManagedWidget ("Days",
           xmScaleWidgetClass, rowcol,
           XtVaTypedArg, XmNtitleString, XmRString, "Days", 5,
           XmNmaximum,   7,
           XmNminimum,   1,
           XmNvalue,     1,
           XmNshowValue, True,
           NULL);
       XtAddCallback (scale, XmNvalueChangedCallback, new_value, NULL);

       scale = XtVaCreateManagedWidget ("Weeks",
           xmScaleWidgetClass, rowcol,
           XtVaTypedArg, XmNtitleString, XmRString, "Weeks", 6,
           XmNmaximum,   52,
           XmNminimum,   1,
           XmNvalue,     1,
           XmNshowValue, True,
           NULL);
       XtAddCallback (scale, XmNvalueChangedCallback, new_value, NULL);

       scale = XtVaCreateManagedWidget ("Months",
           xmScaleWidgetClass, rowcol,
           XtVaTypedArg, XmNtitleString, XmRString, "Months", 7,
           XmNmaximum,   12,
           XmNminimum,   1,
           XmNvalue,     1,
           XmNshowValue, True,
           NULL);
       XtAddCallback (scale, XmNvalueChangedCallback, new_value, NULL);

       scale = XtVaCreateManagedWidget ("Years",
           xmScaleWidgetClass, rowcol,
           XtVaTypedArg, XmNtitleString, XmRString, "Years", 6,
           XmNmaximum,   20,
           XmNminimum,   1,
           XmNvalue,     1,
           XmNshowValue, True,
           NULL);
       XtAddCallback (scale, XmNvalueChangedCallback, new_value, NULL);

       XtManageChild (rowcol);

       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }

   void
   new_value(scale_w, client_data, call_data)
   Widget scale_w;
   XtPointer client_data;
   XtPointer call_data;
   {
       XmScaleCallbackStruct *cbs = (XmScaleCallbackStruct *) call_data;

       printf("%s: %d0, XtName(scale_w), cbs->value);
   }

The output of this program is shown in the figure.

figs.eps/V6a.13.02.eps.png
Output of simple_scale.c

The four Scales represent the number of days, weeks, months, and years, respectively. Each Scale displays a title that is specified by the XmNtitleString resource. Just as with other Motif widgets that display strings, the XmNtitleString must be set as a compound string, not a normal C string. The easiest way to make the conversion is to use the XtVaTypedArg feature, as we've done in this example. The use of this conversion method is described in detail in Chapter 19, Compound Strings.

A Scale cannot have a pixmap as its label. Since real estate for the label is limited in a Scale widget, you should take care to use small strings. If you need to use a longer string, you should include a separator so that the text is printed on two lines. If the string is too long, the label may be too wide and look awkward as a result. For a horizontal Scale, the label is displayed beneath the slider, while for a vertical Scale it is shown to the side of the slider.

The maximum and minimum values are set with the XmNmaximum and XmNminimum resources, respectively. The minimum values are set to 1 for the user's benefit; the minimum value of a Scale defaults to 0. Note that if you set a minimum value other than 0, you must also provide a default value for XmNvalue that is at least as large as the value of XmNminimum, as we have done in our example. Each Scale displays its current value because the XmNshowValue resource is set to True.

14.2 Scale Values

The value of a Scale can only be stored as an integer. This restriction is largely based on the fact that variables of type float and double cannot be passed through XtVaSetValues(), XtVaGetValues(), or any of the widget creation functions. While the Xt functions mentioned do allow the passing of the address of a variable of type float or double, the Scale widget does not support this type of value representation. If you need to represent fractional values, you must use the XmNdecimalPoints resource. This resource specifies the number of places to move the decimal point to the left in the displayed value, which gives the user the impression that the value displayed is fractional.

For example, a Scale widget used to display the value of a barometer might range from 29 to 31, with a granularity of 1-100th. The necessary widget could be created as shown in the following code fragment:

   XtVaCreateManagedWidget ("barometer", xmScaleWidgetClass, rowcol,
       XtVaTypedArg, XmNtitleString, XmRString,
           "Barometric0ressure", 19,
       XmNmaximum,         3100,
       XmNminimum,         2900,
       XmNdecimalPoints,   2,
       XmNvalue,           3000,
       XmNshowValue,       True,
       NULL);
The value for XmNdecimalPoints is 2, so that the value displayed is 30.00, rather than 3000. If you are using a Scale to represent fractional values, it is probably a good idea to set XmNshowValue to True since fine tuning is probably necessary.

There is no limit to the values that can be specified for the XmNmaximum, XmNvalue, and XmNminimum resources, provided they can be represented by the int type, which includes negative numbers. In the previous example, the initial value of the Scale (XmNvalue) is set arbitrarily; the value must be set within the minimum and maximum values. If the value of the Scale is retrieved using XtVaGetValues() or through a callback routine, the integer value is returned. To get the appropriate decimal value, you need to divide the value by 10 to the power of the value of XmNdecimalPoints. For example, since XmNdecimalPoints is 2, the value needs to be divided by 10 to the power of 2, or 100.

The value of a Scale can be set and retrieved using XtVaSetValues() and XtVaGetValues() on the XmNvalue resource. Motif also provides the functions XmScaleSetValue() and XmScaleGetValue() to serve the same purpose. These functions take the following form:

   void
   XmScaleSetValue (scale_w, value)
       Widget  scale_w;
       int     value;
   void
   XmScaleGetValue (scale_w, value)
       Widget  scale_w;
       int    *value;
The advantage of using the Motif convenience routines, rather than the Xt routines, is that the Motif routines manipulate data in the widget directly, rather than using the set and get methods of the Scale. As a result, there is less overhead involved, although the added overhead of the Xt methods are negligible.

14.3 Scale Orientation and Movement

A Scale can be either vertical or horizontal and the maximum and minimum values can be on either end of the Scale. By default, as shown in the examples so far, the Scale is oriented vertically with the maximum on the top and the minimum on the bottom. The XmN­orientation resource can be set to XmHORIZONTAL to produce a horizontal Scale. The XmNprocessingDirection resource controls the location of the maximum and minimum values. The possible values for the resource are:

   XmMAX_ON_TOP
   XmMAX_ON_BOTTOM
   XmMAX_ON_LEFT
   XmMAX_ON_RIGHT
Unfortunately, you cannot set the processing direction unless you know the orientation of the Scale, so if you hard-code one resource, you should set both of them. If the Scale is oriented vertically, the default value is XmMAX_ON_TOP, but if it is horizontal, the default depends on the value of XmNstringDirection. If you use a font that is read from right to left, then the maximum value is displayed on the left rather than on the right.

As the user drags the slider, the value of the Scale changes incrementally in the direction of the movement. If the user clicks the middle mouse button inside the Scale widget, but not on the slider itself, the slider moves to the location of the click. Unfortunately, in a small Scale widget, the slider takes up a lot of space, so this method provides very poor control for moving the slider close to its current location.

If the user clicks the left mouse button inside the slider area, but not on the slider itself, the slider moves in increments determined by the value of XmNscaleMultiple. The value of this resource defaults to the difference between the maximum and minimum values divided by 10. As of Release 1.2 of the Motif toolkit, you should set XmNscaleMultiple explicitly if the difference between XmNmaximum and XmNminimum is less than 10. Otherwise, incremental scaling won't work. For example, a Scale widget whose maximum value is 250 has a scale increment of 25. If the user presses the left mouse button over the area above or below the slider, the Scale's value increases of decreases by 25. If the button is held down, the movement continues until the button is released, even if the slider moves past the location of the pointer.

14.4 Scale Callbacks

The Scale widget provides two callbacks that can be used to monitor the value of the Scale. The XmNdragCallback callback routines are invoked whenever the user drags the slider. This action does not mean that the value of the Scale has actually changed or that it will change; it just indicates that the slider is being moved.

The XmNvalueChangedCallback is invoked when the user releases the slider, which results in an actual change of the Scale's value. It is possible for the XmNvalueChangedCallback to be called without the XmNdragCallback having been called. For example, when the user adjusts the Scale using the keyboard or moves the slider incrementally by clicking in the slider area, but not on the slider itself, only the XmNvalueChangedCallback is invoked.

These callback routines take the form of an XtCallbackProc, just like any other callback. As with all Motif callback routines, Motif defines a callback structure for the Scale widget callbacks. The XmScaleCallbackStruct is defined as follows:

   typedef struct {
      int      reason;
      XEvent  *event;
      int      value;
   } XmScaleCallbackStruct;
The reason field of this structure is set to XmCR_DRAG or XmCR_VALUE_CHANGED, depending on the action that invoked the callback. The value field represents the current value of the Scale widget.

the source code shows another example of how the Scale widget can be used. In this case, we create a color previewer that uses Scales to control the red, green, and blue values of the color that is being edited. This example demonstrates how the XmNdragCallback can be used to automatically adjust colors as the slider is being dragged. The XmNvalueChangedCallback is also used to handle the cases where the user adjusts the Scale without dragging the slider. For a discussion of the Xlib color setting routines used in this program, see Volume One, Xlib Programming Manual. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4.

   /* color_slide.c -- Use scale widgets to display the different
    * colors of a colormap.
    */
   #include <Xm/LabelG.h>
   #include <Xm/Scale.h>
   #include <Xm/RowColumn.h>
   #include <Xm/DrawingA.h>

   Widget colorwindow; /* the window the displays a solid color */
   XColor color;       /* the color in the colorwindow */

   main(argc, argv)
   int argc;
   char *argv[];
   {
       Widget        toplevel, rowcol, scale;
       XtAppContext  app;
       void          new_value();
       XtVarArgsList arglist;

       XtSetLanguageProc (NULL, NULL, NULL);

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

       if (DefaultDepthOfScreen (XtScreen (toplevel)) < 2) {
           puts ("You must be using a color screen.");
           exit (1);
       }

       color.flags = DoRed | DoGreen | DoBlue;
       /* initialize first color */
       XAllocColor (XtDisplay (toplevel),
           DefaultColormapOfScreen (XtScreen (toplevel)), &color);

       rowcol = XtVaCreateManagedWidget ("rowcol",
           xmRowColumnWidgetClass, toplevel, NULL);

       colorwindow = XtVaCreateManagedWidget ("colorwindow",
           widgetClass,   rowcol,
           XmNheight,     100,
           XmNbackground, color.pixel,
           NULL);

       /* use rowcol again to create another RowColumn under the 1st */
       rowcol = XtVaCreateWidget ("rowcol", xmRowColumnWidgetClass, rowcol,
           XmNorientation, XmHORIZONTAL,
           NULL);

       arglist = XtVaCreateArgsList (NULL,
           XmNshowValue, True,
           XmNmaximum, 255,
           XmNscaleMultiple, 5,
           NULL);

       scale = XtVaCreateManagedWidget ("Red",
           xmScaleWidgetClass, rowcol,
           XtVaNestedList, arglist,
           XtVaTypedArg, XmNtitleString, XmRString, "Red", 4,
           XtVaTypedArg, XmNforeground, XmRString, "Red", 4,
           NULL);
       XtAddCallback (scale, XmNdragCallback, new_value, DoRed);
       XtAddCallback (scale, XmNvalueChangedCallback, new_value, DoRed);

       scale = XtVaCreateManagedWidget ("Green",
           xmScaleWidgetClass, rowcol,
           XtVaNestedList, arglist,
           XtVaTypedArg, XmNtitleString, XmRString, "Green", 6,
           XtVaTypedArg, XmNforeground, XmRString, "Green", 6,
           NULL);
       XtAddCallback (scale, XmNdragCallback, new_value, DoGreen);
       XtAddCallback (scale, XmNvalueChangedCallback, new_value, DoGreen);

       scale = XtVaCreateManagedWidget ("Blue",
           xmScaleWidgetClass, rowcol,
           XtVaNestedList, arglist,
           XtVaTypedArg, XmNtitleString, XmRString, "Blue", 5,
           XtVaTypedArg, XmNforeground, XmRString, "Blue", 5,
           NULL);
       XtAddCallback (scale, XmNdragCallback, new_value, DoBlue);
       XtAddCallback (scale, XmNvalueChangedCallback, new_value, DoBlue);

       XtFree (arglist);

       XtManageChild (rowcol);

       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }

   void
   new_value(scale_w, client_data, call_data)
   Widget scale_w;
   XtPointer client_data;
   XtPointer call_data;
   {
       int rgb = (int) client_data;
       XmScaleCallbackStruct *cbs = (XmScaleCallbackStruct *) call_data;
       Colormap cmap = DefaultColormapOfScreen (XtScreen (scale_w));

       switch (rgb) {
           case DoRed :
               color.red = (cbs->value << 8);
               break;
           case DoGreen :
               color.green = (cbs->value << 8);
               break;
           case DoBlue :
               color.blue = (cbs->value << 8);
       }

       /* reuse the same color again and again */
       XFreeColors (XtDisplay (scale_w), cmap, &color.pixel, 1, 0);
       if (!XAllocColor (XtDisplay (scale_w), cmap, &color)) {
           puts ("Couldn't XAllocColor!");
     exit(1);
       }
       XtVaSetValues (colorwindow, XmNbackground, color.pixel, NULL);
   }
The output of this program is shown in the figure. Obviously, a black and white book makes it difficult to show how this application really looks. However, when you run the program, you should get a feel for using Scale widgets.

figs.eps/V6a.13.03.eps.png
Output of color_slide.c

One interesting aspect of the color_slide.c program is the use of XtVaCreateArgsList(). We use this function to build a single argument list that we use repeatedly. If we didn't use the function, we would have to duplicate the argument list for each call to XtVaCreateManagedWidget(). The function allocates and returns a pointer to an object of type XtVarArgsList . This type is an opaque pointer to an array of XtVaTypedArgList objects, which means that you can specify normal resource-value pairs or the quadruplet used by XtVaTypedArg. We use the latter form to specify resource values that are not in the appropriate type, so that the toolkit handles the type conversion. For a discussion on type conversion and the use of XtVaTypedArg, see Volume Four, X Toolkit Intrinsics Programming Manual.

14.5 Scale Tick Marks

The Motif Style Guide suggests that a Scale widget can have "tick marks" that represent the incremental positions of the Scale. The Scale widget does not provide these marks by default, but you can add them yourself by creating Labels as children of a Scale widget, as demonstrated in the source code Each of the Label gadgets are given the same name (a dash), which is used as the actual label since the XmNlabelString resource is not set. Obviously, in a more complex application, the Labels should specify information that helps the user to read the Scale. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4.

   /* tick_marks.c -- demonstrate a scale widget with tick marks. */

   #include <Xm/Scale.h>
   #include <Xm/LabelG.h>

   #define MAX_VAL 10 /* arbitrary value */

   main(argc, argv)
   int argc;
   char *argv[];
   {
       Widget        toplevel, scale;
       XtAppContext  app;
       int           i;

       XtSetLanguageProc (NULL, NULL, NULL);

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

       scale = XtVaCreateManagedWidget ("load",
           xmScaleWidgetClass, toplevel,
           XtVaTypedArg,     XmNtitleString, XmRString, "Process Load", 13,
           XmNmaximum,       MAX_VAL * 100,
           XmNminimum,       100,
           XmNvalue,         100,
           XmNdecimalPoints, 2,
           XmNshowValue,     True,
           NULL);

       for (i = 0; i < MAX_VAL; i++)
           XtVaCreateManagedWidget ("-", xmLabelGadgetClass, scale, NULL);

       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }
The output of this program is shown in the figure.

figs.eps/V6a.13.04.eps.png
Output of tick_marks.c

The Scale can have any kind of widget as a child, but it is common to use Labels to represent tick marks. All of the children are evenly distributed along the axis of the slider; no other layout method is possible. As you can see in the figure, the tick marks are placed all the way to the left of the Scale widget to leave space for the value indicator. It is not possible to force the tick marks up against the Scale by using the XmNalignment resource of the Labels or to control the layout of the tick marks in any way.

14.6 Summary

The Scale widget is a simple widget, both in concept and in practical use. In this chapter, we have showed a few possible uses of the Scale to represent a range of values. The range of a Scale, as well as its orientation, are customizable. The widget also provides callbacks that allow an application to keep track of the value of the Scale as the user changes it. These features make the Scale quite versatile.


Contents Previous Next