Back to index

tetex-bin  3.0
message-window.c
Go to the documentation of this file.
00001 /*------------------------------------------------------------
00002 message-window.c: message popups for xdvi.
00003 
00004 Permission is hereby granted, free of charge, to any person obtaining a copy
00005 of this software and associated documentation files (the "Software"), to
00006 deal in the Software without restriction, including without limitation the
00007 rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
00008 sell copies of the Software, and to permit persons to whom the Software is
00009 furnished to do so, subject to the following conditions:
00010 
00011 The above copyright notice and this permission notice shall be included in
00012 all copies or substantial portions of the Software.
00013 
00014 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
00015 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
00016 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
00017 IN NO EVENT SHALL PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE
00018 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
00019 OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
00020 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
00021 
00022 ------------------------------------------------------------*/
00023 
00024 
00025 
00026 /*
00027 
00028 ============================================
00029 Suggested Policy for using the GUI messages:
00030 ============================================
00031 
00032 - Use the statusline for shorter messages, a message window for more
00033   important messages or such where you'd like to give further help info
00034   (see the `helptext' argument of popup_message()). When in doubt,
00035   prefer the statusline (excessive use of popup windows is a nuisance
00036   for the user).
00037 
00038 - Don't use any of the GUI messages to report internal workings of
00039   the program; for important internal information, there should be a
00040   debugging setting to print it to stderr. Use the GUI messages
00041   only in situations such as the following:
00042 
00043   - to give the user feedback on actions that (s)he initiated
00044 
00045   - to indicate that an internal action causes a delay perceptible
00046     by the user (as a rough guide: a delay of more than half a second)
00047 
00048   - to report situations that might require new actions by the user.
00049 
00050 */
00051 
00052 #include <ctype.h>
00053 
00054 #include "xdvi-config.h"
00055 #include "xdvi.h"
00056 #include "string-utils.h"
00057 
00058 /* Xaw specific stuff */
00059 #include <X11/Intrinsic.h>
00060 #include <X11/StringDefs.h>
00061 #ifdef MOTIF
00062 # include <Xm/DialogS.h>
00063 # include <Xm/MessageB.h>
00064 # include <Xm/PushB.h>
00065 # include <Xm/Label.h>
00066 # include <Xm/Form.h>
00067 # include <Xm/MenuShell.h>
00068 # include <Xm/Protocols.h>
00069 # include <Xm/AtomMgr.h>
00070 #else
00071 # include <X11/Xaw/Paned.h>
00072 # include <X11/Xaw/Box.h>
00073 # include <X11/Xaw/MenuButton.h>
00074 # include <X11/Xaw/SimpleMenu.h>
00075 # include <X11/Xaw/Sme.h>
00076 # include <X11/Xaw/SmeBSB.h>
00077 # include <X11/Xaw/AsciiText.h>
00078 # include <X11/Xaw/Dialog.h>
00079 #endif
00080 
00081 #include "kpathsea/c-vararg.h"
00082 #include "xdvi.h"
00083 #include "util.h"
00084 #include "string-utils.h"
00085 #include "x_util.h"
00086 #include "message-window.h"
00087 
00088 /* have no more than MAX_POPUPS open simultaneously */
00089 #define MAX_POPUPS 10
00090 
00091 /* offset for cascading popups */
00092 #define POPUP_OFFSET ((my_popup_num * 20))
00093 
00094 #ifdef MOTIF
00095 /* wrap messages after MSG_WRAP_LEN characters at whitespace */
00096 #define MSG_WRAP_LEN 60
00097 #endif
00098 
00099 /* array of active popups: */
00100 static int g_popup_array[MAX_POPUPS];
00101 
00102 static Atom WM_DELETE_WINDOW;
00103 
00104 /* arrays for saving the widgets of multiple popups;
00105  * same index as in g_popup_array:
00106  */
00107 #ifdef MOTIF
00108 static Widget popup_window[MAX_POPUPS], dialog[MAX_POPUPS];
00109 #else
00110 static Widget popup_window[MAX_POPUPS], message_box[MAX_POPUPS],
00111     message_text[MAX_POPUPS], message_paned[MAX_POPUPS],
00112     message_ok[MAX_POPUPS], message_help[MAX_POPUPS], message_not_ok[MAX_POPUPS];
00113 #endif
00114 
00115 /* map popupMessageT's to strings/motif dialog elements */
00116 static struct message_map {
00117     char *window_title;
00118 #ifdef MOTIF
00119     int motif_msg_type;
00120 #endif
00121 } my_msg_map[] = {
00122     { "Xdvi Question"
00123 #ifdef MOTIF
00124       , XmDIALOG_QUESTION
00125 #endif
00126     }, /* MSG_QUESTION */
00127     { "Xdvi Help"
00128 #ifdef MOTIF
00129       , XmDIALOG_INFORMATION
00130 #endif
00131     }, /* MSG_HELP */
00132     { "Xdvi Info"
00133 #ifdef MOTIF
00134       , XmDIALOG_INFORMATION
00135 #endif
00136     }, /* MSG_INFO */
00137     { "Xdvi Warning"
00138 #ifdef MOTIF
00139       , XmDIALOG_WARNING
00140 #endif
00141     }, /* MSG_WARN */
00142     { "Xdvi Error"
00143 #ifdef MOTIF
00144       , XmDIALOG_ERROR
00145 #endif
00146     }, /* MSG_ERR */
00147 };
00148 
00149 struct ok_or_cancel_cb {
00150     message_cbT callback; /* callback function */
00151     XtPointer arg;   /* arg for callback function */
00152 };
00153 
00154 struct pre_ok_or_cancel_cb {
00155     pre_message_cbT callback; /* callback function */
00156     XtPointer arg;   /* arg for callback function */
00157 };
00158 
00159 static struct ok_or_cancel_cb yes_callbacks[MAX_POPUPS];
00160 static struct ok_or_cancel_cb no_callbacks[MAX_POPUPS];
00161 static struct ok_or_cancel_cb cancel_callbacks[MAX_POPUPS];
00162 static struct pre_ok_or_cancel_cb pre_callbacks[MAX_POPUPS];
00163 
00164 
00165 static void
00166 popdown_cancel(Widget w, XEvent *event, String *params, Cardinal *num_params)
00167 {
00168     size_t idx;
00169     
00170     UNUSED(w);
00171     UNUSED(event);
00172     UNUSED(params);
00173     UNUSED(num_params);
00174 
00175     ASSERT(*num_params == 1, "Wrong number of parameters in callback");
00176     idx = strtoul(*params, (char **)NULL, 10);
00177 
00178     /* First call pre_message_cb with window widget ID
00179      * as additional parameter.
00180      */
00181     if (pre_callbacks[idx].callback != NULL) {
00182        pre_callbacks[idx].callback(popup_window[idx], pre_callbacks[idx].arg);
00183     }
00184     
00185     /* Then pop down window and mark its position as free, then
00186      * invoke the OK callback.  The reason for this is that the callback
00187      * may need to wait for open windows.
00188      */
00189     XtPopdown(popup_window[idx]);
00190     XtDestroyWidget(popup_window[idx]);
00191     g_popup_array[idx] = 0;
00192     XSync(DISP, True);
00193 
00194     /* invoke the callback if present */
00195     if (cancel_callbacks[idx].callback != NULL) {
00196        cancel_callbacks[idx].callback(cancel_callbacks[idx].arg);
00197     }
00198 }
00199 
00200 #ifndef MOTIF
00201 static void
00202 xaw_popdown(Widget w, XEvent *event, String *params, Cardinal *num_params)
00203 {
00204     size_t idx;
00205 
00206     UNUSED(w);
00207     UNUSED(event);
00208     UNUSED(params);
00209     UNUSED(num_params);
00210 
00211     ASSERT(*num_params == 1, "Wrong number of parameters in callback");
00212     idx = strtoul(*params, (char **)NULL, 10);
00213 
00214     /*
00215       NOTE: first pop down window and mark its position as free, then
00216       invoke the callback.  The reason for this is that the callback
00217       may need to wait for open windows.
00218     */
00219     XtPopdown(popup_window[idx]);
00220     XtDestroyWidget(popup_window[idx]);
00221     g_popup_array[idx] = 0;
00222     XSync(DISP, True);
00223 }
00224 
00225 #endif
00226 
00227 static XtActionsRec popdown_actions[] = {
00228     {"close-popup-cancel",  popdown_cancel },
00229 #if !MOTIF
00230     {"WM_popdown",          popdown_cancel },
00231     {"close-popup",         xaw_popdown      },
00232 #endif
00233 };
00234 
00235 static void
00236 ok_action(Widget w, XtPointer client_data, XtPointer call_data)
00237 {
00238     int idx = -1;
00239     
00240     UNUSED(call_data);
00241     
00242 #if MOTIF
00243     UNUSED(client_data);
00244     XtVaGetValues(w, XmNuserData, &idx, NULL);
00245     ASSERT(idx >= 0, "Couldn't get idx from XmNuserData!");
00246 #else
00247     UNUSED(w);
00248     idx = (int)client_data;
00249 #endif
00250     
00251 #if DEBUG
00252     fprintf(stderr, "quit_action called for popup %d\n", idx);
00253 #endif
00254     ASSERT(idx >= 0 && idx < MAX_POPUPS, "Invalid widget index in ok_action()");
00255 
00256     /* First call pre_message_cb with window widget ID
00257      * as additional parameter.
00258      */
00259     if (pre_callbacks[idx].callback != NULL) {
00260        pre_callbacks[idx].callback(popup_window[idx], pre_callbacks[idx].arg);
00261     }
00262     
00263     /* Then pop down window and mark its position as free, then
00264      * invoke the OK callback.  The reason for this is that the callback
00265      * may need to wait for open windows.
00266      */
00267     XtPopdown(popup_window[idx]);
00268     XtDestroyWidget(popup_window[idx]);
00269     g_popup_array[idx] = 0;
00270     XSync(DISP, True);
00271 
00272     if (yes_callbacks[idx].callback != NULL) {
00273        yes_callbacks[idx].callback(yes_callbacks[idx].arg);
00274     }
00275 }
00276 
00277 /*------------------------------------------------------------
00278  *  help_action
00279  *
00280  *  Arguments:
00281  *     Widget w, XtPointer call_data
00282  *            - (ignored)
00283  *     XtPointer client_data
00284  *            - the help string
00285  *
00286  *  Returns:
00287  *     void
00288  *
00289  *  Purpose:
00290  *     Callback for the `Help' button; opens another window
00291  *     containing the help text. The new window won't have
00292  *     another `Help' button.
00293  *------------------------------------------------------------*/
00294 
00295 static void
00296 help_action(Widget w, XtPointer client_data, XtPointer call_data)
00297 {
00298     UNUSED(call_data);
00299 
00300     /* open another window with the help text */
00301     popup_message(get_matching_parent(w, globals.widgets.top_level, "message_popup", NULL), MSG_HELP, NULL, client_data);
00302 }
00303 
00304 
00305 /*
00306  * Callback for cancel button in choice_dialog.
00307 */
00308 static void
00309 cancel_action(Widget w, XtPointer client_data, XtPointer call_data)
00310 {
00311     int idx = -1;
00312     
00313     UNUSED(call_data);
00314 
00315 #if MOTIF
00316     /* for motif, the index is in XmNuserData */
00317     UNUSED(client_data);
00318     if (strcmp(XtName(w), Xdvi_MESSAGE_SHELL_NAME) == 0) {
00319        /* invoked by the WM, get the messagebox child */
00320        Widget child;
00321        if (get_widget_by_name(&child, w, Xdvi_MESSAGE_DIALOG_NAME, True)) {
00322            XtVaGetValues(child, XmNuserData, &idx, NULL);
00323        }
00324     }
00325     else {
00326        XtVaGetValues(w, XmNuserData, &idx, NULL);
00327     }
00328     ASSERT(idx >= 0, "Couldn't get idx from XmNuserData!");
00329 #else
00330     UNUSED(w);
00331     idx = (int)client_data;
00332 #endif
00333     ASSERT(idx >= 0 && idx < MAX_POPUPS, "Invalid widget index in cancel_action()");
00334 
00335     /* First call pre_message_cb with window widget ID
00336      * as additional parameter.
00337      */
00338     if (pre_callbacks[idx].callback != NULL) {
00339        pre_callbacks[idx].callback(popup_window[idx], pre_callbacks[idx].arg);
00340     }
00341     
00342     /* Then pop down window and mark its position as free, then
00343      * invoke the OK callback.  The reason for this is that the callback
00344      * may need to wait for open windows.
00345      */
00346     XtPopdown(popup_window[idx]);
00347     XtDestroyWidget(popup_window[idx]);
00348     g_popup_array[idx] = 0;
00349     XSync(DISP, True);
00350 
00351     /* invoke the callback if present */
00352     if (cancel_callbacks[idx].callback != NULL) {
00353        cancel_callbacks[idx].callback(cancel_callbacks[idx].arg);
00354     }
00355 }
00356 
00357 #if MOTIF
00358 static void
00359 not_ok_action(Widget w, XtPointer client_data, XtPointer call_data)
00360 {
00361     /* Note: unmanage the parent of the button */
00362     int idx = -1;
00363 
00364     UNUSED(client_data);
00365 
00366     UNUSED(call_data);
00367     XtVaGetValues(w, XmNuserData, &idx, NULL);
00368     ASSERT(idx >= 0, "Couldn't get idx from XmNuserData!");
00369     ASSERT(idx >= 0 && idx < MAX_POPUPS, "Invalid widget index in ok_action()");
00370 
00371     /* First call pre_message_cb with window widget ID
00372      * as additional parameter.
00373      */
00374     if (pre_callbacks[idx].callback != NULL) {
00375        pre_callbacks[idx].callback(popup_window[idx], pre_callbacks[idx].arg);
00376     }
00377     
00378     /* Then pop down window and mark its position as free, then
00379      * invoke the OK callback.  The reason for this is that the callback
00380      * may need to wait for open windows.
00381      */
00382     XtPopdown(popup_window[idx]);
00383     XtDestroyWidget(popup_window[idx]);
00384     g_popup_array[idx] = 0;
00385     XSync(DISP, True);
00386 
00387     if (no_callbacks[idx].callback != NULL) {
00388        no_callbacks[idx].callback(no_callbacks[idx].arg);
00389     }
00390 }
00391 #endif /* MOTIF */
00392 
00393 /*------------------------------------------------------------
00394  *  create_dialogs
00395  *
00396  *  Arguments:
00397  *     Widget toplevel - parent for the dialog window
00398  *
00399  *     helptext      - if not-NULL, create an additional
00400  *                     `help' button
00401  *
00402  *     cnt           - number of current popup dialog
00403  *
00404  *  Returns:
00405  *     void
00406  *
00407  *  Purpose:
00408  *     Create message dialog widgets
00409  *------------------------------------------------------------*/
00410 
00411 static Widget
00412 create_dialogs(popupMessageSizeHintT size,
00413               Widget parent,
00414               int cnt,
00415               const char *helptext,
00416               pre_message_cbT pre_cb, XtPointer arg,
00417               const char *yes_button, message_cbT yes_cb, XtPointer yes_arg,
00418               const char *no_button, message_cbT no_cb, XtPointer no_arg,
00419               const char *cancel_button, message_cbT cancel_cb, XtPointer cancel_arg)
00420 {
00421     Widget new_popup_window;
00422     char *translations_str = NULL;
00423 #ifdef MOTIF
00424     Widget new_dialog;
00425     UNUSED(size);
00426 #else
00427     char *key_translations_str = NULL;
00428     int msg_w = 400, msg_h = 100;
00429     Widget new_message_paned, new_message_text, new_message_box, new_message_ok,
00430        new_message_help = 0, new_message_not_ok;
00431     XtTranslations wm_translations, key_translations;
00432 #endif
00433 
00434     /* save callbacks to global arrays */
00435     pre_callbacks[cnt].callback = pre_cb;
00436     pre_callbacks[cnt].arg = arg;
00437     
00438     yes_callbacks[cnt].callback = yes_cb;
00439     yes_callbacks[cnt].arg = yes_arg;
00440 
00441     no_callbacks[cnt].callback = no_cb;
00442     no_callbacks[cnt].arg = no_arg;
00443 
00444     cancel_callbacks[cnt].callback = cancel_cb;
00445     cancel_callbacks[cnt].arg = cancel_arg;
00446 
00447     XtAddActions(popdown_actions, XtNumber(popdown_actions));
00448 
00449 #ifndef MOTIF
00450     /* get index into WM_popdown arg */
00451     translations_str = get_string_va("<Message>WM_PROTOCOLS: WM_popdown(%d)", cnt);
00452     wm_translations = XtParseTranslationTable(translations_str);
00453     free(translations_str);
00454 #endif
00455 
00456     if (!XtIsRealized(globals.widgets.top_level)) {
00457        /* If toplevel window hasn't been realized yet, create a new toplevel shell
00458           (otherwise, setting visual/color map wouldn't work); use same application names
00459           so that resource settings will also apply to this window.
00460        */
00461        new_popup_window = XtVaAppCreateShell("xdvi", "Xdvi",
00462                                          transientShellWidgetClass, DISP,
00463                                          NULL);
00464     }
00465     else {
00466        new_popup_window = XtVaCreatePopupShell(Xdvi_MESSAGE_SHELL_NAME,
00467 #ifdef MOTIF
00468                                           xmDialogShellWidgetClass, parent,
00469                                           XmNdeleteResponse, XmDO_NOTHING, /* we'll take care of that ourselves */
00470 #else
00471                                           transientShellWidgetClass, parent,
00472                                           XtNx, 60,
00473                                           XtNy, 80,
00474                                           XtNtranslations, wm_translations,
00475                                           XtNaccelerators, G_accels_cr,
00476 #endif
00477                                           XtNtransientFor, parent,
00478                                           XtNmappedWhenManaged, False,
00479                                           NULL);
00480     }
00481     
00482 #ifdef MOTIF
00483 
00484     WM_DELETE_WINDOW = XmInternAtom(XtDisplay(new_popup_window), "WM_DELETE_WINDOW", False);
00485     XmAddWMProtocolCallback(new_popup_window, WM_DELETE_WINDOW, cancel_action, NULL);
00486     
00487     /* We also need to override the default ESC binding to use our internal
00488        housekeeping functions */
00489     translations_str = get_string_va("#override\n<Key>osfCancel:close-popup-cancel(%d)", cnt);
00490 /*      { */
00491 /*     XtTranslations xlats; */
00492 /*     char *translation_str = get_string_va("<Key>osfCancel:close-popup-cancel(%d)", cnt); */
00493 /*     xlats = XtParseTranslationTable(translation_str); */
00494 /*     free(translation_str); */
00495 /*     XtOverrideTranslations(new_dialog, xlats); */
00496 /*      } */
00497 
00498     new_dialog = XtVaCreateWidget(Xdvi_MESSAGE_DIALOG_NAME, xmMessageBoxWidgetClass, new_popup_window,
00499                               XmNdialogType, XmDIALOG_WARNING, /* default */
00500                               XmNtraversalOn, True,
00501                               XmNhighlightOnEnter, True,
00502                               XmNuserData, cnt,
00503                               XmNtranslations, XtParseTranslationTable(translations_str),
00504                               NULL);
00505     free(translations_str);
00506     XtAddCallback(new_dialog, XmNokCallback, ok_action, NULL);
00507 
00508     if (no_button != NULL) {
00509        Arg args[4];
00510        Widget b;
00511        XmString b_str = XmStringCreateLocalized((char *)no_button);
00512        XtSetArg(args[0], XmNlabelString, b_str);
00513        b = XmCreatePushButton(new_dialog, "no_button", args, 1);
00514        XtAddCallback(b, XmNactivateCallback, not_ok_action, NULL);
00515        XtManageChild(b);
00516     }
00517     
00518     if (cancel_button != NULL) {
00519        XmString cancel_label = XmStringCreateLtoR((char *)cancel_button, G_charset);
00520        XtVaSetValues(XmMessageBoxGetChild(new_dialog, XmDIALOG_CANCEL_BUTTON),
00521                     XmNlabelString, cancel_label, NULL);
00522        XmStringFree(cancel_label);
00523        XtAddCallback(new_dialog, XmNcancelCallback, cancel_action, NULL);
00524     }
00525     else {
00526        XtUnmanageChild(XmMessageBoxGetChild(new_dialog, XmDIALOG_CANCEL_BUTTON));
00527     }
00528     XtInstallAllAccelerators(new_dialog,
00529                           XmMessageBoxGetChild(new_dialog, XmDIALOG_OK_BUTTON));
00530 
00531     if (helptext != NULL) {
00532        XtAddCallback(new_dialog, XmNhelpCallback, help_action, (XtPointer)helptext);
00533     }
00534     else {
00535        XtUnmanageChild(XmMessageBoxGetChild(new_dialog, XmDIALOG_HELP_BUTTON));
00536     }
00537 
00538     if (yes_button != NULL) { /* change `OK' button label */
00539        XmString yes_label;
00540        yes_label = XmStringCreateLtoR((char *)yes_button, G_charset);
00541        XtVaSetValues(XmMessageBoxGetChild(new_dialog, XmDIALOG_OK_BUTTON),
00542                     XmNlabelString, yes_label, NULL);
00543        XmStringFree(yes_label);
00544     }
00545     
00546     /* insert the new widgets into the global arrays */
00547     dialog[cnt] = new_dialog;
00548 
00549 #else /* MOTIF */
00550     switch (size) {
00551     case SIZE_SMALL:
00552        msg_w = 300;
00553        msg_h = 100;
00554        break;
00555     case SIZE_MEDIUM:
00556        msg_w = 430;
00557        msg_h = 160;
00558        break;
00559     case SIZE_LARGE:
00560        msg_w = 450;
00561        msg_h = 180;
00562        break;
00563     }
00564     WM_DELETE_WINDOW = XInternAtom(XtDisplay(new_popup_window), "WM_DELETE_WINDOW", False);
00565     
00566     new_message_paned = XtVaCreateManagedWidget("message_paned", panedWidgetClass, new_popup_window,
00567                                           XtNaccelerators, G_accels_cr,
00568                                           NULL);
00569 
00570     new_message_text = XtVaCreateManagedWidget("message_text", asciiTextWidgetClass, new_message_paned,
00571 /*                                        XtNheight, 100, */
00572 /*                                        XtNwidth, 400, */
00573                                           XtNwidth, msg_w,
00574                                           XtNheight, msg_h,
00575                                           /* wrap horizontally instead of scrolling
00576                                           * TODO: this won't work for the first widget instance?
00577                                           */
00578                                           XtNwrap, XawtextWrapWord,
00579                                           XtNscrollVertical, XAW_SCROLL_ALWAYS,
00580                                           XtNeditType, XawtextRead,
00581                                           XtNinput, True,
00582                                           XtNdisplayCaret, False,
00583                                           XtNleftMargin, 5,
00584                                           XtNaccelerators, G_accels_cr,
00585                                           NULL);
00586 
00587     /* box for the OK/Cancel button */
00588     new_message_box = XtVaCreateManagedWidget("message_box", formWidgetClass, new_message_paned,
00589                                          /* resizing by user isn't needed */
00590                                          XtNshowGrip, False,
00591                                          XtNdefaultDistance, 6, /* some padding */
00592                                          /* resizing the window shouldn't influence this box,
00593                                           * but only the text widget
00594                                           */
00595                                          XtNskipAdjust, True,
00596                                          XtNaccelerators, G_accels_cr,
00597                                          NULL);
00598 
00599     new_message_ok = XtVaCreateManagedWidget(yes_button == NULL ? "OK" : yes_button,
00600                                         commandWidgetClass, new_message_box,
00601                                         XtNtop, XtChainTop,
00602                                         XtNbottom, XtChainBottom,
00603                                         XtNleft, XtChainLeft,
00604                                         XtNright, XtChainLeft,
00605                                         XtNaccelerators, G_accels_cr,
00606                                         NULL);
00607     /* add quit_action callback for the "OK" button */
00608     /* FIXME: how to make accelerators be accepted by new_popup_window as well? */
00609     key_translations_str = get_string_va("<Key>q:close-popup-cancel(%d)\n"
00610                                     "<Key>Return:close-popup-cancel(%d)\n"
00611                                     "<Key>Escape:close-popup-cancel(%d)\n",
00612                                     cnt, cnt, cnt);
00613     key_translations = XtParseTranslationTable(key_translations_str);
00614     free(key_translations_str);
00615     XtOverrideTranslations(new_popup_window, key_translations);
00616     XtOverrideTranslations(new_message_paned, key_translations);
00617     XtOverrideTranslations(new_message_text, key_translations);
00618     
00619     XtInstallAllAccelerators(new_message_box, new_message_ok);
00620     XtAddCallback(new_message_ok, XtNcallback, ok_action, (XtPointer)cnt);
00621 
00622     /* we create additional buttons in any case,
00623        to make the sizing more consistent */
00624     new_message_help = XtVaCreateManagedWidget("Help", commandWidgetClass, new_message_box,
00625                                           XtNtop, XtChainTop,
00626                                           XtNfromHoriz, new_message_ok,
00627                                           XtNbottom, XtChainBottom,
00628                                           XtNleft, XtChainRight,
00629                                           XtNright, XtChainRight,
00630                                           XtNaccelerators, G_accels_cr,
00631                                           NULL);
00632     message_help[cnt] = new_message_help;
00633     
00634     /* add cancel button */
00635     new_message_not_ok = XtVaCreateManagedWidget(cancel_button == NULL ? "Cancel" : cancel_button,
00636                                            commandWidgetClass, new_message_box,
00637                                            XtNtop, XtChainTop,
00638                                            XtNfromHoriz, new_message_ok,
00639                                            XtNbottom, XtChainBottom,
00640                                            XtNleft, helptext == NULL ? XtChainRight : XtChainLeft,
00641                                            XtNright, helptext == NULL ? XtChainRight : XtChainLeft,
00642                                            XtNaccelerators, G_accels_cr,
00643                                            NULL);
00644     message_not_ok[cnt] = new_message_not_ok;
00645 
00646     if (no_button != NULL) {
00647        ASSERT(0, "third button not yet implemented in Xaw!!!");
00648     }
00649 
00650     
00651     { /* set all buttons to same size */
00652        Dimension w1, w2, w3, max;
00653        XtVaGetValues(new_message_ok, XtNwidth, &w1, NULL);
00654        XtVaGetValues(new_message_help, XtNwidth, &w2, NULL);
00655        XtVaGetValues(new_message_not_ok, XtNwidth, &w3, NULL);
00656        max = w1;
00657        if (w2 > max)
00658            max = w2;
00659        if (w3 > max)
00660            max = w3;
00661        XtVaSetValues(new_message_ok, XtNwidth, max, NULL);
00662        XtVaSetValues(new_message_help, XtNwidth, max, NULL);
00663        XtVaSetValues(new_message_not_ok, XtNwidth, max, NULL);
00664     }
00665     /* if helptext argument is not-NULL, add help_action callback,
00666        else unmanage help button */
00667     if (helptext != NULL) {
00668        XtAddCallback(new_message_help, XtNcallback, help_action, (XtPointer)helptext);
00669     }
00670     else {
00671        XtUnmanageChild(new_message_help);
00672     }
00673 
00674     if (cancel_button != NULL) {
00675        XtAddCallback(new_message_not_ok, XtNcallback, cancel_action, (XtPointer)cnt);
00676     }
00677     else {
00678        XtUnmanageChild(new_message_not_ok);
00679     }
00680     /* insert the new widgets into the global arrays */
00681     message_box[cnt] = new_message_box;
00682     message_paned[cnt] = new_message_paned;
00683     message_text[cnt] = new_message_text;
00684     message_ok[cnt] = new_message_ok;
00685 
00686 #endif /* MOTIF */
00687     popup_window[cnt] = new_popup_window;
00688 
00689     return new_popup_window;
00690 }
00691 
00692 
00693 /*
00694  * Popup a window with wrapped text in it.
00695  * For Motif, the text is explicitly wrapped inside this method.
00696  */
00697 static Widget
00698 internal_popup_window(Widget parent,
00699                     popupMessageSizeHintT size,
00700                     popupMessageT type,
00701                     int x_coord, int y_coord,
00702                     const char *helptext,
00703                     char *msg_buf,
00704 #ifndef MOTIF
00705                     const char *xaw_ret_action_str,
00706 #endif
00707                     pre_message_cbT pre_cb, XtPointer arg,
00708                     const char *yes_button, message_cbT yes_cb, XtPointer yes_arg,
00709                     const char *no_button, message_cbT no_cb, XtPointer no_arg,
00710                     const char *cancel_button, message_cbT cancel_cb, XtPointer cancel_arg)
00711 {
00712     char win_title[64];
00713     int my_popup_num = 0;
00714 #ifdef MOTIF
00715     XmString str;
00716 #endif
00717     Widget ret;
00718 
00719     ASSERT(type < (sizeof my_msg_map / sizeof my_msg_map[0]), "too few elements in my_msg_map");
00720     sprintf(win_title, my_msg_map[type].window_title);
00721 
00722 #if DEBUG
00723     fprintf(stderr, "internal_popup_window called with prompt: \"%s\"\n", msg_buf);
00724 #endif
00725 
00726     if (globals.widgets.top_level == 0) {
00727        /* If toplevel window hasn't been create yet, dump messages to STDERR and return.
00728           All text must be passed as arguments to fprintf (NOT puts), since they are
00729           supposed to be printf-format strings (i.e. with doubled `%' to escape them)
00730        */
00731        fprintf(stderr, "\n%s:\n", my_msg_map[type].window_title);
00732        fprintf(stderr, msg_buf);
00733        fprintf(stderr, "\n");
00734        if (helptext) {
00735            fprintf(stderr, "---------- helptext ----------\n");
00736            fprintf(stderr, helptext);
00737            fprintf(stderr, "\n---------- end of helptext ----------\n");
00738        }
00739        return NULL;
00740     }
00741     /* search for first free position in g_popup_array */
00742     while (my_popup_num < MAX_POPUPS && (g_popup_array[my_popup_num] == 1)) {
00743        my_popup_num++;
00744     }
00745     if (my_popup_num == MAX_POPUPS) {
00746        /* already enough popups on screen, just dump it to stderr */
00747        fprintf(stderr, "%s:\n", win_title);
00748        fprintf(stderr, msg_buf);
00749        /* Note: If a mad function continues to open popups, this will
00750         * stop after MAX_POPUPS, but open a new window for each
00751         * window the user pops down. Maybe we ought to do something
00752         * about this.
00753         */
00754        return NULL;
00755     }
00756     else {
00757        /* mark it as non-free */
00758        g_popup_array[my_popup_num] = 1;
00759     }
00760 #if DEBUG
00761     fprintf(stderr, "first free position in g_popup_array: %d\n", my_popup_num);
00762 #endif
00763 
00764     /* just to make sure ... */
00765     if (parent == NULL)
00766        parent = globals.widgets.top_level;
00767 
00768     /* create a new set of widgets for the additional popup. */
00769     ret = create_dialogs(size, parent,
00770                       my_popup_num,
00771                       helptext,
00772                       pre_cb, arg,
00773                       yes_button, yes_cb, yes_arg,
00774                       no_button, no_cb, no_arg,
00775                       cancel_button, cancel_cb, cancel_arg);
00776 #ifdef MOTIF
00777     XtVaSetValues(popup_window[my_popup_num], XmNtitle, win_title, NULL);
00778     XtVaSetValues(dialog[my_popup_num], XmNdialogType, my_msg_map[type].motif_msg_type, NULL);
00779     { /* wrap message at space before MSG_WRAP_LEN */
00780        char *testwrap = msg_buf;
00781        int ctr;
00782        for (ctr = 0; *testwrap++; ctr++) {
00783            if (*testwrap == '\n') {
00784               ctr = 0;
00785            }
00786            else if (ctr > MSG_WRAP_LEN) {
00787               size_t before_len = 0, after_len = 0;
00788               char *before_ptr, *after_ptr;
00789               before_ptr = after_ptr = testwrap;
00790               /* try to find shortest sequence before or after point to wrap at;
00791                  this seems to give the most pleasing results.
00792                */
00793               while (before_ptr > msg_buf && !isspace((int)*--before_ptr)) {
00794                   before_len++;
00795               }
00796               while (*after_ptr != '\0' && !isspace((int)*++after_ptr)) {
00797                   after_len++;
00798               }
00799 
00800               if (before_len < after_len && isspace((int)*before_ptr)) {
00801                   /* use last in sequence of multiple spaces */
00802                   while (isspace((int)*++before_ptr)) { ; }
00803                   /* back up, and wrap */
00804                   *--before_ptr = '\n';
00805                   ctr = 0;
00806               }
00807               else if (isspace((int)*after_ptr)) {
00808                   /* use last in sequence of multiple spaces */
00809                   while (isspace((int)*++after_ptr)) { ; }
00810                   /* back up, and wrap */
00811                   *--after_ptr = '\n';
00812                   ctr = 0;
00813               }
00814            }
00815        }
00816     }
00817     str = XmStringCreateLtoR((char *)msg_buf, G_charset);
00818     XtVaSetValues(dialog[my_popup_num],
00819                 XmNmessageString, str,
00820                 XmNtraversalOn, True,
00821                 XmNhighlightOnEnter, True,
00822                 NULL);
00823     XmStringFree(str);
00824 
00825     XtManageChild(dialog[my_popup_num]);
00826     
00827     if (x_coord > 0 && y_coord > 0) {
00828        position_window(XtParent(dialog[my_popup_num]), (Position)x_coord, (Position)y_coord);
00829     }
00830     
00831     XtPopup(XtParent(dialog[my_popup_num]), XtGrabNone);
00832 /*      XtPopup(XtParent(dialog[my_popup_num]), XtGrabExclusive); */
00833 
00834 #else /* MOTIF */
00835 
00836     /* add a binding of xaw_ret_action_str to <Return> to relevant widgets.
00837        The callbacks (xaw_ret_action_str) are responsible for parsing the
00838        passed arguments (pointers, or empty arguments).       
00839     */
00840     if (xaw_ret_action_str != NULL) {
00841        XtTranslations xlats;
00842        char *translation_str;
00843 
00844        if (yes_arg != NULL)
00845            translation_str = get_string_va("<Key>Return:close-popup(%d)%s(%p)",
00846                                        my_popup_num, xaw_ret_action_str, yes_arg);
00847        else
00848            translation_str = get_string_va("<Key>Return:close-popup(%d)%s()",
00849                                        my_popup_num, xaw_ret_action_str);
00850        
00851        xlats = XtParseTranslationTable(translation_str);
00852        free(translation_str);
00853        XtOverrideTranslations(popup_window[my_popup_num], xlats);
00854        XtOverrideTranslations(message_paned[my_popup_num], xlats);
00855        XtOverrideTranslations(message_text[my_popup_num], xlats);
00856     }
00857     
00858     XtVaSetValues(popup_window[my_popup_num], XtNtitle, win_title, NULL);
00859     XtVaSetValues(message_text[my_popup_num], XtNstring, msg_buf, NULL);
00860     XtRealizeWidget(popup_window[my_popup_num]);
00861 
00862     XSetWMProtocols(XtDisplay(popup_window[my_popup_num]), XtWindow(popup_window[my_popup_num]),
00863                   &WM_DELETE_WINDOW, 1);
00864 
00865     if (x_coord <= 0 || y_coord <= 0)
00866        center_window(popup_window[my_popup_num], parent);
00867     else
00868        position_window(popup_window[my_popup_num], (Position)x_coord, (Position)y_coord);
00869     
00870     if (my_popup_num > 0) {
00871        /* some window managers position new windows exactly above the
00872           existing one; to prevent this, move it with some offset
00873           from the previous one: */
00874        Position x = 0, y = 0;
00875        XtVaGetValues(popup_window[my_popup_num-1], XtNx, &x, XtNy, &y, NULL);
00876        XtVaSetValues(popup_window[my_popup_num], XtNx, x + POPUP_OFFSET, XtNy, y + POPUP_OFFSET, NULL);
00877 
00878     }
00879     XtPopup(popup_window[my_popup_num], XtGrabNone);
00880 /*      XtPopup(XtParent(popup_window[my_popup_num]), XtGrabExclusive); */
00881     if (XtIsManaged(message_not_ok[my_popup_num]) && XtIsManaged(message_help[my_popup_num])) {
00882        /* center the help button. This is something of a sham, since it won't
00883           survive resizing; but in general most users won't resize dialogs ;-) */
00884        Position x1, x2, bw;
00885        int w, dist;
00886        
00887        XtVaGetValues(message_ok[my_popup_num], XtNx, &x1, XtNwidth, &w, XtNborderWidth, &bw, NULL);
00888        XtVaGetValues(message_help[my_popup_num], XtNx, &x2, NULL);
00889        /* following formula is measured, not calculated -
00890           I have no idea why it's e.g. 2 * w, not 1.5 * w ... */
00891        dist = (x2 - x1 - 2 * w) / 2 - 2 * bw;
00892        XtVaSetValues(message_not_ok[my_popup_num], XtNhorizDistance, dist, NULL);
00893     }
00894     
00895 #endif /* MOTIF */
00896     return ret;
00897 }
00898 
00899 
00900 /*------------------------------------------------------------
00901  *  popup_message
00902  *
00903  *  Arguments:
00904  *     popupMessageT - info, warning, error etc; see message-window.h for details
00905  *
00906  *     char *helptext
00907  *           - if not-null, this will add a `Help'
00908  *            button to the message widget that pops
00909  *            up another message widget containing
00910  *            <helptext>.
00911  *
00912  *
00913  *     char *msg, ...
00914  *           - format string followed by a variable
00915  *            number of arguments to be formatted.
00916  *
00917  *  Returns:
00918  *     void
00919  *
00920  *  Purpose:
00921  *     Pop up a message window containing <msg>.
00922  *     If there are already n popups open, will open
00923  *     a new one unless n >= MAX_POPUPS.
00924  *------------------------------------------------------------*/
00925 
00926 Widget
00927 popup_message(Widget parent, popupMessageT type, const char *helptext, const char *format, ...)
00928 {
00929     char *msg_buf = NULL;
00930     Widget w;
00931 
00932     XDVI_GET_STRING_ARGP(msg_buf, format);
00933 
00934     w = internal_popup_window(parent,
00935                            SIZE_SMALL,
00936                            type,
00937                            -1, -1, /* just center it */
00938                            helptext, msg_buf,
00939 #ifndef MOTIF
00940                            NULL,
00941 #endif
00942                            /* no special callbacks here */
00943                            NULL, NULL,
00944                            NULL, NULL, NULL,
00945                            NULL, NULL, NULL,
00946                            NULL, NULL, NULL);
00947     free(msg_buf);
00948     return w;
00949 }
00950 
00951 Widget
00952 popup_message_sized(Widget parent,
00953                   popupMessageT type,
00954                   popupMessageSizeHintT sizehint,
00955                   const char *helptext,
00956                   const char *format, ...)
00957 {
00958     char *msg_buf = NULL;
00959     Widget w;
00960     
00961     XDVI_GET_STRING_ARGP(msg_buf, format);
00962 
00963     w = internal_popup_window(parent,
00964                            sizehint,
00965                            type,
00966                            -1, -1, /* just center it */
00967                            helptext, msg_buf,
00968 #ifndef MOTIF
00969                            NULL,
00970 #endif
00971                            /* empty callbacks */
00972                            NULL, NULL,
00973                            NULL, NULL, NULL,
00974                            NULL, NULL, NULL,
00975                            NULL, NULL, NULL);
00976     free(msg_buf);
00977     return w;
00978 }
00979 
00980 Widget
00981 positioned_popup_message(Widget parent,
00982                       popupMessageT type,
00983                       int x, int y,
00984                       const char *helptext, const char *format, ...)
00985 {
00986     char *msg_buf = NULL;
00987     Widget w;
00988     
00989     XDVI_GET_STRING_ARGP(msg_buf, format);
00990 
00991     w = internal_popup_window(parent,
00992                            SIZE_SMALL,
00993                            type,
00994                            x, y, /* position at these coordinates */
00995                            helptext, msg_buf,
00996 #ifndef MOTIF
00997                            NULL,
00998 #endif
00999                            /* empty callbacks */
01000                            NULL, NULL,
01001                            NULL, NULL, NULL,
01002                            NULL, NULL, NULL,
01003                            NULL, NULL, NULL);
01004     free(msg_buf);
01005     return w;
01006 }
01007 
01008 Widget
01009 choice_dialog(Widget parent,
01010              popupMessageT type,
01011              const char *helptext,
01012 #ifndef MOTIF
01013              const char *xaw_ret_action_str,
01014 #endif
01015              pre_message_cbT pre_cb, XtPointer arg,
01016              const char *ok_label, message_cbT ok_cb, XtPointer ok_arg,
01017              const char *cancel_label, message_cbT cancel_cb, XtPointer cancel_arg,
01018              const char *format, ...)
01019 {
01020     char *msg_buf = NULL;
01021     Widget w;
01022     
01023     XDVI_GET_STRING_ARGP(msg_buf, format);
01024 
01025     w = internal_popup_window(parent,
01026                            SIZE_SMALL,
01027                            type,
01028                            -1, -1, /* just center it */
01029                            helptext, msg_buf,
01030 #ifndef MOTIF
01031                            xaw_ret_action_str,
01032 #endif
01033                            pre_cb, arg,
01034                            ok_label, ok_cb, ok_arg,
01035                            NULL, NULL, NULL,
01036                            cancel_label, cancel_cb, cancel_arg);
01037     free(msg_buf);
01038     return w;
01039 }
01040 
01041 #if MOTIF
01042 Widget
01043 choice3_dialog(Widget parent,
01044               popupMessageT type,
01045               const char *helptext,
01046               pre_message_cbT pre_cb, XtPointer arg,
01047               const char *yes_label, message_cbT yes_cb, XtPointer yes_arg,
01048               const char *no_label, message_cbT no_cb, XtPointer no_arg,
01049               const char *cancel_label, message_cbT cancel_cb, XtPointer cancel_arg,
01050               const char *format, ...)
01051 {
01052     char *msg_buf = NULL;
01053     Widget w;
01054     
01055     XDVI_GET_STRING_ARGP(msg_buf, format);
01056 
01057     w = internal_popup_window(parent,
01058                            SIZE_SMALL,
01059                            type,
01060                            -1, -1, /* just center it */
01061                            helptext, msg_buf,
01062                            pre_cb, arg,
01063                            yes_label, yes_cb, yes_arg,
01064                            no_label, no_cb, no_arg,
01065                            cancel_label, cancel_cb, cancel_arg);
01066     free(msg_buf);
01067     return w;
01068 }
01069 #endif
01070 
01071 Widget
01072 choice_dialog_sized(Widget parent,
01073                   popupMessageT type,
01074                   popupMessageSizeHintT sizehint,
01075                   const char *helptext,
01076 #ifndef MOTIF
01077                   const char *xaw_ret_action_str,
01078 #endif
01079                   pre_message_cbT pre_cb, XtPointer arg,
01080                   const char *ok_label, message_cbT ok_cb, XtPointer ok_arg,
01081                   const char *cancel_label, message_cbT cancel_cb, XtPointer cancel_arg,
01082                   const char *format, ...)
01083 {
01084     char *msg_buf = NULL;
01085     Widget w;
01086     
01087     XDVI_GET_STRING_ARGP(msg_buf, format);
01088 
01089     w = internal_popup_window(parent,
01090                            sizehint,
01091                            type,
01092                            -1, -1, /* just center it */
01093                            helptext, msg_buf,
01094 #ifndef MOTIF
01095                            xaw_ret_action_str,
01096 #endif
01097                            pre_cb, arg,
01098                            ok_label, ok_cb, ok_arg,
01099                            NULL, NULL, NULL,
01100                            cancel_label, cancel_cb, cancel_arg);
01101     free(msg_buf);
01102     return w;
01103 }
01104 
01105 Widget
01106 positioned_choice_dialog(Widget parent,
01107                       popupMessageT type,
01108                       int x_pos, int y_pos,
01109                       const char *helptext,
01110 #ifndef MOTIF
01111                       const char *xaw_ret_action_str,
01112 #endif
01113                       pre_message_cbT pre_cb, XtPointer arg,
01114                       const char *ok_label, message_cbT ok_cb, XtPointer ok_arg,
01115                       const char *cancel_label, message_cbT cancel_cb, XtPointer cancel_arg,
01116                       const char *format, ...)
01117 {
01118     char *msg_buf = NULL;
01119     Widget w;
01120     
01121     XDVI_GET_STRING_ARGP(msg_buf, format);
01122 
01123     w = internal_popup_window(parent,
01124                            SIZE_SMALL,
01125                            type,
01126                            x_pos, y_pos,
01127                            helptext, msg_buf,
01128 #ifndef MOTIF
01129                            xaw_ret_action_str,
01130 #endif
01131                            pre_cb, arg,
01132                            ok_label, ok_cb, ok_arg,
01133                            NULL, NULL, NULL,
01134                            cancel_label, cancel_cb, cancel_arg);
01135     free(msg_buf);
01136     return w;
01137 }
01138 
01139 void
01140 warn_overstrike(void)
01141 {
01142     static Boolean warned_overstrike = False;
01143 
01144     if (!warned_overstrike) {
01145        popup_message(globals.widgets.top_level,
01146                     MSG_WARN,
01147                     /* helptext */
01148                     "Greyscaling is running in copy mode; this will cause overstrike characters to "
01149                     "appear incorrectly, and may result in poor display quality.  "
01150                     "Possible fixes are:\n"
01151                     "- Use the ``-thorough'' command-line option.\n"
01152                     "- Quit some other color-hungry applications (e.g. Netscape).\n"
01153                     "- Use the ``-install'' command-line option.\n"
01154                     "See the section ``GREYSCALING AND COLORMAPS'' in the "
01155                     "xdvi manual page for more details.",
01156                     /* text */
01157                     "Couldn't allocate enough colors - expect low display quality.");
01158        warned_overstrike = True;
01159     }
01160 }
01161 
01162 Boolean
01163 is_message_window(Widget w)
01164 {
01165     int i;
01166     for (i = 0; i < MAX_POPUPS; i++) {
01167        if (w == popup_window[i])
01168            return True;
01169     }
01170     return False;
01171 }
01172 
01173 Boolean
01174 kill_message_window(Widget w)
01175 {
01176     int i;
01177     for (i = 0; i < MAX_POPUPS; i++) {
01178        if (g_popup_array[i] != 0 && XtIsRealized(popup_window[i]) && w == popup_window[i]) {
01179            g_popup_array[i] = 0;
01180            XtPopdown(popup_window[i]);
01181            XtDestroyWidget(popup_window[i]);
01182            XSync(DISP, True);
01183            return True;
01184        }
01185     }
01186     return False;
01187 }
01188 
01189 /*
01190   Raise any popups that currently exist; return True iff such popups
01191   exist, else False.
01192 */
01193 Boolean
01194 raise_message_windows(void)
01195 {
01196     int i;
01197     Boolean have_popups = False;
01198     
01199     for (i = 0; i < MAX_POPUPS; i++) {
01200        if (g_popup_array[i] != 0 && XtIsRealized(popup_window[i])) {
01201            XRaiseWindow(DISP, XtWindow(popup_window[i]));
01202            have_popups = True;
01203        }
01204     }
01205     
01206     return have_popups;
01207 }