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.