X Motif widget driver

Author
M. C. Shepherd, 1996.

Supported device
Restricted to Motif applications on workstations running the X Window System (Version 11 Release 4 and above).

Availability
Released as a beta-test driver in PGPLOT 5.1.0.
Updated for general use in PGPLOT 5.2.0. See the changes section for revision details.

Device type code
/XMOTIF

Device name specification
The device name used to refer to an already created and realized XmPgplot widget is the name that was given to the widget as the first argument of XtVaCreateManagedWidget() when the widget was created. The full device specification used to open a given widget to PGPLOT with cpgbeg() or cpgopen() can also be obtained through the xmp_device_name() convenience function.

Default view surface dimensions
By default the size of an XmPgplot widget window is 256×256 pixels. This can be overridden by setting the XmNheight and XmNwidth X resources when creating the widget.

Resolution
This depends on the monitor on which the windows are to be displayed.

Color capability
Colormaps of types PseudoColor, StaticColor, GrayScale, StaticGray, and TrueColor are supported. By default, colors are allocated from the colormap used by the parent window of the widget, unless it has too few colors, in which case the window uses the black and white pixels of the screen to implement a monochrome window.

Note that with PseudoColor and GrayScale colormaps, colors of already drawn graphics will respond to changes to their color representations, whereas with other colormap types, color representation changes will only effect the colors of subsequently drawn graphics.

Input capability
The cursor is usually controlled by a mouse. Cursor input is achieved by moving the cursor into the window of the widget, and pressing either a mouse button or a keyboard key to select a given position in the window and return a key value. The mouse buttons are mapped to return characters A, D, and X.

An asynchronous alternative to the cpgcurs() and cpgband() procedures is provided. This provides the same functionality as cpgband() except that once armed, cursor input is delivered to the application via callback functions.

Facilities are also provided to support applications that want to handle cursor input themselves via XtAddEventHandler(). This includes optional augmentation of the X cursor with rubber-bands, and functions for converting between X-window coordinates and world coordinates.

Multiple open device capability
The XmPgplot driver places no restrictions on the number of XmPgplot widgets that can be open to PGPLOT simultaneously. Note, however that PGPLOT does set limits on the total number of open devices. Currently (April 96) this limit is 8.

Required files from the PGPLOT installation
C include files:
XmPgplot.h
This header file should be included by all application source code files from which XmPgplot widget functions or resources are used.
cpgplot.h
Since Motif is usually used from C, the include file for the C PGPLOT wrapper library should also be included in all Motif applications that make calls to the PGPLOT library.

UNIX PGPLOT libraries:
libXmPgplot.a
This library contains the Motif PGPLOT widget and its PGPLOT driver. The main PGPLOT library only contains a stub version of the driver so when linking Motif applications, it is essential that libXmPgplot.a appear before the main PGPLOT library. For example, a snapshot of the link line under UNIX would be:

   -lXmPgplot -lcpgplot -lpgplot -lXm -lXt -lX11
 
libcpgplot.a and libpgplot.a
The PGPLOT C wrapper library and the FORTRAN PGPLOT library.

VMS PGPLOT libraries:
XMPGPLOT.OBJ
This object file contains the compiled Motif PGPLOT widget and its PGPLOT driver. Note that the main PGPLOT library only contains a stub version of the driver and that programs that use the Motif widget must link with XMPGPLOT.OBJ in addition to the the main PGPLOT library.
GRPCKG.OLB and CPGPLOT.OLB
The PGPLOT C wrapper library and the static version of the main PGPLOT library. The shareable PGPLOT library can not be used when the Motif driver is required.
XMOTIF.OPT
This file can be used to link PGPLOT applications that use the XmPgplot widget driver. It is a linker options file that lists the PGPLOT, Motif, X-toolkit and X11 libraries. It should be used like:

   LINK PROGRAM,PGPLOT_DIR:PGMOTIF.OPT/OPT
 

Configuration
The configuration of individual widgets is controlled by X resources. It is usually most convenient to set such resources in the call to XtVaCreateManagedWidget() when each PGPLOT widget is created, but they can also be placed in app-defaults files or, on POSIX-compliant systems, the .Xdefaults file in your home directory. Under VMS this file is called DECW$USER_DEFAULTS:DECW$XDEFAULTS.DAT. Note that by default DECW$USER_DEFAULTS is defined as SYS$LOGIN.

Configuration of the widget colormap and visual.
XmPgplot widgets allocate colors from the Visual and Colormap specified via the XmNcolormap and XmNvisual resources. These default to CopyFromParent, as they do in most other widgets. As a result, XmPgplot widgets normally allocate colors from the default colormap and visual of the screen. If the default colormap of the screen is insufficient for your needs and you need to allocate a private colormap then I suggest that you register the result with the top-level widget of the application rather than with a specific XmPgplot widget. This will then be inherited by all widgets in the application, including XmPgplot widgets. The steps to perform to allocate a private colormap and assign it to the top-level widget of the application are as follows:

  • Call XtVaAppinitialize() as you normally would.
    For the sake of the example, let's say that you store the returned top-level widget in a variable called w_top.
  • Call XtDisplay(w_top) to acquire the Display Xlib context object.
  • Use the normal Xlib functions to find an appropriate visual and allocate the required private colormap.
  • Register the selected colormap and visual to the top-level widget:
      XtVaSetValues(w_top,
                    XmNcolormap, colormap,
                    XmNvisual,   visual,
                    NULL);
    

Alternatively if you really want to assign private colormaps to individual XmPgplot widgets then note that to get the window manager to automatically install the colormap of a widget window, one has to set the WM_COLORMAP_WINDOWS property on the top-level window of the host application. The X toolkit provides the function XtSetWMColormapWindows() to do this.

Other X resources that effect the way that colors are allocated are:

XmpNminColors
This has a default value of 2. Its value determines the minimum acceptable number of colors in a colormap. If fewer colors are available than this number, then the color allocation code will give up and fall back on using just the black and white colors of the screen to draw PGPLOT graphics.
XmpNmaxColors
This has a default value of 100. It specifies how many colors to attempt to allocate per widget. Thus if you have two PGPLOT widgets sharing a colormap that has only 100 free colors then you would need to make the XmpNmaxColors resource values of the two widgets add up to no more than 100.

Configuring how widget resizes are handled.
When geometry management widgets are resized by users, their child widgets generally also get resized. This means that the PGPLOT widget can get resized at unpredictable times. However PGPLOT assumes that the size of its view surface remains unchanged between the start of one page and the start of the next. To handle this XmPgplot widgets use an off screen pixmap for buffering and backing store. This is kept fixed in size for the duration of each page, and is only resized to fit the window when cpgpage() is called. If the widget window is resized to a smaller size, then part of the plot will become obscured until the next page is started. The XmPgplot widget driver supports two optional ways to improve this situation.

Scrolled PGPLOT windows.
If you arrange for the parent widget of a XmPgplot widget to be a Motif ScrolledWindow widget then the PGPLOT widget will automatically adopt the scroll-bars of its parent and arrange that they scroll the underlying pixmap relative to the window. Then if the window is resized to a smaller size the scroll bars can be used to see the obscured part of the plot.
Using a resize callback.
Another way to handle the situation is to register a callback to be called whenever the PGPLOT widget gets resized. For this to be useful you should have the callback call the PGPLOT cpgpage() procedure so as to synchronize the size of the pixmap with the window, and then re-draw any graphics that you want to re-appear there. Registering such a callback is just a matter of calling XtAddCallback() with the PGPLOT widget as the first argument, XmNresizeCallback as the second argument, your callback function as the third argument and an optional client-context data argument as the final argument. The callback function should be declared like:

void whatever(w, client_data, call_data)
Where the arguments are declared and interpreted as:

Widget w
The XmPgplot widget that has been resized.
XtPointer client_data
This is the client_data pointer that was registered with XtAddCallback(). It should be cast back to its actual type before use.
XtPointer call_data
This argument is always NULL.

Cursor input functions
The standard PGPLOT cpgcurs() and cpgband() cursor-input functions are not recommended for use in Motif applications because they block the toolkit event loop. Instead an alternative callback system, designed to mimic cpgband() but without blocking the event loop, has been provided. Arming and disarming the cursor is achieved through two functions:

int xmp_arm_cursor(widget, mode, xref, yref, callback, client_data)
This function augments the X cursor with the type of rubber-band specified in the mode argument. It also optionally registers a callback function to be passed the world coordinates and character of key-press and button-press events. The cursor can subsequently be disarmed via a call to xmp_disarm_cursor(). The cursor is automatically disarmed when the device is closed to PGPLOT by cpgclos() or cpgend(), and whenever the cursor is re-armed.

Note that when the cursor is augmented, the rubber band is redrawn every time that the PGPLOT buffer is flushed. This happens after every call to PGPLOT functions unless sensible use is made of cpgbbuf() and cpgebuf() to buffer sequential calls. Be sure to use these functions properly if you don't want your PGPLOT code to be slow.

The arguments of xmp_arm_cursor() are:

Widget widget
The target PGPLOT widget. The widget must be open to PGPLOT.
int mode
The type of cursor to display, from:
XMP_NORM_CURSOR
The un-augmented X cursor.
XMP_LINE_CURSOR
A line drawn between (xref,yref) and the pointer.
XMP_RECT_CURSOR
A rectangular cursor with opposing vertices at (xref,yref) and the pointer.
XMP_YRNG_CURSOR
Two horizontal lines, one through xref and the other through the pointer.
XMP_XRNG_CURSOR
Two vertical lines, one through yref and the other through the pointer.
XMP_HLINE_CURSOR
A horizontal line through the pointer.
XMP_VLINE_CURSOR
A vertical line through the pointer.
XMP_CROSS_CURSOR
A cross-hair cursor centered on the pointer.
float xref, yref
The cursor reference position in the current world-coordinate system. If the world coordinate system is subsequently changed the reference position will not be recomputed.
XtCallbackProc callback - The cursor-input callback function, or 0 if not required.
This is used just like any other Xt callback function, except that its call_data argument is a pointer to a struct of the following declaration, cast to (XtPointer).

 typedef struct {
   float x,y;  /* The world-coordinate position of the cursor */
   char key;   /* The key pressed by the user. Mouse buttons */
               /* are encode as 'A','D','X' (left to right) */
 } XmpCursorCallbackStruct;
 

The callback function will be called whenever a key (if the widget has keyboard input focus) or pointer button is pressed while the pointer is in the associated PGPLOT widget.

void *client_data - Client context data.
This pointer will be passed verbatim to the callback function whenever a cursor-input event is reported.

The return value of xmp_arm_cursor() is 0 if successful or 1 if the specified widget is not an open PGPLOT widget or the callback argument is NULL.

int xmp_disarm_cursor(widget)
This function can be called to undo the effect of a previous call to xmp_arm_cursor() for the specified widget. Note that this function can be called even when the cursor has not been previously armed or when the widget is not open to PGPLOT.

The xmp_disarm_cursor() function returns 0 if successful or 1 if the specified widget is not a PGPLOT widget.

Advanced cursor-input
On its own xmp_arm_cursor() provides a convenient but limited cursor-input facility, designed to mimic cpgband(). What it doesn't provide is a general means of responding to the full variety of X-window events. For example, it doesn't report Motion events via its callback. This section describes the facilities available for establishing one's own cursor-input handlers.

To display a rubber band cursor without using its callback facility, call xmp_arm_cursor() with its callback argument specified as 0. This tells the widget's cursor event handler not to select for button-press and key-press events and disables the special interpretation of mouse-buttons and Tab keys with respect to keyboard-focus management .

To register input event handlers externally to xmp_arm_cursor() use the standard XtAddEventHander(). In order to convert from the reported X-window coordinates to PGPLOT world-coordinates, use the xmp_pixel_to_world() function described later.

For example, having created a Label widget w_label and a PGPLOT widget w_plot, the following code would result in a world-coordinate readout of the cursor being displayed in the label whenever the cursor was moved over the PGPLOT widget.

  XtAddEventHandler(w_plot, PointerMotionMask, False,
                    report_cursor, (XtPointer)w_label);
  ...

  static void report_cursor(Widget w, XtPointer context,
              XEvent *event, Boolean *continue_dispatch)
  {
    Widget w_label = (Widget) context;
    char text[80];
    float wx, wy;
/*
 * Convert from X-window coordinates to world coordinates.
 */
    if(xmp_pixel_to_world(w, event->xmotion.x, event->xmotion.y,
                          &wx, &wy) == 0) {
      sprintf(text, "X=%-10g Y=%-10g", wx, wy);
      XtVaSetValues(w_label,
           XtVaTypedArg, XmNlabelString, XmRString,
           text, strlen(text)+1, NULL);
    }
    *continue_dispatch = True;
  }

Keyboard focus issues
A widget that receives keyboard input is said to have the keyboard input focus. Only one widget can have this at one time. If you want to be told of keyboard input when an XmPgplot cursor is armed, then read on about the awkward issue of how an XmPgplot widget receives the input focus. Alternatively, if you only care about receiving cursor input from button presses, then you should set the XmNtraversalOn widget resource to False, otherwise some button-presses will be lost to focus management.

When a Motif application has the keyboard input focus, the Motif library decides which of the application's widgets will receive keyboard input. This is independent of the method used by the window manager for top-level windows. Motif supports two schemes, either of which can be selected by setting the value of the XmNkeyboardFocusPolicy resource of the top-level widget to one of the following values:

  1. XmEXPLICIT (The default focus policy).

    This implements a click-to-focus model whereby the user must explicitly set the input focus either by pressing the TAB key repeatedly until the required widget is reached, or by moving the pointer into the widget window and pressing a mouse button.

    Unfortunately, this means that when a PGPLOT widget doesn't have the keyboard input focus, the first button press is used to acquire the input focus, rather than being reported as cursor-input. Similarly, once the widget has the keyboard input focus, TAB characters are used to move the input focus to the next widget rather than being delivered as cursor input. This is confusing to users, so it is better to either use the XmPOINTER focus policy, or to tell the widget that keyboard input is not desired, as described above.

    In order to allow users to determine in advance whether an XmPgplot widget has input focus, the border of the widget is changed from the background color of its parent to white. This color was chosen so as to be distinct from the default black background color of XmPgplot widgets. The color and other aspects of highlighting can be changed via the highlighting resources of the Primitive widget.

    Note that X conventions discourage applications from moving the keyboard focus unless under user direction. However, in cases where the user presses a button who's primary function is to activate an XmPgplot cursor for user input, you might consider actively setting the keyboard input focus to the respective XmPgplot widget via a statement of the following form,

      XmProcessTraversal(widget, XmTRAVERSE_CURRENT);
    
    placed just after the call to xmp_arm_cursor(widget, ..). This statement has no effect if the focus policy is not XmEXPLICIT.

  2. XmPOINTER

    In this scheme the widget in which the pointer lies is the one that receives keyboard input, and button presses are always unambiguously treated as cursor input. This avoids all keyboard focus complications. However, some users don't like it because they like to be able to move the pointer out of the window in which they are typing.

Note that the above resource values can either be hard-coded via the call to XtVaAppInitialize() or specified in the application's app-defaults file.

If having read this, you still want to be told about keyboard input in addition to mouse-button input, but you don't like the way xmp_arm_cursor() manages the keyboard input focus, then you can write your own using the facilities described above under "Advanced cursor-input".

Coordinate conversion functions
The following functions are designed for use with custom event handlers. They perform conversions between widget X-window coordinates and PGPLOT world coordinates.

int xmp_pixel_to_world(widget, px, py, wx, wy)
This function takes the X-window coordinates of a pixel within a PGPLOT widget and returns the corresponding PGPLOT world coordinates. If the widget is not open to PGPLOT the reported world coordinates will be 0.0,0.0. The function arguments are:

Widget widget
The target PGPLOT widget.
int px, py
The pixel coordinates to be converted. These are interpretted with respect to the origin at the top left corner of the widget window. This is the normal coordinate system used by X windows.
float *wx, *wy
On output the variables pointed to by wx and wy will be assigned the PGPLOT world coordinates that correspond to pixel px, py.

The function returns 0 on success or 1 if the widget is invalid.

int xmp_world_to_pixel(widget, wx, wy, px, py)
This function takes the PGPLOT world coordinates of a point on the viewsurface of a PGPLOT widget and returns the X-window coordinates of the nearest pixel. If the widget is not open to PGPLOT the reported coordinates will be 0,0. The function arguments are:

Widget widget
The target PGPLOT widget.
float wx, wy
The PGPLOT world coordinates to be converted.
int *px, *py
On output the variables pointed to by px and py will be assigned the X-window coordinates of the pixel nearest to world coordinate wx, wy.

The function returns 0 on success or 1 if the widget is invalid.

Widget identification functions
Two convenience functions are provided for determining the name and PGPLOT id associated with a particular PGPLOT widget.
int xmp_device_name(Widget widget)
This function returns a device-name string suitable for use with the cpgopen() or cpgbeg() functions to open the specified XmPgplot widget to PGPLOT. An example of how this can be used with cpgopen() is shown later. The returned string is part of the specified widget and must be treated as read-only by the caller.

The form of the returned string is "widget_name/XMOTIF" where widget_name is the name that was given to the widget when it was created.

If the specified widget is not a PGPLOT widget then an error message will be emitted and the returned device name will be "/null".

int xmp_device_id(Widget widget)
When a PGPLOT device is opened with the cpgopen() function, PGPLOT returns an integral identifier. This may then be used, via the cpgslct() function, to select which of the open PGPLOT devices is to be the current graphics device. The xmp_device_id() convenience function returns the PGPLOT identifier associated with the given XmPgplot widget. This can be useful in callbacks, where the Widget argument passed to the callback function can then be used to select the widget as the current PGPLOT device.

If the Widget argument passed to the xmp_device_id() function either hasn't been opened to PGPLOT or hasn't been re-opened after being closed, then xmp_device_id() emits an error message and returns 0.

Inherited widget resources.
XmPgplot widgets inherit all of the X resources of the Core and Primitive Motif widget classes.

Note that the XmNbackground and XmNforeground resources change the color representations of pgplot color indexes 0 and 1. The default background is black and the default foreground is white. Thus to create an XmPgplot widget with these colors swapped, one could type:

  plot = XtVaCreateManagedWidget("plot", xmPgplotWidgetClass,	parent,
	XmNheight, 400,
	XmNwidth, 400,
	XmpNmaxColors, 50,
	XmNtraversalOn, False,
	XtVaTypedArg, XmNbackground, XmRString, "white", strlen("white")+1,
	XtVaTypedArg, XmNforeground, XmRString, "black", strlen("black")+1,
	NULL);

How to create and use a Motif PGPLOT widget with PGPLOT
Motif PGPLOT widgets are created just like other widgets, by calling XtVaCreateManagedWidget. The first argument to XtVaCreateManagedWidget must be the device name by which you wish to refer to the widget in cpgbeg() or cpgopen(), and the second argument must be xmPgplotWidgetClass. The third argument specifies the parent widget. If you want the widget to have scroll bars then this should be a Motif ScrollBar widget. The remaining arguments are a list of X resource-value pairs terminated by a NULL argument. These should be used to configure the widget, via the resources listed earlier.

A non-variadic "convenience" function for creating a PGPLOT widget is also available, called XmCreatePgplot() and the equivalent function to create both a PGPLOT widget and an associated ScrollBar widget is called XmCreateScrolledPgplot(). Note that in both cases you should apply XtManageChild() to the returned widget.

Before a PGPLOT widget can be used from PGPLOT it has to be opened by calling cpgbeg() or cpgopen(). This can be done any time after the PGPLOT widget has been realized.

Note that PGPLOT now supports multiple open PGPLOT devices via cpgopen(), cpgslct() and cpgclos(). You can thus create and have multiple PGPLOT widgets open to PGPLOT simultaneously. If you do this, be sure to call cpgslct() in each callback to ensure that the intended PGPLOT widget is addressed. The id to pass to cpgslct() to select a given XmPgplot widget can be obtained via the xmp_device_id() convenience function.

Please see the previous sections on configuration, resize options and how to get cursor input.

An example of creating a PGPLOT widget as the child of a scroll bar widget is as follows:

         #include <stdio.h>
         #include <X11/Intrinsic.h>
         #include <Xm/Xm.h>
         #include <Xm/ScrolledW.h>

         #include "XmPgplot.h"
         #include "cpgplot.h"

         int main(int argc, char *argv[])
         {
            XtAppContext app; /* Application context */
            Widget w_top;     /* The top-level widget of the application */
            Widget w_scroll;  /* Scroll-bar widget */
            Widget w_pgplot1; /* PGPLOT widget */
         /*
          * Initialize Motif and request a pointer following
          * keyboard-focus policy. 
          */
            XtSetLanguageProc(NULL, NULL, NULL);
            w_top = XtVaAppInitialize(&app, "Pgplot", NULL, 0,
                                     &argc, argv, NULL,
                                     XmNkeyboardFocusPolicy, XmPOINTER,
                                     NULL);
         /*
          * Create a ScrollBar widget.
          */
            w_scroll = XtVaCreateManagedWidget("pgplot_scrollbar",
                                     xmScrolledWindowWidgetClass, w_top,
                                     NULL);
         /*
          * Create a PGPLOT widget as a child of the scroll-bar widget.
          * Give it an initial size of 300x300 pixels and allow it to
          * allocate up to 60 colors from the colormap used by the parent
          * widget.
          */
            w_pgplot1 = XtVaCreateManagedWidget("pgplot1",
                                     xmPgplotWidgetClass, w_scroll,
                                     XmNheight, 300,
                                     XmNwidth, 300,
                                     XmpNmaxColors, 60);
         /*
          * Create and display the application windows.
          */
            XtRealizeWidget(w_top);
         /*
          * Open w_pgplot1 as the current PGPLOT device.
          */
            if(cpgopen(xmp_device_name(w_pgplot1)) <= 0) {
              fprintf(stderr, "Failed to open PGPLOT widget: %s\n",
                      xmp_device_name(w_pgplot1));
              exit(1);
            };
         /*
          * Proceed with the Xt event loop.
          */
            XtAppMainLoop(app);
         /* NOT REACHED */
            return 0; 
         };
   

This example doesn't actually do anything useful except create a PGPLOT widget with scroll-bars and open it to PGPLOT. To make the example useful you would have to arrange for something to draw into the PGPLOT widget. This could be a work procedure, a callback registered to a communication stream for logging incoming data, or a push-button callback.

The pgmdemo demo program provides a much more complete example. The source code for it can be found in the PGPLOT distribution in pgplot/drivers/xmotif/pgmdemo.c. It is compiled automatically when the xmdriv driver is uncommented in drivers.list during PGPLOT installation.

Changes in the PGPLOT 5.2.0 release.
The rubber-band and cursor input facilities have been reworked. The changes include:

Martin Shepherd (mcs·astro.caltech.edu).