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.
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.
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.
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.
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.
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 XmNorientation 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_RIGHTUnfortunately, 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.
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.
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.
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.
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.
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.