Tag unused parameters with _U_ to squelch GCC compiler warnings (they're
[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.29 2004/05/21 00:18:46 guy 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 #if GTK_MAJOR_VERSION >= 2
290 /* this is called, when a dialog was closed */
291 void on_dialog_destroyed(GtkWidget *dialog _U_, gpointer data)
292 {
293     /* bring main window back to front (workaround for a bug in win32 GTK2.x)
294        XXX - do this only on Windows? */
295     gtk_window_present(GTK_WINDOW(data));
296 }
297 #endif
298
299
300 /* Create a dialog box window that belongs to Ethereal's main window. */
301 GtkWidget *
302 dlg_window_new(const gchar *title)
303 {
304   GtkWidget *win;
305
306 #if GTK_MAJOR_VERSION < 2
307   win = window_new(GTK_WINDOW_DIALOG, title);
308 #else
309   win = window_new(GTK_WINDOW_TOPLEVEL, title);
310 #endif
311   /*
312    * XXX - if we're running in the capture child process, we can't easily
313    * make this window transient for the main process's window.  We just
314    * punt here.
315    *
316    * Perhaps the child process should only capture packets, write them to
317    * a file, and somehow notify the parent process and let *it* do all
318    * the GUI work.  If we can do that efficiently (so that we don't drop
319    * more packets), perhaps we can also do so even when we're *not* doing
320    * an "Update list of packets in real time" capture.  That'd let the
321    * child process run set-UID on platforms where you need that in order
322    * to capture, and might also simplify the job of having the GUI main
323    * loop wait both for user input and packet arrival.
324    */
325   if (top_level) {
326     gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
327   }
328 #if GTK_MAJOR_VERSION >= 2
329   gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER_ON_PARENT);
330   if(top_level)
331     SIGNAL_CONNECT(win, "destroy", on_dialog_destroyed, top_level);
332 #endif
333   return win;
334 }
335
336 /* Create a file selection dialog box window that belongs to Ethereal's
337    main window. */
338 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
339 GtkWidget *
340 file_selection_new(const gchar *title, file_selection_action_t action)
341 {
342   GtkWidget *win;
343   GtkFileChooserAction gtk_action;
344   const gchar *ok_button_text;
345
346   switch (action) {
347
348   case FILE_SELECTION_OPEN:
349     gtk_action = GTK_FILE_CHOOSER_ACTION_OPEN;
350     ok_button_text = GTK_STOCK_OPEN;
351     break;
352
353   case FILE_SELECTION_SAVE:
354     gtk_action = GTK_FILE_CHOOSER_ACTION_SAVE;
355     ok_button_text = GTK_STOCK_SAVE;
356     break;
357
358   default:
359     g_assert_not_reached();
360     gtk_action = -1;
361     ok_button_text = NULL;
362     break;
363   }
364   win = gtk_file_chooser_dialog_new(title, GTK_WINDOW(top_level), gtk_action,
365                                     ok_button_text, GTK_RESPONSE_ACCEPT,
366                                     GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
367                                     NULL);
368   return win;
369 }
370 #else
371 GtkWidget *
372 file_selection_new(const gchar *title, file_selection_action_t action _U_)
373 {
374   GtkWidget *win;
375
376   win = gtk_file_selection_new(title);
377 #if GTK_MAJOR_VERSION >= 2
378   gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER_ON_PARENT);
379 #endif
380   gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
381   return win;
382 }
383 #endif
384
385 /* Set the current folder for a file selection dialog. */
386 gboolean
387 file_selection_set_current_folder(GtkWidget *fs, const gchar *filename)
388 {
389 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
390   return gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fs), filename);
391 #else
392   gtk_file_selection_set_filename(GTK_FILE_SELECTION(fs), filename);
393   return TRUE;
394 #endif
395 }
396
397 /* Set the "extra" widget for a file selection dialog, with user-supplied
398    options. */
399 void
400 file_selection_set_extra_widget(GtkWidget *fs, GtkWidget *extra)
401 {
402 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
403   gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(fs), extra);
404 #else
405   gtk_box_pack_start(GTK_BOX(GTK_FILE_SELECTION(fs)->action_area), extra,
406                      FALSE, FALSE, 0);
407 #endif
408 }
409
410 /* Set the "activate" signal for a widget to call a routine to
411    activate the "OK" button for a dialog box.
412
413    XXX - there should be a way to specify that a GtkEntry widget
414    shouldn't itself handle the Return key, but should let it be
415    passed on to the parent, so that you don't have to do this
416    by hand for every GtkEntry widget in a dialog box, but, alas,
417    there isn't.  (Does this problem exist for other widgets?
418    I.e., are there any others that seize the Return key? */
419 void
420 dlg_set_activate(GtkWidget *widget, GtkWidget *ok_button)
421 {
422   SIGNAL_CONNECT(widget, "activate", dlg_activate, ok_button);
423 }
424
425 static void
426 dlg_activate (GtkWidget *widget _U_, gpointer ok_button)
427 {
428   gtk_widget_activate(GTK_WIDGET(ok_button));
429 }
430
431 /* Set the "key_press_event" signal for a top-level dialog window to
432    call a routine to activate the "Cancel" button for a dialog box if
433    the key being pressed is the <Esc> key.
434
435    XXX - there should be a GTK+ widget that'll do that for you, and
436    let you specify a "Cancel" button.  It should also not impose
437    a requirement that there be a separator in the dialog box, as
438    the GtkDialog widget does; the visual convention that there's
439    such a separator between the rest of the dialog boxes and buttons
440    such as "OK" and "Cancel" is, for better or worse, not universal
441    (not even in GTK+ - look at the GtkFileSelection dialog!). */
442 void
443 dlg_set_cancel(GtkWidget *widget, GtkWidget *cancel_button)
444 {
445   SIGNAL_CONNECT(widget, "key_press_event", dlg_key_press, cancel_button);
446 }
447
448 static gint
449 dlg_key_press (GtkWidget *widget, GdkEventKey *event, gpointer cancel_button)
450 {
451   g_return_val_if_fail (widget != NULL, FALSE);
452   g_return_val_if_fail (event != NULL, FALSE);
453
454   if (event->keyval == GDK_Escape) {
455     gtk_widget_activate(GTK_WIDGET(cancel_button));
456     return TRUE;
457   }
458
459   return FALSE;
460 }
461
462 #if GTK_MAJOR_VERSION < 2
463 /* Sigh.  GTK+ appears not to acknowledge that it should be possible
464    to attach mnemonics to anything other than menu items; provide
465    routines to create radio and check buttons with labels that
466    include mnemonics.  */
467 typedef struct {
468         GtkWidget *button;
469         GtkAccelGroup *accel_group;
470 } fix_label_args_t;
471
472 static void
473 dlg_fix_label_callback(GtkWidget *label_widget, gpointer data)
474 {
475   fix_label_args_t *args = data;
476   gchar *label;
477   guint accel_key;
478
479   gtk_label_get(GTK_LABEL(label_widget), &label);
480   accel_key = gtk_label_parse_uline(GTK_LABEL(label_widget), label);
481   if (accel_key != GDK_VoidSymbol) {
482     /* Yes, we have a mnemonic. */
483     gtk_widget_add_accelerator(args->button, "clicked", args->accel_group,
484                                 accel_key, 0, GTK_ACCEL_LOCKED);
485     gtk_widget_add_accelerator(args->button, "clicked", args->accel_group,
486                                 accel_key, GDK_MOD1_MASK, GTK_ACCEL_LOCKED);
487   }
488 }
489
490 static void
491 dlg_fix_button_label(GtkWidget *button, GtkAccelGroup *accel_group)
492 {
493   fix_label_args_t args;
494
495   args.button = button;
496   args.accel_group = accel_group;
497   gtk_container_foreach(GTK_CONTAINER(button), dlg_fix_label_callback, &args);
498 }
499
500 GtkWidget *
501 dlg_radio_button_new_with_label_with_mnemonic(GSList *group,
502                 const gchar *label, GtkAccelGroup *accel_group)
503 {
504   GtkWidget *radio_button;
505
506   radio_button = gtk_radio_button_new_with_label (group, label);
507   dlg_fix_button_label(radio_button, accel_group);
508   return radio_button;
509 }
510
511 GtkWidget *
512 dlg_check_button_new_with_label_with_mnemonic(const gchar *label,
513                         GtkAccelGroup *accel_group)
514 {
515   GtkWidget *check_button;
516
517   check_button = gtk_check_button_new_with_label (label);
518   dlg_fix_button_label(check_button, accel_group);
519   return check_button;
520 }
521
522 GtkWidget *
523 dlg_toggle_button_new_with_label_with_mnemonic(const gchar *label,
524                         GtkAccelGroup *accel_group)
525 {
526   GtkWidget *toggle_button;
527
528   toggle_button = gtk_toggle_button_new_with_label (label);
529   dlg_fix_button_label(toggle_button, accel_group);
530   return toggle_button;
531 }
532 #endif