[comp.windows.x] Creating a Message Box

arthur@media.uucp (Art Poley) (08/16/90)

Can anyone show me exactly how one goes about creating a message box 
under Motif that will not allow an application to continue until the
user replies?  I'm trying to display some message to a user that must 
be acknowledged before the underlying application resumes processing.
For example displaying a message such as,

"File already exists.  Overwrite existing file?"

To which the user may reply Yes or No.  At this point the application
must wait for the user to reply before continuing with the operation.
I've looked into Message Boxes and the various variations that are 
offered, but don't know how to wait for a reply.  I'd prefer not to
set the sensitivity of the application to False while awaiting a 
reply.  I have also looked into Popup shells and working with the
Grab settings, but to no avail.

Any help that you can offer in this matter would be much appreciated.
Thanks ahead of time.

 - Art -
-- 
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Art Poley - Media Cybernetics		Phone: (301)495-3305
Internet: arthur%media@uunet.uu.net 	UUCP: {uunet,hqda-ai}!media!arthur

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

argv@turnpike.Eng.Sun.COM (Dan Heller) (08/17/90)

In article <1990Aug16.025738.13408@media.uucp> arthur@media.uucp (Art Poley) writes:
> Can anyone show me exactly how one goes about creating a message box 
> under Motif that will not allow an application to continue until the
> user replies?  I'm trying to display some message to a user that must 
> be acknowledged before the underlying application resumes processing.
> For example displaying a message such as,
> 
> "File already exists.  Overwrite existing file?"
> 
> To which the user may reply Yes or No.  At this point the application
> must wait for the user to reply before continuing with the operation.

Yet another candidate for the "most frequently asked questions."

The answer is remarkably simple in design, altho your particular
implemenatation may require more work.

Simply -- create a dialog box with XmNdialogStyle to be either
XmAPPLICATION_MODAL or XmSYSTEM_MODAL.  I recommend the former
unless you have a very severe system error where you don't want
the user to interact with any application *at all* -- not just
your particular application (e.g., use system-modal sparingly).
Both modes will provide you with the ability to do what you
want to do.

Define a local variable:
    int answer = 0;

Next, set the callback functions for XmNokCallback, XmNcancelCallback
and XmNhelpCallback to go to *one* function, say: response().

        XtAddCallback(dialog, XmNokCallback, response, &answer);
        XtAddCallback(dialog, XmNcancelCallback, response, &answer);
        XtAddCallback(dialog, XmNhelpCallback, response, &answer);

The "client_data" for the function (in each case) is &answer.  The
idea is that the function itself is going to change the value of
the variable when one of the buttons is selected.  Thus:

void
response(w, answer, reason)
Widget w;
int *answer;
XmAnyCallbackStruct *reason;
{
    switch (reason->reason) {
        case XmCR_OK:
            *answer = 1;
            break;
        case XmCR_CANCEL:
            *answer = 2;
            break;
        case XmCR_HELP:
            *answer = 3;
            break;
        default:
            return;
    }
    XtDestroyWidget(XtParent(w));
}

Next, back to your original function, Manage the dialog box, pop it
up, and loop until the value of "answer" has changed.  Thus:

    XtManageChild(dialog);
    XtPopup(XtParent(dialog), XtGrabNone);

    /* while the user hasn't provided an answer, simulate XtMainLoop.
     * The answer changes as soon as the user selects one of the
     * buttons and the callback routine changes its value.  Don't
     * break loop until XtPending() also returns False to assure
     * widget destruction.
     */
    while (answer == AskUnknown || XtPending()) {
        XEvent event;
        XtNextEvent(&event);
        XtDispatchEvent(&event);
    }

You may or may not be using application contexts -- keep that in mind.

And there you have it.  After the loop exits, you have your answer.
You have three possible answers -- if you want to provide help,
then you've got your help answer.  However, I have found that for such
questions, many times they are clear enough that you don't need help.
So what do you do with that extra button (the help button)?  I use it
to provide the user with the option to choose between "yes" "no" and
"cancel".  Consider this scenario: Do you want to overwrite file?
Well, a yes answer is obvious.  A no answer can imply: no, I don't
want to overwrite the file, append to the file.  Or use another file..
It depends on the context of your application.  The "cancel" button
would imply that the user doesn't want to do anything -- just abort
the whole operation.  Again, you have to decide what is appropriate
for your application.  You should use the resources XmNokLabelString,
XmNcancelLabelString and XmNhelpLabelString to set these labels to
be "Yes", "No", and "Cancel" or whatever you want.

Another thing to note -- the destroy function destroys the dialog
shell.  This is to save on memory and other resources (free that which
you don't use).  If you generlize this routine to be callable from
anywhere, you might wish to reconsider whether you want to destroy
the widget or just unmanage it.  If you just unmanage it, be sure to
avoid recreating it every time the function is called.

Also don't forget that the user can "close" the widget using the
window manager menu.  Here, you have the option of programmatically
turning off the menu, removing the window manager decorations, or
resetting what function is called when the "close" button is selected.

As I said -- fundamentally the problem is simple.  In reality, the
problem is more complicated because you have to consider all those 
things that the user can do or what you want to provide for the user.

Example code for this type of behavior is available in the WidgetWrap
library I provided for the R3 distribution under contrib/widgets.
This is also the predecessor for the new R4 varargs interface funcs.
The examples are not specific to motif -- they use any arbitrary widget
set.

--
dan
----------------------------------------------------
O'Reilly && Associates   argv@sun.com / argv@ora.com
Opinions expressed reflect those of the author only.

jordan@Morgan.COM (Jordan Hayes) (08/18/90)

Actually, although Dan's suggestion is decent, i'd rather do it with
the callback taking the action directly, rather than setting a local
variable and doing your own main loop (what if you also have other
input sources?  why block them?).  When the callback gets called, go
open the file.

/jordan

argv@turnpike.Eng.Sun.COM (Dan Heller) (08/19/90)

In article <1511@s5.Morgan.COM> jordan@Morgan.COM (Jordan Hayes) writes:
> Actually, although Dan's suggestion is decent, i'd rather do it with
> the callback taking the action directly, rather than setting a local
> variable and doing your own main loop (what if you also have other
> input sources?  why block them?).  When the callback gets called, go
> open the file.

I can't see the "blocking of other input sources" as being a problem or
having any negative effect here.  If you post a dialog box and return,
your input sources are not blocked because the XtMainLoop() routine will
continue to get events and read input, etc.  Writing your own XtMainLoop
in the middle of another routine (a callback) has no affect on that at
all -- those other input sources remain unblocked.  Furthermore, since
the dialog box is *modal* the user has no ability to interact with
the application anyway.  In fact, your own XtMainLoop() has no real
negative side effects here.

Once you understand that your own XtMainLoop() is no different from
returning to the first (original) XtMainLoop() called in the program,
you begin to understand how this feature can be xploited to solve
many logistical problems within an application design.


For example, consider is a button in the main application labled "save"
that when the user selects it, its corresponding callback routine
saves the current data to a file specified in some text widget or
file selection box somewhere.

    if (file_exists(filename) == False) {
	/* ask user to he wants to create it */
	answer = AskUser("File does not exist, create it?");
	if (answer != YES)
	    return;
    } else {
	answer = AskUser("File Exists, Overwrite?");
	if (answer == CANCEL)
	    return;
	mode = (answer == YES)? "w" : "a";
    }
    if (!(fp = fopen(filename, mode))) {
	post_error("Can't open filename for %s",
	    *mode == 'w'? "writing" : "appending");
	return;
    }

In the above scenario, it is clear that the flow of control is much
easier to understand (as a reader) and easier to control (for the
programmer).  Maintenance is also easier because if had we used the
design of return-to-main-loop-after-each-dialog-is-popped-up, then
you would have to write a large amount of callback routines for each
dialog box and each question answered.  If we used your suggestion
of "open the file within the 'answer' callback routine", then you
must write a specialized routine for each scenario that may come up.

Code size and complexity is substantially reduced by _generalizing_
the problem into one or two small routines that can be used in an
arbitrary way.
The only routines required are the AskUser() routine and the response()
routine I outlined in my previous posting. AskUser() is the "public"
function that takes a string as a "question" and puts it into an
InformationDialog (if you're using Motif) and sets the okCallback and
cancelCallback to be response().  That is the "static" function used only
by AskUser() that sets the "answer" variable which is ultimately returned
to AskUser(), which is ultimately returned to the code displayed above).

[note: post_error() above just needs to post a dialog and return; it does
not need to simulate XtMainLoop() like AskUser does.]
--
dan
----------------------------------------------------
O'Reilly && Associates   argv@sun.com / argv@ora.com
Opinions expressed reflect those of the author only.