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.
10 ScrolledWindows and ScrollBars10.4 Implementing True Application−defined Scrolling
274
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),
NULL);
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
10 ScrolledWindows and ScrollBars10.4 Implementing True Application−defined Scrolling
275
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
10 ScrolledWindows and ScrollBars10.4 Implementing True Application−defined Scrolling
276
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,
NULL);
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
10 ScrolledWindows and ScrollBars10.4 Implementing True Application−defined Scrolling
277
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:
void
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:
void
XmScrollBarGetValues(widget, value, slider_size, increment,
page_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.
10 ScrolledWindows and ScrollBars10.4 Implementing True Application−defined Scrolling
278

Get Volume 6A: Motif Programming Manual now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.