Contents Previous Next

10 ScrolledWindows and ScrollBars

This chapter describes the ins and outs of scrolling. It pays particular attention to application-defined scrolling, which is often required when the simple scrolling provided by the ScrolledWindow widget is insufficient.

The ScrolledWindow widget provides a viewing area into another, usually larger, visual object. The viewport may be adjusted by the user through the use of ScrollBars that are attached to the ScrolledWindow. The Motif MainWindow, ScrolledList, and ScrolledText objects use ScrolledWindows to implement scrolling for their respective contents. The ScrolledWindow can also be used independently to provide a viewport into another large object, such as a DrawingArea or a manager widget that contains a large group of widgets. All of these scenarios are explored in this chapter.

10.1 The ScrolledWindow Design Model

The user always interacts with a ScrolledWindow through ScrollBars. Internally, however, there are several ways to implement what the user sees. These methods are based on two different scrolling models: automatic scrolling and application-defined scrolling. In either case, the application gives the ScrolledWindow a work window that contains the visual data to be viewed. Although the two models are different, they share many of the same concepts and features.

In automatic scrolling mode, the ScrolledWindow operates entirely on its own, adjusting the viewport as necessary in response to ScrollBar activity. The application simply creates the desired data, such as a Label widget that contains a large pixmap, and makes that widget the work window for the ScrolledWindow. When the user operates the ScrollBars to change the visible area, the ScrolledWindow adjusts the Label so that the appropriate portion is visible. This design is demonstrated in Chapter 4, The Main Window, and Chapter 10, The DrawingArea Widget.

With application-defined scrolling, the ScrolledWindow operates under the assumption that the work window is not complete. The widget assumes that another entity, such as the application or the internals of another widget, controls the data within the work window and that the data may change dynamically as the user scrolls. In order to control scrolling, the application must control all aspects of the ScrollBars. This level of control is necessary when it is impossible or impractical for an application to provide the ScrolledWindow with a sufficiently large work window (or the data for it) at any one time.

10.1.1 The Automatic Scrolling Model

Most of the time, the ScrolledWindow widget is used in automatic scrolling mode. When it is used in this mode, the ScrolledWindow contains at most three internal widgets: two ScrollBars and a clip window. The clip window is implemented as a DrawingArea widget. The ScrolledWindow creates these widgets automatically. The work area is an external widget (specified by the XmNworkWindow resource) that is clipped by the clip window. This work window is a child of the ScrolledWindow that is provided by the application; it is not created automatically by the ScrolledWindow. When the user interacts with the ScrollBars, the work window is adjusted so that the appropriate part is visible through the clip window. The general design of the ScrolledWindow in automatic scrolling mode is illustrated in the figure.

Design of an automatic ScrolledWindow

The work window can be almost any widget, but there can be only one work window per ScrolledWindow. If you want to have more than one widget inside of a ScrolledWindow, you can place all of the widgets in a manager widget and make that manager the work window. The clip window is always the size of the viewport portion of the ScrolledWindow, which is the size of the ScrolledWindow minus the size of the ScrollBars and any borders and margins. The clip window is not adjusted in size unless the ScrolledWindow is resized. The clip window is always positioned at the origin, which means that you cannot use XtMoveWidget() or change its XmNx and XmNy resources to reposition it in the ScrolledWindow. The internals of the ScrolledWindow are solely responsible for changing the view in the clip window, although you can affect this behavior. While you can get a handle to the clip window, you cannot remove it or replace it with another window.

10.1.2 The Application-defined Scrolling Model

In the application-defined scrolling model, which is the default model, the ScrolledWindow always makes itself the same size as the work window. Just as for automatic scrolling, the application must provide the work window as a child of the ScrolledWindow. The main reason to use application-defined scrolling is if the work window contains more data than can possibly be loaded in the automatic scrolling mode. An application may also require different scrolling behavior than the default pixel-by-pixel increments provided by the automatic scrolling mode. Application-defined scrolling is also the best option when the contents of the work window changes dynamically and the application does not want to rely on the ScrolledWindow to scroll new data into view.

The disadvantage of application-defined scrolling is that the application, not the ScrolledWindow, is responsible for the ScrollBars. The application must create and manage the ScrollBars, as well as respond to the scrolling actions initiated by the user. Since what is displayed in the clip window and the work window are identical, the ScrolledWindow widget does not bother to create a clip window. However, there are still some limitations as to what the ScrolledWindow can support. It is important that you understand the limitations before designing your application, so let's look at two examples.

A Text widget that displays the contents of an arbitrarily large file provides a classic example of application-defined scrolling. Under the automatic scrolling model, the application might have to provide the ScrolledWindow with a work window that is large enough to render thousands of lines of text, so that all of the text is immediately available to the user. An object of such proportions is prohibitive for reasonable performance and resource consumption. Since the work window cannot be as large as it would need to be for automatic scrolling, it might as well be as small as possible, which is the size of the clip window. When the Text widget is a child of a ScrolledWindow, the Text widget creates its own ScrollBars and attaches callback routines to them so that it can be notified of scrolling actions. When the user scrolls, the Text widget changes the text in the work window to the text that corresponds to the new region that just scrolled into view. The user has the illusion that scrolling is taking place, but in reality, the data in the work window has simply changed, thereby saving a great deal of overhead in system and server resources. The List widget uses the same method when it is the child of a ScrolledWindow. The Text and List widgets are the only examples of application-defined scrolling that are supported by the current implementation of the ScrolledWindow.

There is another scenario in which a large amount of data is retrieved dynamically and is not all available at the same time. Even though the ScrolledWindow does not really support this scenario, you should be familiar with the situation, since it may come up in a large application. There are some possible workarounds that we'll discuss later in the chapter. Let's say that the Pacific Gas and Electric Company has an online database that contains all of the pipeline information for California and that an operator wants to view the data for San Francisco county. To display this information, the application must read the data from the database and convert that data into an image that can be presented in a ScrolledWindow.

Although the database cannot get all the information for the whole county all at once, it can get more information than the window can display. Let's say that the window can display 10% of the county and the database can return information on 20% of the county in a reasonable amount of time. The application needs to use the application-defined mechanisms because 100% of the data is not available for automatic scrolling. The fact that more than what can be displayed is available just means that the application could optimize performance by avoiding unnecessary retrieval of data from the database whenever scrolling takes place. The application could reuse the existing work window as a cache, so that if the user scrolls by an amount that is small enough, the work window is redisplayed in a way similar to the automatic scrolling mechanism. The application would still have to control this behavior manually, though.

Unfortunately, the ScrolledWindow does not support this type of behavior. The ScrolledWindow always expands to the size of its work window in application-defined scrolling mode. In other words, you cannot have a work window that is a different size from the clip window. This situation leaves you with several design decisions. You could reduce the amount of data obtained from a database query, throw away excess information not used in your display, or make the viewport of an automatic ScrolledWindow large enough for each query. In any case, the best approach is to use some method that makes the size of the work window the same as the clip window. While this requirement may present some logistical problems with the design of your application, we'll discuss some workarounds for the situation later in the chapter.

In the two preceding examples, we have defined two fundamentally similar methods of scrolling: semi-automatic scrolling and true application-defined scrolling. In the first case, Text and List widgets handle their own scrolling internally through special-case routines attached to the ScrollBars. We call this method semi-automatic scrolling, since the application programmer is not responsible for the scrolling of these widgets. Nevertheless, the ScrolledWindow is in the application-defined scrolling mode. This situation is in contrast to true application-defined scrolling, where you must handle the ScrollBars and the associated scrolling actions entirely on your own. This method is more intricate and requires a significant amount of code to be implemented properly.

Obviously, the automatic scrolling mechanism provided by the ScrolledWindow is much simpler than the application-defined mechanism and it requires much less application intervention. However, there are some drawbacks in the implementation of automatic scrolling. Automatic ScrolledWindows only scroll in single-pixel increments. If other scrolling behavior is required, you must use application-defined scrolling. And while application-defined scrolling is far more complicated, the advantage is that it provides more flexibility in the ways that the object is scrolled.

10.2 Creating a ScrolledWindow

Creating a ScrolledWindow is no different from creating other kinds of Motif widgets. Applications that wish to use ScrolledWindows must include the header file <Xm/ScrolledW.h>. The process of creating a ScrolledWindow is shown in the following code fragment:

   Widget  scrollw;

   scrollw = XtVaCreateManagedWidget ("name",
       xmScrolledWindowWidgetClass, parent,
The parent can be a Shell or any manager widget. The ScrolledWindow can be created as a managed widget, since the addition of its child does not cause it to renegotiate its size. (See Chapter 8, Manager Widgets, for a discussion of when manager widgets should be created as managed or unmanaged widgets.) The resource-value pairs control the behavior of the ScrolledWindow, as well as its visual effects. The most important resources are XmN­scrollingPolicy, XmN­visualPolicy, and XmN­scrollBarDisplayPolicy. The value for XmNscrollingPolicy can be set to either XmAUTOMATIC or XmAPPLICATION_DEFINED, depending on which scrolling method you want to use. The use of other ScrolledWindow resources varies depending on the scrolling behavior that is specified.

10.2.1 Automatic Scrolling

In automatic scrolling mode, the ScrolledWindow assumes that all of the data is already available in the work window and that the size of the work window represents the entire size of the viewable data. Even if the data changes and the size of work window is modified, the ScrolledWindow can still manage its display automatically. The ScrolledWindow should never resize itself due to changes in the work windows, so XmNvisualPolicy is typically set to XmCONSTANT. This value tells the ScrolledWindow not to resize itself when the work window grows or shrinks. If XmNvisualPolicy is set to XmVARIABLE, the ScrolledWindow always sizes itself to contain the entire work window, which nullifies the need for an automatic ScrolledWindow. Like any other widget, the only time that a ScrolledWindow should change size is when the parent resizes it, presumably for one of the following reasons:

The default size of the ScrolledWindow is never the same size as the work area, unless it's a coincidence. The internals to the ScrolledWindow widget happen to set the width and height to 100 pixels, although this fact is not officially documented by OSF. The default size is not very useful, so you should probably specify the XmNwidth and XmNheight resources for a ScrolledWindow. A problem arises if you want the ScrolledWindow to initialize itself to the size of the work window and have it be in automatic scrolling mode. To make the ScrolledWindow the same size as the work window, you must use application-defined scrolling.

For automatic scrolling, the only thing left to decide is how you want the ScrollBars to be displayed if the work window dynamically grows or shrinks. There may be situations where the work window is the same size as or smaller than the clip window. In this case, you may not want to display the ScrollBars, since they are not needed. If so, you can set XmN­scrollBarDisplayPolicy to XmAS_NEEDED. If you always want the ScrollBars to be visible, whether or not they are needed, you can set the resource to XmSTATIC. Some people prefer static ScrollBars, so that consistency is maintained in the interface; having ScrollBars appear and disappear frequently may be confusing. Perhaps the best thing to do is to allow the user to specify the XmNscrollBarDisplayPolicy. You can always set your preference in the application defaults file, as shown below:

   *XmScrolledWindow.scrollBarDisplayPolicy: static

10.2.2 Application-defined Scrolling

In the application-defined scrolling mode, XmNscrollingPolicy is set to XmAPPLICATION_DEFINED. In this case, the work window must be the same size as the clip window, so the size of the work window is set by the toolkit. As a result, the XmNvisualPolicy resource has the value of XmVARIABLE, which indicates that the work window grows and shrinks with the ScrolledWindow. Since the two windows are the same size, the ScrolledWindow doesn't need to have a clip window, so it doesn't create one.

Because application-defined scrolling implies that you are responsible for the creation and management of the ScrollBars, the toolkit forces the XmNscrollBarDisplayPolicy to XmSTATIC. which means that the ScrolledWindow always displays the ScrollBars if they are managed. Since the ScrolledWindow cannot know the size of the entire data, it cannot automate the visibility of the ScrollBars. If you want your application to emulate the XmAS_NEEDED behavior, you must monitor the size of the ScrolledWindow and the work area and manage the ScrollBars manually.

10.2.3 Additional Resources

Another ScrolledWindow resource is the XmNworkWindow, which is used to identify the widget that acts as the ScrolledWindow's work window. A ScrolledWindow can have only one work window and a work window can be associated with only one ScrolledWindow. In other words, you cannot assign the same widget ID to multiple ScrolledWindows to get multiple views into the same object. There are ways of achieving this effect, though, that will become apparent as we go through the chapter.

The XmNclipWindow resource specifies the widget ID for the clip window. This resource is read-only, so it is illegal to set the clip window manually or to reset it to NULL . For practical purposes, this resource should be left alone. The XmNverticalScrollBar and XmNhorizontalScrollBar resources specify the widget IDs of the ScrollBars in the ScrolledWindow. These resources allow you to set and retrieve the ScrollBars, which is useful for monitoring scrolling actions and setting up application-defined scrolling. Like any other manager, the ScrolledWindow also has resources that control the margin height and width and other visual attributes.

10.2.4 An Automatic ScrolledWindow Example

Automatic scrolling is the simpler of the two types of scrolling policies available. Fortunately, it is also the more common of the two. You shouldn't let this simplicity sway you too much, though, as it is a common design error for programmers to use the automatic scrolling mechanisms for designs that are better suited to the application-defined model. On the other hand, if you merely want to monitor scrolling without necessarily controlling it, you can install your own callback routines on the ScrollBars in an automatic ScrolledWindow, as we'll describe in the next section

In automatic mode, a ScrolledWindow automatically creates its own ScrollBars and handles their callback procedures to position the work window in the clip window. All of the examples that use ScrolledWindows in the rest of the chapters in this book (such as those in Chapter 4, The Main Window, and Chapter 10, The DrawingArea Widget) use the automatic scrolling mode. The only exceptions are the ScrolledList and ScrolledText objects, but the List and Text widgets handle application-defined scrolling internally.

the source code shows a large panel of Labels, ToggleButtons, and Text widgets that are arranged in a collection of Form and RowColumn widgets and managed by a ScrolledWindow widget. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4.

   /* getusers.c -- demonstrate a simple ScrolledWindow by showing
    * how it can manage a RowColumn that contains a vertical stack of
    * Form widgets, each of which contains a Toggle, two Labels and
    * a Text widget.  The program fills the values of the widgets
    * using various pieces of information from the password file.
    * Note: there are no callback routines associated with any of the
    * widgets created here -- this is for demonstration purposes only.
   #include <Xm/PushBG.h>
   #include <Xm/LabelG.h>
   #include <Xm/ToggleB.h>
   #include <Xm/ScrolledW.h>
   #include <Xm/RowColumn.h>
   #include <Xm/Form.h>
   #include <Xm/Text.h>
   #include <pwd.h>

   typedef struct {
       String      login;
       int         uid;
       String      name;
       String      homedir;
   } UserInfo;

   /* use getpwent() to read data in the password file to store
    * information about all the users on the system.  The list is
    * a dynamically grown array, the last of which has a NULL login.
   UserInfo *
       /* extern struct *passwd getpwent(); */
       extern char *strcpy();
       struct passwd *pw;
       UserInfo *users = NULL;
       int n;


       /* getpwent() returns NULL when there are no more users */
       for (n = 0; pw = getpwent(); n++) {
           /* reallocate the pointer to contain one more entry.  You may choose
            * to optimize by adding 10 entries at a time, or perhaps more?
           users = (UserInfo *) XtRealloc (users, (n+1) * sizeof (UserInfo));
           users[n].login = strcpy (XtMalloc
               (strlen (pw->pw_name)+1), pw->pw_name);
           users[n].name = strcpy (XtMalloc
               (strlen (pw->pw_gecos)+1), pw->pw_gecos);
           users[n].homedir = strcpy (XtMalloc
               (strlen (pw->pw_dir)+1), pw->pw_dir);
           users[n].uid = pw->pw_uid;
       /* allocate one more item and set its login string to NULL */
       users = (UserInfo *) XtRealloc (users, (n+1) * sizeof (UserInfo));
       users[n].login = NULL;
       return users; /* return new array */

   main(argc, argv)
   int argc;
   char *argv[];
       Widget toplevel, sw, main_rc, form, toggle;
       XtAppContext app;
       UserInfo *users;

       XtSetLanguageProc (NULL, NULL, NULL);

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

       /* Create a 500x300 scrolled window.  This value is arbitrary,
        * but happens to look good initially.  It is resizable by the user.
       sw = XtVaCreateManagedWidget ("scrolled_w",
           xmScrolledWindowWidgetClass, toplevel,
           XmNwidth,           500,
           XmNheight,          300,
           XmNscrollingPolicy, XmAUTOMATIC,

       /* RowColumn is the work window for the widget */
       main_rc = XtVaCreateWidget ("main_rc", xmRowColumnWidgetClass, sw, NULL);
       /* load the users from the passwd file */
       if (!(users = getusers())) {
           perror ("Can't read user data info");
           exit (1);
       /* for each login entry found in the password file, create a
        * form containing a toggle button, two labels and a text widget.
       while (users->login) { /* NULL login terminates list */
           char uid[8];
           form = XtVaCreateWidget (NULL, xmFormWidgetClass, main_rc, NULL);
           XtVaCreateManagedWidget (users->login, xmToggleButtonWidgetClass, form,
               XmNalignment,        XmALIGNMENT_BEGINNING,
               XmNtopAttachment,    XmATTACH_FORM,
               XmNbottomAttachment, XmATTACH_FORM,
               XmNleftAttachment,   XmATTACH_FORM,
               XmNrightAttachment,  XmATTACH_POSITION,
               XmNrightPosition,    15,
           sprintf (uid, "%d", users->uid);
           XtVaCreateManagedWidget (uid, xmLabelGadgetClass, form,
               XmNalignment,        XmALIGNMENT_END,
               XmNtopAttachment,    XmATTACH_FORM,
               XmNbottomAttachment, XmATTACH_FORM,
               XmNleftAttachment,   XmATTACH_POSITION,
               XmNleftPosition,     15,
               XmNrightAttachment,  XmATTACH_POSITION,
               XmNrightPosition,    20,
           XtVaCreateManagedWidget (users->name, xmLabelGadgetClass, form,
               XmNalignment,        XmALIGNMENT_BEGINNING,
               XmNtopAttachment,    XmATTACH_FORM,
               XmNbottomAttachment, XmATTACH_FORM,
               XmNleftAttachment,   XmATTACH_POSITION,
               XmNleftPosition,     20,
               XmNrightAttachment,  XmATTACH_POSITION,
               XmNrightPosition,    50,
           /* Although the home directory is readonly, it may be longer
            * than expected, so don't use a Label widget.  Use a Text widget
            * so that left-right scrolling can take place.
           XtVaCreateManagedWidget (users->homedir, xmTextWidgetClass, form,
               XmNeditable,         False,
               XmNcursorPositionVisible, False,
               XmNtopAttachment,    XmATTACH_FORM,
               XmNbottomAttachment, XmATTACH_FORM,
               XmNleftAttachment,   XmATTACH_POSITION,
               XmNleftPosition,     50,
               XmNrightAttachment,  XmATTACH_FORM,
               XmNvalue,            users->homedir,
           XtManageChild (form);
       XtManageChild (main_rc);

       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
Those of you who are familiar with UNIX programming techniques should find the use of getpwent() and endpwent() quite familiar. If you are not aware of these functions, you should consult the documentation for your UNIX system. In short, they can be used to return information about the contents of the password file (typically /etc/passwd), which contains information about all of the users on the system. The first call to getpwent() opens the password file and returns a data structure describing the first entry. Subsequent calls return consecutive entries. When the entries have been exhausted, getpwent() returns NULL and endpwent() closes the password file. In the source code the information from the password file is represented using ToggleButtons, Labels, and Text widgets, as shown in the figure.

Output of getusers.c

The components in the program do not have any functionality; the program is used solely to demonstrate how panels of arbitrary widgets can be displayed in a ScrolledWindow. The widget hierarchy is irrelevant to the operation of the ScrolledWindow. In this particular case, the ScrolledWindow is a child of the top-level shell. We could have used a MainWindow widget in place of a ScrolledWindow; these two components are interchangeable because the MainWindow is subclassed from the ScrolledWindow. See Chapter 4, The Main Window , for more details on how the MainWindow widget fits into the design of an application.

We used arbitrary values for the width and height of the ScrolledWindow; they were chosen because they seemed to work best. If you are using a ScrolledWindow with a number of other widgets in an interface, you do not need to specify an initial size for the ScrolledWindow. Since the ScrolledWindow is extremely flexible, you can allow its parent or its siblings to control its size. ScrolledWindows work well with PanedWindows because they can be adjusted easily. However, the ScrolledWindow does not have a sensible default size, so you should provide an initial geometry if the ScrolledWindow is going to control its own size. In this case, the size that you choose for the widget should be based on the aesthetics of the data that is being displayed.

In the example, the child of the ScrolledWindow is the main_rc widget, which is a RowColumn that contains all of the children that represent the password file information. After getusers() is called, the program loops through each item in the array of UserInfo structures and creates a Form widget that contains a ToggleButton, two Labels, and a Text widget. All of the Forms are stacked vertically on top of one another in the RowColumn. Once complete, the user can scroll around and access any of the elements without the application having to support any of the scrolling mechanisms because they are completely automated by the toolkit. In most cases, an application does not need to do anything other than what we described in this section to take advantage of automatic scrolling.

10.3 Working Directly With ScrollBars

The ScrollBar is the backbone of the ScrolledWindow. Although the ScrollBar is a stand­alone widget that can be created and manipulated without being the child of a ScrolledWindow, we are not going to discuss this usage because it is not consistent with the Motif Style Guide. The kinds of things that you can do with a ScrollBar individually are no more interesting than the sorts of things that you can do with them as children of ScrolledWindows, anyway. We are going to discuss how to control a ScrollBar directly from an application in the context of a ScrolledWindow widget. This information is useful if you want to monitor scrolling, if you want to fine-tune the way that automatic scrolling is handled, or if you want to implement application-defined scrolling.

Before we begin, it is important to understand that the ScrollBar does not handle scrolling itself. The widget merely reports scrolling actions through its callback routines. It is up to the internals of an application or a widget to install callback procedures on the ScrollBar that adjust the work window appropriately. The ScrollBar manages its own display in accordance with scrolling actions, so you do not need to update the ScrollBar's display unless the underlying data of the object being scrolled changes. To change the display, you can set resources that are associated with the different elements of the ScrollBar. the figure illustrates the design of a ScrollBar and identifies its elements. This figure represents a vertical ScrollBar; a ScrollBar can also be oriented horizontally.

Elements of a ScrollBar

The appearance and behavior of a ScrollBar is directly related to the object that it scrolls. The relationship between the ScrollBar and the object it scrolls is proportional, so that the size of the slider in the ScrollBar represents how much of the object that is being scrolled is visible in the clip window. The size of the object being scrolled is broken down into equally sized units; the size of the units is called the unit length. When the user clicks on one of the incremental arrows (also called directional arrows), the ScrollBar scrolls in the direction indicated by the arrow in unit increments. It is important to realize that the unit length is stored and interpreted internally by the object being scrolled; it is of no interest to the ScrollBar itself, since it does not affect the display of the ScrollBar. While this value is not set on the ScrollBar itself, it plays a key role in understanding how ScrollBars work.

All of the other resource values for the ScrollBar are measured in terms of the unit length. A Text widget might set its unit length for the vertical ScrollBar to the height of the tallest character in the widget's font set, plus some margin for whitespace on the top and bottom of the character. As a result, vertical scrolling adjusts the window so that the text is always displayed without lines being partially obscured. However, it is the Text widget's responsibility to know the unit length value. The unit length for the horizontal ScrollBar unit length might be the average width of the characters in the font that is being used.

The value of a ScrollBar is the offset, measured in unit lengths, of the data in the clip window from the object's origin. For example, if the top of the clip window displays the fourth line of text in a Text widget, the ScrollBar is said to have a value of 3, since it is offset from 0. Clicking and dragging the slider directly changes the ScrollBar's value to an absolute number; clicking on either of the directional arrows changes the ScrollBar's value incrementally; clicking in the scrolling region, but not on the slider itself changes the ScrollBar's value by page lengths. The value is measured in units, not pixels.

The view length is the size of the viewable area (clip window), as measured in unit lengths. The vertical ScrollBar for a Text widget that is displaying 15 lines of text would have a view length of 15. The horizontal ScrollBar's view length would be the number of columns that the clip window can display.

The page length is measured in unit lengths and is usually one less than the view length. If the user scrolls the window by a page increment, the first line from the old view is retained as the last line in the new view for visual reference because otherwise, the user might lose her orientation.

10.3.1 Resources

the figure illustrates the relationship between the elements listed above and introduces the ScrollBar resources that correspond to these values.

Conceptual relationship between a ScrollBar and the object it scrolls

The XmNincrement resource represents the number of units that the ScrollBar reports having scrolled when the user clicks on its incremental arrows. The value for XmNincrement in the figure is 1 because each incremental scroll on the vertical ScrollBar should scroll the text one line. Internally, the Text widget knows that the number of pixels associated with XmNincrement is the height of a line. For an automatic ScrolledWindow, it is rare to set the resource to any value other than 1.

The XmNpageIncrement resource specifies the number of units that the ScrollBar should report having scrolled when the user moves the ScrollBar by a page. Again, the ScrollBar doesn't actually perform the scrolling, it just reports the scrolling action. However, the ScrollBar does use this value to calculate the new visual position for the slider within the scrolling area and to update its display. The application can use this value, multiplied by pixels-per-unit, to determine the new data to display in the work window.

The XmNmaximum resource is the largest size, measured in unit increments, that the object can have. For the Text widget shown above, the value for XmNmaximum is 9 . The Motif Text widget sets its horizontal ScrollBar's XmNmaximum to the number of characters in its widest visible line, rather than the widest of all of its lines. The XmN­minimum resource is the smallest size, measured in unit increments, that the object will ever have. The XmNsliderSize resource corresponds to the view length. The resource specifies the size of the clip window in unit lengths. For example, in the figure, the clip window can display five lines, so XmNsliderSize is 5.

The XmNvalue is the number of units that the data in the clip window is offset from the beginning of the work window. For example, if the Text widget has been scrolled down by four lines from the top, the value of the vertical ScrollBar's XmNvalue resource would be 4.

the source code demonstrates how the vertical ScrollBar resources get their values from a typical ScrolledText object. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4.

   /* simple_sb.c -- demonstrate the Scrollbar resource values from
    * a ScrolledText object.  This is used as an introductory examination
    * of the resources used by Scrollbars.
   #include <Xm/ScrolledW.h>
   #include <Xm/RowColumn.h>
   #include <Xm/PushBG.h>
   #include <Xm/Text.h>

   /* print the "interesting" resource values of a scrollbar */
   get_sb(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
       Widget scrollbar = (Widget) client_data;
       int increment=0, maximum=0, minimum=0;
       int page_incr=0, slider_size=0, value=0;

       XtVaGetValues (scrollbar,
           XmNincrement,     &increment,
           XmNmaximum,       &maximum,
           XmNminimum,       &minimum,
           XmNpageIncrement, &page_incr,
           XmNsliderSize,    &slider_size,
           XmNvalue,         &value,
       printf ("increment=%d, max=%d, min=%d, page=%d, slider=%d, value=%d0,
           increment, maximum, minimum, page_incr, slider_size, value);

   main(argc, argv)
   int argc;
   char *argv[];
       Widget        toplevel, rowcol, text_w, pb, sb;
       XtAppContext  app;
       Arg           args[10];
       int           n = 0;

       XtSetLanguageProc (NULL, NULL, NULL);

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

       /* RowColumn contains ScrolledText and PushButton */
       rowcol = XtVaCreateWidget ("rowcol",
           xmRowColumnWidgetClass, toplevel, NULL);

       XtSetArg (args[n], XmNrows,      10); n++;
       XtSetArg (args[n], XmNcolumns,   80); n++;
       XtSetArg (args[n], XmNeditMode,  XmMULTI_LINE_EDIT); n++;
       XtSetArg (args[n], XmNscrollHorizontal,  False); n++;
       XtSetArg (args[n], XmNwordWrap,  True); n++;
       text_w = XmCreateScrolledText (rowcol, "text_w", args, n);
       XtManageChild (text_w);

       /* get the scrollbar from ScrolledWindow associated with Text widget */
       XtVaGetValues (XtParent (text_w), XmNverticalScrollBar, &sb, NULL);

       /* provide a pushbutton to obtain the scrollbar's resource values */
       pb = XtVaCreateManagedWidget ("Print ScrollBar Values",
           xmPushButtonGadgetClass, rowcol, NULL);
       XtAddCallback (pb, XmNactivateCallback, get_sb, sb);

       XtManageChild (rowcol);

       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
This program simply displays a ScrolledText object and a PushButton. The ScrolledText object does not contain any text by default; you can cut and paste some text into the object. The graphical output of the program is displayed in the figure.

Output of simple_sb.c

When the PushButton is activated, it retrieves some resource values from the vertical ScrollBar of the Text widget's ScrolledWindow. These values are output to stdout. The following output shows some possible values for the different resources:

   increment=1, max=12, min=0, page=9, slider=10, value=0
   increment=1, max=12, min=0, page=9, slider=10, value=1
   increment=1, max=25, min=0, page=9, slider=10, value=6
   increment=1, max=25, min=0, page=9, slider=10, value=12
   increment=1, max=25, min=0, page=9, slider=10, value=15
The value for XmNincrement is always 1, which indicates that the incremental arrow buttons scroll the text by one unit in either direction. The value for XmNmaximum changes according to the number of lines of text that there are in the window. The value of XmN­minimum is always 0 because this object can have as few as zero lines of text.

The values for XmNsliderSize and XmNpageIncrement are 10 and 9, respectively. The values never changed because the ScrolledWindow was not resized. If it had been, the slider size and page increment values would have changed to match the new number of lines displayed in the window. The page increment is one less than the number of lines that can be displayed in the clip window, so that if the user scrolls by a page, the new view contains at least one of the previously-viewed lines for reference.

The value for XmNvalue varies depending on the line that is displayed at the top of the clip window. If the beginning of the text is displayed, XmNvalue is 0. As the user scrolls through the text, the value for XmNvalue increases or decreases, but it is always a positive value.

Incidentally, you can adjust these resource values to get some different results. For example, you could set the XmNincrement resource to 2 in order to modify the number of lines that are scrolled when the user selects the incremental arrows. However, you should not change these resources arbitrarily, as you could really confuse the user.

As mentioned at the beginning of this section, the most important thing to remember about the ScrollBar widget is that it does not cause any actual scrolling of the object in the work window. The widget merely reports scrolling activity through its callback routines. When scrolling occurs, it is the callback routines that are responsible for modifying the data in the work window, by adjusting elements or redrawing the image. The ScrollBar updates its own display according to the scrolling action. If the widget or the application that owns the callback routines fails to modify the display, the user will see an inconsistency between the ScrollBar display and the data in the clip window.

10.3.2 Orientation

Two ScrollBar resources that are closely related are XmNorientation and XmN­processingDirection. These resources specify the horizontal or vertical orientation of the ScrollBar and its normal processing direction. The value for XmNorientation can be either XmHORIZONTAL or XmVERTICAL. When a ScrollBar is oriented horizontally, the normal processing direction for it is such that the minimum value is on the left and the maximum is on the right. When the orientation is vertical, the minimum is on the bottom and the maximum is on the top. You can change the processing direction using the XmN­processingDirection resource. This resource can have the following values:

These values only need to be changed when the user's environment is such that the natural language for the locale is read from right-to-left. In this case, The XmNscrollBarPlacement resource for the ScrolledWindow needs to be changed to match the processing direction. This resource can have the following values:

10.3.3 Callback Routines

The callback routines associated with the ScrollBar are its only links into the internal mechanisms that actually scroll the data. You can use these callback routines in various contexts, depending on what you want to accomplish. For example, you can monitor scrolling in an automatic or semi-automatic ScrolledWindow, such as a ScrolledText or ScrolledList object. These two activities are identical when it comes to the implementation of what we are about to describe. You can also implement application-defined scrolling, which requires intimate knowledge of the internals of the object being scrolled.

There are different parts of a ScrollBar that the user can manipulate to cause a scrolling action. In fact, each part of the ScrollBar has a separate callback routine associated with it. These callback routines are used both to monitor automatic (or semi-automatic) scrolling and to implement application-defined scrolling. As with all Motif callbacks, the callback routines take the form of an XtCallbackProc. All of the ScrollBar callbacks pass a structure of type XmScrollBarCallbackStruct for the third parameter. This structure takes the following form:

   typedef struct {
      int      reason;
      XEvent  *event;
      int      value;
      int      pixel;
   } XmScrollBarCallbackStruct;
The reason field specifies the scrolling action performed by the user. Each callback has a corresponding reason that indicates the action. lists the callback name, reason, and scrolling action for each ScrollBar callback resource. tab(@), linesize(2); l | l | l lp9fCW | lp9fCW | lw(1.7i). Resource Name@Reason@Action
XmNincrementCallback@XmCR_INCREMENT@T{ Top or right directional arrow clicked T} XmNdecrementCallback@XmCR_DECREMENT@T{ Bottom or left directional arrow clicked T} XmNpageIncrementCallback@XmCR_PAGE_INCREMENT@T{ Area above or right of slider clicked T} XmNpageDecrementCallback@XmCR_PAGE_DECREMENT@T{ Area below or left of slider clicked T} XmNtoTopCallback@XmCR_TO_TOP@T{ Top or right directional arrow CTRL-clicked T} XmNtoBottomCallback@XmCR_TO_BOTTOM@T{ Bottom or left directional arrow CTRL-clicked T} XmNdragCallback@XmCR_DRAG@T{ Slider dragged T} XmNvalueChangedCallback@XmCR_VALUE_CHANGED@T{ Value changed (see explanation) T}
_ The scrolling action that invokes the various increment and decrement callbacks depends on the value of the XmNprocessingDirection resource; the table shows the actions for a left-to-right environment. The XmNvalueChangedCallback is invoked when the user releases the mouse button after dragging the slider. The callback is also invoked for any of the other scrolling actions if the corresponding callback resource is not set, with the exception of the XmNdragCallback. This feature is convenient for cases where you are handling your own scrolling and you are not concerned with the type of scrolling the user invoked.

The value field of the callback structure indicates the new position of the ScrollBar. This value can range from XmNminimum to XmNmaximum. The pixel field indicates the x or y coordinate of the mouse location relative to the origin of the ScrollBar for the XmNtoTopCallback , XmNtoBottomCallback, and XmNdragCallback routines. The origin is the top of a vertical ScrollBar or the left side of a horizontal ScrollBar, regardless of the value of XmNprocessingDirection.

the source code demonstrates how a callback routine can be hooked up to each of the callback resources to allow you to monitor the scrolling in a List widget more precisely. For Text and List widgets, you really should not be using the callback routines to change the default scrolling behavior. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4.

   /* monitor_sb.c -- demonstrate the ScrollBar callback routines by
    * monitoring the ScrollBar for a ScrolledList.  Functionally, this
    * program does nothing.  However, by tinkering with the Scrolled
    * List and watching the output from the ScrollBar's callback routine,
    * you'll see some interesting behavioral patterns.  By interacting
    * with the *List* widget to cause scrolling, the ScrollBar's callback
    * routine is never called.  Thus, monitoring the scrolling actions
    * of a ScrollBar should not be used to keep tabs on exactly when
    * the ScrollBar's value changes!
   #include <Xm/List.h>

   /* print the interesting resource values of a scrollbar */
   scroll_action(scrollbar, client_data, call_data)
   Widget scrollbar;
   XtPointer client_data;
   XtPointer call_data;
       XmScrollBarCallbackStruct *cbs =
           (XmScrollBarCallbackStruct *) call_data;

       printf ("cbs->reason: %s, cbs->value = %d, cbs->pixel = %d0,
           cbs->reason == XmCR_DRAG? "drag" :
           cbs->reason == XmCR_VALUE_CHANGED? "value changed" :
           cbs->reason == XmCR_INCREMENT? "increment" :
           cbs->reason == XmCR_DECREMENT? "decrement" :
           cbs->reason == XmCR_PAGE_INCREMENT? "page increment" :
           cbs->reason == XmCR_PAGE_DECREMENT? "page decrement" :
           cbs->reason == XmCR_TO_TOP? "top" :
           cbs->reason == XmCR_TO_BOTTOM? "bottom" : "unknown",
           cbs->value, cbs->pixel);

   main(argc, argv)
   int argc;
   char *argv[];
       Widget        toplevel, list_w, sb;
       XtAppContext  app;
       char *items = "choice0, choice1, choice2, choice3, choice4,                    choice5, choice6, choice7, choice8, choice9,                    choice10, choice11, choice12, choice13, choice14";

       XtSetLanguageProc (NULL, NULL, NULL);

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

       list_w = XmCreateScrolledList (toplevel, "list_w", NULL, 0);
       XtVaSetValues (list_w,
           /* Rather than convert the entire list of items into an array
            * of compound strings, let's just let Motif's type converter
            * do it for us and save lots of effort (altho not much time).
           XtVaTypedArg, XmNitems, XmRString, items, strlen (items)+1,
           XmNitemCount, 15,
           XmNvisibleItemCount, 5,
       XtManageChild (list_w);

       /* get the scrollbar from ScrolledWindow associated with Text widget */
       XtVaGetValues (XtParent (list_w), XmNverticalScrollBar, &sb, NULL);
       XtAddCallback (sb, XmNvalueChangedCallback, scroll_action, NULL);
       XtAddCallback (sb, XmNdragCallback, scroll_action, NULL);
       XtAddCallback (sb, XmNincrementCallback, scroll_action, NULL);
       XtAddCallback (sb, XmNdecrementCallback, scroll_action, NULL);
       XtAddCallback (sb, XmNpageIncrementCallback, scroll_action, NULL);
       XtAddCallback (sb, XmNpageDecrementCallback, scroll_action, NULL);
       XtAddCallback (sb, XmNtoTopCallback, scroll_action, NULL);
       XtAddCallback (sb, XmNtoBottomCallback, scroll_action, NULL);

       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
The program displays a simple ScrolledList that contains 15 entries, as shown in the figure.

Output of monitor_sb.c

The entries in the List are not important; the way that the ScrollBar reacts to the user's interaction is what is interesting. The following output shows what happens when the user scrolls the List:

   cbs->reason: increment, cbs->value = 1, cbs->pixel = 0
   cbs->reason: page increment, cbs->value = 5, cbs->pixel = 0
   cbs->reason: drag, cbs->value = 6, cbs->pixel = 46
   cbs->reason: drag, cbs->value = 7, cbs->pixel = 50
   cbs->reason: value changed, cbs->value = 7, cbs->pixel = 50
   cbs->reason: decrement, cbs->value = 6, cbs->pixel = 0
   cbs->reason: top, cbs->value = 0, cbs->pixel = 11

If you use the keyboard to select elements or scroll around in the list, you'll notice that the callbacks for the ScrollBar are not invoked because the List widget is taking all of the keyboard events from the ScrollBar. Like any other widget, the ScrollBar can receive keyboard events, and it even has translations to map certain key sequences to scrolling actions. However, the List widget sets XmNtraversalOn to False for the ScrollBar, so that the List can process its own keyboard actions, some of which scroll the window. The Text widget does the same thing with its ScrollBars. As a result, there is a limit to what you can accomplish by monitoring ScrollBar actions on semi-automatic scrolling objects like List and Text widgets.

10.4 Implementing True Application-defined Scrolling

In this section, we pull together what we've learned in this chapter and put it to work to implement application-defined scrolling. We are going to use an example that displays a large number of individual bitmaps in a ScrolledWindow, so that the user can view all of the bitmaps by scrolling the window. The intent is to make the appearance and functionality of the ScrolledWindow mimic the automatic scrolling mode as much as possible.

There are actually several ways to go about writing this program, depending on the constraints that we impose. The simplest method is to render each bitmap into one large pixmap and use that pixmap as the XmNlabelPixmap for a Label widget. The Label widget can then be used as the work window for an automatic ScrolledWindow. This design is similar to most of the other examples of ScrolledWindows used throughout the book. However, we want to add a constraint such that each incremental scrolling action causes the display to shift by one bitmap cell, so that the top and left sides of the viewport always show a full bitmap. In other words, no partially-displayed bitmaps are allowed. Furthermore, when the user drags the slider, we want the display to scroll in cell-increments, not pixel-by-pixel.

The constraints that we just described define the behavior that the List and Text widgets use for their own displays. Like those widgets, our example program has a conceptual unit size that is represented by the object being scrolled. For the Text and List widgets, the unit size is the height and width of the font used by the entries. For our bitmap viewer, the heights and widths of the bitmaps vary more dramatically than the characters in a font, so for consistency, the unit size is set to the largest of all of the bitmaps. The design of our program is based on the same principles used by the ScrolledWindow's automatic scrolling method. Only in this case, we are going to do the work ourselves. The reason that we need to use application-defined scrolling is that the automatic scrolling method cannot support the scrolling constraints described above; there is no way to change the number of pixels per scrolling unit with an automatic ScrolledWindow.

In our implementation, the work window is a DrawingArea widget whose size is constrained by the size of the viewport in the ScrolledWindow. Initially, the ScrolledWindow sizes itself to the size of the DrawingArea widget, but once the program is running, the size of the DrawingArea is changed by the ScrolledWindow as it is resized. The bitmaps are rendered into a large pixmap, which is rendered into the DrawingArea in connection with scrolling actions. The offset of the pixmap and how much of it is copied into the DrawingArea is controlled by the application, following the same algorithm that the ScrolledWindow uses in automatic scrolling mode. The only difference is that we can adjust for the pixels-per-unit value, whereas the automatic ScrolledWindow is only aware of single-pixel units.

Proper scrolling is not a particularly difficult problem to solve, as it only involves simple arithmetic. The real problem is handling the case where the user or the application causes the ScrolledWindow to resize, since this action changes all of the variables in the calculation. When resizing happens, the ScrolledWindow passes that resizing onto the DrawingArea widget, which must recalculate its size and update the ScrollBar resources so that the display and the graphic representation match. Basically, the program has to solve four independent problems:

Although each of these problems has a simple solution, when combined the general solution becomes quite complex. Rather than trying to solve each problem individually, a well-designed application integrates the solutions to the problems into a single, elegant design. the source code demonstrates our implementation of the bitmap viewer. Although the program is quite long, you can follow along with the comments embedded in the code to understand what is going on. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4.
   /* app_scroll.c - Displays bitmaps specified on the command line.  All
    * bitmaps are drawn into a pixmap, which is rendered into a DrawingArea
    * widget, which is used as the work window for a ScrolledWindow.  This
    * method is only used to demonstrate application-defined scrolling for
    * the motif ScrolledWindow.  Automatic scrolling is much simpler, but
    * does not allow the programmer to impose incremental scrolling units.
    * The bitmaps are displayed in an equal number of rows and columns if
    * possible.
    * Example:
    *  app_scroll /usr/include/X11/bitmaps/*

   #include <stdio.h>
   #include <strings.h>
   #include <Xm/ScrolledW.h>
   #include <Xm/DrawingA.h>
   #include <Xm/ScrollBar.h>

   #ifdef max  /* just in case--we don't know, but these are commonly set */
   #undef max  /* by arbitrary unix systems.  Also, we cast to int! */
   /* redefine "max" and "min" macros to take into account "unsigned" values */
   #define max(a,b) ((int)(a)>(int)(b)?(int)(a):(int)(b))
   #define min(a,b) ((int)(a)<(int)(b)?(int)(a):(int)(b))

   /* don't accept bitmaps larger than 100x100 .. This value is arbitrarily
    * chosen, but is sufficiently large for most images.  Handling extremely
    * large bitmaps would eat too much memory and make the interface awkward.
   #define MAX_WIDTH  100
   #define MAX_HEIGHT 100

   typedef struct {
       char *name;
       int len; /* strlen(name) */
       unsigned int width, height;
       Pixmap bitmap;
   } Bitmap;

   /* get the integer square root of n -- used to calculate an equal
    * number of rows and colums for a given number of elements.
   register int n;
       register int i, s = 0, t;
       for (i = 15; i >= 0; i--) {
           t = (s | (1L << i));
           if (t * t <= n)
               s = t;
       return s;

   /* Global variables */
   Widget drawing_a, vsb, hsb;
   Pixmap pixmap; /* used as the image for DrawingArea widget */
   Display *dpy;
   Dimension view_width = 300, view_height = 300;
   int rows, cols;
   unsigned int cell_width, cell_height;
   unsigned int pix_hoffset, pix_voffset, sw_hoffset, sw_voffset;
   void redraw();

   main(argc, argv)
   int argc;
   char *argv[];
       extern char *strcpy();
       XtAppContext app;
       Widget toplevel, scrolled_w;
       Bitmap *list = (Bitmap *) NULL;
       GC gc;
       char *p;
       XFontStruct *font;
       int i = 0, total = 0;
       unsigned int bitmap_error;
       int j, k;
       void scrolled(), expose_resize();

       XtSetLanguageProc (NULL, NULL, NULL);

       toplevel = XtAppInitialize (&app, argv[0], NULL, 0,
           &argc, argv, NULL, NULL, 0);
       dpy = XtDisplay (toplevel);

       font = XLoadQueryFont (dpy, "fixed");

       /* load bitmaps from filenames specified on command line */
       while (*++argv) {
           printf ("Loading
           if (i == total) {
               total += 10; /* allocate bitmap structures in groups of 10 */
               if (!(list = (Bitmap *) XtRealloc (list, total * sizeof (Bitmap))))
                   XtError ("Not enough memory for bitmap data");
           /* read bitmap file using standard X routine.  Save the resulting
            * image if the file isn't too big.
           if ((bitmap_error = XReadBitmapFile (dpy, DefaultRootWindow (dpy),
                   *argv, &list[i].width, &list[i].height, &list[i].bitmap,
                   &j, &k)) == BitmapSuccess) {
               /* Get just the base filename (minus leading pathname)
                * We save this value for later use when we caption the bitmap.
               if (p = rindex (*argv, '/'))
                   p = *argv;
               if (list[i].width > MAX_WIDTH || list[i].height > MAX_HEIGHT) {
                   printf ("%s: bitmap too big0, p);
                   XFreePixmap (dpy, list[i].bitmap);
               list[i].len = strlen (p);
               list[i].name = p;  /* we'll be getting it later */
               printf ("Size: %dx%d0, list[i].width, list[i].height);
           } else {
               printf ("Couldn't load bitmap:
               switch (bitmap_error) {
                   case BitmapOpenFailed : puts ("Open failed."); break;
                   case BitmapFileInvalid : puts ("Bad file format."); break;
                   case BitmapNoMemory : puts ("Not enough memory."); break;
       if ((total = i) == 0) {
           puts ("Couldn't load any bitmaps.");
           exit (1);
       printf ("Total bitmaps loaded: %d0, total);
       /* calculate size for pixmap by getting the dimensions of each. */
       printf ("Calculating sizes for pixmap..."), fflush (stdout);
       for (i = 0; i < total; i++) {
           if (list[i].width > cell_width)
               cell_width = list[i].width;
           if (list[i].height > cell_height)
               cell_height = list[i].height;
           /* the bitmap's size is one thing, but its caption may exceed it */
           if ((j = XTextWidth (font, list[i].name, list[i].len)) > cell_width)
               cell_width = j;
       /* compensate for font in the vertical dimension; add a 6 pixel padding */
       cell_height += 6 + font->ascent + font->descent;
       cell_width += 6;
       cols = int_sqrt (total);
       rows = (total + cols-1)/cols;

       printf ("Creating pixmap area of size %dx%d (%d rows, %d cols)0,
           cols * cell_width, rows * cell_height, rows, cols);

       /* Create a single, 1-bit deep pixmap */
       if (!(pixmap = XCreatePixmap (dpy, DefaultRootWindow (dpy),
               cols * cell_width + 1, rows * cell_height + 1, 1)))
           XtError ("Can't Create pixmap");

       if (!(gc = XCreateGC (dpy, pixmap, NULL, 0)))
           XtError ("Can't create gc");
       XSetForeground(dpy, gc, 0); /* 1-bit deep pixmaps use 0 as background */
       /* Clear the pixmap by setting the entire image to 0's */
       XFillRectangle (dpy, pixmap, gc, 0, 0,
           cols * cell_width, rows * cell_height);
       XSetForeground (dpy, gc, 1); /* Set the foreground to 1 (1-bit deep) */
       XSetFont (dpy, gc, font->fid); /* to print bitmap filenames (captions) */

       /* Draw the grid lines between bitmaps */
       for (j = 0; j <= rows * cell_height; j += cell_height)
           XDrawLine (dpy, pixmap, gc, 0, j, cols * cell_width, j);
       for (j = 0; j <= cols * cell_width; j += cell_width)
           XDrawLine (dpy, pixmap, gc, j, 0, j, rows*cell_height);

       /* Draw each of the bitmaps into the big picture */
       for (i = 0; i < total; i++) {
           int x = cell_width * (i % cols);
           int y = cell_height * (i / cols);
           XDrawString (dpy, pixmap, gc, x + 5, y + font->ascent,
               list[i].name, list[i].len);
           XCopyArea (dpy, list[i].bitmap, pixmap, gc,
               0, 0, list[i].width, list[i].height,
               x + 5, y + font->ascent + font->descent);
           /* Once we copy it into the big picture, we don't need the bitmap */
           XFreePixmap (dpy, list[i].bitmap);
       XtFree (list); /* don't need the array of structs anymore */
       XFreeGC (dpy, gc); /* nor do we need this GC */

       /* Create automatic Scrolled Window */
       scrolled_w = XtVaCreateManagedWidget ("scrolled_w",
           xmScrolledWindowWidgetClass, toplevel,
           XmNscrollingPolicy, XmAPPLICATION_DEFINED, /* default values */

XmNvisualPolicy, XmVARIABLE, /* specified for clarity */ NULL); /* Create a drawing area as a child of the ScrolledWindow. * The DA's size is initialized (arbitrarily) to view_width and * view_height. The ScrolledWindow will expand to this size. */ drawing_a = XtVaCreateManagedWidget ("drawing_a", xmDrawingAreaWidgetClass, scrolled_w, XmNwidth, view_width, XmNheight, view_height, NULL); XtAddCallback (drawing_a, XmNexposeCallback, expose_resize, NULL); XtAddCallback (drawing_a, XmNresizeCallback, expose_resize, NULL); /* Application-defined ScrolledWindows won't create their own * ScrollBars. So, we create them ourselves as children of the * ScrolledWindow widget. The vertical ScrollBar's maximum size is * the number of rows that exist (in unit values). The horizontal * ScrollBar's maximum width is represented by the number of columns. */ vsb = XtVaCreateManagedWidget ("vsb", xmScrollBarWidgetClass, scrolled_w, XmNorientation, XmVERTICAL, XmNmaximum, rows, XmNsliderSize, min (view_height / cell_height, rows), XmNpageIncrement, max ((view_height / cell_height) - 1, 1), NULL); if (view_height / cell_height > rows) sw_voffset = (view_height - rows * cell_height) / 2; hsb = XtVaCreateManagedWidget ("hsb", xmScrollBarWidgetClass, scrolled_w, XmNorientation, XmHORIZONTAL, XmNmaximum, cols, XmNsliderSize, min (view_width / cell_width, cols), XmNpageIncrement, max ((view_width / cell_width) - 1, 1), NULL); if (view_width / cell_width > cols) sw_hoffset = (view_width - cols * cell_width) / 2; /* Allow the ScrolledWindow to initialize itself accordingly...*/ XmScrolledWindowSetAreas (scrolled_w, hsb, vsb, drawing_a); /* use same callback for both ScrollBars and all callback reasons */ XtAddCallback (vsb, XmNvalueChangedCallback, scrolled, XmVERTICAL); XtAddCallback (hsb, XmNvalueChangedCallback, scrolled, XmHORIZONTAL); XtAddCallback (vsb, XmNdragCallback, scrolled, XmVERTICAL); XtAddCallback (hsb, XmNdragCallback, scrolled, XmHORIZONTAL); XtRealizeWidget (toplevel); XtAppMainLoop (app); } /* React to scrolling actions. Reset position of ScrollBars; call redraw() * to do actual scrolling. cbs->value is ScrollBar's new position. */ void scrolled(scrollbar, client_data, call_data) Widget scrollbar; XtPointer client_data; XtPointer call_data; { int orientation = (int) client_data; /* XmVERTICAL or XmHORIZONTAL */ XmScrollBarCallbackStruct *cbs = (XmScrollBarCallbackStruct *) call_data; if (orientation == XmVERTICAL) { pix_voffset = cbs->value * cell_height; if (((rows * cell_height) - pix_voffset) > view_height) XClearWindow (dpy, XtWindow (drawing_a)); } else { pix_hoffset = cbs->value * cell_width; if (((cols * cell_width) - pix_hoffset) > view_width) XClearWindow (dpy, XtWindow (drawing_a)); } redraw (XtWindow (drawing_a)); } /* This function handles both expose and resize (configure) events. * For XmCR_EXPOSE, just call redraw() and return. For resizing, * we must calculate the new size of the viewable area and possibly * reposition the pixmap's display and position offsets. Since we * are also responsible for the ScrollBars, adjust them accordingly. */ void expose_resize(drawing_a, client_data, call_data) Widget drawing_a; XtPointer client_data; XtPointer call_data; { Dimension new_width, new_height, oldw, oldh; Boolean do_clear = False; XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct *) call_data; if (cbs->reason == XmCR_EXPOSE) { redraw (cbs->window); return; } oldw = view_width; oldh = view_height; /* Unfortunately, the cbs->event field is NULL, so we have to have * get the size of the drawing area manually. A misdesign of * the DrawingArea widget--not a bug (technically). */ XtVaGetValues (drawing_a, XmNwidth, &view_width, XmNheight, &view_height, NULL); /* Get the size of the viewable area in "units lengths" where * each unit is the cell size for each dimension. This prevents * rounding error for the pix_voffset and pix_hoffset values later. */ new_width = view_width / cell_width; new_height = view_height / cell_height; /* When the user resizes the frame bigger, expose events are generated, * so that's not a problem, since the expose handler will repaint the * whole viewport. However, when the window resizes smaller, no * expose event is generated. The window does not need to be * redisplayed if the old viewport was smaller than the pixmap. * (The existing image is still valid--no redisplay is necessary.) * The window WILL need to be redisplayed if: * 1) new view size is larger than pixmap (pixmap needs to be centered). * 2) new view size is smaller than pixmap, but the OLD view size was * larger than pixmap. */ if ((int) new_height >= rows) { /* The height of the viewport is taller than the pixmap, so set * pix_voffset = 0, so the top origin of the pixmap is shown, * and the pixmap is centered vertically in viewport. */ pix_voffset = 0; sw_voffset = (view_height - rows * cell_height)/2; /* Case 1 above */ do_clear = True; /* scrollbar is maximum size */ new_height = rows; } else { /* Pixmap is larger than viewport, so viewport will be completely * redrawn on the redisplay. (So, we don't need to clear window.) * Make sure upper side has origin of a cell (bitmap). */ pix_voffset = min (pix_voffset, (rows-new_height) * cell_height); sw_voffset = 0; /* no centering is done */ /* Case 2 above */ if (oldh > rows * cell_height) do_clear = True; } XtVaSetValues (vsb, XmNsliderSize, max (new_height, 1), XmNvalue, pix_voffset / cell_height, XmNpageIncrement, max (new_height-1, 1), NULL); /* identical to vertical case above */ if ((int) new_width >= cols) { /* The width of the viewport is wider than the pixmap, so set * pix_hoffset = 0, so the left origin of the pixmap is shown, * and the pixmap is centered horizontally in viewport. */ pix_hoffset = 0; sw_hoffset = (view_width - cols * cell_width)/2; /* Case 1 above */ do_clear = True; /* scrollbar is maximum size */ new_width = cols; } else { /* Pixmap is larger than viewport, so viewport will be completely * redrawn on the redisplay. (So, we don't need to clear window.) * Make sure left side has origin of a cell (bitmap). */ pix_hoffset = min (pix_hoffset, (cols-new_width)*cell_width); sw_hoffset = 0; /* Case 2 above */ if (oldw > cols * cell_width) do_clear = True; } XtVaSetValues (hsb, XmNsliderSize, max (new_width, 1), XmNvalue, pix_hoffset / cell_width, XmNpageIncrement, max (new_width-1, 1), NULL); if (do_clear) { /* XClearWindow() doesn't generate an ExposeEvent */ XClearArea (dpy, cbs->window, 0, 0, 0, 0, True); /* all 0's means the whole window */ } } void redraw(window) Window window; { static GC gc; /* static variables are *ALWAYS* initialized to NULL */ if (!gc) { /* !gc means that this GC hasn't yet been created. */ /* We create our own gc because the other one is based on a 1-bit * bitmap and the drawing area window might be color (multiplane). * Remember, we're rendering a multiplane pixmap, not the original * single-plane bitmaps! */ gc = XCreateGC (dpy, window, NULL, 0); XSetForeground (dpy, gc, BlackPixelOfScreen (XtScreen (drawing_a))); XSetBackground (dpy, gc, WhitePixelOfScreen (XtScreen (drawing_a))); } if (DefaultDepthOfScreen (XtScreen (drawing_a)) > 1) XCopyPlane (dpy, pixmap, window, gc, pix_hoffset, pix_voffset, view_width, view_height, sw_hoffset, sw_voffset, 1L); else XCopyArea (dpy, pixmap, window, gc, pix_hoffset, pix_voffset, view_width, view_height, sw_hoffset, sw_voffset); } The bitmaps to be displayed are specified on the command line, as shown in the following command:

   % app_scroll /usr/include/X11/bitmaps/*
The output of this command is shown in the figure.

Output of app_scroll.c

The program begins by loading the bitmaps into an array of Bitmap structures that are specially designed for this application. Since each bitmap can have a different size, we save all of the information about them for comparison after they are all loaded. At that time, the largest bitmap is found and its size is used as the cell size for the viewer. The pixmap is created with a single-plane (a bitmap), since color is not used to render the standard X11 bitmaps when they are created. This pixmap is used as a virtual work window; its contents are rendered into the real DrawingArea work window.

After the bitmaps are loaded, the ScrolledWindow and DrawingArea are created. The DrawingArea has XmNexposeCallback and XmNresizeCallback callbacks installed so that the pixmap can be rendered or repositioned within the DrawingArea at any time. Resizing does not change the pixmap, but it may cause its origin to be repositioned relative to the DrawingArea widget. We create the ScrollBars explicitly, since they are not created automatically when XmNscrollingPolicy is set to XmAPPLICATION_DEFINED. The ScrollBars are created as children of the ScrolledWindow, as shown in the following fragment:

   vsb = XtVaCreateManagedWidget ("vsb",
       xmScrollBarWidgetClass, scrolled_w,
       XmNorientation, XmVERTICAL,
       XmNmaximum,     rows,
       XmNsliderSize,  min (view_height / cell_height, rows),
       XmNpageIncrement, max ((view_height / cell_height) - 1, 1),
   if (view_height / cell_height > rows)
       sw_voffset = (view_height - rows * cell_height) / 2;
The ScrollBars are initialized so that the XmNmaximum values are set to the number of rows and columns in the pixmap. Similarly, XmNsliderSize is set to the number of bitmap cells that can fit in the viewport in the horizontal and vertical dimensions. Internally, the application knows how many pixels each scrolling unit represents, since there is no ScrollBar resource for this value. The variables sw_hoffset and sw_voffset are used when the pixmap is smaller than the actual ScrolledWindow. In this case, the variables indicate the origin of the pixmap in the DrawingArea, so that the pixmap appears centered, as shown in the figure.

Output of app_scroll.c when the viewport is larger than the pixmap

The call to XmScrolledWindowSetAreas() initializes the ScrolledWindow appropriately. This function assigns the ScrollBars and the DrawingArea widget to internal variables within the ScrolledWindow, so that the widget functions properly. While this call is opaque for automatic scrolling, it must be done for application-defined scrolling.

The ScrollBars are assigned a callback routine for the XmNvalueChangedCallback and XmNdragCallback callbacks. The scrolled() routine handles all of the scrolling actions, including incremental and page scrolling, that cause the value of the ScrollBar to change. We pass the values XmHORIZONTAL and XmVERTICAL as client_data, so that the routine knows which of the two ScrollBars invoked it. The routine determines the portion of the pixmap that should be rendered in the DrawingArea by calculating offsets into the pixmap. These offsets are calculated by multiplying the value of the ScrollBar by the pixels-per-unit value for the pixmap.

Finally, the top-level widget is realized and the main loop is started. At this point, the DrawingArea is realized, so the XmNexposeCallback is activated, which causes the DrawingArea to draw itself and display the first image of the pixmap. The function expose_resize() handles both the Expose and ConfigureNotify (resize) events. The function determines which event was delivered by checking the reason field of the callback structure passed to the function. When the DrawingArea is resized, we need to adjust a number of resources so that the pixmap is scrolled properly. For Expose events, no recalculation of variables is necessary, so all we need to do is redraw the display using redraw().

The position at which the pixmap is rendered into the DrawingArea's window is somewhat complicated to calculate. If the pixmap is larger than the clip window, the clip window acts as a view into the pixmap, so only a portion of the pixmap can be seen. If the pixmap is smaller than the clip window, the entire pixmap can be seen, so the pixmap should be centered in the middle of the viewable area. The application controls this behavior using a number of global variables.

The view_width and view_height variables represent the dimensions of the ScrolledWindow, which are also the dimensions of the DrawingArea window. The area specified by these values is the area of the pixmap that is going to be copied into the window. The pix_hoffset and pix_voffset variables represent the horizontal and vertical offsets into the pixmap when it is rendered into the DrawingArea. If the pixmap is larger than the clip window, these values are calculated in the scrolled() callback routine when the user performs a scrolling action. If the pixmap is smaller than the DrawingArea, these values are set to 0 because the origin of the pixmap is always visible. The sw_hoffset and sw_voffset variables are used when the pixmap is smaller than the DrawingArea. The values indicate the offsets into the DrawingArea where the entire pixmap is rendered so that it appears centered in the viewport.

The redraw() routine depends on these variables being set. In order to maintain the values, the application monitors the size of the DrawingArea. When a ConfigureNotify event occurs on the DrawingArea, the expose_resize() callback routine is invoked. The routine gets the new dimensions of the DrawingArea so that it can update the six variables mentioned above. Normally, we can get the new dimensions directly from the event field of the callback structure. However, the DrawingArea widget invokes the XmNresizeCallback from within the Resize() method, instead of from an action routine, so the callback does not have an XEvent structure associated with it. All widget internals have methods that are invoked automatically by the X Toolkit Intrinsics and are not associated with the translation tables normally used to handle events. Resize() is one such method. See Volume Four, X Toolkit Intrinsics Programming Manual, for more information. Since the event field of the callback structure is set to NULL, we have to get the window's size in another way. We use XtVaGetValues(), as shown in the following code fragment:

   XtVaGetValues (drawing_a,
       XmNwidth,  &view_width,
       XmNheight, &view_height,
Once we have the dimensions, we need to recalculate the value of the other four variables. Since our variables represent pixel values, while the ScrollBar resources that we need to set use an abstract unit size, we must convert between the two types of values using the cell_width and cell_height values. The variables new_width and new_height represent the new viewport width and height in ScrollBar units.

If the new viewport height exceeds the number of rows in the pixmap, we know that the height of the viewport exceeds the height of the pixmap. In this case, the value for sw_voffset is calculated to determine the offset that causes the pixmap to be centered vertically in the viewport. Since the viewport needs to be completely redisplayed, we set the local variable do_clear to True. We use this variable instead of calling XClearWindow() directly because we may have to do it again later when we calculate the values for the horizontal ScrollBar. The value for new_height is going to be used to set the XmN­sliderSize for the vertical ScrollBar, so we make sure that it does not exceed its XmN­maximum value.

On the other hand, if the new viewport height does not exceed the total number of rows, we know that the pixmap is larger than the viewport vertically. The pixmap is not going to be centered in the DrawingArea, so sw_voffset is set to 0. pix_voffset is set to the minimum of its existing value and the difference between the total number of rows and the new height of the viewport. If the viewport used to be bigger than the pixmap, but is now smaller, we need to clear the window and do a complete redisplay. If the pixmap was bigger than the viewport and it still is, then we do not need to clear the window because the current view is still accurate. The different between these two cases is subtle and it is the sort of thing that you catch only when you test your program thoroughly.

After the calculations are performed, the application sets the XmNsliderSize, XmN­value, and XmNpageIncrement resources for the vertical ScrollBar. The exact same calculations are done for the horizontal dimension and the same resources are set on the horizontal ScrollBar. With these resources set, scrolling continues to function properly when the DrawingArea is resized. When redraw() is called, it uses the global variables to copy the relevant portion of the full pixmap directly into the DrawingArea. If the program is running on a color screen, the routine uses XCopyPlane() because the DrawingArea cannot create a 1-bit deep window on a color screen. (Motif widgets always create windows of the same depth as the screen on which they reside.) If the application is run on a monochrome screen, the routine uses XCopyArea(). We determine the depth of the screen using DefaultDepthOfScreen().

Incidentally, while we did not use it, XmScrollBarSetValues() could have been used to set the resources on the ScrollBars. This function takes the following form:

   XmScrollBarSetValues(widget, value, slider_size, increment,
                                     page_increment, notify)
       Widget widget;
       int value;
       int slider_size;
       int increment;
       int page_increment;
       Boolean notify;
The notify parameter specifies whether you want the XmN­valueChangedCallback for the ScrollBar to be invoked. Using this interface is probably slightly faster than using the XtVaSetValues() method, but only by a small margin, so we chose to maintain consistency with our own style. The companion function for XmScrollBarSetValues() is XmScrollBarGetValues(). This function retrieves the values from the ScrollBar widget and takes the following form:
   XmScrollBarGetValues(widget, value, slider_size, increment,
       Widget widget;
       int *value;
       int *slider_size;
       int *increment;
       int *page_increment;
Before closing this section, let's examine what the Text and List widgets do and compare it with what we have done in the source code We stated earlier that while we mimic much of what these widgets do internally, the implementation is quite different. The major difference is that we are fortunate enough to have all of the bitmaps loaded into a large, statically-sized pixmap that we can render at will using the redraw() function. This function is clearly a convenience, since it simply calls XCopyArea() or XCopyPlane() to copy the pixmap into the DrawingArea using pre-calculated internal variables. The Text and List widgets do not have this luxury; they must redraw their respective data directly into the work windows each time they need to redisplay.

If we were to implement the bitmap viewer using this technique, we would have to move the functionality of the main for loop in main() into redraw() and calculate the location of each individual bitmap in the DrawingArea. This process is quite painstaking and very error-prone. If you do not take into account multiple exposures, exposure regions, and other low-level Xlib functionality, you might run into X performance issues. We didn't even take these issues into account in our program. For example, our redraw() routine completely repaints the entire window for every Expose event. Strictly speaking, repainting is inefficient and may not perform adequately for all applications, especially graphic-intensive ones. To avoid this problem, you could come up with a generic set of routines to handle exposures, so that of all your applications could use the same methodology, but that's the point of a toolkit.

Let's take another look at the PG&E scenario that we discussed at the beginning of the chapter. As you recall, the problem with that particular situation was that the database could retrieve 20% of the county (the work window), but the graphic resolution was such that only 10% of it could be displayed at one time (the viewport). The fundamental problem with the application-defined scrolling mode is that the work window cannot be a different size from the viewport. However, we can work around this problem by complying with the restriction that the work window and viewport are the same size, but we can use the enlarged pixmap idea from the source code to accomplish the task. Each database query can be converted and rendered into a sufficiently large pixmap, which can then be rendered into the work window as necessary. If the scrolling is small enough, another part of the pixmap can be rendered into the work window, instead of performing a completely new database lookup.

10.5 Working With Keyboard Traversal in ScrolledWindows

As we described in Chapter 8, Manager Widgets , manager widgets play a significant role in handling keyboard traversal mechanisms. As a manager, the ScrolledWindow supports keyboard traversal. However, one significant difference is that the widgets in a ScrolledWindow may not be visible at all times. In Motif 1.1, the toolkit does not provide a mechanism to allow keyboard traversal to widgets that are not visible. While it might be possible to implement this feature, it certainly would not be an easy task. By default, the ScrolledWindow only allows the user to traverse to widgets that are visible.

In Motif 1.2, there is a new callback for the ScrolledWindow that supports keyboard traversal in a ScrolledWindow. The XmNtraverseObscuredCallback is invoked when the user attempts to traverse to a widget that is not visible in a ScrolledWindow. If there is no routine specified for this callback, the keyboard traversal functionality is the same as in Motif 1.1. An application can use the callback to cause the ScrolledWindow to make a widget visible, so that it can receive the input focus. the source code shows the use of the XmNtraverseObscuredCallback. This example uses functionality that is new in Motif 1.2, so it only works with the 1.2 version of the Motif toolkit.

   /* traversal.c -- demonstrate keyboard traversal in a ScrolledWindow
    * using the XmNtraverseObscuredCallback.
   #include <Xm/PushB.h>
   #include <Xm/ToggleB.h>
   #include <Xm/ScrolledW.h>
   #include <Xm/RowColumn.h>

   main(argc, argv)
   int argc;
   char *argv[];
       Widget toplevel, sw, rc;
       XtAppContext app;
       void traverse();
       int i;
       char name[10];

       XtSetLanguageProc (NULL, NULL, NULL);

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

       sw = XtVaCreateManagedWidget ("scrolled_w",
           xmScrolledWindowWidgetClass, toplevel,
           XmNscrollingPolicy, XmAUTOMATIC,

       XtAddCallback (sw, XmNtraverseObscuredCallback, traverse, NULL);

       /* RowColumn is the work window for the widget */
       rc = XtVaCreateWidget ("rc",
           xmRowColumnWidgetClass, sw,
     XmNorientation, XmHORIZONTAL,
           XmNpacking, XmPACK_COLUMN,
     XmNnumColumns, 10,

       for ( i = 0; i < 10; i++ ) {
           sprintf (name, "Toggle %d", i);
           XtVaCreateManagedWidget (name, xmToggleButtonWidgetClass, rc, NULL);
           sprintf (name, "Button %d", i);
           XtVaCreateManagedWidget (name, xmPushButtonWidgetClass, rc, NULL);
       XtManageChild (rc);

       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);

   traverse(widget, client_data, call_data)
   Widget widget;
   XtPointer client_data;
   XtPointer call_data;
       XmTraverseObscuredCallbackStruct *cbs =
           (XmTraverseObscuredCallbackStruct *) call_data;

       XmScrollVisible (widget, cbs->traversal_destination, 10, 10);

This program creates a bunch of ToggleButtons and PushButtons in a RowColumn widget that is the work area for a ScrolledWindow. The traverse() routine is installed as the XmNtraverseObscuredCallback. The call_data parameter is an XmTraverseObscuredCallbackStruct, which is defined as follows:
   typedef struct {
       int                   reason;
       XEvent               *event;
       Widget                traversal_destination;
       XmTraversalDirection  direction;
   } XmTraverseObscuredCallbackStruct;
The reason field contains the value XmCR_OBSCURED_TRAVERSAL. The traversal_destination field specifies the widget that is to receive the input focus and direction specifies the direction of traversal. The traverse() routine calls XmScrollVisible() to make the traversal_destination widget visible. This routine takes the following form:
   XmScrollVisible(scrollw, widget, hor_margin, ver_margin)
       Widget scrollw;
       Widget widget;
       Dimension hor_margin;
       Dimension ver_margin;
The scrollw parameter specifies the ScrolledWindow widget, while the widget parameter specifies the widget that is to be made visible. The hor_margin and ver_margin arguments indicate the margins that are used if the viewport of the ScrolledWindow needs to be adjusted to make the widget visible. In early versions of the Motif 1.2 toolkit, there is a bug in XmScrollVisible() that may cause it to function incorrectly when the margins are set to any value other than 0. If you run the program in the source code you can use the arrow keys to traverse all of the widgets in the ScrolledWindow.

10.6 Summary

The ScrolledWindow provides a convenient interface for displaying large amounts of data when you have limited screen real estate. For most situations, the automatic scrolling mode is all that you really need. In this mode, a ScrolledWindow requires very little care and feeding. By installing callback routines on the ScrollBars, you can even monitor the scrolling actions. However, there are some drawbacks to the automatic scrolling mode: all of the data must be rendered into the work window widget and scrolling occurs in single-pixel increments. If the size of the work window that you need is prohibitively large or if you need to support scrolling in other than single-pixel increments, you must use application-defined scrolling.

As demonstrated in the source code there is quite a bit of work involved in supporting real application-defined scrolling because of the different states in the relationship between the size of the work window and the underlying data. You must be able to support not only the underlying data, but also the way it is rendered into the work window, the ScrollBars, and all of the auxiliary variables required for the scrolling calculations. And that work is just to support the scrolling functionality. When you introduce the complexity of a real application, there is a greater chance of a poor design model. The xshowbitmap.c program in the Appendix, Additional Example Programs, is fundamentally the same program as app_scroll.c, but it has been enhanced into more of a real-world program.

10.7 Exercises

The following exercises focus on the concepts and methods described in this chapter.

Contents Previous Next