1 static const char CVSID
[] = "$Id: DialogF.c,v 1.17 2001/08/14 08:37:16 jlous Exp $";
2 /*******************************************************************************
4 * DialogF -- modal dialog printf routine *
6 * Copyright (C) 1999 Mark Edel *
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 *
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 *
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 *
22 * Nirvana Text Editor *
25 * Written by Joy Kyriakopulos *
27 *******************************************************************************/
34 #include <Xm/MessageB.h>
35 #include <Xm/DialogS.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>
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
,
63 static void help_callback (Widget w
, struct dfcallbackstruct
*client_data
,
65 static void cancel_callback (Widget w
, struct dfcallbackstruct
*client_data
,
67 static void ok_callback (Widget w
, struct dfcallbackstruct
*client_data
,
69 static void destroy_callback (Widget w
, struct dfcallbackstruct
*client_data
,
71 static void focusCB(Widget w
, Widget dialog
, caddr_t call_data
);
72 static void addEscapeHandler(Widget dialog
, struct dfcallbackstruct
*df
,
74 static void escapeHelpCB(Widget w
, XtPointer callData
, XEvent
*event
,
76 static void escapeApplyCB(Widget w
, XtPointer callData
, XEvent
*event
,
78 static void createMnemonics(Widget w
);
80 /******************************************************************************
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. *
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 *
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)*
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!", *
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 char *msgstr
, ...) /* variable # arguments */
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;
146 struct dfcallbackstruct df
;
148 static int dialog_types
[] = { /* Supported dialog types */
150 XmDIALOG_INFORMATION
,
156 static char *dialog_name
[] = { /* Corresponding dialog names */
164 static char *button_name
[] = { /* Motif button names */
166 XmNapplyLabelString
, /* button #2, if managed */
167 XmNcancelLabelString
,
170 /* Validate input parameters */
171 if ((dialog_type
> NUM_DIALOGS_SUPPORTED
) || (dialog_type
<= 0)) {
172 printf ("\nError calling DialogF - Unsupported dialog type\n");
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");
183 df
.done_with_dialog
= False
;
184 df
.destroyed
= False
;
185 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 */
193 df
.apply_up
= 0; /* Apply button will not be managed */
195 for (argcount
= 0; argcount
< n
; ++argcount
) { /* Set up button labels */
196 but_lbl
= va_arg(var
, char *);
197 but_lbl_xms
[num_but_lbls
] = XmStringCreateLtoR (but_lbl
,
198 XmSTRING_DEFAULT_CHARSET
);
199 but_index
= df
.apply_up
? argcount
:
200 ((argcount
== 0) ? argcount
: argcount
+1);
201 XtSetArg (args
[argcount
], button_name
[but_index
],
202 but_lbl_xms
[num_but_lbls
++]);
203 if (!strcmp(but_lbl
, "Cancel") || !strcmp(but_lbl
, "Dismiss"))
204 cancel_index
= but_index
;
207 /* Get & translate msg string
209 vsprintf (msgstr_vsp
, msgstr
, 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
);
217 XtSetArg (args
[argcount
], XmNdialogTitle
, titstr_xms
);
219 XtSetArg (args
[argcount
], XmNdialogStyle
, XmDIALOG_FULL_APPLICATION_MODAL
);
222 dialog
= CreatePromptDialog(parent
, dialog_name
[dialog_num
], args
,
224 XtAddCallback (dialog
, XmNokCallback
, (XtCallbackProc
)ok_callback
,
226 XtAddCallback (dialog
, XmNcancelCallback
,
227 (XtCallbackProc
)cancel_callback
, (char *)&df
);
228 XtAddCallback (dialog
, XmNhelpCallback
, (XtCallbackProc
)help_callback
,
230 XtAddCallback (dialog
, XmNapplyCallback
, (XtCallbackProc
)apply_callback
,
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 */
250 break; /* or default of 3 buttons */
252 XtUnmanageChild (XmSelectionBoxGetChild (dialog
,
253 XmDIALOG_CANCEL_BUTTON
) );
255 XtUnmanageChild (XmSelectionBoxGetChild (dialog
,
256 XmDIALOG_HELP_BUTTON
) );
259 XtManageChild (XmSelectionBoxGetChild (dialog
,
260 XmDIALOG_APPLY_BUTTON
) );
261 df
.apply_up
= 1; /* apply button managed */
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 */
298 XmProcessTraversal(XmSelectionBoxGetChild(dialog
, XmDIALOG_TEXT
),
301 /* Wait for a response to the dialog */
302 while (!df
.done_with_dialog
&& !df
.destroyed
)
303 XtAppProcessEvent (XtWidgetToApplicationContext(dialog
), XtIMAll
);
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
,
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
]);
328 XtSetArg (args
[argcount
], XmNdialogTitle
, titstr_xms
);
330 XtSetArg (args
[argcount
], XmNdialogStyle
, XmDIALOG_FULL_APPLICATION_MODAL
);
333 dialog_shell
= CreateDialogShell (parent
, dialog_name
[dialog_num
],
335 dialog
= XmCreateMessageBox (dialog_shell
, "msg box", args
, argcount
);
337 XtAddCallback (dialog
, XmNokCallback
, (XtCallbackProc
)ok_callback
,
339 XtAddCallback (dialog
, XmNcancelCallback
,
340 (XtCallbackProc
)cancel_callback
, (char *)&df
);
341 XtAddCallback (dialog
, XmNhelpCallback
, (XtCallbackProc
)help_callback
,
344 /* Make extraneous buttons disappear */
345 switch (n
) { /* n = number of buttons requested */
347 break; /* default (0) gets you 3 buttons */
349 XtUnmanageChild (XmMessageBoxGetChild (dialog
,
350 XmDIALOG_CANCEL_BUTTON
) );
352 XtUnmanageChild (XmMessageBoxGetChild (dialog
,
353 XmDIALOG_HELP_BUTTON
) );
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
);
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. */
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 */
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
,
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
,
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
,
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
,
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
,
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
,
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
,
496 if (event
->xkey
.keycode
!= XKeysymToKeycode(XtDisplay(w
), XK_Escape
))
498 if (callData
!= NULL
)
499 help_callback(w
, (struct dfcallbackstruct
*)callData
, NULL
);
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
,
510 if (event
->xkey
.keycode
!= XKeysymToKeycode(XtDisplay(w
), XK_Escape
))
512 if (callData
!= NULL
)
513 apply_callback(w
, (struct dfcallbackstruct
*)callData
, NULL
);
518 ** Only used by createMnemonics(Widget w)
520 static void recurseCreateMnemonics(Widget w
, Boolean
*mnemonicUsed
)
523 Cardinal numChildren
, i
;
526 XmNchildren
, &children
,
527 XmNnumChildren
, &numChildren
,
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
))
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
);
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
);