workaround for GTK2 bug: bring main window back to front,
[obnox/wireshark/wip.git] / gtk / dlg_utils.c
1 /* dlg_utils.c
2  * Utilities to use when constructing dialogs
3  *
4  * $Id: dlg_utils.c,v 1.27 2004/05/19 22:17:08 ulfl Exp $
5  *
6  * Ethereal - Network traffic analyzer
7  * By Gerald Combs <gerald@ethereal.com>
8  * Copyright 1998 Gerald Combs
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23  */
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include <gtk/gtk.h>
30 #include <gdk/gdkkeysyms.h>
31
32 #include "gtkglobals.h"
33 #include "ui_util.h"
34 #include "dlg_utils.h"
35 #include "compat_macros.h"
36
37 #include <string.h>
38 #include <stdio.h>
39 #include <stdarg.h>
40
41 static void
42 dlg_activate (GtkWidget *widget, gpointer ok_button);
43
44 static gint
45 dlg_key_press (GtkWidget *widget, GdkEventKey *event, gpointer cancel_button);
46
47
48
49 /* create a button for the button row (helper for dlg_button_row_new) */
50 static GtkWidget *
51 dlg_button_new(GtkWidget *hbox, GtkWidget *button_hbox, gchar *stock_id)
52 {
53     GtkWidget *button;
54
55     button = BUTTON_NEW_FROM_STOCK(stock_id);
56     GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
57     OBJECT_SET_DATA(hbox, stock_id, button);
58     gtk_box_pack_end(GTK_BOX(button_hbox), button, FALSE, FALSE, 0);
59     gtk_widget_show(button);
60     return button;
61 }
62
63
64 /* create a button row for a dialog */
65
66 /* The purpose of this is, to have one place available, where all button rows 
67  * from all dialogs are layouted. This will:
68  *
69  * a.) keep the button layout more consistent over the different dialogs
70  * b.) being able to switch between different button layouts, e.g.:
71  *     GTK1 (e.g. win32) "OK" "Apply" "Cancel"
72  *     GTK2 (e.g. GNOME) "Apply" "Cancel" "OK"
73  */
74 GtkWidget *
75 dlg_button_row_new(gchar *stock_id_first, ...)
76 {
77     gint        buttons = 0;
78     va_list     stock_id_list;
79     gchar       *stock_id = stock_id_first;
80     GtkWidget   *hbox;
81     GtkWidget   *button_hbox;
82     GtkWidget   *help_hbox;
83     GtkWidget   *button;
84
85     gchar *ok           = NULL;
86     gchar *apply        = NULL;
87     gchar *save         = NULL;
88     gchar *cancel       = NULL;
89     gchar *close        = NULL;
90     gchar *clear        = NULL;
91     gchar *stop         = NULL;
92     gchar *create_stat  = NULL;
93     gchar *help         = NULL;
94     gchar *print        = NULL;
95     gchar *find         = NULL;
96     gchar *jump         = NULL;
97     gchar *yes          = NULL;
98     gchar *no           = NULL;
99
100
101     va_start(stock_id_list, stock_id_first);
102
103     /* get all buttons needed */
104     while(stock_id != NULL) {
105         if (strcmp(stock_id, GTK_STOCK_OK) == 0) {
106             ok = stock_id;
107         } else if (strcmp(stock_id, ETHEREAL_STOCK_CREATE_STAT) == 0) {
108             create_stat = stock_id;
109         } else if (strcmp(stock_id, GTK_STOCK_APPLY) == 0) {
110             apply = stock_id;
111         } else if (strcmp(stock_id, GTK_STOCK_SAVE) == 0) {
112             save = stock_id;
113         } else if (strcmp(stock_id, GTK_STOCK_CANCEL) == 0) {
114             cancel = stock_id;
115         } else if (strcmp(stock_id, GTK_STOCK_CLOSE) == 0) {
116             close = stock_id;
117         } else if (strcmp(stock_id, GTK_STOCK_CLEAR) == 0) {
118             clear = stock_id;
119         } else if (strcmp(stock_id, GTK_STOCK_STOP) == 0) {
120             stop = stock_id;
121         } else if (strcmp(stock_id, GTK_STOCK_HELP) == 0) {
122             help = stock_id;
123         } else if (strcmp(stock_id, GTK_STOCK_PRINT) == 0) {
124             print = stock_id;
125         } else if (strcmp(stock_id, GTK_STOCK_FIND) == 0) {
126             find = stock_id;
127         } else if (strcmp(stock_id, GTK_STOCK_JUMP_TO) == 0) {
128             jump = stock_id;
129         } else if (strcmp(stock_id, GTK_STOCK_YES) == 0) {
130             yes = stock_id;
131         } else if (strcmp(stock_id, GTK_STOCK_NO) == 0) {
132             no = stock_id;
133         } else {
134             /* we don't know that button! */
135             g_assert_not_reached();
136         }
137         buttons++;
138         stock_id = va_arg(stock_id_list, gchar *);
139     }
140     va_end(stock_id_list);
141
142     /* we should have at least one button */
143     g_assert(buttons);
144
145
146     hbox = gtk_hbox_new(FALSE, 0);
147     gtk_widget_show(hbox);
148
149     button_hbox = gtk_hbutton_box_new();
150     gtk_box_pack_end(GTK_BOX(hbox), button_hbox, TRUE, TRUE, 0);
151     gtk_widget_show(button_hbox);
152
153     help_hbox = gtk_hbutton_box_new();
154     gtk_box_pack_end(GTK_BOX(hbox), help_hbox, FALSE, FALSE, 0);
155     gtk_widget_show(help_hbox);
156
157     if (buttons == 1) {
158         /* if only one button, simply put it in the middle (default) */
159         dlg_button_new(hbox, button_hbox, stock_id_first);
160         return hbox;
161     }
162
163     /* do we have a help button? -> special handling for it */
164     if (help) {
165         button = BUTTON_NEW_FROM_STOCK(help);
166         GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
167         OBJECT_SET_DATA(hbox, help, button);
168         gtk_box_pack_start(GTK_BOX(help_hbox), button, FALSE, FALSE, 0);
169         gtk_widget_show(button);
170         buttons--;
171     }
172
173     /* if more than one button, sort buttons from left to right */
174     /* (the whole button cluster will then be right aligned) */
175     gtk_button_box_set_layout (GTK_BUTTON_BOX(button_hbox), GTK_BUTTONBOX_END);
176     gtk_button_box_set_spacing(GTK_BUTTON_BOX(button_hbox), 5);
177
178 #if !WIN32 && GTK_MAJOR_VERSION >= 2
179     /* beware: sequence of buttons are important! */
180
181     /* XXX: this can be implemented more elegant of course, but it works as it should */
182     if (buttons == 2) {
183         if (ok && cancel) {
184             dlg_button_new(hbox, button_hbox, cancel);
185             dlg_button_new(hbox, button_hbox, ok);
186             return hbox;
187         }
188         if (print && cancel) {
189             dlg_button_new(hbox, button_hbox, cancel);
190             dlg_button_new(hbox, button_hbox, print);
191             return hbox;
192         }
193         if (find && cancel) {
194             dlg_button_new(hbox, button_hbox, cancel);
195             dlg_button_new(hbox, button_hbox, find);
196             return hbox;
197         }
198         if (jump && cancel) {
199             dlg_button_new(hbox, button_hbox, cancel);
200             dlg_button_new(hbox, button_hbox, jump);
201             return hbox;
202         }
203         if (save && cancel) {
204             dlg_button_new(hbox, button_hbox, cancel);
205             dlg_button_new(hbox, button_hbox, save);
206             return hbox;
207         }
208         if (ok && clear) {
209             dlg_button_new(hbox, button_hbox, clear);
210             dlg_button_new(hbox, button_hbox, ok);
211             return hbox;
212         }
213         if (save && close) {
214             dlg_button_new(hbox, button_hbox, close);
215             dlg_button_new(hbox, button_hbox, save);
216             return hbox;
217         }
218         if (create_stat && cancel) {
219             dlg_button_new(hbox, button_hbox, cancel);
220             dlg_button_new(hbox, button_hbox, create_stat);
221             return hbox;
222         }
223     }
224     if (buttons == 3) {
225         if (ok && save && close) {
226             dlg_button_new(hbox, button_hbox, save);
227             dlg_button_new(hbox, button_hbox, close);
228             dlg_button_new(hbox, button_hbox, ok);
229             return hbox;
230         }
231         if (ok && apply && cancel) {
232             dlg_button_new(hbox, button_hbox, apply);
233             dlg_button_new(hbox, button_hbox, cancel);
234             dlg_button_new(hbox, button_hbox, ok);
235             return hbox;
236         }
237         if (apply && save && close) {
238             dlg_button_new(hbox, button_hbox, save);
239             dlg_button_new(hbox, button_hbox, close);
240             dlg_button_new(hbox, button_hbox, apply);
241             return hbox;
242         }
243         if (yes && no && cancel) {
244             dlg_button_new(hbox, button_hbox, yes);
245             dlg_button_new(hbox, button_hbox, no);
246             dlg_button_new(hbox, button_hbox, cancel);
247             return hbox;
248         }
249     }
250     if (buttons == 4) {
251         if (ok && apply && save && cancel) {
252             dlg_button_new(hbox, button_hbox, save);
253             dlg_button_new(hbox, button_hbox, apply);
254             dlg_button_new(hbox, button_hbox, cancel);
255             dlg_button_new(hbox, button_hbox, ok);
256             return hbox;
257         }
258         if (ok && apply && save && close) {
259             dlg_button_new(hbox, button_hbox, save);
260             dlg_button_new(hbox, button_hbox, apply);
261             dlg_button_new(hbox, button_hbox, close);
262             dlg_button_new(hbox, button_hbox, ok);
263             return hbox;
264         }
265     }
266 #endif
267
268     /* beware: sequence of buttons is important! */
269     if (ok      != NULL) dlg_button_new(hbox, button_hbox, ok);
270     if (jump    != NULL) dlg_button_new(hbox, button_hbox, jump);
271     if (find    != NULL) dlg_button_new(hbox, button_hbox, find);
272     if (print   != NULL) dlg_button_new(hbox, button_hbox, print);
273     if (create_stat != NULL) dlg_button_new(hbox, button_hbox, create_stat);
274     if (apply   != NULL) dlg_button_new(hbox, button_hbox, apply);
275     if (yes     != NULL) dlg_button_new(hbox, button_hbox, yes);
276     if (no      != NULL) dlg_button_new(hbox, button_hbox, no);
277     if (save    != NULL) dlg_button_new(hbox, button_hbox, save);
278     if (stop    != NULL) dlg_button_new(hbox, button_hbox, stop);
279     if (close   != NULL) dlg_button_new(hbox, button_hbox, close);
280     if (clear   != NULL) dlg_button_new(hbox, button_hbox, clear);
281     if (cancel  != NULL) dlg_button_new(hbox, button_hbox, cancel);
282
283     /* GTK2: we don't know that button combination, add it to the above list! */
284     /* g_assert_not_reached(); */
285     return hbox;
286 }
287
288
289 /* this is called, when a dialog was closed */
290 void on_dialog_destroyed(GtkWidget *dialog, gpointer data)
291 {
292     /* bring main window back to front (workaround for a bug in win32 GTK2.x) */
293     gtk_window_present(GTK_WINDOW(data));
294 }
295
296
297 /* Create a dialog box window that belongs to Ethereal's main window. */
298 GtkWidget *
299 dlg_window_new(const gchar *title)
300 {
301   GtkWidget *win;
302
303 #if GTK_MAJOR_VERSION < 2
304   win = window_new(GTK_WINDOW_DIALOG, title);
305 #else
306   win = window_new(GTK_WINDOW_TOPLEVEL, title);
307 #endif
308   /*
309    * XXX - if we're running in the capture child process, we can't easily
310    * make this window transient for the main process's window.  We just
311    * punt here.
312    *
313    * Perhaps the child process should only capture packets, write them to
314    * a file, and somehow notify the parent process and let *it* do all
315    * the GUI work.  If we can do that efficiently (so that we don't drop
316    * more packets), perhaps we can also do so even when we're *not* doing
317    * an "Update list of packets in real time" capture.  That'd let the
318    * child process run set-UID on platforms where you need that in order
319    * to capture, and might also simplify the job of having the GUI main
320    * loop wait both for user input and packet arrival.
321    */
322   if (top_level) {
323     gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
324   }
325 #if GTK_MAJOR_VERSION >= 2
326   gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER_ON_PARENT);
327   if(top_level)
328     SIGNAL_CONNECT(win, "destroy", on_dialog_destroyed, top_level);
329 #endif
330   return win;
331 }
332
333 /* Create a file selection dialog box window that belongs to Ethereal's
334    main window. */
335 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
336 GtkWidget *
337 file_selection_new(const gchar *title, file_selection_action_t action)
338 {
339   GtkWidget *win;
340   GtkFileChooserAction gtk_action;
341   const gchar *ok_button_text;
342
343   switch (action) {
344
345   case FILE_SELECTION_OPEN:
346     gtk_action = GTK_FILE_CHOOSER_ACTION_OPEN;
347     ok_button_text = GTK_STOCK_OPEN;
348     break;
349
350   case FILE_SELECTION_SAVE:
351     gtk_action = GTK_FILE_CHOOSER_ACTION_SAVE;
352     ok_button_text = GTK_STOCK_SAVE;
353     break;
354
355   default:
356     g_assert_not_reached();
357     gtk_action = -1;
358     ok_button_text = NULL;
359     break;
360   }
361   win = gtk_file_chooser_dialog_new(title, GTK_WINDOW(top_level), gtk_action,
362                                     ok_button_text, GTK_RESPONSE_ACCEPT,
363                                     GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
364                                     NULL);
365   return win;
366 }
367 #else
368 GtkWidget *
369 file_selection_new(const gchar *title, file_selection_action_t action _U_)
370 {
371   GtkWidget *win;
372
373   win = gtk_file_selection_new(title);
374 #if GTK_MAJOR_VERSION >= 2
375   gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER_ON_PARENT);
376 #endif
377   gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
378   return win;
379 }
380 #endif
381
382 /* Set the current folder for a file selection dialog. */
383 gboolean
384 file_selection_set_current_folder(GtkWidget *fs, const gchar *filename)
385 {
386 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
387   return gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fs), filename);
388 #else
389   gtk_file_selection_set_filename(GTK_FILE_SELECTION(fs), filename);
390   return TRUE;
391 #endif
392 }
393
394 /* Set the "extra" widget for a file selection dialog, with user-supplied
395    options. */
396 void
397 file_selection_set_extra_widget(GtkWidget *fs, GtkWidget *extra)
398 {
399 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
400   gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(fs), extra);
401 #else
402   gtk_box_pack_start(GTK_BOX(GTK_FILE_SELECTION(fs)->action_area), extra,
403                      FALSE, FALSE, 0);
404 #endif
405 }
406
407 /* Set the "activate" signal for a widget to call a routine to
408    activate the "OK" button for a dialog box.
409
410    XXX - there should be a way to specify that a GtkEntry widget
411    shouldn't itself handle the Return key, but should let it be
412    passed on to the parent, so that you don't have to do this
413    by hand for every GtkEntry widget in a dialog box, but, alas,
414    there isn't.  (Does this problem exist for other widgets?
415    I.e., are there any others that seize the Return key? */
416 void
417 dlg_set_activate(GtkWidget *widget, GtkWidget *ok_button)
418 {
419   SIGNAL_CONNECT(widget, "activate", dlg_activate, ok_button);
420 }
421
422 static void
423 dlg_activate (GtkWidget *widget _U_, gpointer ok_button)
424 {
425   gtk_widget_activate(GTK_WIDGET(ok_button));
426 }
427
428 /* Set the "key_press_event" signal for a top-level dialog window to
429    call a routine to activate the "Cancel" button for a dialog box if
430    the key being pressed is the <Esc> key.
431
432    XXX - there should be a GTK+ widget that'll do that for you, and
433    let you specify a "Cancel" button.  It should also not impose
434    a requirement that there be a separator in the dialog box, as
435    the GtkDialog widget does; the visual convention that there's
436    such a separator between the rest of the dialog boxes and buttons
437    such as "OK" and "Cancel" is, for better or worse, not universal
438    (not even in GTK+ - look at the GtkFileSelection dialog!). */
439 void
440 dlg_set_cancel(GtkWidget *widget, GtkWidget *cancel_button)
441 {
442   SIGNAL_CONNECT(widget, "key_press_event", dlg_key_press, cancel_button);
443 }
444
445 static gint
446 dlg_key_press (GtkWidget *widget, GdkEventKey *event, gpointer cancel_button)
447 {
448   g_return_val_if_fail (widget != NULL, FALSE);
449   g_return_val_if_fail (event != NULL, FALSE);
450
451   if (event->keyval == GDK_Escape) {
452     gtk_widget_activate(GTK_WIDGET(cancel_button));
453     return TRUE;
454   }
455
456   return FALSE;
457 }
458
459 #if GTK_MAJOR_VERSION < 2
460 /* Sigh.  GTK+ appears not to acknowledge that it should be possible
461    to attach mnemonics to anything other than menu items; provide
462    routines to create radio and check buttons with labels that
463    include mnemonics.  */
464 typedef struct {
465         GtkWidget *button;
466         GtkAccelGroup *accel_group;
467 } fix_label_args_t;
468
469 static void
470 dlg_fix_label_callback(GtkWidget *label_widget, gpointer data)
471 {
472   fix_label_args_t *args = data;
473   gchar *label;
474   guint accel_key;
475
476   gtk_label_get(GTK_LABEL(label_widget), &label);
477   accel_key = gtk_label_parse_uline(GTK_LABEL(label_widget), label);
478   if (accel_key != GDK_VoidSymbol) {
479     /* Yes, we have a mnemonic. */
480     gtk_widget_add_accelerator(args->button, "clicked", args->accel_group,
481                                 accel_key, 0, GTK_ACCEL_LOCKED);
482     gtk_widget_add_accelerator(args->button, "clicked", args->accel_group,
483                                 accel_key, GDK_MOD1_MASK, GTK_ACCEL_LOCKED);
484   }
485 }
486
487 static void
488 dlg_fix_button_label(GtkWidget *button, GtkAccelGroup *accel_group)
489 {
490   fix_label_args_t args;
491
492   args.button = button;
493   args.accel_group = accel_group;
494   gtk_container_foreach(GTK_CONTAINER(button), dlg_fix_label_callback, &args);
495 }
496
497 GtkWidget *
498 dlg_radio_button_new_with_label_with_mnemonic(GSList *group,
499                 const gchar *label, GtkAccelGroup *accel_group)
500 {
501   GtkWidget *radio_button;
502
503   radio_button = gtk_radio_button_new_with_label (group, label);
504   dlg_fix_button_label(radio_button, accel_group);
505   return radio_button;
506 }
507
508 GtkWidget *
509 dlg_check_button_new_with_label_with_mnemonic(const gchar *label,
510                         GtkAccelGroup *accel_group)
511 {
512   GtkWidget *check_button;
513
514   check_button = gtk_check_button_new_with_label (label);
515   dlg_fix_button_label(check_button, accel_group);
516   return check_button;
517 }
518
519 GtkWidget *
520 dlg_toggle_button_new_with_label_with_mnemonic(const gchar *label,
521                         GtkAccelGroup *accel_group)
522 {
523   GtkWidget *toggle_button;
524
525   toggle_button = gtk_toggle_button_new_with_label (label);
526   dlg_fix_button_label(toggle_button, accel_group);
527   return toggle_button;
528 }
529 #endif