Some Useful X Routines

by John Cwikla

Copyright © 1995,1996,1997,1998,1999,2000,2001,2002 John Cwikla, All Rights Reserved.

Greetings once again. This month I thought I would take a look at some routines that I use over and over again in the programs I write. All of these are (mostly) self-contained procedures, so should be easy to incorporate in just about any Xt program. All programmers have a library of routines they call upon, and hopefully these functions will be a welcomed supplement.

Shell Routines

Widget MXtGetShell(Widget _w)
{
    Widget temp;

    temp = _w;
    while(temp && !XtIsSubclass(temp, shellWidgetClass))
        temp = XtParent(temp);

    return temp;
}

If you read Beyond the Default Visual you have already seen one of the uses of this function. Given any widget it goes up the widget hierarchy until it finds a shell widget. This is especially useful for the shell's visual resource, which only shells have. When you want to match the visual's for subshells, MXtGetShell() alleviates the need for storing the visual value inside a global variable. Instead, it allows you to look at the supershell's resources, retrieve them, and then set the values into the new shell.

I also use this function when I need to save some information in a global window context, for instance in a dialog. In our application we share code across many windowing systems, so our convention for dialogs is that each item is identified by a unique integer. For any action routine, we need two items: our internal structure defining our dialog, and the item number. Instead of malloc'ing a structure containing both for each item and passing that as the closure argument to a callback, I pass the item number as the closure argument and then store the structure in the shell (I subclassed the transientShellWidgetClass to have an XtNuserData resource, but you could just easily use XSaveContext()/XFindContext, see Extending Widget Attributes for more information). Now during a callback I have easy access to the two pieces of information, one that is shared between all the items.


Widget MXtGetShellChild(Widget _w)
{
    Cardinal numKids;
    WidgetList kids;

    if (_w == (Widget)NULL)
        return (Widget)NULL;

    if (!XtIsSubclass(_w, shellWidgetClass))
        return NULL;

    XtVaGetValues(_w, XtNnumChildren, &numKids, XtNchildren, &kids, NULL);
    if (numKids == 0)
        return (Widget)NULL;
    else
        return kids[0];
}

void MXtShellAllowResize(Widget _shell, int _flags)
{
    Arg warg[4];
    int n;

    n = 0;
    if (_flags & CWWidth)
    {
        XtSetArg(warg[n], XtNminWidth, 0); n++;
        XtSetArg(warg[n], XtNmaxWidth, 32767); n++;
    }
    if (_flags & CWHeight)
    {
        XtSetArg(warg[n], XtNminHeight, 0); n++;
        XtSetArg(warg[n], XtNmaxHeight, 32767); n++;
    }
    if (n)
        XtSetValues(_shell, warg, n);
}

void MXtShellRestrictResize(Widget _shell, int _flags)
{
    Widget child;
    Arg warg[4];
    int n;
    Dimension width, height;

    n = 0;
    XtSetArg(warg[n], XtNwidth, &width); n++;
    XtSetArg(warg[n], XtNheight, &height); n++;
    XtGetValues(child, warg, n);

    child = MXtGetShellChild(_shell);

    n = 0;
    if (_flags & CWWidth)
    {
        XtSetArg(warg[n], XtNminWidth, width); n++;
        XtSetArg(warg[n], XtNmaxWidth, width); n++;
    }
    if (_flags & CWHeight)
    {
        XtSetArg(warg[n], XtNminHeight, height); n++;
        XtSetArg(warg[n], XtNmaxHeight, height); n++;
    }
    if (n)
        XtSetValues(_shell, warg, n);
}

static void dialogNoResizeCB(Widget shell, XtPointer flag, XtPointer _nothing)
{
    int noSize = (int)flag;
    MXtShellRestrictResize(shell, noSize);
}

Have you ever needed to restrict the size of a dialog? Usually I restrict the size of the dialogs whenever it contains no text fields. If a dialog has a text field I think it is a good idea to allow the user to resize the window so they can see as much of the entry as they desire. By adding the callback:

noSize = CWWidth;
XtAddCallback(shell, XtNpopupCallback, (XtCallbackProc)dialogNoResizeCB, (XtPointer)noSize);

you can restrict the dialog to being the child's preferred width. (The same is possible by setting the CWHeight flag.) This way when the dialog is about to popup, it check the child's size, and sets the XtNminWidth and XtNmaxWidth resources equal to one another, the only ICCCM sanctioned way of restricting a dialogs dimensions If I need to resize the dialog, simply call MXtShellAllowResize().


void MXtShellCenter(Widget _shell, XtPointer _remove)
{
    int remove;
    Position x, y;
    Widget child;
    Arg warg[2];
    Dimension width, height;
    int n;

    remove = (int)_remove;

    child = XtGetShellChild(_shell);

    if (child == (Widget)NULL)
        child = _shell;

    n = 0;
    XtSetArg(warg[n], XtNwidth, &width); n++;
    XtSetArg(warg[n], XtNheight, &height); n++;
    XtGetValues(child, warg, n);

    x = DisplayWidth(XtDisplay(_shell), DefaultScreen(XtDisplay(_shell)))/2 - width/2;
    y = DisplayHeight(XtDisplay(_shell), DefaultScreen(XtDisplay(_shell)))/2 - height/2;

    n = 0;
    XtSetArg(warg[n], XtNx, x); n++;
    XtSetArg(warg[n], XtNy, y); n++;
    XtSetValues(_shell, warg, n);

    if (remove)
        XtRemoveCallback(_shell, XtNpopupCallback, (XtCallbackProc)MXtShellCenter, (XtPointer)_remove);
}
Want to center a dialog? No problem, use MXtShellCenter. After creating the dialog add:
XtAddCallback(parent, XtNpopupCallback, (XtCallbackProc)MXtShellCenter, (XtPointer)TRUE);
The closure argument specifies whether the shell should always be centered when popped up (like in an "About Box"), or whether the dialog should stay wherever the user places it after its initial appearance. You can easily extend this routine to do other initial placements such as under the pointer or corner placements.
Boolean MXtShellVisible(Widget _w)
{
    XWindowAttributes wa;
    if (!XtIsRealized(_w))
        return FALSE;

    if (!XtIsSubclass(_w, shellWidgetClass))
        _w = XtGetShell(_w);

    XGetWindowAttributes(XtDisplay(_w), XtWindow(_w), &wa);

    return (wa.map_state == IsViewable);
}

If you need to test whether a window is actually visible on the screen use MXtShellVisible(). Want to force your users to see your startup screen? Put MXtShellVisible() into a loop with the dialog as the Widget argument (be sure the widget is realized!). A more useful idea would be to eliminate any intense drawing when your application is obscured or checking to see if a status dialog is visible on the screen.


A Useful Pixmap Allocation Routine

static Boolean PixmapAllocError = False ;

static int pixmapErrorHandler(Display *dpy, XErrorEvent *error)
{
        PixmapAllocError = True;
}

/* smart create pixmap function: checks for error in creation */

Pixmap MXtCreatePixmap(Display *dpy, Drawable drawable, unsigned width, unsigned height, unsigned depth)
{
    Pixmap thePixmap;
    Drawable root;
    int x, y;
    unsigned int rw, rh,bw,rd;
    XErrorHandler   oldHandler = NULL;

    if (width < 0)
        width = 1;

    if (height < 0)
        height = 1;

    PixmapAllocError = False;
    /* install our error handler */
    oldHandler = XSetErrorHandler(pixmapErrorHandler);
    /* attempt to create pixmap */
    thePixmap = XCreatePixmap(dpy, drawable, width, height, depth);
    XGetGeometry(dpy, thePixmap, &root, &x, &y, &rw, &rh, &bw, &rd);
    /* wait to see if server allocated pixmap */
    XSync(dpy, False);
    /* restore standard error handler */
    XSetErrorHandler(oldHandler);

    return  PixmapAllocError? (Pixmap)0 : thePixmap;
}

XCreatePixmap() can fail. Again, XCreatePixmap() can fail. Unfortunately XCreatePixmap() does not fail immediately, and that latency can cause severe problems if you are not prepared for it. However XCreatePixmap() always returns, the error comes later. MXtCreatePixmap() creates the pixmap, install an error handler, and then calls XGetGeometry() forcing a round trip to the server, allowing any error event to arrive and be caught. If the error occurs the routine returns 0, and you can deal with it appropriately.


These are just a few routines that I have used time, and time again. I hope that they, or some derivative, can be as useful to you as they are to me.