Motif Programming Manual (Volume 6A)

Previous Next Contents Document Index Motif Docs Imperial Software Technology X-Designer

X-Designer - The Leading X/Motif GUI Builder - Click to download a FREE evaluation

In this chapter:

Chapter: 17

The Notebook Widget

This chapter describes the Notebook widget, introduced in Motif 2.0.

The Notebook is a Constraint Manager which organises its children as though they are pages in a book. At any given time, only one child is visible. The Notebook has visuals so that it appears as though the children are stacked on top of each other like real pages; a spiral binding and overlapping back pages complete the appearance.

Continuing the analogy, the pages of a Notebook can be divided into sections and sub-sections by creating tabs which are inserted along the edges of the Notebook pages, just as real notebooks have tab inserts. Tabs can be associated with pages, so that activating a tab causes a specific child (page) to be displayed. Each section involves the creation of a Major Tab - this is simply a button with a particular constraint resource set, and each sub-section is added by creating a Minor Tab, which again is simply a button with an appropriate constraint. Minor Tabs appear on a different edge to the Major Tabs along the Notebook sides, which is possibly the one aspect of behavior which is not the same as a real notebook. The Notebook automatically creates a Tab Scroller, consisting of a pair of ArrowButtonGadgets, for scrolling along the set of Tabs when there are more Tabs than can be displayed along the Notebook edge.

The Notebook can also contain a Page Scroller, so that logical pages can be displayed by rotating through the values of a SpinBox, rather than having to manually select each tab individually.

Any given page can also be assigned a Status Area, which is a region of the Notebook display which can be used for information such as the current page number, date, and so forth.

Typically, pages are added to a Notebook simply by adding a Manager child to the widget: the various elements of the page are added to this Manager in turn. The Notebook has a default algorithm which takes a note of each child as it is added: it assumes that any added Manager is supposed to represent a page, a button will form a Major Tab, a textual widget will form a Status Area, and a SpinBox will form a Page Scroller. These are usually valid assumptions, and so it is not normally necessary to assign roles to each of the children as they are added to the Notebook by assigning the relevant constraint resource. For typical usage, only the role of Minor Tab need to be explicitly coded by the programmer. As for associating Tabs with pages, again the Notebook has a default algorithm which assigns ascending page numbers to objects in the order in which they are added. The first Page child is assigned the logical page number 1 by default. The programmer is free to explicitly assign logical pages to each of the various Tab and Page children: there can even be gaps in the numbering scheme, so that a Tab can refer to a number for which no corresponding Page child exists. In this case, the Notebook simply displays a blank background when the Tab is activated. It is also possible to assign logical page numbers to Tabs and Pages out of order to child widget creation: logical page numbers can be assigned in any order, although in this case the programmer should of course make sure that his own code does not implicitly rely on the order of child creation in any way. The Notebook will automatically sort the children into ascending page order.

This all seems to indicate that a Notebook needs to be fully loaded with all pages before displaying the widget to the user. This is not the case: it is possible to create pages dynamically by associating a callback with the Notebook which is called whenever a Tab is activated: the relevant page associated with the Tab can then be created on demand.

Figure 17-1 shows a Notebook with all the various elements indicated.

Figure  17-1 The Notebook widget and its constituent parts

The Notebook is highly configurable. Each of the various components of the Notebook are fully under programmer control, so that it is possible to convert the Notebook into a traditional Tab Manager, simply through setting appropriate resources to disable some of the more florid aspects of the illusion and to rotate the Tabs onto the top of the pages. The Spiral Binding can be configured for various styles of presentation, including a programmer-supplied pixmap; it can also be removed, as can the overlapping page illusion. The Page Scroller, Status Area, and Tabs are entirely optional, although it is difficult to imagine the usefulness of a Notebook which contained neither Page Scroller nor Tabs: selecting between pages would then become problematic as far as the user is concerned, although this arrangement would make some sense if page display is fully under program control.

Figure 17-2 is an example of this kind of traditional Tab Manager configuration., where the spiral binding and overlapping page illusions have been removed,the Tabs rotated to the top of the Notebook, and the Page Scroller disabled.

Figure  17-2 A Notebook configured to behave like a traditional Tab Manager

Creating a Notebook

Incorporating the Notebook widget into your code is straightforward. An application which uses the Notebook widget must include the header file <Xm/Notebook.h>. The header file declares the types of the public Notebook functions and the widget class name xmNotebookWidgetClass. A Notebook can be created in either of the ways as shown in the following code fragment:

Widget notebook = XmCreateNotebook ( parent, name, resource-value-array, 
Widget notebook = XtCreateWidget ( "name", xmNotebookWidgetClass, parent,
                                   resource-value-list, NULL);
The Notebook can potentially manage a very large number of children, and so it is probably best not to create the widget in a managed state (XtCreateManagedWidget()) otherwise performance may suffer. See Chapter 8, Manager Widgets, for a discussion of when widgets should be created in the managed or unmanaged state.

The parent of the Notebook can be any Shell or Manager widget. Once the Notebook has been created, the next step is to add logical Pages to the Notebook, and thereafter add Tab inserts when and if required. The Notebook is a constraint widget: as children are added, the role which the child is to take is specified using a constraint resource, XmNnotebookChildType. The possible values of the resource are as follows:

The Notebook has a default algorithm for assigning roles to children. Unless specified otherwise, any Manager child is assigned the XmNnotebookChildType constraint value XmPAGE, any PushButton is assigned the value XmMAJOR_TAB, any Textual component is given the value XmSTATUS_AREA, and any SpinBox or derivative is given the role XmPAGE_SCROLLER. If no page scroller is added to the Notebook, the widget creates one for its own use automatically.

Creating Notebook Pages

As stated above, any Manager child which is added to the Notebook is assumed to be a logical page. Logical page numbers are assigned by the Notebook in ascending order as required. Adding a Page to the Notebook is therefore simple: create a Form or other Manager widget as child of the Notebook, and then add the page contents by way of adding extra children to the Form. The following code in Example 17-1 creates a Notebook with four pages.

Example  17-1 The simple_notebook.c program

/* simple_notebook - create a Notebook with four pages

#include <Xm/Xm.h>
#include <Xm/Notebook.h>
#include <Xm/RowColumn.h>
#include <Xm/Label.h>

/* Arbitrary data for the first "page" */
char *page1_labels[] =
    "The Motif Programming Model",
    "Overview of the Motif Toolkit",
    "The Main Window",
    "Introduction to Dialogs",
    (char *) 0
/* Arbitrary data for the second "page" */
char *pane2_;labels[] =
    "Selection Dialogs",
    "Custom Dialogs",
    "Manager Widgets",
    "The Container and IconGadget Widgets",
    "Scrolled Windows and ScrollBars",
    (char *) 0
/* Arbitrary data for the third "page" */
char *page3_labels[] =
    "The DrawingArea",
    "Labels and Buttons",
    "The List Widget",
    "The ComboBox Widget",
    "The SpinBox and SimpleSpinBox Widgets",
    (char *) 0
/* Arbitrary data for the fourth "page" */
char *page4_labels[] =
    "The Scale Widget",
    "The Notebook Widget",
    "Text Widgets",
    (char *) 0

/* A pointer to all the "page" data */
char **pages[] =
    (char **) 0

void main (int argc, char **argv)
    Widget           toplevel, notebook, page, label;
    XtAppContext     app;
    Arg              args[4];
    XmString         xms;
    int              i, j, n;

    XtSetLanguageProc (NULL, NULL, NULL);
    toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv,
                                    NULL, sessionShellWidgetClass, NULL);

    /* Create the Notebook */
    notebook = XmCreateNotebook (toplevel, "notebook", NULL, 0);

    /* Create the "pages" */
    for (i = 0; pages[i] != (char **) 0; i++) {
        page = XmCreateRowColumn (notebook, "page", NULL, 0);

    for (j = 0; pages[i][j] != (char *) 0; j++) {
        xms = XmStringGenerate (pages[i][j], XmFONTLIST_DEFAULT_TAG, 
                                XmCHARSET_TEXT, NULL);

    n = 0;
    XtSetArg (args[n], XmNlabelString, xms); n++;
            label = XmCreateLabel (page, "label", args, n);
            XtManageChild (label);
            XmStringFree (xms);
        XtManageChild (page);

    XtManageChild (notebook);
    XtRealizeWidget (toplevel);
    XtAppMainLoop (app);
The output of the program is given in Figure 17-3.

Figure  17-3 Output of the simple_notebook program

Creating Notebook Tabs

Just as the Notebook assumes that Manager children are pages, so it assumes that PushButton children are to form Tab inserts. Tabs are considered to be of two kinds: Major Tabs, which presumably separate the important sections of the Notebook children, and Minor Tabs, which mark less important boundaries between the Major Tabs. The Notebook by default assumes that any Tab child is a Major Tab, and so explicit action must always be taken by the programmer when providing the Minor Tab inserts by specifying the appropriate constraint resources. A Major Tab has the XmNnotebookChildType constraint set to XmMAJOR_TAB, and a Minor Tab has the value XmMINOR_TAB. The following additional code adds a Major and a Minor Tab to Example 17-1 (Figure 17-3).

Widget       tab;
char         buffer[32];
/* Create the "pages" */
for (i = 0; pages[i] != (char **) 0; i++) {
    page = XmCreateRowColumn (notebook, "page", NULL, 0);

    for (j = 0; pages[i][j] != (char *) 0; j++) {

    /* An even page is a Major Tab */
    /* And an odd page is a Minor Tab */
    n = 0;
    if ((i % 2) == 0) {
        XtSetArg (args[n], XmNnotebookChildType, XmMAJOR_TAB); n++;
    else {
        XtSetArg (args[n], XmNnotebookChildType, XmMINOR_TAB); n++;
    (void) sprintf (buffer, "%s %d", ((i % 2) == 0) ? "Major" : "Minor", i);
    tab = XmCreatePushButton (notebook, buffer, args, n);
    XtManageChild (tab);
    XtManageChild (page);
The output of the modifications results in the dialog shown in Figure 17-4.

Figure  17-4 The Notebook with added Tabs

Clearly there is a difference in the way that the Notebook displays Major and Minor Tabs. All the Major Tabs are displayed along one edge of the Notebook. However, only the Minor Tabs which logically are associated with a page number between the current active Major Tab and the next Major Tab are displayed. If in the Figure 17-4 Major Tab "Page 2" is activated, only the Minor Tab "Page 3" is displayed along the bottom.

Manipulating the Page Scroller

The Notebook creates a SpinBox with a single TextField child to act as a Page Scroller automatically, unless the programmer takes steps to provide her own Page Scroller when the Notebook is created. This means that in principle trying to create Notebook pages which are only accessible through the Tab mechanisms looks a little tricky. In practice, the default Page Scroller can be removed simply enough by accessing the built-in SpinBox widget, and then unmanaging it. The Notebook creates the SpinBox using the constant name "PageScroller", and so the following code fragment will effectively hide the Page Scroller from view:

extern Widget notebook;
Widget scroller;

scroller = XtNameToWidget (notebook, "PageScroller");
XtUnmanageChild (scroller);
If you wanted to add your own Page Scroller, you would need to create the widget as a child of the Notebook, setting the XmNnotebookChildType constraint to XmPAGE_SCROLLER.

Notebook Resources

The resources associated with the Notebook fall into three basic sets: those which configure the current pages on view, those which controls the visuals associated with the binding and page illusions, and those which configure the Tab placement.

Page Resources

The current logical page displayed by the Notebook is specified using the resource XmNcurrentPageNumber. This can be used to programmatically display a given page. If the programmer wishes to set lower and upper bounds upon the pages in view, the resources XmNfirstPageNumber and XmNlastPageNumber resources can be specified. The Notebook will not display any Tabs or Pages whose logical page assignments fall outside the range falling between the two values. How logical page numbers are assigned to children is covered in the following Notebook Constraints Section. Any Page which has an assigned page number outside the range can only be displayed if the programmer either modifies the range, or re-assigns the logical number associated with the Page so that it falls within the first/last page bounds. By default, the XmNlastPageNumber value is maintained by the Notebook itself as page children are added to the widget. However, if the programmer sets this XmNlastPageNumber value herself, the Notebook no longer maintains the value, and it becomes the programmers responsibility to maintain the value from then on. The automatic page numbering scheme uses the XmNfirstPageNumber resource to seed the counting algorithm. By default, the first page number is 1.

Visual Resources

The page illusion can be configured through a variety of resources, both to configure the general layout, and to specify the appearance.

The color of the back page is controlled using XmNbackPageForeground and XmNbackPageBackground, which are Pixel-valued resources. The number of overlapping pages which constitute the back page (or rather, the number of lines drawn to give the appearance of overlapping pages) is controlled through the XmNbackPageNumber resource, the thickness of the lines drawn being specified using the XmNbackPageSize resource.

The size of the binding drawn along the Notebook is bounded by the value of the XmNbindingWidth resource, the binding style itself is specified through XmNbindingType. This has the following possible values:

The default is XmSPIRAL, which displays a spiral binding down the edge of the Notebook. XmSOLID draws a solid binding using the foreground color of the widget. The binding can be removed using the value XmNONE: this would be done if you wanted to turn the Notebook into something more along the lines of a traditional Tab Manager. You can also supply your own binding in the form of a Pixmap using the XmNbindingPixmap resource. If you do this, you need to also specify that the XmNbindingType is either XmPIXMAP or XmPIXMAP_OVERLAP_ONLY. The difference is that if the type is XmPIXMAP, your image will not be clipped by any XmNbindingWidth resource - the binding will grow if your Pixmap is wider than the current XmNbindingWidth. On the other hand, if the binding type is XmPIXMAP_OVERLAP_ONLY, the size of the binding drawn is bounded by the XmNbindingWidth value.

Figure 17-5 shows the effect of setting the various binding appearance resources.

Figure  17-5 The Notebook binding styles

The general layout of the Notebook is configured using the XmNbackPagePlacement resource. This is probably misnamed, in that although the resource does indeed configure the placement of the back page illusion, it also configures the location of the Tabs and the binding as a side effect. The resource has the following possible values:

The default value is sensitive to the XmNlayoutDirection and XmNorientation resources. Figure 17-6 shows the effects of setting the various values:

Figure  17-6 The Notebook binding placements

Clearly the XmNbackPagePlacement resource also affects the binding and Tabs, because the binding is always opposite the overlapping back page illusion, and the Major Tabs are placed along the back page opposite the binding.

The orientation of the Notebook can be configured through the XmNorientation resource, which has the possible values XmHORIZONTAL and XmVERTICAL. Figure 17-7 shows a vertically-oriented Notebook. The spiral binding has also been removed so that the Notebook looks more like a traditional Tab Manager. The Tabs, however, appear along the top as opposed to the sides, which happens with the horizontal arrangement. If you wanted the Major Tabs to appear on the bottom of the Notebook, you would need to set the XmNorientation to XmVERTICAL, and the XmNbackPagePlacement to XmBOTTOM_LEFT or XmBOTTOM_RIGHT to choice.


Figure  17-7 The Notebook with a vertical orientation

Tab Resources

Tab placement has already been discussed in the previous paragraphs - the XmNbackPagePlacement resource configures the Tab placement as a side effect. All that remains which can potentially be configured is the spacing between the Tabs and the Notebook pages. The distance between Major Tabs and the page border is specified using the XmNmajorTabSpacing resource, and the distance between Minor Tabs and the page is specified through the XmNminorTabSpacing resource. Setting these values to zero, as well as specifying the XmNbackPageNumber resource as zero, gives an appearance which is more consistent with a normal Tab Manager.

Notebook Constraints

The roles which each child can take in the various Notebook operations is controlled through the XmNnotebookChildType resource. This can take the following values:

The XmNnotebookChildType resource is create-only, which means that you must specify the resource when you add the child if the required role differs from the default which would be assigned by the Notebook. Formally, the default algorithm assumes that if the child supports the XmQTactivatable Trait, then the child is assigned the constraint value XmMAJOR_TAB. The PushButton, DrawnButton, ArrowButton and derived classes support this Trait. If the child widget supports the XmQTnavigator Trait, then the child by default is assigned the constraint value XmPAGE_SCROLLER. The ScrollBar, SpinBox, and derived widget classes support this Trait. If the child widget supports the XmQTaccessTextual Trait, then the role XmSTATUS_AREA is the default. The Label, LabelGadget, Text, TextField, and derived classes support the XmQTaccessTextual Trait. Everything else is assigned the default role XmPAGE. It follows that all Minor Tabs must be explicitly set by the programmer since the default algorithm does not assign this role. The internals of the Trait mechanisms are beyond the scope of this book.

The association between Tabs and Pages is specified using the XmNpageNumber constraint. A Tab will display a given Page when activated if the Tab and Page child share the same XmNpageNumber constraint value.

The last constraint defined by the Notebook is the XmNresizable resource, which simply specifies whether the Notebook will process resize requests from the given child.

Notebook Callbacks

The Notebook defines a single callback, the XmNpageChangedCallback. This is called whenever there is a request to change the current Logical Page, whether through the activation of a Tab or through selection using a Page Scroller. Each callback when invoked is passed a pointer to an XmNotebookCallbackStruct structure, which is defined as follows:

typedef struct
    int          reason;
    XEvent       *event;
    int          page_number;
    Widget       page_widget;
    int          prev_page_number;
    Widget       prev_page_widget;
} XmNotebookCallbackStruct;
The reason element specifies the cause of callback invocation. It may have any of the following possible values:

The value will be XmCR_NONE on Notebook invocation, as the widget chooses the first current page. It will also be XmCR_NONE if the XmNcurrentPageNumber resource is changed programmatically. Otherwise, the value will reflect a user action, depending upon whether a Tab of some kind has been activated, or the Page Scroller selected.

The page_number field represents the new logical page number. This may, or may not, be associated with a real Page. If the page_number does refer to a child with a matching XmNpageNumber constraint value, then the page_widget element will be set to this child. Otherwise, the page_widget element will be NULL, and the Notebook will display a blank page.

The prev_page_number and prev_page_widget elements specify the old current page. At Notebook initialisation, prev_page_number is the value XmUNSPECIFIED_PAGE_NUMBER, and prev_page_widget is NULL. The prev_page_widget element will also be NULL if the Notebook is currently displaying a blank Page - no child widget has the constraint XmNpageNumber which matches the prev_page_number value.

The fields of the callback structure are not used by the Notebook routines when the XmNpageChangedCallback terminates. This means that in effect the elements are all read-only and purely notificatory in effect: modifying the page_number or page_widget element in the hope of programmatically setting the next page will have no effect. The resource XmNcurrentPageNumber should be set directly if this is the required behavior.

The following code fragment simply prints out the state information as various changes take place to the Notebook current page:

void notebook_changed_callback ( Widget         w,
                                 XtPointer      client_data,
                                 XtPointer      call_data)
    XmNotebookCallbackStruct *nptr;
    nptr = (XmNotebookCallbackStruct *) call_data;

    if (nptr->reason == XmCR_NONE) {
        if (nptr->prev_page_number == XmUNSPECIFIED_PAGE_NUMBER) {
            printf ("Notebook initialisation: first page %d\n",
        else {
            printf ("Program request: new page %d\n",
    else {
        printf ("New Page: %d %s Old page: %d %s\n",
                (nptr->page_widget ?
                    XtName(nptr->page_widget) :
                (nptr->prev_page_widget ?
                    XtName(nptr->prev_page_widget) :

Notebook Functions

A programmer can request information about a logical page of the Notebook using the routine XmNotebookGetPageInfo(), which has the following signature:

XmNotebookPageStatus XmNotebookGetPageInfo ( Widget               notebook,
                                             int                  page_number,
                                             XmNotebookPageInfo   *page_info)
The return value is an XmNotebookPageStatus, which is an enumerated type. Possible values are:

XmPAGE_FOUND                 XmPAGE_EMPTY
If the requested page_number falls outside the bounds between the notebook XmNfirstPageNumber and XmNlastPageNumber resources, the function returns XmPAGE_INVALID. Otherwise, if exactly one child which has an XmNpageNumber constraint which matches page_number is found, the return value is XmPAGE_FOUND. If more than one child shares the given page number, the return value is XmPAGE_DUPLICATED. Otherwise, the return value is XmPAGE_EMPTY.

The page_info parameter specifies an address where data about the requested page_number is returned. The data is filled in by the XmNotebookGetPageInfo() routine except if the return value is XmPAGE_INVALID. Where there are duplicate pages with the requested page_number, the information will refer to the last child found. The XmNotebookPageInfo data type is defined as follows:

typedef struct
    int        page_number;
    Widget     page_widget;
    Widget     status_area_widget;
    Widget     major_tab_widget;
    Widget     minor_tab_widget;
} XmNotebookPageInfo;
If the matching child found is a Status Area, the status_area_widget element will be filled in with the widget ID of the child. Similarly, a matching Major Tab child is placed into the major_tab_widget element, a matching Minor Tab is stored in the minor_tab_widget element, and a matching Page child is stored in the page_widget field.

In addition, Major Tabs and Minor Tabs are stored into the major_tab_widget and minor_tab_widget fields as they are encountered regardless of whether the page number matches. Since children of the Notebook are stored internally in sorted ascending XmNpageNumber order, and since the search terminates if a child is found with a page number exceeding the request, it means that the major_tab_widget and minor_tab_widget elements contain the Tab with a page number nearest to (but not in excess of) the requested page number in addition to any data returned in the page_widget field.


The Notebook is a most useful, if some what over-decorous, addition to the Motif 2.0 widget set. It performs the services of a traditional Tab Manager by displaying only one given page child at a time. Indeed the visuals of the Notebook can be configured to give the usual appearance of a Tab Manager, and it is in this form that it is most likely to be used in typical application programming. It works by assigning roles to the various children as they are added: the default algorithm for assigning page numbers and roles is more than sufficient for most purposes, but the programmer has sufficient control should the need to override the defaults arise.

X-Designer - The Leading X/Motif GUI Builder - Click to download a FREE evaluation

Previous Next Contents Document Index Motif Docs Imperial Software Technology X-Designer

Copyright © 1991, 1994, 2000, 2001, 2002 O'Reilly & Associates, Inc., Antony Fountain and Jeremy Huxtable. All Rights Reserved.