Force Motif to be pulled in statically by default. Linux users tend to upgrade
[nedit.git] / util / DialogF.c
blob1b951c394c344086a08748f5bec1df5d47a805a4
1 static const char CVSID[] = "$Id: DialogF.c,v 1.20 2001/12/04 17:50:37 amai Exp $";
2 /*******************************************************************************
3 * *
4 * DialogF -- modal dialog printf routine *
5 * *
6 * Copyright (C) 1999 Mark Edel *
7 * *
8 * This is free software; you can redistribute it and/or modify it under the *
9 * terms of the GNU General Public License as published by the Free Software *
10 * Foundation; either version 2 of the License, or (at your option) any later *
11 * version. *
12 * *
13 * This software is distributed in the hope that it will be useful, but WITHOUT *
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
16 * for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License along with *
19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
20 * Place, Suite 330, Boston, MA 02111-1307 USA *
21 * *
22 * Nirvana Text Editor *
23 * April 26, 1991 *
24 * *
25 * Written by Joy Kyriakopulos *
26 * *
27 *******************************************************************************/
28 #include <stdio.h>
29 #include <stdarg.h>
30 #include <string.h>
31 #include <ctype.h>
32 #include <limits.h>
33 #include <Xm/Xm.h>
34 #include <Xm/MessageB.h>
35 #include <Xm/DialogS.h>
36 #include <Xm/PushB.h>
37 #include <Xm/PushBG.h>
38 #include <Xm/SelectioB.h>
39 #include <X11/StringDefs.h>
40 #include <X11/Intrinsic.h>
41 #include <X11/keysym.h>
42 #include "misc.h"
43 #include "DialogF.h"
45 #define NUM_DIALOGS_SUPPORTED 6
46 #define NUM_BUTTONS_SUPPORTED 3 /* except prompt dialog */
47 #define NUM_BUTTONS_MAXPROMPT 4
49 enum dialogBtnIndecies {OK_BTN, APPLY_BTN, CANCEL_BTN, HELP_BTN};
51 struct dfcallbackstruct {
52 unsigned button; /* button pressed by user */
53 Boolean done_with_dialog; /* set by callbacks; dialog can be destroyed */
54 unsigned apply_up; /* will = 1 when apply button managed */
55 Boolean destroyed; /* set when dialog is destroyed unexpectedly */
58 static char **PromptHistory = NULL;
59 static int NPromptHistoryItems = -1;
61 static void apply_callback (Widget w, struct dfcallbackstruct *client_data,
62 caddr_t call_data);
63 static void help_callback (Widget w, struct dfcallbackstruct *client_data,
64 caddr_t call_data);
65 static void cancel_callback (Widget w, struct dfcallbackstruct *client_data,
66 caddr_t call_data);
67 static void ok_callback (Widget w, struct dfcallbackstruct *client_data,
68 caddr_t call_data);
69 static void destroy_callback (Widget w, struct dfcallbackstruct *client_data,
70 caddr_t call_data);
71 static void focusCB(Widget w, Widget dialog, caddr_t call_data);
72 static void addEscapeHandler(Widget dialog, struct dfcallbackstruct *df,
73 int whichBtn);
74 static void escapeHelpCB(Widget w, XtPointer callData, XEvent *event,
75 Boolean *cont);
76 static void escapeApplyCB(Widget w, XtPointer callData, XEvent *event,
77 Boolean *cont);
78 static void createMnemonics(Widget w);
80 /******************************************************************************
81 * DialogF () *
82 * *
83 * function to put up modal versions of the XmCreate<type>Dialog *
84 * functions (where <type> is Error, Information, Prompt, Question, *
85 * Message, or Warning). The purpose of this routine is to allow a *
86 * printf-style dialog box to be invoked in-line without giving control *
87 * back to the main loop. The message string can contain vsprintf *
88 * specifications. DialogF displays the dialog in application-modal *
89 * style, blocking the application and keeping the modal dialog as the *
90 * top window until the user responds. DialogF accepts a variable *
91 * number of arguments, so the calling routine needs to #include *
92 * <stdarg.h>. The first button is automatically marked as the default *
93 * button (activated when the user types Return, surrounded by a special *
94 * outline), and any button named either Cancel, or Dismiss is marked as *
95 * the cancel button (activated by the ESC key). Buttons marked Dismiss *
96 * or Cancel are also triggered by close of dialog via the window close *
97 * box. If there's no Cancel or Dismiss button, button 1 is invoked *
98 * when the close box is pressed. *
99 * *
100 * Arguments: *
102 * unsigned dialog_type - dialog type (e.g. DF_ERR for error dialog) *
103 * (refer to DialogF.h for dialog type values) *
104 * Widget parent - parent widget ID *
105 * unsigned n - # of buttons to display *
106 * if = 0 use defaults in XmCreate<type>Dialog *
107 * value in range 0 to NUM_BUTTONS_SUPPORTED *
108 * (for prompt dialogs: NUM_BUTTONS_MAXPROMPT) *
109 * char * msgstr - message string (may contain conversion *
110 * specifications for vsprintf) *
111 * char * input_string - if dialog type = DF_PROMPT, then: *
112 * a character string array in which to put *
113 * the string input by the user. Do NOT *
114 * include an input_string argument for other *
115 * dialog types. *
116 * char * but_lbl - button label(s) for buttons requested *
117 * (if n > 0, one but_lbl argument per button) *
118 * <anytype> <args> - arguments for vsprintf (if any) *
120 * Returns: - button selected by user (i.e. 1, 2, or 3. or 4 for prompt)*
122 * Examples: *
124 * but_pressed = DialogF (DF_QUES, toplevel, 3, "Direction?", "up", *
125 * "down", "other"); *
126 * but_pressed = DialogF (DF_ERR, toplevel, 1, "You can't do that!", *
127 * "Acknowledged"); *
128 * but_pressed = DialogF (DF_PROMPT, toplevel, 0, "New %s", *
129 * new_sub_category, categories[i]); *
132 unsigned DialogF (int dialog_type, Widget parent, unsigned n,
133 const char *msgstr, ...) /* variable # arguments */
135 va_list var;
137 Widget dialog, dialog_shell;
138 unsigned dialog_num, prompt;
139 XmString but_lbl_xms[NUM_BUTTONS_MAXPROMPT];
140 XmString msgstr_xms, input_string_xms, titstr_xms;
141 char msgstr_vsp[DF_MAX_MSG_LENGTH+1];
142 char *but_lbl, *input_string = NULL, *input_string_ptr;
143 int argcount, num_but_lbls = 0, i, but_index, cancel_index = -1;
144 Arg args[256];
146 struct dfcallbackstruct df;
148 static int dialog_types[] = { /* Supported dialog types */
149 XmDIALOG_ERROR,
150 XmDIALOG_INFORMATION,
151 XmDIALOG_MESSAGE,
152 XmDIALOG_QUESTION,
153 XmDIALOG_WARNING,
154 XmDIALOG_PROMPT
156 static char *dialog_name[] = { /* Corresponding dialog names */
157 "Error",
158 "Information",
159 "Message",
160 "Question",
161 "Warning",
162 "Prompt"
164 static char *button_name[] = { /* Motif button names */
165 XmNokLabelString,
166 XmNapplyLabelString, /* button #2, if managed */
167 XmNcancelLabelString,
168 XmNhelpLabelString
170 /* Validate input parameters */
171 if ((dialog_type > NUM_DIALOGS_SUPPORTED) || (dialog_type <= 0)) {
172 printf ("\nError calling DialogF - Unsupported dialog type\n");
173 return (0);
175 dialog_num = dialog_type - 1;
176 prompt = (dialog_type == DF_PROMPT);
177 if ((!prompt && (n > NUM_BUTTONS_SUPPORTED)) ||
178 (prompt && (n > NUM_BUTTONS_MAXPROMPT))) {
179 printf ("\nError calling DialogF - Too many buttons specified\n");
180 return (0);
183 df.done_with_dialog = False;
184 df.destroyed = False;
186 va_start (var, msgstr);
187 if (prompt) { /* Get where to put dialog input string */
188 input_string = va_arg(var, char*);
190 if (n == NUM_BUTTONS_MAXPROMPT)
191 df.apply_up = 1; /* Apply button will be managed */
192 else
193 df.apply_up = 0; /* Apply button will not be managed */
194 for (argcount = 0; argcount < (int)n; ++argcount) { /* Set up button labels */
195 but_lbl = va_arg(var, char *);
196 but_lbl_xms[num_but_lbls] = XmStringCreateLtoR (but_lbl,
197 XmSTRING_DEFAULT_CHARSET);
198 but_index = df.apply_up ? argcount :
199 ((argcount == 0) ? argcount : argcount+1);
200 XtSetArg (args[argcount], button_name[but_index],
201 but_lbl_xms[num_but_lbls++]);
202 if (!strcmp(but_lbl, "Cancel") || !strcmp(but_lbl, "Dismiss"))
203 cancel_index = but_index;
206 /* Get & translate msg string
208 vsprintf (msgstr_vsp, msgstr, var);
209 va_end(var);
211 msgstr_xms = XmStringCreateLtoR (msgstr_vsp, XmSTRING_DEFAULT_CHARSET);
212 titstr_xms = XmStringCreateLtoR (" ", XmSTRING_DEFAULT_CHARSET);
214 if (prompt) { /* Prompt dialog */
215 XtSetArg (args[argcount], XmNselectionLabelString, msgstr_xms);
216 argcount++;
217 XtSetArg (args[argcount], XmNdialogTitle, titstr_xms);
218 argcount++;
219 XtSetArg (args[argcount], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL);
220 argcount ++;
222 dialog = CreatePromptDialog(parent, dialog_name[dialog_num], args,
223 argcount);
224 XtAddCallback (dialog, XmNokCallback, (XtCallbackProc)ok_callback,
225 (char *)&df);
226 XtAddCallback (dialog, XmNcancelCallback,
227 (XtCallbackProc)cancel_callback, (char *)&df);
228 XtAddCallback (dialog, XmNhelpCallback, (XtCallbackProc)help_callback,
229 (char *)&df);
230 XtAddCallback (dialog, XmNapplyCallback, (XtCallbackProc)apply_callback,
231 (char *)&df);
232 RemapDeleteKey(XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT));
234 /* Text area in prompt dialog should get focus, not ok button
235 since user enters text first. To fix this, we need to turn
236 off the default button for the dialog, until after keyboard
237 focus has been established */
238 XtVaSetValues(dialog, XmNdefaultButton, NULL, NULL);
239 XtAddCallback(XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT),
240 XmNfocusCallback, (XtCallbackProc)focusCB, (char *)dialog);
242 /* Limit the length of the text that can be entered in text field */
243 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT),
244 XmNmaxLength, DF_MAX_PROMPT_LENGTH-1, NULL);
246 /* Turn on the requested number of buttons in the dialog by
247 managing/unmanaging the button widgets */
248 switch (n) { /* number of buttons requested */
249 case 0: case 3:
250 break; /* or default of 3 buttons */
251 case 1:
252 XtUnmanageChild (XmSelectionBoxGetChild (dialog,
253 XmDIALOG_CANCEL_BUTTON) );
254 case 2:
255 XtUnmanageChild (XmSelectionBoxGetChild (dialog,
256 XmDIALOG_HELP_BUTTON) );
257 break;
258 case 4:
259 XtManageChild (XmSelectionBoxGetChild (dialog,
260 XmDIALOG_APPLY_BUTTON) );
261 df.apply_up = 1; /* apply button managed */
262 default:
263 break;
264 } /* end switch */
266 /* If the button labeled cancel or dismiss is not the cancel button, or
267 if there is no button labeled cancel or dismiss, redirect escape key
268 events (this is necessary because the XmNcancelButton resource in
269 the bulletin board widget class is blocked from being reset) */
270 if (cancel_index == -1)
271 addEscapeHandler(dialog, NULL, 0);
272 else if (cancel_index != CANCEL_BTN)
273 addEscapeHandler(dialog, &df, cancel_index);
275 /* Add a callback to the window manager close callback for the dialog */
276 AddMotifCloseCallback(XtParent(dialog),
277 (XtCallbackProc)(cancel_index == APPLY_BTN ? apply_callback :
278 (cancel_index == CANCEL_BTN ? cancel_callback :
279 (cancel_index == HELP_BTN ? help_callback : ok_callback))), &df);
281 /* Also add a callback to detect unexpected destruction (eg, because
282 the parent window is destroyed) */
283 XtAddCallback(dialog, XmNdestroyCallback,
284 (XtCallbackProc)destroy_callback, &df);
286 /* A previous call to SetDialogFPromptHistory can request that an
287 up-arrow history-recall mechanism be attached. If so, do it here */
288 if (NPromptHistoryItems != -1)
289 AddHistoryToTextWidget(XmSelectionBoxGetChild(dialog,XmDIALOG_TEXT),
290 &PromptHistory, &NPromptHistoryItems);
292 /* Pop up the dialog */
293 ManageDialogCenteredOnPointer(dialog);
295 /* Get the focus to the input string. There is some timing problem
296 within Motif that requires this to be called several times */
297 for (i=0; i<20; i++)
298 XmProcessTraversal(XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT),
299 XmTRAVERSE_CURRENT);
301 /* Wait for a response to the dialog */
302 while (!df.done_with_dialog && !df.destroyed)
303 XtAppProcessEvent (XtWidgetToApplicationContext(dialog), XtIMAll);
305 if (!df.destroyed) {
306 argcount = 0; /* Pass back string user entered */
307 XtSetArg (args[argcount], XmNtextString, &input_string_xms); argcount++;
308 XtGetValues (dialog, args, argcount);
309 XmStringGetLtoR (input_string_xms, XmSTRING_DEFAULT_CHARSET,
310 &input_string_ptr);
311 strcpy (input_string, input_string_ptr); /* This step is necessary */
312 XmStringFree(input_string_xms );
313 XtFree(input_string_ptr);
314 /* Important! Only intercept unexpected destroy events. */
315 XtRemoveCallback(dialog, XmNdestroyCallback,
316 (XtCallbackProc)destroy_callback, &df);
317 XtDestroyWidget(dialog);
319 PromptHistory = NULL;
320 NPromptHistoryItems = -1;
321 } /* End prompt dialog path */
323 else { /* MessageBox dialogs */
324 XtSetArg (args[argcount], XmNmessageString, msgstr_xms); argcount++;
326 XtSetArg (args[argcount], XmNdialogType, dialog_types[dialog_num]);
327 argcount ++;
328 XtSetArg (args[argcount], XmNdialogTitle, titstr_xms);
329 argcount++;
330 XtSetArg (args[argcount], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL);
331 argcount ++;
333 dialog_shell = CreateDialogShell (parent, dialog_name[dialog_num],
334 0, 0);
335 dialog = XmCreateMessageBox (dialog_shell, "msg box", args, argcount);
337 XtAddCallback (dialog, XmNokCallback, (XtCallbackProc)ok_callback,
338 (char *)&df);
339 XtAddCallback (dialog, XmNcancelCallback,
340 (XtCallbackProc)cancel_callback, (char *)&df);
341 XtAddCallback (dialog, XmNhelpCallback, (XtCallbackProc)help_callback,
342 (char *)&df);
344 /* Make extraneous buttons disappear */
345 switch (n) { /* n = number of buttons requested */
346 case 0: case 3:
347 break; /* default (0) gets you 3 buttons */
348 case 1:
349 XtUnmanageChild (XmMessageBoxGetChild (dialog,
350 XmDIALOG_CANCEL_BUTTON) );
351 case 2:
352 XtUnmanageChild (XmMessageBoxGetChild (dialog,
353 XmDIALOG_HELP_BUTTON) );
354 break;
355 default:
356 break;
359 /* Try to create some sensible default mnemonics */
360 createMnemonics(dialog_shell);
361 AddDialogMnemonicHandler(dialog_shell, TRUE);
363 /* If the button labeled cancel or dismiss is not the cancel button, or
364 if there is no button labeled cancel or dismiss, redirect escape key
365 events (this is necessary because the XmNcancelButton resource in
366 the bulletin board widget class is blocked from being reset) */
367 if (cancel_index == -1)
368 addEscapeHandler(dialog, NULL, 0);
369 else if (cancel_index != CANCEL_BTN)
370 addEscapeHandler(dialog, &df, cancel_index);
372 /* Add a callback to the window manager close callback for the dialog */
373 AddMotifCloseCallback(XtParent(dialog),
374 (XtCallbackProc)(cancel_index == APPLY_BTN ? apply_callback :
375 (cancel_index == CANCEL_BTN ? cancel_callback :
376 (cancel_index == HELP_BTN ? help_callback : ok_callback))),&df);
378 /* Also add a callback to detect unexpected destruction (eg, because
379 the parent window is destroyed) */
380 XtAddCallback(dialog_shell, XmNdestroyCallback,
381 (XtCallbackProc)destroy_callback, &df);
383 /* Pop up the dialog, wait for response*/
384 ManageDialogCenteredOnPointer(dialog);
385 while (!df.done_with_dialog && !df.destroyed)
386 XtAppProcessEvent (XtWidgetToApplicationContext(dialog), XtIMAll);
388 if (!df.destroyed) {
389 /* Important! Only intercept unexpected destroy events. */
390 XtRemoveCallback(dialog_shell, XmNdestroyCallback,
391 (XtCallbackProc)destroy_callback, &df);
392 XtDestroyWidget(dialog_shell);
396 XmStringFree(msgstr_xms);
397 XmStringFree(titstr_xms);
398 for (i = 0; i < num_but_lbls; ++i)
399 XmStringFree(but_lbl_xms[i]);
401 /* If the dialog was destroyed unexpectedly, the button was not set yet,
402 so we must set the index of the cancel button. */
403 if (df.destroyed) {
404 df.button = cancel_index == APPLY_BTN ? 2 :
405 (cancel_index == CANCEL_BTN ? 2 + df.apply_up :
406 (cancel_index == HELP_BTN ? 3 + df.apply_up : 1));
409 df.apply_up = 0; /* default is apply button unmanaged */
411 return (df.button);
415 ** Add up-arrow history recall to the next DialogF(DF_PROMPT... call (see
416 ** AddHistoryToTextWidget in misc.c). This must be re-set before each call.
417 ** calling DialogF with a dialog type of DF_PROMPT automatically resets
418 ** this mode back to no-history-recall.
420 void SetDialogFPromptHistory(char **historyList, int nItems)
422 PromptHistory = historyList;
423 NPromptHistoryItems = nItems;
426 static void ok_callback (Widget w, struct dfcallbackstruct *client_data,
427 caddr_t call_data)
429 client_data->done_with_dialog = True;
430 client_data->button = 1; /* Return Button number pressed */
433 static void cancel_callback (Widget w, struct dfcallbackstruct *client_data,
434 caddr_t call_data)
436 client_data->done_with_dialog = True;
437 client_data->button = 2 + client_data->apply_up; /* =3 if apply button managed */
440 static void help_callback (Widget w, struct dfcallbackstruct *client_data,
441 caddr_t call_data)
443 client_data->done_with_dialog = True;
444 client_data->button = 3 + client_data->apply_up; /* =4 if apply button managed */
447 static void apply_callback (Widget w, struct dfcallbackstruct *client_data,
448 caddr_t call_data)
450 client_data->done_with_dialog = True;
451 client_data->button = 2; /* Motif puts between OK and cancel */
454 static void destroy_callback (Widget w, struct dfcallbackstruct *client_data,
455 caddr_t call_data)
457 client_data->destroyed = True;
461 ** callback for returning default button status to the ok button once we're
462 ** sure the text area in the prompt dialog has input focus.
464 static void focusCB(Widget w, Widget dialog, caddr_t call_data)
466 XtVaSetValues(dialog, XmNdefaultButton,
467 XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON), NULL);
471 ** Message and prompt dialogs hardwire the cancel button to the XmNcancelButton
472 ** resource in the bulletin board dialog. Since we rename the buttons, the
473 ** cancel label may not be on the dialog's idea of the Cancel button. The only
474 ** way to make the accelerator for Cancel and Dismiss (the escape key) work
475 ** correctly in this situation is to brutally catch and redirect the event.
476 ** This routine redirects escape key events in the dialog to the callback for
477 ** the button "whichBtn", passing it argument "df". If "df" is NULL, simply
478 ** block the event from reaching the dialog.
480 static void addEscapeHandler(Widget dialog, struct dfcallbackstruct *df,
481 int whichBtn)
483 XtAddEventHandler(dialog, KeyPressMask, False, whichBtn == APPLY_BTN ?
484 escapeApplyCB : escapeHelpCB, (XtPointer)df);
485 XtGrabKey(dialog, XKeysymToKeycode(XtDisplay(dialog), XK_Escape), 0,
486 True, GrabModeAsync, GrabModeAsync);
490 ** Event handler for escape key to redirect the event to the help button.
491 ** Attached when cancel label appears on Help button.
493 static void escapeHelpCB(Widget w, XtPointer callData, XEvent *event,
494 Boolean *cont)
496 if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape))
497 return;
498 if (callData != NULL)
499 help_callback(w, (struct dfcallbackstruct *)callData, NULL);
500 *cont = False;
504 ** Event handler for escape key to redirect event to the apply button.
505 ** Attached when cancel label appears on Apply button.
507 static void escapeApplyCB(Widget w, XtPointer callData, XEvent *event,
508 Boolean *cont)
510 if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape))
511 return;
512 if (callData != NULL)
513 apply_callback(w, (struct dfcallbackstruct *)callData, NULL);
514 *cont = False;
518 ** Only used by createMnemonics(Widget w)
520 static void recurseCreateMnemonics(Widget w, Boolean *mnemonicUsed)
522 WidgetList children;
523 Cardinal numChildren, i;
525 XtVaGetValues(w,
526 XmNchildren, &children,
527 XmNnumChildren, &numChildren,
528 NULL);
530 for (i = 0; i < numChildren; i++)
532 Widget child = children[i];
534 if (XtIsComposite(child))
536 recurseCreateMnemonics(child, mnemonicUsed);
538 else if (XtIsSubclass(child, xmPushButtonWidgetClass) ||
539 XtIsSubclass(child, xmPushButtonGadgetClass))
541 XmString xmslabel;
542 char *label;
543 int c;
545 XtVaGetValues(child, XmNlabelString, &xmslabel, NULL);
546 if (XmStringGetLtoR(xmslabel, XmSTRING_DEFAULT_CHARSET, &label))
548 /* Scan through the string to see if the label is already used */
549 int labelLen = strlen(label);
550 for (c = 0; c < labelLen; c++)
552 unsigned char lc = tolower((unsigned char)label[c]);
554 if (!mnemonicUsed[lc])
556 mnemonicUsed[lc] = TRUE;
557 XtVaSetValues(child, XmNmnemonic, label[c], NULL);
558 break;
562 XtFree(label);
569 ** Automatically create mnemonics for a widget. Traverse all it's
570 ** children. If the child is a push button, snag the first unused letter
571 ** and make that the mnemonic. This is useful for DialogF dialogs which
572 ** can have arbitrary text in the buttons.
575 static void createMnemonics(Widget w)
577 Boolean mnemonicUsed[UCHAR_MAX + 1];
579 memset(mnemonicUsed, FALSE, sizeof mnemonicUsed / sizeof *mnemonicUsed);
580 recurseCreateMnemonics(w, mnemonicUsed);