From: argv@zander.z-code.com (Dan Heller)
Newsgroups: comp.windows.x.motif
Subject: Re: Percent of work done popup?
Date: 11 Dec 92 18:45:35 GMT
Reply-To: argv@z-code.z-code.com
Distribution: inet
Organization: The Internet

> On Dec  2,  5:27pm, Kurt Overberg wrote:
> I remember hearing something about a "Percent Of Work Completed" 
> requester in some X Journal.  Does anyone have any information about 
> this article?  I would be VERY interested in reading it.  It
> is on-line somewhere?  Could someone mail me a copy of it?
> Thanks...

I wrote the article in the November/December issue of The X Journal. It
is based on code from my book, The Motif Programming Manual (ORA Vol 6).

Here's the program from that article:

/* progress.c
 * Written by Dan Heller -- Copyright Z-Code Software Corporation.
 * Use and redistribution of this demonstration program is hereby granted
 * without license fee provided this copyright message is not removed.
 * This program is built for the Motif toolkit (at least version 1.1).
 *
 * This program is a demonstration of a typical scenario where an application
 * wants to go into a long processing loop and needs to perform four main
 * features:
 *    1) display timeout cursors on all application windows where the user
 *       cannot interact using button or keyboard presses;
 *    2) keep the display updated, even though the application can
 *       not return to the main application loop (XtAppMainLoop);
 *    3) allow the user to click on a "stop" button to terminate the task; and
 *    4) provide "work-in-progress" feedback to indicate how much of the
 *	 task has been completed.
 *
 * While the dialog is up, we need to process events, but only those that
 * occur in the Stop button, or any that might require repainting any of
 * the windows in rest of the application.
 *
 * Since the user is not supposed to interact with any other dialogs, we
 * actually intend to "throw away" all button and keyboard events that
 * might happen anywhere outside of the progress dialog.
 *
 * This demonstration sets up a simple shell and a pushbutton widget that,
 * when pressed, immediately goes into its own loop.  This is the hypothetical
 * long-task loop that an application might go into.
 *
 * main() and get_busy() are stubs that would be replaced by a real
 * application; all other functions can be used "as is."
 */
#include <Xm/SelectioB.h>
#include <Xm/PushB.h>
#include <Xm/Scale.h>
#include <X11/cursorfont.h>

Widget shell, slider;

/* enumerated types for the kind of checking CheckForInterrupt() performs */
Boolean CheckForInterrupt(); /* check to see if user pressed stop button */
typedef enum {
    IntrStart,
    IntrCheck,
    IntrEnd
} IntrVal;

main(argc, argv)
int argc;
char *argv[];
{
    XtAppContext app;
    Widget button;
    XmString label;
    void get_busy();

    shell = XtVaAppInitialize(&app, "Demos",
        NULL, 0, &argc, argv, NULL, NULL);

    label = XmStringCreateSimple(
        "Boy, is *this* going to take a long time.");
    button = XtVaCreateManagedWidget("button",
        xmPushButtonWidgetClass, shell,
        XmNlabelString,          label,
        NULL);
    XmStringFree(label);
    XtAddCallback(button, XmNactivateCallback, get_busy, argv[1]);

    XtRealizeWidget(shell);
    XtAppMainLoop(app);
}

void
get_busy(widget)
Widget widget;
{
    int n, total = 20;

    CheckForInterrupt(IntrStart, 0, True);
    for (n = 0; n < total; n++) {
        if (CheckForInterrupt(IntrCheck, (n*100)/total, 0)) {
            puts("Interrupt!");
            break;
        }
        sleep(1); /* this should be replaced by client-specific code */
    }
    CheckForInterrupt(IntrEnd, 1, 0);
}

/* The interesting part of the program -- extract and use at will */
static Boolean stopped;  /* True when user wants to stop processing */
static Widget dialog;    /* WorkingDialog displayed when timed out */

/* TimeoutCursors() turns on the "watch" cursor over the application
 * to provide feedback for the user that he's going to be waiting
 * a while before he can interact with the appliation again.
 */
void
TimeoutCursors(on)
int on;
{
    static int locked;
    static Cursor cursor;
    extern Widget shell;
    XSetWindowAttributes attrs;
    Display *dpy = XtDisplay(shell);

    /* "locked" keeps track if we've already called the function.
     * This allows recursion and is necessary for most situations.
     */
    on? locked++ : locked--;
    if (locked > 1 || locked == 1 && on == 0)
        return; /* already locked and we're not unlocking */

    stopped = False; /* doesn't matter at this point; initialize */
    if (!cursor) /* make sure the timeout cursor is initialized */
        cursor = XCreateFontCursor(dpy, XC_watch);

    /* if "on" is true, then turn on watch cursor, otherwise, return
     * the shell's cursor to normal.
     */
    attrs.cursor = on? cursor : None;

    /* change the main application shell's cursor to be the timeout
     * cursor (or to reset it to normal).  If other shells exist in
     * this application, they will have to be listed here in order
     * for them to have timeout cursors too.
     */
    XChangeWindowAttributes(dpy, XtWindow(shell), CWCursor, &attrs);

    XFlush(dpy);
}


/* User Pressed the "Stop" button in dialog; set global "stopped" value. */
void
stop(dialog)
Widget dialog;
{
    stopped = True;
}

Boolean
CheckForInterrupt(val, n, interruptable)
IntrVal val;
int n, interruptable;
{
    extern Widget shell;
    Display *dpy = XtDisplay(shell);
    Window win = XtWindow(dialog);
    XEvent event;
    Arg args[1];
    XmString str;
    extern void stop();

    if (val == IntrOn) {
	Widget w;
        /* we're timing out, use a PromptDialog to display the fact that
	 * we're busy.  We use this because it is one of the few "default"
	 * Motif widgets that intelligently manages a client-supplied child.
	 * If the WorkingDialog (MessageDialog) worked properly, we'd use
	 * that.  If the process is interruptable, allow a "Stop" button.
	 * Else, remove all actions so the user can't stop the processing.
         */
        str = XmStringCreateSimple("Busy.  Please Wait.");
        XtSetArg(args[0], XmNselectionLabelString, str);
        dialog = XmCreatePromptDialog(shell, "Busy", args, 1);
        XmStringFree(str);
	str = XmStringCreateSimple("Percentage Completed");
	slider = XtVaCreateManagedWidget("slider", xmScaleWidgetClass, dialog,
	    XmNminimum,     0,
	    XmNmaximum,     100,
	    XmNtitleString, str,
	    XmNvalue,       0,
	    XmNshowValue,   True,
	    XmNresizable,   True,
	    XmNorientation, XmHORIZONTAL,
	    NULL);
	XmStringFree(str);
        XtUnmanageChild(
            XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON));
        XtUnmanageChild(
            XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT));
        if (interruptable) {
            str = XmStringCreateSimple("Stop");
            XtVaSetValues(dialog, XmNcancelLabelString, str, NULL);
            XmStringFree(str);
            XtAddCallback(dialog, XmNcancelCallback, stop, NULL);
        } else
            XtUnmanageChild(
                XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
        XtUnmanageChild(
            XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
        XtManageChild(dialog);
	TimeoutCursors(True);
	return 0;
    } else /* !on */ {
	/* we're turning off the timeout-cursors.
         * Get rid of all button and keyboard events that occured
         * during the time out.  The user shouldn't have done anything
         * during this time, so flush for button and keypress events.
         */
        while (XCheckMaskEvent(dpy,
                ButtonPressMask | ButtonReleaseMask | ButtonMotionMask
                | PointerMotionMask | KeyPressMask, &event)) {
            /* do nothing */;
        }
        XtDestroyWidget(dialog);
	TimeoutCursors(False);
	return 0;
    }

    /* percentage -- multiply by 100 to get real value */
    XtVaSetValues(slider, XmNvalue, n, NULL);

    /* Make sure all our requests get to the server */
    XFlush(dpy);

    /* Let motif process all pending exposure events for us. */
    XmUpdateDisplay(shell);

    /* Check the event loop for events in the dialog ("Stop"?) */
    while (XCheckMaskEvent(dpy,
            ButtonPressMask | ButtonReleaseMask | ButtonMotionMask |
            PointerMotionMask | KeyPressMask | KeyReleaseMask,
            &event)) {
        /* got an "interesting" event. */
        if (event.xany.window == win)
            XtDispatchEvent(&event); /* it's in our dialog.. */
        else /* uninteresting event--throw it away and sound bell */
            XBell(dpy, 50);
    }
    /* If an "interesting" event took place (ie. the user pressed the
     * stop button), then the stop() callback would have been invoked,
     * setting the "stopped" value to 1.  return that value here.
     */
    return stopped;
}

  --dan

Newsgroups: comp.windows.x.motif