[ Previous | Next | Contents | Glossary | Home | Search ]
Motif 2.1 Programmer's Guide



Application-Defined Scrolling

In application-defined scrolling, the application is responsible for all aspects of the interactions among the scroll, the viewport, and the ScrollBars. The ScrolledWindow remains responsible for geometry and layout, but the application must adjust both the ScrollBars and the scroll position in response to the user's scrolling actions.

Because this model requires more work on the part of the application, it is most suitable for programs in which automatic scrolling is not adequate. For example, an application may contain a text editor or browser that reads only enough of a file to fill the viewport. This application must be informed of the user's scrolling actions so that it can read more of the file when necessary.

The application implements a scheme of its choosing for the relationship between the scroll and the viewport. Following are two common models:

  1. A fixed-size viewport widget as the parent of a variable-sized scroll widget that contains the data. The application resizes the scroll widget as necessary to contain all the data. As the user interacts with the ScrollBar, the application moves the scroll widget with respect to the viewport, which clips the scroll. This is the model that ScrolledWindow uses for automatic scrolling.

  2. A single widget that serves as the viewport, with the scroll contained in internal data structures or a combination of data structures and files. The application expands the internal structures as necessary to contain all the data. As the user interacts with the ScrollBar, the application retrieves the appropriate portion of the data from the internal structures or files and displays that portion of the data in the viewport. This is the model that the Motif ScrolledList and ScrolledText widgets use.

    In both models, the application must be notified when the viewport is resized. It may need to adjust the scroll with respect to the viewport, and it must recompute ScrollBar resources to reflect the new relation between the viewport and the scroll. If the viewport is a DrawingArea, the application can use the XmNresizeCallback callbacks for this purpose. Otherwise, the application can establish an event handler for ConfigureNotify events.

    The application needs to take the following steps to use application-defined scrolling:

    1. Create and manage a ScrolledWindow, horizontal and vertical ScrollBar children, and a child to serve as the viewport.

    2. If the application is using a separate widget as the scroll, create and manage that widget as a child of the viewport widget.

    3. Add callbacks to the ScrollBars to notify the application when the user interacts with the ScrollBars. The application should at least provide a procedure for the XmNvalueChangedCallback list.

    4. Add a callback (such as the DrawingArea XmNresizeCallback) or an event handler to the viewport widget to notify the application when the widget is resized.

    5. Based on the initial relationship between the viewport and the scroll, supply initial values for the ScrollBars' XmNincrement, XmNpageIncrement, XmNmaximum, XmNminimum, XmNvalue, and XmNsliderSize resources.

    6. Adjust the size of the scroll widget or internal data structures as necessary to contain the data in the scroll.

    7. As the data in the scroll changes, recompute the ScrollBars' XmNmaximum and XmNsliderSize and perhaps XmNminimum and XmNvalue to reflect the new relation between the viewport and the scroll.

    8. When the viewport is resized, reposition and resize the scroll with respect to the viewport if necessary. Recompute the ScrollBars' XmNsliderSize and XmNpageIncrement and possibly other resources to reflect the new relationship between the viewport and the scroll.

    9. As the user interacts with the ScrollBars, if a separate scroll widget exists, reposition the scroll with respect to the viewport. If no separate scroll widget exists, bring in additional data from files if necessary, recompute which portion of the data to make visible, and redisplay the viewport. If the size of the scroll has changed, recompute the ScrollBar resources to reflect the new relationship between the viewport and the scroll.

    10. Example of Application-Defined Scrolling

      This section contains the scrolling-related portions of an example program that uses a ScrolledWindow with an application-defined scrolling policy. As in the example of automatic scrolling, the ScrolledWindow is a MainWindow, and the scroll widget is a DrawingArea. In this example, the scroll widget also serves as the viewport widget, and the scroll data is maintained in internal data structures.

      The application is a simple file browser for C source code. The user selects a filename. The program reads the file and parses it (in the C locale) into an internal table of lines. The application displays in the DrawingArea as many lines as will fit into the current dimensions of the DrawingArea.

      The application uses only a vertical ScrollBar, which allows the user to browse through the file. After reading the file, the program sets the ScrollBar's XmNminimum and XmNvalue to 0, its XmNmaximum to the number of lines in the file, and its XmNsliderSize to the lesser of the number of lines in the file and the number of lines that can be displayed in the viewport.

      The program establishes a ScrollBar XmNvalueChangedCallback and a DrawingArea XmNexposeCallback that redisplay the lines in the viewport. The redisplay procedure fetches and displays lines from the internal data structure, starting with the line indicated by the ScrollBar's XmNvalue and proceeding to the last line that fits in the viewport. The program also establishes a DrawingArea XmNresizeCallback that recomputes the ScrollBar's XmNsliderSize and XmNvalue based on the number of lines that can be displayed in the viewport. The application does not resize the DrawingArea itself.

      This section contains only the portions of the application that relate directly to creating and maintaining the ScrolledWindow. These include:

      1. Creating the MainWindow with an application-defined scrolling policy

      2. Creating the DrawingArea and vertical ScrollBar children of the ScrolledWindow

      3. Establishing an XmNactivateCallback callback for the OK button of the FileSelectionBox invoked from the file menu Open button

      4. Establishing a ScrollBar XmNvalueChangedCallback callback

      5. Establishing a DrawingArea XmNexposeCallback callback and an XmNresizeCallback callback
        /*------------------------------------------------------------
        **      Internal data structure to hold file info.
        */
        typedef struct {
            Widget work_area;
            Widget v_scrb;
            String file_name;
            XFontStruct * font_struct;
            GC draw_gc;
            char ** lines;
            int num_lines;
        } FileData;
         
        /*------------------------------------------------------------
        ** Create a MainWindow with a MenuBar to load a file.
        ** Add the vertical scrollbar and the workarea to filedata.
        */
        void CreateApplication (
        Widget          parent,
        FileData *      filedata)
        {
            Widget main_window, menu_bar, menu_pane, cascade,
                   button;
            Arg args[5];
            int n;
         
            /*  Create app_defined MainWindow.
             *  XmAPPLICATION_DEFINED is the default; however we set it here
             *  to override any values specified by a user in a resource file
             */
            n = 0;
            XtSetArg (args[n], XmNscrollingPolicy,
                      XmAPPLICATION_DEFINED);  n++;
            main_window = XmCreateMainWindow (parent,
                                        "main_window", args, n);
            XtManageChild (main_window);
         
            /*  Create MenuBar in MainWindow.
             */
         
         
            /* Create "File" PulldownMenu with Open and Quit buttons
             */
         
            n = 0;
            menu_pane = XmCreatePulldownMenu (menu_bar,
                                              "menu_pane", args, n);
         
            n = 0;
            button = XmCreatePushButton (menu_pane, "Open...",
                                         args, n);
            XtManageChild (button);
         
            /* pass the file data to the Open callback */
            XtAddCallback (button, XmNactivateCallback,
                           OpenCB, (XtPointer)filedata);
            n = 0;
            button = XmCreatePushButton (menu_pane, "Quit", args, n);
            XtManageChild (button);
            XtAddCallback (button, XmNactivateCallback, QuitCB, NULL);
         
            n = 0;
            XtSetArg (args[n], XmNsubMenuId, menu_pane);  n++;
            cascade = XmCreateCascadeButton (menu_bar, "File",
                                             args, n);
            XtManageChild (cascade);
         
            /*  Create "Help" PulldownMenu with Help button.
             */
         
         
            /*  Create vertical scrollbar only
             */
             n = 0;
            XtSetArg (args[n], XmNorientation, XmVERTICAL);  n++;
            filedata->v_scrb = XmCreateScrollBar (main_window,
                                                  "v_scrb", args, n);
            XtAddCallback (filedata->v_scrb, XmNvalueChangedCallback,
                           ValueCB, (XtPointer)filedata);
            XtManageChild (filedata->v_scrb);
         
            /*  Create work_area in MainWindow
             */
            n = 0;
            filedata->work_area = XmCreateDrawingArea(main_window,
                                              "work_area", args, n);
            XtAddCallback (filedata->work_area, XmNexposeCallback,
                           DrawCB, (XtPointer)filedata);
            XtAddCallback (filedata->work_area, XmNresizeCallback,
                           DrawCB, (XtPointer)filedata);
            XtManageChild (filedata->work_area);
         
            /*  Set MainWindow areas
             */
            XmMainWindowSetAreas (main_window, menu_bar, NULL, NULL,
                                  filedata->v_scrb,
                                  filedata->work_area);
         
        }
         
        /*-------------------------------------------------------------
        **      OpenCB                  - callback for Open button
        */
        void OpenCB (
        Widget          w,              /*  widget id           */
        XtPointer       client_data,    /*  data from application   */
        XtPointer       call_data)     /*  data from widget class  */
        {
                static Widget fsb_box = NULL;
         
                if (!fsb_box) {
                    fsb_box = XmCreateFileSelectionDialog (w,
                                               "Load file", NULL, 0);
                    /* just propagate the file information */
                    XtAddCallback (fsb_box, XmNokCallback, ReadCB,
                                                        client_data);
                }
         
                XtManageChild (fsb_box);
        }
         
        /*-------------------------------------------------------------
        **      ReadCB  - callback for fsb activate
        */
        void ReadCB (
        Widget          w,              /*  widget id           */
        XtPointer       client_data,    /*  data from application   */
        XtPointer       call_data)     /*  data from widget class  */
        {
            FileData * filedata = (FileData *) client_data;
            String file_name;
            Arg args[5];
            int n, slider_size;
            Dimension height;
         
            file_name = XmTextGetString(
                        XmFileSelectionBoxGetChild(w, XmDIALOG_TEXT));
         
            if (!BuildLineTable(filedata, file_name)) {
                WarnUser (w, "Cannot open %s\n", file_name);
            } else {
                filedata->file_name = file_name;
         
                /* ok, we have a new file, so reset some values */
                n = 0;
                XtSetArg (args[n], XmNheight, &height);  n++;
                XtGetValues (filedata->work_area, args, n);
         
                slider_size = (height - 4) /
                                     (filedata->font_struct->ascent
                                    + filedata->font_struct->descent);
                if (slider_size <= 0) slider_size = 1;
                if (slider_size > filedata->num_lines)
                    slider_size = filedata->num_lines;
         
                n = 0;
                XtSetArg (args[n], XmNsliderSize, slider_size);  n++;
                XtSetArg (args[n], XmNmaximum, filedata->num_lines);
                            n++;
                XtSetArg (args[n], XmNvalue, 0);  n++;
                XtSetValues (filedata->v_scrb, args, n);
         
                /* clear and redraw */
                XClearWindow(XtDisplay(filedata->work_area),
                             XtWindow(filedata->work_area));
                ReDraw (filedata);
            }
        }
         
        /*-------------------------------------------------------------
        **      ValueCB         - callback for scrollbar
        */
        void ValueCB (
        Widget          w,              /*  widget id           */
        XtPointer       client_data,    /*  data from application   */
        XtPointer       call_data)     /*  data from widget class  */
        {
            FileData * filedata = (FileData *) client_data;
         
            /* clear and redraw, dumb dumb.. */
            XClearWindow(XtDisplay(filedata->work_area),
                         XtWindow(filedata->work_area));
            ReDraw(filedata);
        }
         
        /*-------------------------------------------------------------
        **      DrawCB                  - callback for drawing area
        */
        void DrawCB (
        Widget          w,              /*  widget id           */
        XtPointer       client_data,    /*  data from application   */
        XtPointer       call_data)     /*  data from widget class  */
        {
         
            XmDrawingAreaCallbackStruct * dacs =
                (XmDrawingAreaCallbackStruct *) call_data;
            FileData * filedata = (FileData *) client_data;
            XSetWindowAttributes xswa;
         
            static Boolean first_time = True;
         
            switch (dacs->reason) {
            case XmCR_EXPOSE:
                if (first_time) {
                    /* Change once the bit gravity of the
                       Drawing Area; default is north west and we
                       want forget, so that resize always
                       generates exposure events */
                    first_time = False;
                    xswa.bit_gravity = ForgetGravity;
                    XChangeWindowAttributes(XtDisplay(w), XtWindow(w),
                                            CWBitGravity, &xswa);
                }
         
                ReDraw(filedata);
         
                break;
            case XmCR_RESIZE:
                ReSize(filedata);
         
                break;
            }
        }
         
        void ReDraw(
        FileData * filedata)
        {
            /* Display as many line as slider_size actually shows,
               since slider_size is computed relative to the
               work_area height */
         
            Cardinal i;
            int value, slider_size;
            Arg args[5];
            int n;
            Position y;
         
            if (filedata->num_lines == 0) return;
         
            n = 0;
            XtSetArg (args[n], XmNvalue, &value);  n++;
            XtSetArg (args[n], XmNsliderSize, &slider_size);  n++;
            XtGetValues (filedata->v_scrb, args, n);
         
            for (i = value, y = 2 + filedata->font_struct->ascent;
                 i < value + slider_size;
                 i++, y += (filedata->font_struct->ascent
                            + filedata->font_struct->descent)) {
                XDrawString(XtDisplay(filedata->work_area),
                            XtWindow(filedata->work_area),
                            filedata->draw_gc,
                            4, y,
                            filedata->lines[i],
                            strlen(filedata->lines[i]));
            }
        }
         
        void ReSize(
        FileData * filedata)
        {
            /* Just update the scrollbar internals here, don't
               bother to redisplay since the gravity is none */
         
            Arg args[5];
            int n;
            int value, slider_size;
            Dimension height;
         
            if (filedata->num_lines == 0) return;
         
            n = 0;
            XtSetArg (args[n], XmNheight, &height);  n++;
            XtGetValues (filedata->work_area, args, n);
         
            /* sliderSize is the number of visible lines */
            slider_size = (height - 4) /
                                  (filedata->font_struct->ascent
                                 + filedata->font_struct->descent);
            if (slider_size <= 0) slider_size = 1;
            if (slider_size > filedata->num_lines)
                slider_size = filedata->num_lines;
         
            n = 0;
            XtSetArg (args[n], XmNvalue, &value);  n++;
            XtGetValues (filedata->v_scrb, args, n);
         
            /* value shouldn't change that often but there are cases
               where it matters */
            if (value > filedata->num_lines - slider_size)
                value = filedata->num_lines - slider_size;
         
            n = 0;
            XtSetArg (args[n], XmNsliderSize, slider_size);  n++;
            XtSetArg (args[n], XmNvalue, value);  n++;
            XtSetArg (args[n], XmNmaximum, filedata->num_lines); n++;
            XtSetValues (filedata->v_scrb, args, n);
        }

      6. [ Previous | Next | Contents | Glossary | Home | Search ]