1 /*------------------------------------------------------------
2   message-window.c: message popups for xdvi.
3 
4   Permission is hereby granted, free of charge, to any person obtaining a copy
5   of this software and associated documentation files (the "Software"), to
6   deal in the Software without restriction, including without limitation the
7   rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8   sell copies of the Software, and to permit persons to whom the Software is
9   furnished to do so, subject to the following conditions:
10 
11   The above copyright notice and this permission notice shall be included in
12   all copies or substantial portions of the Software.
13 
14   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17   IN NO EVENT SHALL PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE
18   LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19   OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20   WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 
22   ------------------------------------------------------------*/
23 
24 
25 
26 /*
27 
28 ============================================
29 Suggested Policy for using the GUI messages:
30 ============================================
31 
32 - Use the statusline for shorter messages, a message window for more
33 important messages or such where you'd like to give further help info
34 (see the `helptext' argument of popup_message()). When in doubt,
35 prefer the statusline (excessive use of popup windows is a nuisance
36 for the user).
37 
38 - Don't use any of the GUI messages to report internal workings of
39 the program; for important internal information, there should be a
40 debugging setting to print it to stderr. Use the GUI messages
41 only in situations such as the following:
42 
43 - to give the user feedback on actions that (s)he initiated
44 
45 - to indicate that an internal action causes a delay perceptible
46 by the user (as a rough guide: a delay of more than half a second)
47 
48 - to report situations that might require new actions by the user.
49 
50 */
51 
52 #include <ctype.h>
53 
54 #include "xdvi-config.h"
55 #include "xdvi.h"
56 #include "string-utils.h"
57 
58     /* Xaw specific stuff */
59 #include <X11/Intrinsic.h>
60 #include <X11/StringDefs.h>
61 #ifdef MOTIF
62 # include <Xm/DialogS.h>
63 # include <Xm/MessageB.h>
64 # include <Xm/PushB.h>
65 # include <Xm/Label.h>
66 # include <Xm/Form.h>
67 # include <Xm/MenuShell.h>
68 # include <Xm/Protocols.h>
69 # include <Xm/AtomMgr.h>
70 #else
71 # include <X11/Xaw/Paned.h>
72 # include <X11/Xaw/Box.h>
73 # include <X11/Xaw/MenuButton.h>
74 # include <X11/Xaw/SimpleMenu.h>
75 # include <X11/Xaw/Sme.h>
76 # include <X11/Xaw/SmeBSB.h>
77 # include <X11/Xaw/AsciiText.h>
78 # include <X11/Xaw/Dialog.h>
79 #endif
80 
81 #include <stdarg.h>
82 #include "xdvi.h"
83 #include "util.h"
84 #include "string-utils.h"
85 #include "x_util.h"
86 #include "message-window.h"
87 
88     /* have no more than MAX_POPUPS open simultaneously */
89 #define MAX_POPUPS 10
90 
91     /* offset for cascading popups */
92 #define POPUP_OFFSET ((my_popup_num * 20))
93 
94 #ifdef MOTIF
95     /* wrap messages after MSG_WRAP_LEN characters at whitespace */
96 #define MSG_WRAP_LEN 60
97 #endif
98 
99     /* array of active popups: */
100     static int g_popup_array[MAX_POPUPS];
101 
102 static Atom WM_DELETE_WINDOW;
103 
104 /* arrays for saving the widgets of multiple popups;
105  * same index as in g_popup_array:
106  */
107 #ifdef MOTIF
108 static Widget popup_window[MAX_POPUPS], dialog[MAX_POPUPS];
109 #else
110 static Widget popup_window[MAX_POPUPS], message_box[MAX_POPUPS],
111     message_text[MAX_POPUPS], message_paned[MAX_POPUPS],
112     message_ok[MAX_POPUPS], message_help[MAX_POPUPS], message_not_ok[MAX_POPUPS];
113 #endif
114 
115 /* map popupMessageT's to strings/motif dialog elements */
116 static const struct message_map {
117     const char *window_title;
118 #ifdef MOTIF
119     int motif_msg_type;
120 #endif
121 } my_msg_map[] = {
122     { "Xdvi Question"
123 #ifdef MOTIF
124       , XmDIALOG_QUESTION
125 #endif
126     },
127     { "Xdvi Help"
128 #ifdef MOTIF
129       , XmDIALOG_INFORMATION
130 #endif
131     },
132     { "Xdvi Info"
133 #ifdef MOTIF
134       , XmDIALOG_INFORMATION
135 #endif
136     },
137     { "Xdvi Warning"
138 #ifdef MOTIF
139       , XmDIALOG_WARNING
140 #endif
141     },
142     { "Xdvi Error"
143 #ifdef MOTIF
144       , XmDIALOG_ERROR
145 #endif
146     },
147 };
148 
149 struct ok_or_cancel_cb {
150     message_cbT callback; /* callback function */
151     XtPointer arg;	/* arg for callback function */
152 };
153 
154 struct pre_ok_or_cancel_cb {
155     pre_message_cbT callback; /* callback function */
156     XtPointer arg;	/* arg for callback function */
157 };
158 
159 static struct ok_or_cancel_cb yes_callbacks[MAX_POPUPS];
160 static struct ok_or_cancel_cb no_callbacks[MAX_POPUPS];
161 static struct ok_or_cancel_cb cancel_callbacks[MAX_POPUPS];
162 static struct pre_ok_or_cancel_cb pre_callbacks[MAX_POPUPS];
163 
164 
165 static void
popdown_cancel(Widget w,XEvent * event,String * params,Cardinal * num_params)166 popdown_cancel(Widget w, XEvent *event, String *params, Cardinal *num_params)
167 {
168     size_t idx;
169 
170     UNUSED(w);
171     UNUSED(event);
172     UNUSED(params);
173     UNUSED(num_params);
174 
175     ASSERT(*num_params == 1, "Wrong number of parameters in callback");
176     idx = strtoul(*params, (char **)NULL, 10);
177 
178     /* First call pre_message_cb with window widget ID
179      * as additional parameter.
180      */
181     if (pre_callbacks[idx].callback != NULL) {
182 	pre_callbacks[idx].callback(popup_window[idx], pre_callbacks[idx].arg);
183     }
184 
185     /* Then pop down window and mark its position as free, then
186      * invoke the OK callback.  The reason for this is that the callback
187      * may need to wait for open windows.
188      */
189     XtPopdown(popup_window[idx]);
190     XtDestroyWidget(popup_window[idx]);
191     g_popup_array[idx] = 0;
192     XSync(DISP, True);
193 
194     /* invoke the callback if present */
195     if (cancel_callbacks[idx].callback != NULL) {
196 	cancel_callbacks[idx].callback(cancel_callbacks[idx].arg);
197     }
198 }
199 
200 #ifndef MOTIF
201 static void
xaw_popdown(Widget w,XEvent * event,String * params,Cardinal * num_params)202 xaw_popdown(Widget w, XEvent *event, String *params, Cardinal *num_params)
203 {
204     size_t idx;
205 
206     UNUSED(w);
207     UNUSED(event);
208     UNUSED(params);
209     UNUSED(num_params);
210 
211     ASSERT(*num_params == 1, "Wrong number of parameters in callback");
212     idx = strtoul(*params, (char **)NULL, 10);
213 
214     /*
215       NOTE: first pop down window and mark its position as free, then
216       invoke the callback.  The reason for this is that the callback
217       may need to wait for open windows.
218     */
219     XtPopdown(popup_window[idx]);
220     XtDestroyWidget(popup_window[idx]);
221     g_popup_array[idx] = 0;
222     XSync(DISP, True);
223 }
224 
225 #endif
226 
227 static XtActionsRec popdown_actions[] = {
228     {"close-popup-cancel",	popdown_cancel },
229 #if !MOTIF
230     {"WM_popdown",		popdown_cancel },
231     {"close-popup",		xaw_popdown	   },
232 #endif
233 };
234 
235 static void
ok_action(Widget w,XtPointer client_data,XtPointer call_data)236 ok_action(Widget w, XtPointer client_data, XtPointer call_data)
237 {
238     XtPointer p;
239     ptrdiff_t idx = -1;
240 
241     UNUSED(call_data);
242 
243 #if MOTIF
244     UNUSED(client_data);
245     XtVaGetValues(w, XmNuserData, &p, NULL);
246     idx = (ptrdiff_t)p;
247     ASSERT(idx >= 0, "Couldn't get idx from XmNuserData!");
248 #else
249     UNUSED(p);
250     UNUSED(w);
251     idx = (ptrdiff_t)client_data;
252 #endif
253 
254 #if DEBUG
255     fprintf(stderr, "ok_action called for popup %ld\n", idx);
256 #endif
257     ASSERT(idx >= 0 && idx < MAX_POPUPS, "Invalid widget index in ok_action()");
258 
259     /* First call pre_message_cb with window widget ID
260      * as additional parameter.
261      */
262     if (pre_callbacks[idx].callback != NULL) {
263 	pre_callbacks[idx].callback(popup_window[idx], pre_callbacks[idx].arg);
264     }
265 
266     /* Then pop down window and mark its position as free, then
267      * invoke the OK callback.  The reason for this is that the callback
268      * may need to wait for open windows.
269      */
270     XtPopdown(popup_window[idx]);
271     XtDestroyWidget(popup_window[idx]);
272     g_popup_array[idx] = 0;
273     XSync(DISP, True);
274 
275     if (yes_callbacks[idx].callback != NULL) {
276 	yes_callbacks[idx].callback(yes_callbacks[idx].arg);
277     }
278 }
279 
280 /*------------------------------------------------------------
281  *  help_action
282  *
283  *  Arguments:
284  *	Widget w, XtPointer call_data
285  *		- (ignored)
286  *	XtPointer client_data
287  *		- the help string
288  *
289  *  Returns:
290  *	void
291  *
292  *  Purpose:
293  *	Callback for the `Help' button; opens another window
294  *	containing the help text. The new window won't have
295  *	another `Help' button.
296  *------------------------------------------------------------*/
297 
298 static void
help_action(Widget w,XtPointer client_data,XtPointer call_data)299 help_action(Widget w, XtPointer client_data, XtPointer call_data)
300 {
301     UNUSED(call_data);
302 
303     /* open another window with the help text */
304     popup_message(get_matching_parent(w, globals.widgets.top_level,
305 	"message_popup", NULL), MSG_HELP, NULL, "%s", client_data);
306 }
307 
308 
309 /*
310  * Callback for cancel button in choice_dialog.
311  */
312 static void
cancel_action(Widget w,XtPointer client_data,XtPointer call_data)313 cancel_action(Widget w, XtPointer client_data, XtPointer call_data)
314 {
315     XtPointer p;
316     ptrdiff_t idx = -1;
317 
318     UNUSED(call_data);
319 
320 #if MOTIF
321     /* for motif, the index is in XmNuserData */
322     UNUSED(client_data);
323     if (strcmp(XtName(w), Xdvi_MESSAGE_SHELL_NAME) == 0) {
324 	/* invoked by the WM, get the messagebox child */
325 	Widget child;
326 	if (get_widget_by_name(&child, w, Xdvi_MESSAGE_DIALOG_NAME, True)) {
327 	    XtVaGetValues(child, XmNuserData, &p, NULL);
328 	    idx = (ptrdiff_t)p;
329 	}
330     }
331     else {
332 	XtVaGetValues(w, XmNuserData, &p, NULL);
333 	idx = (ptrdiff_t)p;
334     }
335     ASSERT(idx >= 0, "Couldn't get idx from XmNuserData!");
336 #else
337     UNUSED(p);
338     UNUSED(w);
339     idx = (ptrdiff_t)client_data;
340 #endif
341     ASSERT(idx >= 0 && idx < MAX_POPUPS, "Invalid widget index in cancel_action()");
342 
343     /* First call pre_message_cb with window widget ID
344      * as additional parameter.
345      */
346     if (pre_callbacks[idx].callback != NULL) {
347 	pre_callbacks[idx].callback(popup_window[idx], pre_callbacks[idx].arg);
348     }
349 
350     /* Then pop down window and mark its position as free, then
351      * invoke the OK callback.  The reason for this is that the callback
352      * may need to wait for open windows.
353      */
354     XtPopdown(popup_window[idx]);
355     XtDestroyWidget(popup_window[idx]);
356     g_popup_array[idx] = 0;
357     XSync(DISP, True);
358 
359     /* invoke the callback if present */
360     if (cancel_callbacks[idx].callback != NULL) {
361 	cancel_callbacks[idx].callback(cancel_callbacks[idx].arg);
362     }
363 }
364 
365 #if MOTIF
366 static void
not_ok_action(Widget w,XtPointer client_data,XtPointer call_data)367 not_ok_action(Widget w, XtPointer client_data, XtPointer call_data)
368 {
369     /* Note: unmanages the parent of the button */
370     XtPointer p;
371     ptrdiff_t idx = -1;
372 
373     UNUSED(client_data);
374 
375     UNUSED(call_data);
376     XtVaGetValues(w, XmNuserData, &p, NULL);
377     idx = (ptrdiff_t)p;
378     ASSERT(idx >= 0, "Couldn't get idx from XmNuserData!");
379     ASSERT(idx >= 0 && idx < MAX_POPUPS, "Invalid widget index in ok_action()");
380 
381     /* First call pre_message_cb with window widget ID
382      * as additional parameter.
383      */
384     if (pre_callbacks[idx].callback != NULL) {
385 	pre_callbacks[idx].callback(popup_window[idx], pre_callbacks[idx].arg);
386     }
387 
388     /* Then pop down window and mark its position as free, then
389      * invoke the OK callback.  The reason for this is that the callback
390      * may need to wait for open windows.
391      */
392     XtPopdown(popup_window[idx]);
393     XtDestroyWidget(popup_window[idx]);
394     g_popup_array[idx] = 0;
395     XSync(DISP, True);
396 
397     if (no_callbacks[idx].callback != NULL) {
398 	no_callbacks[idx].callback(no_callbacks[idx].arg);
399     }
400 }
401 #endif /* MOTIF */
402 
403 /*------------------------------------------------------------
404  *  create_dialogs
405  *
406  *  Arguments:
407  *	Widget toplevel - parent for the dialog window
408  *
409  *	helptext	- if not-NULL, create an additional
410  *			  `help' button
411  *
412  *	cnt		- number of current popup dialog
413  *
414  *  Returns:
415  *	void
416  *
417  *  Purpose:
418  *	Create message dialog widgets
419  *------------------------------------------------------------*/
420 
421 static Widget
create_dialogs(popupMessageSizeHintT size,Widget parent,int cnt,const char * helptext,pre_message_cbT pre_cb,XtPointer arg,const char * yes_button,message_cbT yes_cb,XtPointer yes_arg,const char * no_button,message_cbT no_cb,XtPointer no_arg,const char * cancel_button,message_cbT cancel_cb,XtPointer cancel_arg)422 create_dialogs(popupMessageSizeHintT size,
423 	       Widget parent,
424 	       int cnt,
425 	       const char *helptext,
426 	       pre_message_cbT pre_cb, XtPointer arg,
427 	       const char *yes_button, message_cbT yes_cb, XtPointer yes_arg,
428 	       const char *no_button, message_cbT no_cb, XtPointer no_arg,
429 	       const char *cancel_button, message_cbT cancel_cb, XtPointer cancel_arg)
430 {
431     Widget new_popup_window;
432     char *translations_str = NULL;
433 #ifdef MOTIF
434     Widget new_dialog;
435     UNUSED(size);
436 #else
437     char *key_translations_str = NULL;
438     int msg_w = 400, msg_h = 100;
439     Widget new_message_paned, new_message_text, new_message_box, new_message_ok,
440 	new_message_help = 0, new_message_not_ok;
441     XtTranslations wm_translations, key_translations;
442 #endif
443 
444     /* save callbacks to global arrays */
445     pre_callbacks[cnt].callback = pre_cb;
446     pre_callbacks[cnt].arg = arg;
447 
448     yes_callbacks[cnt].callback = yes_cb;
449     yes_callbacks[cnt].arg = yes_arg;
450 
451     no_callbacks[cnt].callback = no_cb;
452     no_callbacks[cnt].arg = no_arg;
453 
454     cancel_callbacks[cnt].callback = cancel_cb;
455     cancel_callbacks[cnt].arg = cancel_arg;
456 
457     XtAddActions(popdown_actions, XtNumber(popdown_actions));
458 
459 #ifndef MOTIF
460     /* get index into WM_popdown arg */
461     translations_str = get_string_va("<Message>WM_PROTOCOLS: WM_popdown(%d)", cnt);
462     wm_translations = XtParseTranslationTable(translations_str);
463     free(translations_str);
464 #endif
465 
466     if (!XtIsRealized(globals.widgets.top_level)) {
467 	/* If toplevel window hasn't been realized yet, create a new toplevel shell
468 	   (otherwise, setting visual/color map wouldn't work); use same application names
469 	   so that resource settings will also apply to this window.
470 	*/
471 	new_popup_window = XtVaAppCreateShell("xdvi", "Xdvi",
472 					      transientShellWidgetClass, DISP,
473 					      NULL);
474     }
475     else {
476 	new_popup_window = XtVaCreatePopupShell(Xdvi_MESSAGE_SHELL_NAME,
477 #ifdef MOTIF
478 						xmDialogShellWidgetClass, parent,
479 						XmNdeleteResponse, XmDO_NOTHING, /* we'll take care of that ourselves */
480 #else
481 						transientShellWidgetClass, parent,
482 						XtNx, 60,
483 						XtNy, 80,
484 						XtNtranslations, wm_translations,
485 						XtNaccelerators, G_accels_cr,
486 #endif
487 						XtNtransientFor, parent,
488 						XtNmappedWhenManaged, False,
489 						NULL);
490     }
491 
492 #ifdef MOTIF
493 
494     WM_DELETE_WINDOW = XmInternAtom(XtDisplay(new_popup_window), "WM_DELETE_WINDOW", False);
495     XmAddWMProtocolCallback(new_popup_window, WM_DELETE_WINDOW, cancel_action, NULL);
496 
497     /* We also need to override the default ESC binding to use our internal
498        housekeeping functions */
499     translations_str = get_string_va("#override\n<Key>osfCancel:close-popup-cancel(%d)", cnt);
500     /*      { */
501     /*  	XtTranslations xlats; */
502     /*  	char *translation_str = get_string_va("<Key>osfCancel:close-popup-cancel(%d)", cnt); */
503     /*  	xlats = XtParseTranslationTable(translation_str); */
504     /*  	free(translation_str); */
505     /*  	XtOverrideTranslations(new_dialog, xlats); */
506     /*      } */
507 
508     new_dialog = XtVaCreateWidget(Xdvi_MESSAGE_DIALOG_NAME, xmMessageBoxWidgetClass, new_popup_window,
509 				  XmNdialogType, XmDIALOG_WARNING, /* default */
510 				  XmNtraversalOn, True,
511 				  XmNhighlightOnEnter, True,
512 				  XmNuserData, cast_int_to_XtPointer(cnt),
513 				  XmNtranslations, XtParseTranslationTable(translations_str),
514 				  NULL);
515     free(translations_str);
516     XtAddCallback(new_dialog, XmNokCallback, ok_action, NULL);
517 
518     if (no_button != NULL) {
519 	Arg args[4];
520 	Widget b;
521 	XmString b_str = XmStringCreateLocalized((char *)no_button);
522 	XtSetArg(args[0], XmNlabelString, b_str);
523 	b = XmCreatePushButton(new_dialog, "no_button", args, 1);
524 	XtAddCallback(b, XmNactivateCallback, not_ok_action, NULL);
525 	XtManageChild(b);
526     }
527 
528     if (cancel_button != NULL) {
529 	XmString cancel_label = XmStringCreateLtoR((char *)cancel_button, G_charset);
530 	XtVaSetValues(XmMessageBoxGetChild(new_dialog, XmDIALOG_CANCEL_BUTTON),
531 		      XmNlabelString, cancel_label, NULL);
532 	XmStringFree(cancel_label);
533 	XtAddCallback(new_dialog, XmNcancelCallback, cancel_action, NULL);
534     }
535     else {
536 	XtUnmanageChild(XmMessageBoxGetChild(new_dialog, XmDIALOG_CANCEL_BUTTON));
537     }
538     XtInstallAllAccelerators(new_dialog,
539 			     XmMessageBoxGetChild(new_dialog, XmDIALOG_OK_BUTTON));
540 
541     if (helptext != NULL) {
542 	XtAddCallback(new_dialog, XmNhelpCallback, help_action, (XtPointer)helptext);
543     }
544     else {
545 	XtUnmanageChild(XmMessageBoxGetChild(new_dialog, XmDIALOG_HELP_BUTTON));
546     }
547 
548     if (yes_button != NULL) { /* change `OK' button label */
549 	XmString yes_label;
550 	yes_label = XmStringCreateLtoR((char *)yes_button, G_charset);
551 	XtVaSetValues(XmMessageBoxGetChild(new_dialog, XmDIALOG_OK_BUTTON),
552 		      XmNlabelString, yes_label, NULL);
553 	XmStringFree(yes_label);
554     }
555 
556     /* insert the new widgets into the global arrays */
557     dialog[cnt] = new_dialog;
558 
559 #else /* MOTIF */
560     switch (size) {
561     case SIZE_SMALL:
562 	msg_w = 300;
563 	msg_h = 100;
564 	break;
565     case SIZE_MEDIUM:
566 	msg_w = 430;
567 	msg_h = 160;
568 	break;
569     case SIZE_LARGE:
570 	msg_w = 450;
571 	msg_h = 180;
572 	break;
573     }
574     WM_DELETE_WINDOW = XInternAtom(XtDisplay(new_popup_window), "WM_DELETE_WINDOW", False);
575 
576     new_message_paned = XtVaCreateManagedWidget("message_paned", panedWidgetClass, new_popup_window,
577 						XtNaccelerators, G_accels_cr,
578 						NULL);
579 
580     new_message_text = XtVaCreateManagedWidget("message_text", asciiTextWidgetClass, new_message_paned,
581 					       /* 					       XtNheight, 100, */
582 					       /* 					       XtNwidth, 400, */
583 					       XtNwidth, msg_w,
584 					       XtNheight, msg_h,
585 					       /* wrap horizontally instead of scrolling
586 						* TODO: this won't work for the first widget instance?
587 						*/
588 					       XtNwrap, XawtextWrapWord,
589 					       XtNscrollVertical, XAW_SCROLL_ALWAYS,
590 					       XtNeditType, XawtextRead,
591 					       XtNinput, True,
592 					       XtNdisplayCaret, False,
593 					       XtNleftMargin, 5,
594 					       XtNaccelerators, G_accels_cr,
595 					       NULL);
596 
597     /* box for the OK/Cancel button */
598     new_message_box = XtVaCreateManagedWidget("message_box", formWidgetClass, new_message_paned,
599 					      /* resizing by user isn't needed */
600 					      XtNshowGrip, False,
601 					      XtNdefaultDistance, 6, /* some padding */
602 					      /* resizing the window shouldn't influence this box,
603 					       * but only the text widget
604 					       */
605 					      XtNskipAdjust, True,
606 					      XtNaccelerators, G_accels_cr,
607 					      NULL);
608 
609     new_message_ok = XtVaCreateManagedWidget(yes_button == NULL ? "OK" : yes_button,
610 					     commandWidgetClass, new_message_box,
611 					     XtNtop, XtChainTop,
612  					     XtNbottom, XtChainBottom,
613  					     XtNleft, XtChainLeft,
614  					     XtNright, XtChainLeft,
615 					     XtNaccelerators, G_accels_cr,
616 					     NULL);
617     /* add quit_action callback for the "OK" button */
618     /* FIXME: how to make accelerators be accepted by new_popup_window as well? */
619     key_translations_str = get_string_va("<Key>q:close-popup-cancel(%d)\n"
620 					 "<Key>Return:close-popup-cancel(%d)\n"
621 					 "<Key>Escape:close-popup-cancel(%d)\n",
622 					 cnt, cnt, cnt);
623     key_translations = XtParseTranslationTable(key_translations_str);
624     free(key_translations_str);
625     XtOverrideTranslations(new_popup_window, key_translations);
626     XtOverrideTranslations(new_message_paned, key_translations);
627     XtOverrideTranslations(new_message_text, key_translations);
628 
629     XtInstallAllAccelerators(new_message_box, new_message_ok);
630     XtAddCallback(new_message_ok, XtNcallback, ok_action, cast_int_to_XtPointer(cnt));
631 
632     /* we create additional buttons in any case,
633        to make the sizing more consistent */
634     new_message_help = XtVaCreateManagedWidget("Help", commandWidgetClass, new_message_box,
635 					       XtNtop, XtChainTop,
636 					       XtNfromHoriz, new_message_ok,
637 					       XtNbottom, XtChainBottom,
638 					       XtNleft, XtChainRight,
639 					       XtNright, XtChainRight,
640 					       XtNaccelerators, G_accels_cr,
641 					       NULL);
642     message_help[cnt] = new_message_help;
643 
644     /* add cancel button */
645     new_message_not_ok = XtVaCreateManagedWidget(cancel_button == NULL ? "Cancel" : cancel_button,
646 						 commandWidgetClass, new_message_box,
647 						 XtNtop, XtChainTop,
648 						 XtNfromHoriz, new_message_ok,
649 						 XtNbottom, XtChainBottom,
650 						 XtNleft, helptext == NULL ? XtChainRight : XtChainLeft,
651 						 XtNright, helptext == NULL ? XtChainRight : XtChainLeft,
652 						 XtNaccelerators, G_accels_cr,
653 						 NULL);
654     message_not_ok[cnt] = new_message_not_ok;
655 
656     if (no_button != NULL) {
657 	ASSERT(0, "third button not yet implemented in Xaw!!!");
658     }
659 
660     adjust_width_to_max(new_message_ok, new_message_help, new_message_not_ok, NULL);
661 
662     /* if helptext argument is not-NULL, add help_action callback,
663        else unmanage help button */
664     if (helptext != NULL) {
665 	XtAddCallback(new_message_help, XtNcallback, help_action, (XtPointer)helptext);
666     }
667     else {
668 	XtUnmanageChild(new_message_help);
669     }
670 
671     if (cancel_button != NULL) {
672 	XtAddCallback(new_message_not_ok, XtNcallback, cancel_action, cast_int_to_XtPointer(cnt));
673     }
674     else {
675 	XtUnmanageChild(new_message_not_ok);
676     }
677     /* insert the new widgets into the global arrays */
678     message_box[cnt] = new_message_box;
679     message_paned[cnt] = new_message_paned;
680     message_text[cnt] = new_message_text;
681     message_ok[cnt] = new_message_ok;
682 
683 #endif /* MOTIF */
684     popup_window[cnt] = new_popup_window;
685 
686     return new_popup_window;
687 }
688 
689 
690 /*
691  * Popup a window with wrapped text in it.
692  * For Motif, the text is explicitly wrapped inside this method.
693  */
694 static Widget
internal_popup_window(Widget parent,popupMessageSizeHintT size,popupMessageT type,int x_coord,int y_coord,const char * helptext,char * msg_buf,const char * xaw_ret_action_str,pre_message_cbT pre_cb,XtPointer arg,const char * yes_button,message_cbT yes_cb,XtPointer yes_arg,const char * no_button,message_cbT no_cb,XtPointer no_arg,const char * cancel_button,message_cbT cancel_cb,XtPointer cancel_arg)695 internal_popup_window(Widget parent,
696 		      popupMessageSizeHintT size,
697 		      popupMessageT type,
698 		      int x_coord, int y_coord,
699 		      const char *helptext,
700 		      char *msg_buf,
701 #ifndef MOTIF
702 		      const char *xaw_ret_action_str,
703 #endif
704 		      pre_message_cbT pre_cb, XtPointer arg,
705 		      const char *yes_button, message_cbT yes_cb, XtPointer yes_arg,
706 		      const char *no_button, message_cbT no_cb, XtPointer no_arg,
707 		      const char *cancel_button, message_cbT cancel_cb, XtPointer cancel_arg)
708 {
709     int my_popup_num = 0;
710 #ifdef MOTIF
711     XmString str;
712 #endif
713     Widget ret;
714 
715     ASSERT(type < (sizeof my_msg_map / sizeof my_msg_map[0]), "too few elements in my_msg_map");
716 
717 #if DEBUG
718     fprintf(stderr, "internal_popup_window called with prompt: \"%s\"\n", msg_buf);
719 #endif
720 
721     if (globals.widgets.top_level == 0) {
722 	/* If toplevel window hasn't been created yet, dump messages to STDERR
723 	   and return.
724 	*/
725 	fprintf(stderr, "\n%s:\n%s\n", my_msg_map[type].window_title, msg_buf);
726 	if (helptext) {
727 	    fputs("---------- helptext ----------\n", stderr);
728 	    fputs(helptext, stderr);
729 	    fputs("\n---------- end of helptext ----------\n", stderr);
730 	}
731 	return NULL;
732     }
733     /* search for first free position in g_popup_array */
734     while (my_popup_num < MAX_POPUPS && (g_popup_array[my_popup_num] == 1)) {
735 	my_popup_num++;
736     }
737     if (my_popup_num == MAX_POPUPS) {
738 	/* already enough popups on screen, just dump it to stderr */
739 	fprintf(stderr, "%s: %s\n", my_msg_map[type].window_title, msg_buf);
740 	/* Note: If a mad function continues to open popups, this will
741 	 * stop after MAX_POPUPS, but open a new window for each
742 	 * window the user pops down. Maybe we ought to do something
743 	 * about this.
744 	 */
745 	return NULL;
746     }
747     else {
748 	/* mark it as non-free */
749 	g_popup_array[my_popup_num] = 1;
750     }
751 #if DEBUG
752     fprintf(stderr, "first free position in g_popup_array: %d\n", my_popup_num);
753 #endif
754 
755     /* just to make sure ... */
756     if (parent == NULL)
757 	parent = globals.widgets.top_level;
758 
759     /* create a new set of widgets for the additional popup. */
760     ret = create_dialogs(size, parent,
761 			 my_popup_num,
762 			 helptext,
763 			 pre_cb, arg,
764 			 yes_button, yes_cb, yes_arg,
765 			 no_button, no_cb, no_arg,
766 			 cancel_button, cancel_cb, cancel_arg);
767 #ifdef MOTIF
768     XtVaSetValues(popup_window[my_popup_num], XmNtitle,
769 	    my_msg_map[type].window_title, NULL);
770     XtVaSetValues(dialog[my_popup_num], XmNdialogType,
771 	    my_msg_map[type].motif_msg_type, NULL);
772     { /* wrap message at space before MSG_WRAP_LEN */
773 	char *testwrap = msg_buf;
774 	int ctr;
775 	for (ctr = 0; *testwrap++; ctr++) {
776 	    if (*testwrap == '\n') {
777 		ctr = 0;
778 	    }
779 	    else if (ctr > MSG_WRAP_LEN) {
780 		size_t before_len = 0, after_len = 0;
781 		char *before_ptr, *after_ptr;
782 		before_ptr = after_ptr = testwrap;
783 		/* try to find shortest sequence before or after point to wrap at;
784 		   this seems to give the most pleasing results.
785 		*/
786 		while (before_ptr > msg_buf && !isspace((int)*--before_ptr)) {
787 		    before_len++;
788 		}
789 		while (*after_ptr != '\0' && !isspace((int)*++after_ptr)) {
790 		    after_len++;
791 		}
792 
793 		if (before_len < after_len && isspace((int)*before_ptr)) {
794 		    /* use last in sequence of multiple spaces */
795 		    while (isspace((int)*++before_ptr)) { ; }
796 		    /* back up, and wrap */
797 		    *--before_ptr = '\n';
798 		    ctr = 0;
799 		}
800 		else if (isspace((int)*after_ptr)) {
801 		    /* use last in sequence of multiple spaces */
802 		    while (isspace((int)*++after_ptr)) { ; }
803 		    /* back up, and wrap */
804 		    *--after_ptr = '\n';
805 		    ctr = 0;
806 		}
807 	    }
808 	}
809     }
810     str = XmStringCreateLtoR((char *)msg_buf, G_charset);
811     XtVaSetValues(dialog[my_popup_num],
812 		  XmNmessageString, str,
813 		  XmNtraversalOn, True,
814 		  XmNhighlightOnEnter, True,
815 		  NULL);
816     XmStringFree(str);
817 
818     XtManageChild(dialog[my_popup_num]);
819 
820     if (x_coord > 0 && y_coord > 0) {
821 	position_window(XtParent(dialog[my_popup_num]), (Position)x_coord, (Position)y_coord);
822     }
823 
824     XtPopup(XtParent(dialog[my_popup_num]), XtGrabNone);
825     /*      XtPopup(XtParent(dialog[my_popup_num]), XtGrabExclusive); */
826 
827 #else /* MOTIF */
828 
829     /* add a binding of xaw_ret_action_str to <Return> to relevant widgets.
830        The callbacks (xaw_ret_action_str) are responsible for parsing the
831        passed arguments (pointers, or empty arguments).
832     */
833     if (xaw_ret_action_str != NULL) {
834 	XtTranslations xlats;
835 	char *translation_str;
836 
837 	if (yes_arg != NULL)
838 	    translation_str = get_string_va("<Key>Return:close-popup(%d)%s(%p)",
839 					    my_popup_num, xaw_ret_action_str, yes_arg);
840 	else
841 	    translation_str = get_string_va("<Key>Return:close-popup(%d)%s()",
842 					    my_popup_num, xaw_ret_action_str);
843 
844 	xlats = XtParseTranslationTable(translation_str);
845 	free(translation_str);
846 	XtOverrideTranslations(popup_window[my_popup_num], xlats);
847 	XtOverrideTranslations(message_paned[my_popup_num], xlats);
848 	XtOverrideTranslations(message_text[my_popup_num], xlats);
849     }
850 
851     XtVaSetValues(popup_window[my_popup_num], XtNtitle,
852 	    my_msg_map[type].window_title, NULL);
853     XtVaSetValues(message_text[my_popup_num], XtNstring, msg_buf, NULL);
854     XtRealizeWidget(popup_window[my_popup_num]);
855 
856     XSetWMProtocols(XtDisplay(popup_window[my_popup_num]), XtWindow(popup_window[my_popup_num]),
857 		    &WM_DELETE_WINDOW, 1);
858 
859     if (x_coord <= 0 || y_coord <= 0)
860 	center_window(popup_window[my_popup_num], parent);
861     else
862 	position_window(popup_window[my_popup_num], (Position)x_coord, (Position)y_coord);
863 
864     if (my_popup_num > 0) {
865 	/* some window managers position new windows exactly above the
866 	   existing one; to prevent this, move it with some offset
867 	   from the previous one: */
868 	Position x = 0, y = 0;
869 	XtVaGetValues(popup_window[my_popup_num-1], XtNx, &x, XtNy, &y, NULL);
870 	XtVaSetValues(popup_window[my_popup_num], XtNx, x + POPUP_OFFSET, XtNy, y + POPUP_OFFSET, NULL);
871 
872     }
873     XtPopup(popup_window[my_popup_num], XtGrabNone);
874     /*      XtPopup(XtParent(popup_window[my_popup_num]), XtGrabExclusive); */
875     if (XtIsManaged(message_not_ok[my_popup_num]) && XtIsManaged(message_help[my_popup_num])) {
876 	/* center the help button. This is something of a sham, since it won't
877 	   survive resizing; but in general most users won't resize dialogs ;-) */
878 	Position x1, x2, bw;
879 	int w, dist;
880 
881 	XtVaGetValues(message_ok[my_popup_num], XtNx, &x1, XtNwidth, &w, XtNborderWidth, &bw, NULL);
882 	XtVaGetValues(message_help[my_popup_num], XtNx, &x2, NULL);
883 	/* following formula is measured, not calculated -
884 	   I have no idea why it's e.g. 2 * w, not 1.5 * w ... */
885 	dist = (x2 - x1 - 2 * w) / 2 - 2 * bw;
886 	XtVaSetValues(message_not_ok[my_popup_num], XtNhorizDistance, dist, NULL);
887     }
888 
889 #endif /* MOTIF */
890     return ret;
891 }
892 
893 
894 /*------------------------------------------------------------
895  *  popup_message
896  *
897  *  Arguments:
898  *	popupMessageT - info, warning, error etc; see message-window.h for details
899  *
900  *	char *helptext
901  *	      - if not-null, this will add a `Help'
902  *		button to the message widget that pops
903  *		up another message widget containing
904  *		<helptext>.
905  *
906  *
907  *	char *msg, ...
908  *	      - format string followed by a variable
909  *		number of arguments to be formatted.
910  *
911  *  Returns:
912  *	void
913  *
914  *  Purpose:
915  *	Pop up a message window containing <msg>.
916  *	If there are already n popups open, will open
917  *	a new one unless n >= MAX_POPUPS.
918  *------------------------------------------------------------*/
919 
920 Widget
popup_message(Widget parent,popupMessageT type,const char * helptext,const char * format,...)921 popup_message(Widget parent, popupMessageT type, const char *helptext, const char *format, ...)
922 {
923     char *msg_buf = NULL;
924     Widget w;
925 
926     XDVI_GET_STRING_ARGP(msg_buf, format);
927 
928     w = internal_popup_window(parent,
929 			      SIZE_SMALL,
930 			      type,
931 			      -1, -1, /* just center it */
932 			      helptext, msg_buf,
933 #ifndef MOTIF
934 			      NULL,
935 #endif
936 			      /* no special callbacks here */
937 			      NULL, NULL,
938 			      NULL, NULL, NULL,
939 			      NULL, NULL, NULL,
940 			      NULL, NULL, NULL);
941     free(msg_buf);
942     return w;
943 }
944 
945 #if 0	/* This function is currently unused. */
946 Widget
947 popup_message_sized(Widget parent,
948 		    popupMessageT type,
949 		    popupMessageSizeHintT sizehint,
950 		    const char *helptext,
951 		    const char *format, ...)
952 {
953     char *msg_buf = NULL;
954     Widget w;
955 
956     XDVI_GET_STRING_ARGP(msg_buf, format);
957 
958     w = internal_popup_window(parent,
959 			      sizehint,
960 			      type,
961 			      -1, -1, /* just center it */
962 			      helptext, msg_buf,
963 #ifndef MOTIF
964 			      NULL,
965 #endif
966 			      /* empty callbacks */
967 			      NULL, NULL,
968 			      NULL, NULL, NULL,
969 			      NULL, NULL, NULL,
970 			      NULL, NULL, NULL);
971     free(msg_buf);
972     return w;
973 }
974 #endif
975 
976 Widget
positioned_popup_message(Widget parent,popupMessageT type,int x,int y,const char * helptext,const char * format,...)977 positioned_popup_message(Widget parent,
978 			 popupMessageT type,
979 			 int x, int y,
980 			 const char *helptext, const char *format, ...)
981 {
982     char *msg_buf = NULL;
983     Widget w;
984 
985     XDVI_GET_STRING_ARGP(msg_buf, format);
986 
987     w = internal_popup_window(parent,
988 			      SIZE_SMALL,
989 			      type,
990 			      x, y, /* position at these coordinates */
991 			      helptext, msg_buf,
992 #ifndef MOTIF
993 			      NULL,
994 #endif
995 			      /* empty callbacks */
996 			      NULL, NULL,
997 			      NULL, NULL, NULL,
998 			      NULL, NULL, NULL,
999 			      NULL, NULL, NULL);
1000     free(msg_buf);
1001     return w;
1002 }
1003 
1004 Widget
choice_dialog(Widget parent,popupMessageT type,const char * helptext,const char * xaw_ret_action_str,pre_message_cbT pre_cb,XtPointer arg,const char * ok_label,message_cbT ok_cb,XtPointer ok_arg,const char * cancel_label,message_cbT cancel_cb,XtPointer cancel_arg,const char * format,...)1005 choice_dialog(Widget parent,
1006 	      popupMessageT type,
1007 	      const char *helptext,
1008 #ifndef MOTIF
1009 	      const char *xaw_ret_action_str,
1010 #endif
1011 	      pre_message_cbT pre_cb, XtPointer arg,
1012 	      const char *ok_label, message_cbT ok_cb, XtPointer ok_arg,
1013 	      const char *cancel_label, message_cbT cancel_cb, XtPointer cancel_arg,
1014 	      const char *format, ...)
1015 {
1016     char *msg_buf = NULL;
1017     Widget w;
1018 
1019     XDVI_GET_STRING_ARGP(msg_buf, format);
1020 
1021     w = internal_popup_window(parent,
1022 			      SIZE_SMALL,
1023 			      type,
1024 			      -1, -1, /* just center it */
1025 			      helptext, msg_buf,
1026 #ifndef MOTIF
1027 			      xaw_ret_action_str,
1028 #endif
1029 			      pre_cb, arg,
1030 			      ok_label, ok_cb, ok_arg,
1031 			      NULL, NULL, NULL,
1032 			      cancel_label, cancel_cb, cancel_arg);
1033     free(msg_buf);
1034     return w;
1035 }
1036 
1037 #if MOTIF
1038 Widget
choice3_dialog(Widget parent,popupMessageT type,const char * helptext,pre_message_cbT pre_cb,XtPointer arg,const char * yes_label,message_cbT yes_cb,XtPointer yes_arg,const char * no_label,message_cbT no_cb,XtPointer no_arg,const char * cancel_label,message_cbT cancel_cb,XtPointer cancel_arg,const char * format,...)1039 choice3_dialog(Widget parent,
1040 	       popupMessageT type,
1041 	       const char *helptext,
1042 	       pre_message_cbT pre_cb, XtPointer arg,
1043 	       const char *yes_label, message_cbT yes_cb, XtPointer yes_arg,
1044 	       const char *no_label, message_cbT no_cb, XtPointer no_arg,
1045 	       const char *cancel_label, message_cbT cancel_cb, XtPointer cancel_arg,
1046 	       const char *format, ...)
1047 {
1048     char *msg_buf = NULL;
1049     Widget w;
1050 
1051     XDVI_GET_STRING_ARGP(msg_buf, format);
1052 
1053     w = internal_popup_window(parent,
1054 			      SIZE_SMALL,
1055 			      type,
1056 			      -1, -1, /* just center it */
1057 			      helptext, msg_buf,
1058 			      pre_cb, arg,
1059 			      yes_label, yes_cb, yes_arg,
1060 			      no_label, no_cb, no_arg,
1061 			      cancel_label, cancel_cb, cancel_arg);
1062     free(msg_buf);
1063     return w;
1064 }
1065 #endif
1066 
1067 Widget
choice_dialog_sized(Widget parent,popupMessageT type,popupMessageSizeHintT sizehint,const char * helptext,const char * xaw_ret_action_str,pre_message_cbT pre_cb,XtPointer arg,const char * ok_label,message_cbT ok_cb,XtPointer ok_arg,const char * cancel_label,message_cbT cancel_cb,XtPointer cancel_arg,const char * format,...)1068 choice_dialog_sized(Widget parent,
1069 		    popupMessageT type,
1070 		    popupMessageSizeHintT sizehint,
1071 		    const char *helptext,
1072 #ifndef MOTIF
1073 		    const char *xaw_ret_action_str,
1074 #endif
1075 		    pre_message_cbT pre_cb, XtPointer arg,
1076 		    const char *ok_label, message_cbT ok_cb, XtPointer ok_arg,
1077 		    const char *cancel_label, message_cbT cancel_cb, XtPointer cancel_arg,
1078 		    const char *format, ...)
1079 {
1080     char *msg_buf = NULL;
1081     Widget w;
1082 
1083     XDVI_GET_STRING_ARGP(msg_buf, format);
1084 
1085     w = internal_popup_window(parent,
1086 			      sizehint,
1087 			      type,
1088 			      -1, -1, /* just center it */
1089 			      helptext, msg_buf,
1090 #ifndef MOTIF
1091 			      xaw_ret_action_str,
1092 #endif
1093 			      pre_cb, arg,
1094 			      ok_label, ok_cb, ok_arg,
1095 			      NULL, NULL, NULL,
1096 			      cancel_label, cancel_cb, cancel_arg);
1097     free(msg_buf);
1098     return w;
1099 }
1100 
1101 Widget
positioned_choice_dialog(Widget parent,popupMessageT type,int x_pos,int y_pos,const char * helptext,const char * xaw_ret_action_str,pre_message_cbT pre_cb,XtPointer arg,const char * ok_label,message_cbT ok_cb,XtPointer ok_arg,const char * cancel_label,message_cbT cancel_cb,XtPointer cancel_arg,const char * format,...)1102 positioned_choice_dialog(Widget parent,
1103 			 popupMessageT type,
1104 			 int x_pos, int y_pos,
1105 			 const char *helptext,
1106 #ifndef MOTIF
1107 			 const char *xaw_ret_action_str,
1108 #endif
1109 			 pre_message_cbT pre_cb, XtPointer arg,
1110 			 const char *ok_label, message_cbT ok_cb, XtPointer ok_arg,
1111 			 const char *cancel_label, message_cbT cancel_cb, XtPointer cancel_arg,
1112 			 const char *format, ...)
1113 {
1114     char *msg_buf = NULL;
1115     Widget w;
1116 
1117     XDVI_GET_STRING_ARGP(msg_buf, format);
1118 
1119     w = internal_popup_window(parent,
1120 			      SIZE_SMALL,
1121 			      type,
1122 			      x_pos, y_pos,
1123 			      helptext, msg_buf,
1124 #ifndef MOTIF
1125 			      xaw_ret_action_str,
1126 #endif
1127 			      pre_cb, arg,
1128 			      ok_label, ok_cb, ok_arg,
1129 			      NULL, NULL, NULL,
1130 			      cancel_label, cancel_cb, cancel_arg);
1131     free(msg_buf);
1132     return w;
1133 }
1134 
1135 void
warn_overstrike(void)1136 warn_overstrike(void)
1137 {
1138     static Boolean warned_overstrike = False;
1139 
1140     if (!warned_overstrike) {
1141 	popup_message(globals.widgets.top_level,
1142 		      MSG_WARN,
1143 		      /* helptext */
1144 		      "Greyscaling is running in copy mode; this will cause overstrike characters to "
1145 		      "appear incorrectly, and may result in poor display quality.  "
1146 		      "Possible fixes are:\n"
1147 		      "- Use the ``-thorough'' command-line option.\n"
1148 		      "- Quit some other color-hungry applications (e.g. Netscape).\n"
1149 		      "- Use the ``-install'' command-line option.\n"
1150 		      "See the section ``GREYSCALING AND COLORMAPS'' in the "
1151 		      "xdvi manual page for more details.",
1152 		      /* text */
1153 		      "Couldn't allocate enough colors - expect low display quality.");
1154 	warned_overstrike = True;
1155     }
1156 }
1157 
1158 Boolean
is_message_window(Widget w)1159 is_message_window(Widget w)
1160 {
1161     int i;
1162     for (i = 0; i < MAX_POPUPS; i++) {
1163 	if (w == popup_window[i])
1164 	    return True;
1165     }
1166     return False;
1167 }
1168 
1169 Boolean
kill_message_window(Widget w)1170 kill_message_window(Widget w)
1171 {
1172     int i;
1173     for (i = 0; i < MAX_POPUPS; i++) {
1174 	if (g_popup_array[i] != 0 && XtIsRealized(popup_window[i]) && w == popup_window[i]) {
1175 	    g_popup_array[i] = 0;
1176 	    XtPopdown(popup_window[i]);
1177 	    XtDestroyWidget(popup_window[i]);
1178 	    XSync(DISP, True);
1179 	    return True;
1180 	}
1181     }
1182     return False;
1183 }
1184 
1185 /*
1186   Raise any popups that currently exist; return True iff such popups
1187   exist, else False.
1188 */
1189 Boolean
raise_message_windows(void)1190 raise_message_windows(void)
1191 {
1192     int i;
1193     Boolean have_popups = False;
1194 
1195     for (i = 0; i < MAX_POPUPS; i++) {
1196 	if (g_popup_array[i] != 0 && XtIsRealized(popup_window[i])) {
1197 	    XRaiseWindow(DISP, XtWindow(popup_window[i]));
1198 	    have_popups = True;
1199 	}
1200     }
1201 
1202     return have_popups;
1203 }
1204