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