X-Designer - The Leading X/Motif GUI Builder - Click to download a FREE evaluation |
In this chapter:
Chapter: 16
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. Figure 16-1 shows how Scale widgets 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. In Motif 1.2, the standard way to create a read-only Scale is to specify that it is insensitive. Unfortunately, this technique has the side-effect of greying out the widget. In Motif 2.0 and later, the widget supports the XmNeditable resource: setting this to False effects the required gauge behavior without the described side-effect.
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: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 directly 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. In Motif 1.2, this technique is used primarily to provide "tick marks" for the Scale. In Motif 2.0 and later, tick marks can be added automatically through the function XmScaleSetTicks(), which will be described later in the chapter. In all other respects, a Scale can be treated just like other primitive widgets. Example 16-1 shows a program that creates some Scale widgets.1Widget scale = XtVaCreateWidget ( "name", xmScaleWidgetClass, parent, resource-value-list, NULL); Widget scale = XmCreateScale ( parent, "name", resource-value-array, resource-value-count);
The output of this program is shown in Figure 16-2./* simple_scale.c -- demonstrate a few scale widgets. */ #include <Xm/Scale.h> #include <Xm/RowColumn.h> Widget create_scale (Widget parent, char *title, int max, int min, int value) { Arg args[8]; int n = 0; XmString xms = XmStringCreateLocalized (title); void new_value(); /* callback for Scale widgets */ Widget scale; XtSetArg (args[n], XmNtitleString, xms); n++; XtSetArg (args[n], XmNmaximum, max); n++; XtSetArg (args[n], XmNminimum, min); n++; XtSetArg (args[n], XmNvalue, value); n++; XtSetArg (args[n], XmNshowValue, True); n++; scale = XmCreateScale (parent, title, args, n); XtAddCallback (scale, XmNvalueChangedCallback, new_value, NULL); XtManageChild (scale); return scale; } main (int argc, char *argv[]) { Widget toplevel, rowcol, scale; Arg args[2]; XtAppContext app; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, NULL); XtSetArg (args[0], XmNorientation, XmHORIZONTAL); rowcol = XmCreateRowColumn (toplevel, "rowcol", args, 1); scale = create_scale (rowcol, "Days", 7, 1, 1); scale = create_scale (rowcol, "Weeks", 52, 1, 1); scale = create_scale (rowcol, "Months", 12, 1, 1); scale = create_scale (rowcol, "Years", 20, 1, 1); XtManageChild (rowcol); XtRealizeWidget (toplevel); XtAppMainLoop (app); } void new_value (Widget scale_w, XtPointer client_data, XtPointer call_data) { XmScaleCallbackStruct *cbs = (XmScaleCallbackStruct *) call_data; printf("%s: %d\n", XtName (scale_w), cbs->value); }
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. Conversion between C strings and compound strings is described in detail in Chapter 25, 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.
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 trivially be passed through XtVaSetValues(), XtVaGetValues(), or any of the widget creation functions.2If 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:
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.Widget scale; Arg prgs[...]; int n; ... XtSetArg (args[n], XmNmaximum, 3100); n++; XtSetArg (args[n], XmNminimum, 2900); n++; XtSetArg (args[n], XmNdecimalPoints, 2); n++; XtSetArg (args[n], XmNvalue, 3000); n++; XtSetArg (args[n], XmNshowValue, True); n++; scale = XmCreateScale (parent, "barometer",args, n);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:
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.void XmScaleSetValue (Widget scale_w, int value) void XmScaleGetValue (Widget scale_w, int *value)
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 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: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 XmNlayoutDirection3. 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.XmMAX_ON_TOP XmMAX_ON_BOTTOM XmMAX_ON_LEFT XmMAX_ON_RIGHTAs 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.4For 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 or 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.
Scale Resources
The Scale widget is internally implemented using a ScrollBar: many of the ScrollBar resources listed in Chapter 10, ScrolledWindows and ScrollBars, are explicitly implemented to support Scale visuals: the ScrollBar resources XmNslidingMode, XmNsliderMark, XmNsliderVisual are mirrored in the Scale widget class5. For example, setting the XmNslidierVisual resource on the Scale internally sets the resource for the constituent ScrollBar.Probably the most interesting of the mirrored resources is the XmNslidingMode resource. The default value, XmSLIDER, gives the familiar Scale behavior, whereby the slider moves freely between the maximum and minimum points at the Scale ends. The Scale can, however, behave like a classic thermometer, with the slider anchored at one end. XmTHERMOMETER is the required value of XmNslidingMode for this effect. Figure 16-3 shows the difference between the two settings.
Each of the two Scales have identical XmNmaximum, XmNminimum, XmNvalue resources: they differ only in the value of XmNslidingMode. In the right-hand thermometer Scale, the slider is anchored at one end. Note that when the Scale is configured as a thermometer, directly setting the XmNsliderSize resource has no effect.
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:
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.typedef struct { int reason; XEvent *event; int value; } XmScaleCallbackStruct;Example 16-2 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 1, Xlib Programming Manual.6
The output of this program is shown in Figure 16-4. 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./* 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 */ Widget create_scale (Widget parent, char *name, int mask) { Arg args[8]; int n = 0; Widget scale; XrmValue from; XrmValue to; XmString xms = XmStringCreateLocalized (name); void new_value(); to.addr = NULL; from.addr = name; from.size = strlen (name) + 1; XtConvertAndStore (parent, XmRString, &from, XmRPixel, &to); XtSetArg (args[n], XmNforeground, (*(Pixel *) to.addr)); n++; XtSetArg (args[n], XmNtitleString, xms); n++; XtSetArg (args[n], XmNshowValue, True); n++; XtSetArg (args[n], XmNmaximum, 255); n++; XtSetArg (args[n], XmNscaleMultiple, 5); n++; scale = XmCreateScale (parent, name, args, n); XmStringFree (xms); XtAddCallback (scale, XmNdragCallback, new_value, (XtPointer) mask); XtAddCallback (scale, XmNvalueChangedCallback, new_value, (XtPointer) mask); XtManageChild (scale); return scale; } main (int argc, char *argv[]) { Widget toplevel, rowcol, rc, scale; XtAppContext app; Arg args[8]; int n; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, 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 = XmCreateRowColumn (toplevel, "rowcol", NULL, 0); /* create a canvas for the color display */ n = 0; XtSetArg (args[n], XmNheight, 100); n++; XtSetArg (args[n], XmNbackground, color.pixel); n++; colorwindow = XmCreateDrawingArea (rowcol, "colorwindow", args, n); XtManageChild (colorwindow); /* create another RowColumn under the 1st */ n = 0; XtSetArg (args[n], XmNorientation, XmHORIZONTAL); n++; rc = XmCreateRowColumn (rowcol, "rc", args, n); /* create the scale widgets */ scale = create_scale (rc, "Red", DoRed); scale = create_scale (rc, "Green", DoGreen); scale = create_scale (rc, "Blue", DoBlue); XtManageChild (rc); XtManageChild (rowcol); XtRealizeWidget (toplevel); XtAppMainLoop (app); } void new_value (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); }
One interesting aspect of the color_slide.c program is the use of XtConvertAndStore(). We use this function to convert from a string representation of a color name to a Pixel value, so that the toolkit handles the type conversion. For a discussion on type conversion and the use of XtConvertAndStore(), see Volume 4, X Toolkit Intrinsics Programming Manual.
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, either by creating Labels or Separators as children of a Scale widget, or through the routine XmScaleSetTicks()7. This convenience function considers ticks to be of three kinds: small, medium, and big. Medium ticks are evenly spaced between the big ticks, and small ticks are evenly spaced between the medium ticks. The function takes the following form:The function works by creating SeparatorGadget children along the edges of the Scale. The parameter big_every specifies the number of scale values between big ticks. The number of medium ticks between big ticks is given by num_medium, and the number of small ticks between the medium ticks is given by num_small. The size_* parameters refer to the size of each tick in Pixels. Example 16-3 creates a Scale with tick marks: the code places big ticks every 100 units of scale value, medium ticks at every 10 units in between, and small ticks in the intervening 5 unit mark8. This means that there are 9 (not ten) medium ticks between the big ticks, and only one small tick between the medium ticks.void XmScaleSetTicks ( Widget scale, int big_every, Cardinal num_medium, Cardinal num_small, Dimension size_big, Dimension size_medium, Dimension size_small)
The output of this program is shown in Figure 16-5./* tick_marks.c -- demonstrate a scale widget with tick marks. */ #include <Xm/Scale.h> main (int argc, char *argv[]) { Widget toplevel, scale; XtAppContext app; int n; Arg args[8]; XmString xms; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, NULL); xms = XmStringCreateLocalized ("Process Load"); n = 0; XtSetArg (args[n], XmNtitleString, xms); n++; XtSetArg (args[n], XmNminimum, 0); n++; XtSetArg (args[n], XmNmaximum, 200); n++; XtSetArg (args[n], XmNvalue, 100); n++; XtSetArg (args[n], XmNshowValue, True); n++; scale = XmCreateScale (toplevel, "load", args, n); XmScaleSetTicks(scale, 100, 9, 1, 16, 8, 4); XtManageChild (scale) ; XtRealizeWidget (toplevel); XtAppMainLoop (app); }
The Scale can have any kind of widget as a child. All of the children are evenly distributed along the axis of the slider. As you can see in Figure 16-5, the tick marks are placed all the way to the left of the Scale widget to leave space for the value indicator. If you wanted to add ticks using XmScaleSetTicks() as well as adding other children, it could be rather difficult to achieve any kind of sensible layout without some particularly non-trivial coding.
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.
1 XtVaAppInitialize() is considered deprecated in X11R6.2 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.
3 As of Motif 2.0, XmNstringDirection is obsolete, and is replaced by the XmNlayoutDirection resource.
4 You should set XmNscaleMultiple explicitly if the difference between XmNmaximum and XmNminimum is less than 10. Otherwise, incremental scaling does not work.
5 XmNsliderMark, XmNsliderVisual, XmNslidingMode are available only from Motif 2.0 onwards.
6 XtVaAppInitialize() is considered deprecated in X11R6.
7 XmScaleSetTicks() is only available from Motif 2.0 onwards.
8 XtVaAppInitialize() is considered deprecated in X11R6. XmScaleSetTicks() is only available from Motif 2.0 onwards.
|