Don't quit if getpwuid() fails. It can fail if some nameservices are not
[nedit.git] / util / DialogF.c
blob1f72d7e24713e88cab5fccca1e1c641193f2d37b
1 static const char CVSID[] = "$Id: DialogF.c,v 1.26 2003/04/10 17:37:24 edg 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 *******************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
33 #include "DialogF.h"
34 #include "misc.h"
36 #include <stdio.h>
37 #include <stdarg.h>
38 #include <string.h>
39 #include <ctype.h>
40 #include <limits.h>
42 #include <Xm/Xm.h>
43 #include <Xm/MessageB.h>
44 #include <Xm/DialogS.h>
45 #include <Xm/PushB.h>
46 #include <Xm/PushBG.h>
47 #include <Xm/SelectioB.h>
48 #include <X11/StringDefs.h>
49 #include <X11/Intrinsic.h>
50 #include <X11/keysym.h>
52 #ifdef HAVE_DEBUG_H
53 #include "../debug.h"
54 #endif
56 #define NUM_DIALOGS_SUPPORTED 6
57 #define NUM_BUTTONS_SUPPORTED 3 /* except prompt dialog */
58 #define NUM_BUTTONS_MAXPROMPT 4
59 #define MAX_TITLE_LEN 256
61 enum dialogBtnIndecies {OK_BTN, APPLY_BTN, CANCEL_BTN, HELP_BTN};
63 struct dfcallbackstruct {
64 unsigned button; /* button pressed by user */
65 Boolean done_with_dialog; /* set by callbacks; dialog can be destroyed */
66 unsigned apply_up; /* will = 1 when apply button managed */
67 Boolean destroyed; /* set when dialog is destroyed unexpectedly */
70 static char **PromptHistory = NULL;
71 static int NPromptHistoryItems = -1;
73 static void apply_callback (Widget w, struct dfcallbackstruct *client_data,
74 caddr_t call_data);
75 static void help_callback (Widget w, struct dfcallbackstruct *client_data,
76 caddr_t call_data);
77 static void cancel_callback (Widget w, struct dfcallbackstruct *client_data,
78 caddr_t call_data);
79 static void ok_callback (Widget w, struct dfcallbackstruct *client_data,
80 caddr_t call_data);
81 static void destroy_callback (Widget w, struct dfcallbackstruct *client_data,
82 caddr_t call_data);
83 static void focusCB(Widget w, Widget dialog, caddr_t call_data);
84 static void addEscapeHandler(Widget dialog, struct dfcallbackstruct *df,
85 int whichBtn);
86 static void escapeHelpCB(Widget w, XtPointer callData, XEvent *event,
87 Boolean *cont);
88 static void escapeApplyCB(Widget w, XtPointer callData, XEvent *event,
89 Boolean *cont);
90 static void createMnemonics(Widget w);
92 /*******************************************************************************
93 * DialogF() *
94 * *
95 * function to put up modal versions of the XmCreate<type>Dialog functions *
96 * (where <type> is Error, Information, Prompt, Question, Message, or Warning). *
97 * The purpose of this routine is to allow a printf-style dialog box to be *
98 * invoked in-line without giving control back to the main loop. The message *
99 * string can contain vsprintf specifications. *
100 * DialogF displays the dialog in application-modal style, blocking the *
101 * application and keeping the modal dialog as the top window until the user *
102 * responds. DialogF accepts a variable number of arguments, so the calling *
103 * routine needs to #include <stdarg.h>. The first button is automatically *
104 * marked as the default button (activated when the user types Return, *
105 * surrounded by a special outline), and any button named either Cancel, or *
106 * Dismiss is marked as the cancel button (activated by the ESC key). Buttons *
107 * marked Dismiss or Cancel are also triggered by close of dialog via the *
108 * window close box. If there's no Cancel or Dismiss button, button 1 is *
109 * invoked when the close box is pressed. *
111 * Arguments: *
113 * unsigned dialog_type dialog type (e.g. DF_ERR for error dialog, refer to *
114 * DialogF.h for dialog type values) *
115 * Widget parent parent widget ID *
116 * unsigned n # of buttons to display; if set to 0, use defaults in *
117 * XmCreate<type>Dialog; value in range 0 to *
118 * NUM_BUTTONS_SUPPORTED (for prompt dialogs: *
119 * NUM_BUTTONS_MAXPROMPT) *
120 * char* title dialog title *
121 * char* msgstr message string (may contain conversion specifications *
122 * for vsprintf) *
123 * char* input_string if dialog type = DF_PROMPT, then: a character string *
124 * array in which to put the string input by the user. Do *
125 * NOT include an input_string argument for other dialog *
126 * types. *
127 * char* but_lbl button label(s) for buttons requested (if n > 0, one *
128 * but_lbl argument per button) *
129 * <anytype> <args> arguments for vsprintf (if any) *
132 * Returns: *
134 * button selected by user (i.e. 1, 2, or 3. or 4 for prompt) *
137 * Examples: *
139 * but_pressed = DialogF (DF_QUES, toplevel, 3, "Direction?", "up", *
140 * "down", "other"); *
141 * but_pressed = DialogF (DF_ERR, toplevel, 1, "You can't do that!", *
142 * "Acknowledged"); *
143 * but_pressed = DialogF (DF_PROMPT, toplevel, 0, "New %s", *
144 * new_sub_category, categories[i]); *
146 unsigned DialogF(int dialog_type, Widget parent, unsigned n, const char* title,
147 const char* msgstr, ...) /* variable # arguments */
149 va_list var;
151 Widget dialog, dialog_shell;
152 unsigned dialog_num, prompt;
153 XmString but_lbl_xms[NUM_BUTTONS_MAXPROMPT];
154 XmString msgstr_xms, input_string_xms, titstr_xms;
155 char msgstr_vsp[DF_MAX_MSG_LENGTH+1];
156 char *but_lbl, *input_string = NULL, *input_string_ptr;
157 int argcount, num_but_lbls = 0, i, but_index, cancel_index = -1;
158 Arg args[256];
159 char titleCopy[MAX_TITLE_LEN];
161 struct dfcallbackstruct df;
163 static int dialog_types[] = { /* Supported dialog types */
164 XmDIALOG_ERROR,
165 XmDIALOG_INFORMATION,
166 XmDIALOG_MESSAGE,
167 XmDIALOG_QUESTION,
168 XmDIALOG_WARNING,
169 XmDIALOG_PROMPT
171 static char *dialog_name[] = { /* Corresponding dialog names */
172 "Error",
173 "Information",
174 "Message",
175 "Question",
176 "Warning",
177 "Prompt"
179 static char *button_name[] = { /* Motif button names */
180 XmNokLabelString,
181 XmNapplyLabelString, /* button #2, if managed */
182 XmNcancelLabelString,
183 XmNhelpLabelString
185 /* Validate input parameters */
186 if ((dialog_type > NUM_DIALOGS_SUPPORTED) || (dialog_type <= 0)) {
187 printf ("\nError calling DialogF - Unsupported dialog type\n");
188 return (0);
190 dialog_num = dialog_type - 1;
191 prompt = (dialog_type == DF_PROMPT);
192 if ((!prompt && (n > NUM_BUTTONS_SUPPORTED)) ||
193 (prompt && (n > NUM_BUTTONS_MAXPROMPT))) {
194 printf ("\nError calling DialogF - Too many buttons specified\n");
195 return (0);
198 df.done_with_dialog = False;
199 df.destroyed = False;
201 va_start (var, msgstr);
202 if (prompt) { /* Get where to put dialog input string */
203 input_string = va_arg(var, char*);
205 if (n == NUM_BUTTONS_MAXPROMPT)
206 df.apply_up = 1; /* Apply button will be managed */
207 else
208 df.apply_up = 0; /* Apply button will not be managed */
209 for (argcount = 0; argcount < (int)n; ++argcount) { /* Set up button labels */
210 but_lbl = va_arg(var, char *);
211 but_lbl_xms[num_but_lbls] = XmStringCreateLtoR (but_lbl,
212 XmSTRING_DEFAULT_CHARSET);
213 but_index = df.apply_up ? argcount :
214 ((argcount == 0) ? argcount : argcount+1);
215 XtSetArg (args[argcount], button_name[but_index],
216 but_lbl_xms[num_but_lbls++]);
217 if (!strcmp(but_lbl, "Cancel") || !strcmp(but_lbl, "Dismiss"))
218 cancel_index = but_index;
221 /* Get & translate msg string
223 vsprintf (msgstr_vsp, msgstr, var);
224 va_end(var);
226 strncpy(&titleCopy[0], title, MAX_TITLE_LEN);
227 titleCopy[MAX_TITLE_LEN-1] = '\0';
229 msgstr_xms = XmStringCreateLtoR(msgstr_vsp, XmSTRING_DEFAULT_CHARSET);
230 titstr_xms = XmStringCreateLtoR(titleCopy, XmSTRING_DEFAULT_CHARSET);
232 if (prompt) { /* Prompt dialog */
233 XtSetArg (args[argcount], XmNselectionLabelString, msgstr_xms);
234 argcount++;
235 XtSetArg (args[argcount], XmNdialogTitle, titstr_xms);
236 argcount++;
237 XtSetArg (args[argcount], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL);
238 argcount ++;
240 dialog = CreatePromptDialog(parent, dialog_name[dialog_num], args,
241 argcount);
242 XtAddCallback (dialog, XmNokCallback, (XtCallbackProc)ok_callback,
243 (char *)&df);
244 XtAddCallback (dialog, XmNcancelCallback,
245 (XtCallbackProc)cancel_callback, (char *)&df);
246 XtAddCallback (dialog, XmNhelpCallback, (XtCallbackProc)help_callback,
247 (char *)&df);
248 XtAddCallback (dialog, XmNapplyCallback, (XtCallbackProc)apply_callback,
249 (char *)&df);
250 RemapDeleteKey(XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT));
252 /* Text area in prompt dialog should get focus, not ok button
253 since user enters text first. To fix this, we need to turn
254 off the default button for the dialog, until after keyboard
255 focus has been established */
256 XtVaSetValues(dialog, XmNdefaultButton, NULL, NULL);
257 XtAddCallback(XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT),
258 XmNfocusCallback, (XtCallbackProc)focusCB, (char *)dialog);
260 /* Limit the length of the text that can be entered in text field */
261 XtVaSetValues(XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT),
262 XmNmaxLength, DF_MAX_PROMPT_LENGTH-1, NULL);
264 /* Turn on the requested number of buttons in the dialog by
265 managing/unmanaging the button widgets */
266 switch (n) { /* number of buttons requested */
267 case 0: case 3:
268 break; /* or default of 3 buttons */
269 case 1:
270 XtUnmanageChild (XmSelectionBoxGetChild (dialog,
271 XmDIALOG_CANCEL_BUTTON) );
272 case 2:
273 XtUnmanageChild (XmSelectionBoxGetChild (dialog,
274 XmDIALOG_HELP_BUTTON) );
275 break;
276 case 4:
277 XtManageChild (XmSelectionBoxGetChild (dialog,
278 XmDIALOG_APPLY_BUTTON) );
279 df.apply_up = 1; /* apply button managed */
280 default:
281 break;
282 } /* end switch */
284 /* If the button labeled cancel or dismiss is not the cancel button, or
285 if there is no button labeled cancel or dismiss, redirect escape key
286 events (this is necessary because the XmNcancelButton resource in
287 the bulletin board widget class is blocked from being reset) */
288 if (cancel_index == -1)
289 addEscapeHandler(dialog, NULL, 0);
290 else if (cancel_index != CANCEL_BTN)
291 addEscapeHandler(dialog, &df, cancel_index);
293 /* Add a callback to the window manager close callback for the dialog */
294 AddMotifCloseCallback(XtParent(dialog),
295 (XtCallbackProc)(cancel_index == APPLY_BTN ? apply_callback :
296 (cancel_index == CANCEL_BTN ? cancel_callback :
297 (cancel_index == HELP_BTN ? help_callback : ok_callback))), &df);
299 /* Also add a callback to detect unexpected destruction (eg, because
300 the parent window is destroyed) */
301 XtAddCallback(dialog, XmNdestroyCallback,
302 (XtCallbackProc)destroy_callback, &df);
304 /* A previous call to SetDialogFPromptHistory can request that an
305 up-arrow history-recall mechanism be attached. If so, do it here */
306 if (NPromptHistoryItems != -1)
307 AddHistoryToTextWidget(XmSelectionBoxGetChild(dialog,XmDIALOG_TEXT),
308 &PromptHistory, &NPromptHistoryItems);
310 /* Pop up the dialog */
311 ManageDialogCenteredOnPointer(dialog);
313 /* Get the focus to the input string. There is some timing problem
314 within Motif that requires this to be called several times */
315 for (i=0; i<20; i++)
316 XmProcessTraversal(XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT),
317 XmTRAVERSE_CURRENT);
319 /* Wait for a response to the dialog */
320 while (!df.done_with_dialog && !df.destroyed)
321 XtAppProcessEvent (XtWidgetToApplicationContext(dialog), XtIMAll);
323 if (!df.destroyed) {
324 argcount = 0; /* Pass back string user entered */
325 XtSetArg (args[argcount], XmNtextString, &input_string_xms); argcount++;
326 XtGetValues (dialog, args, argcount);
327 XmStringGetLtoR (input_string_xms, XmSTRING_DEFAULT_CHARSET,
328 &input_string_ptr);
329 strcpy (input_string, input_string_ptr); /* This step is necessary */
330 XmStringFree(input_string_xms );
331 XtFree(input_string_ptr);
332 /* Important! Only intercept unexpected destroy events. */
333 XtRemoveCallback(dialog, XmNdestroyCallback,
334 (XtCallbackProc)destroy_callback, &df);
335 XtDestroyWidget(dialog);
337 PromptHistory = NULL;
338 NPromptHistoryItems = -1;
339 } /* End prompt dialog path */
341 else { /* MessageBox dialogs */
342 XtSetArg (args[argcount], XmNmessageString, msgstr_xms); argcount++;
344 XtSetArg (args[argcount], XmNdialogType, dialog_types[dialog_num]);
345 argcount ++;
346 XtSetArg (args[argcount], XmNdialogTitle, titstr_xms);
347 argcount++;
348 XtSetArg (args[argcount], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL);
349 argcount ++;
351 dialog_shell = CreateDialogShell (parent, dialog_name[dialog_num],
352 0, 0);
353 dialog = XmCreateMessageBox (dialog_shell, "msg box", args, argcount);
355 XtAddCallback (dialog, XmNokCallback, (XtCallbackProc)ok_callback,
356 (char *)&df);
357 XtAddCallback (dialog, XmNcancelCallback,
358 (XtCallbackProc)cancel_callback, (char *)&df);
359 XtAddCallback (dialog, XmNhelpCallback, (XtCallbackProc)help_callback,
360 (char *)&df);
362 /* Make extraneous buttons disappear */
363 switch (n) { /* n = number of buttons requested */
364 case 0: case 3:
365 break; /* default (0) gets you 3 buttons */
366 case 1:
367 XtUnmanageChild (XmMessageBoxGetChild (dialog,
368 XmDIALOG_CANCEL_BUTTON) );
369 case 2:
370 XtUnmanageChild (XmMessageBoxGetChild (dialog,
371 XmDIALOG_HELP_BUTTON) );
372 break;
373 default:
374 break;
377 /* Try to create some sensible default mnemonics */
378 createMnemonics(dialog_shell);
379 AddDialogMnemonicHandler(dialog_shell, TRUE);
381 /* If the button labeled cancel or dismiss is not the cancel button, or
382 if there is no button labeled cancel or dismiss, redirect escape key
383 events (this is necessary because the XmNcancelButton resource in
384 the bulletin board widget class is blocked from being reset) */
385 if (cancel_index == -1)
386 addEscapeHandler(dialog, NULL, 0);
387 else if (cancel_index != CANCEL_BTN)
388 addEscapeHandler(dialog, &df, cancel_index);
390 /* Add a callback to the window manager close callback for the dialog */
391 AddMotifCloseCallback(XtParent(dialog),
392 (XtCallbackProc)(cancel_index == APPLY_BTN ? apply_callback :
393 (cancel_index == CANCEL_BTN ? cancel_callback :
394 (cancel_index == HELP_BTN ? help_callback : ok_callback))),&df);
396 /* Also add a callback to detect unexpected destruction (eg, because
397 the parent window is destroyed) */
398 XtAddCallback(dialog_shell, XmNdestroyCallback,
399 (XtCallbackProc)destroy_callback, &df);
401 /* Pop up the dialog, wait for response*/
402 ManageDialogCenteredOnPointer(dialog);
403 while (!df.done_with_dialog && !df.destroyed)
404 XtAppProcessEvent (XtWidgetToApplicationContext(dialog), XtIMAll);
406 if (!df.destroyed) {
407 /* Important! Only intercept unexpected destroy events. */
408 XtRemoveCallback(dialog_shell, XmNdestroyCallback,
409 (XtCallbackProc)destroy_callback, &df);
410 XtDestroyWidget(dialog_shell);
414 XmStringFree(msgstr_xms);
415 XmStringFree(titstr_xms);
416 for (i = 0; i < num_but_lbls; ++i)
417 XmStringFree(but_lbl_xms[i]);
419 /* If the dialog was destroyed unexpectedly, the button was not set yet,
420 so we must set the index of the cancel button. */
421 if (df.destroyed) {
422 df.button = cancel_index == APPLY_BTN ? 2 :
423 (cancel_index == CANCEL_BTN ? 2 + df.apply_up :
424 (cancel_index == HELP_BTN ? 3 + df.apply_up : 1));
427 df.apply_up = 0; /* default is apply button unmanaged */
429 return (df.button);
433 ** Add up-arrow history recall to the next DialogF(DF_PROMPT... call (see
434 ** AddHistoryToTextWidget in misc.c). This must be re-set before each call.
435 ** calling DialogF with a dialog type of DF_PROMPT automatically resets
436 ** this mode back to no-history-recall.
438 void SetDialogFPromptHistory(char **historyList, int nItems)
440 PromptHistory = historyList;
441 NPromptHistoryItems = nItems;
444 static void ok_callback (Widget w, struct dfcallbackstruct *client_data,
445 caddr_t call_data)
447 client_data->done_with_dialog = True;
448 client_data->button = 1; /* Return Button number pressed */
451 static void cancel_callback (Widget w, struct dfcallbackstruct *client_data,
452 caddr_t call_data)
454 client_data->done_with_dialog = True;
455 client_data->button = 2 + client_data->apply_up; /* =3 if apply button managed */
458 static void help_callback (Widget w, struct dfcallbackstruct *client_data,
459 caddr_t call_data)
461 client_data->done_with_dialog = True;
462 client_data->button = 3 + client_data->apply_up; /* =4 if apply button managed */
465 static void apply_callback (Widget w, struct dfcallbackstruct *client_data,
466 caddr_t call_data)
468 client_data->done_with_dialog = True;
469 client_data->button = 2; /* Motif puts between OK and cancel */
472 static void destroy_callback (Widget w, struct dfcallbackstruct *client_data,
473 caddr_t call_data)
475 client_data->destroyed = True;
479 ** callback for returning default button status to the ok button once we're
480 ** sure the text area in the prompt dialog has input focus.
482 static void focusCB(Widget w, Widget dialog, caddr_t call_data)
484 XtVaSetValues(dialog, XmNdefaultButton,
485 XmSelectionBoxGetChild(dialog, XmDIALOG_OK_BUTTON), NULL);
489 ** Message and prompt dialogs hardwire the cancel button to the XmNcancelButton
490 ** resource in the bulletin board dialog. Since we rename the buttons, the
491 ** cancel label may not be on the dialog's idea of the Cancel button. The only
492 ** way to make the accelerator for Cancel and Dismiss (the escape key) work
493 ** correctly in this situation is to brutally catch and redirect the event.
494 ** This routine redirects escape key events in the dialog to the callback for
495 ** the button "whichBtn", passing it argument "df". If "df" is NULL, simply
496 ** block the event from reaching the dialog.
498 static void addEscapeHandler(Widget dialog, struct dfcallbackstruct *df,
499 int whichBtn)
501 XtAddEventHandler(dialog, KeyPressMask, False, whichBtn == APPLY_BTN ?
502 escapeApplyCB : escapeHelpCB, (XtPointer)df);
503 XtGrabKey(dialog, XKeysymToKeycode(XtDisplay(dialog), XK_Escape), 0,
504 True, GrabModeAsync, GrabModeAsync);
508 ** Event handler for escape key to redirect the event to the help button.
509 ** Attached when cancel label appears on Help button.
511 static void escapeHelpCB(Widget w, XtPointer callData, XEvent *event,
512 Boolean *cont)
514 if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape))
515 return;
516 if (callData != NULL)
517 help_callback(w, (struct dfcallbackstruct *)callData, NULL);
518 *cont = False;
522 ** Event handler for escape key to redirect event to the apply button.
523 ** Attached when cancel label appears on Apply button.
525 static void escapeApplyCB(Widget w, XtPointer callData, XEvent *event,
526 Boolean *cont)
528 if (event->xkey.keycode != XKeysymToKeycode(XtDisplay(w), XK_Escape))
529 return;
530 if (callData != NULL)
531 apply_callback(w, (struct dfcallbackstruct *)callData, NULL);
532 *cont = False;
536 ** Only used by createMnemonics(Widget w)
538 static void recurseCreateMnemonics(Widget w, Boolean *mnemonicUsed)
540 WidgetList children;
541 Cardinal numChildren, i;
543 XtVaGetValues(w,
544 XmNchildren, &children,
545 XmNnumChildren, &numChildren,
546 NULL);
548 for (i = 0; i < numChildren; i++)
550 Widget child = children[i];
552 if (XtIsComposite(child))
554 recurseCreateMnemonics(child, mnemonicUsed);
556 else if (XtIsSubclass(child, xmPushButtonWidgetClass) ||
557 XtIsSubclass(child, xmPushButtonGadgetClass))
559 XmString xmslabel;
560 char *label;
561 int c;
563 XtVaGetValues(child, XmNlabelString, &xmslabel, NULL);
564 if (XmStringGetLtoR(xmslabel, XmSTRING_DEFAULT_CHARSET, &label))
566 /* Scan through the string to see if the label is already used */
567 int labelLen = strlen(label);
568 for (c = 0; c < labelLen; c++)
570 unsigned char lc = tolower((unsigned char)label[c]);
572 if (!mnemonicUsed[lc])
574 mnemonicUsed[lc] = TRUE;
575 XtVaSetValues(child, XmNmnemonic, label[c], NULL);
576 break;
580 XtFree(label);
582 XmStringFree(xmslabel);
588 ** Automatically create mnemonics for a widget. Traverse all it's
589 ** children. If the child is a push button, snag the first unused letter
590 ** and make that the mnemonic. This is useful for DialogF dialogs which
591 ** can have arbitrary text in the buttons.
593 static void createMnemonics(Widget w)
595 Boolean mnemonicUsed[UCHAR_MAX + 1];
597 memset(mnemonicUsed, FALSE, sizeof mnemonicUsed / sizeof *mnemonicUsed);
598 recurseCreateMnemonics(w, mnemonicUsed);