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:
Read the bitmaps and load them into a sufficiently large pixmap.
Create the ScrolledWindow, a DrawingArea widget, and two ScrollBars; the program must initialize each of
these widgets' resources so that the ratio between their sizes and the size of the pixmap is consistent.
Set up a callback routine for the ScrollBars to respond to scrolling actions.
Provide a callback routine for the DrawingArea widget's XmNresizeCallback that updates all of the
widgets' resources according to the new ratio between the widgets and the pixmap.
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.
10 ScrolledWindows and ScrollBars10.4 Implementing True Application−defined Scrolling
269
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! */
#endif
/* 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.
*/
int_sqrt(n)
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;
}
10 ScrolledWindows and ScrollBars10.4 Implementing True Application−defined Scrolling
270
/* 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++;
else
p = *argv;
if (list[i].width > MAX_WIDTH || list[i].height > MAX_HEIGHT) {
printf ("%s: bitmap too big0, p);
XFreePixmap (dpy, list[i].bitmap);
continue;
}
list[i].len = strlen (p);
10 ScrolledWindows and ScrollBars10.4 Implementing True Application−defined Scrolling
271
list[i].name = p; /* we'll be getting it later */
printf ("Size: %dx%d0, list[i].width, list[i].height);
i++;
} 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);
10 ScrolledWindows and ScrollBars10.4 Implementing True Application−defined Scrolling
272
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 /
10 ScrolledWindows and ScrollBars10.4 Implementing True Application−defined Scrolling
273

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

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.