/* Written by Dan Heller and Paula Ferguson. * Copyright 1994, O'Reilly & Associates, Inc. * Permission to use, copy, and modify this program without * restriction is hereby granted, as long as this copyright * notice appears in each copy of the program source code. * This program is freely distributable without licensing fees and * is provided without guarantee or warrantee expressed or implied. * This program is -not- in the public domain. */ /* busy.c -- demonstrate how to use a WorkingDialog and to process * only important events. e.g., those that may interrupt the * task or to repaint widgets for exposure. Set up a simple shell * and a widget that, when pressed, immediately goes into its own * loop. Set a timeout cursor on the shell and pop up a WorkingDialog. * Then enter loop and sleep for one second ten times, checking between * each interval to see if the user clicked the Stop button or if * any widgets need to be refreshed. Ignore all other events. * * main() and get_busy() are stubs that would be replaced by a real * application; all other functions can be used as is. */ #include <Xm/MessageB.h> #include <Xm/PushB.h> #include <X11/cursorfont.h> Widget shell; void TimeoutCursors(); Boolean CheckForInterrupt(); main(argc, argv) int argc; char *argv[]; { XtAppContext app; Widget button; XmString label; void get_busy(); XtSetLanguageProc (NULL, NULL, NULL); shell = XtVaAppInitialize (&app, "Demos", NULL, 0, &argc, argv, NULL, NULL); label = XmStringCreateLocalized ("Press Here To Start A Long Task"); button = XtVaCreateManagedWidget ("button", xmPushButtonWidgetClass, shell, XmNlabelString, label, NULL); XmStringFree (label); XtAddCallback (button, XmNactivateCallback, get_busy, NULL); XtRealizeWidget (shell); XtAppMainLoop (app); } void get_busy(widget, client_data, call_data) Widget widget; XtPointer client_data; XtPointer call_data; { int n; TimeoutCursors (True, True); for (n = 0; n < 10; n++) { sleep (1); if (CheckForInterrupt ()) { puts ("Interrupt!"); break; } } if (n == 10) puts ("Done"); TimeoutCursors (False, False); } /* The interesting part of the program -- extract and use at will */ static Boolean stopped; /* True when user wants to stop task */ static Widget dialog; /* WorkingDialog displayed */ /* TimeoutCursors() -- turns on the watch cursor over the application * to provide feedback for the user that she's going to be waiting * a while before she can interact with the application again. */ void TimeoutCursors(on, interruptable) Boolean on, interruptable; { static int locked; static Cursor cursor; extern Widget shell; XSetWindowAttributes attrs; Display *dpy = XtDisplay (shell); XEvent event; Arg args[5]; int n; XmString str; extern void stop_(); /* "locked" keeps track if we've already called the function. * This allows recursion and is necessary for most situations. */ if (on) locked++; else locked--; if (locked > 1 || locked == 1 && on == 0) return; /* already locked and we're not unlocking */ stopped = False; if (!cursor) 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); if (on) { /* we're timing out, put up a WorkingDialog. If the process * is interruptable, allow a "Stop" button. Otherwise, remove * all actions so the user can't stop the processing. */ n = 0; str = XmStringCreateLocalized ("Busy -- Please Wait."); XtSetArg (args[n], XmNmessageString, str); n++; dialog = XmCreateWorkingDialog (shell, "busy", args, n); XmStringFree (str); XtUnmanageChild (XmMessageBoxGetChild (dialog, XmDIALOG_OK_BUTTON)); XtUnmanageChild (XmMessageBoxGetChild (dialog, XmDIALOG_HELP_BUTTON)); if (interruptable) { str = XmStringCreateLocalized ("Stop"); XtVaSetValues (dialog, XmNcancelLabelString, str, NULL); XmStringFree (str); XtAddCallback (dialog, XmNcancelCallback, stop_, NULL); } else XtUnmanageChild (XmMessageBoxGetChild (dialog, XmDIALOG_CANCEL_BUTTON)); XtManageChild (dialog); } else { /* 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. * KeyRelease events are not discarded because accelerators * require the corresponding release event before normal input * can continue. */ while (XCheckMaskEvent (dpy, ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | PointerMotionMask | KeyPressMask, &event)) { /* do nothing */; } XtDestroyWidget (dialog); } } /* stop() -- user pressed the "Stop" button in dialog. */ void stop_(dialog, client_data, call_data) Widget dialog; XtPointer client_data; XtPointer call_data; { stopped = True; } /* CheckForInterrupt() -- check events in event queue and process * the interesting ones. */ Boolean CheckForInterrupt() { extern Widget shell; Display *dpy = XtDisplay (shell); Window win = XtWindow (dialog); XEvent event; /* 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, &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); } return stopped; }