Replace a handful of tabs with spaces.
[obnox/wireshark/wip.git] / gtk / dlg_utils.c
1 /* dlg_utils.c
2  * Utilities to use when constructing dialogs
3  *
4  * $Id$
5  *
6  * Wireshark - Network traffic analyzer
7  * By Gerald Combs <gerald@wireshark.org>
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 "gui_utils.h"
34 #include "dlg_utils.h"
35 #include "compat_macros.h"
36
37 #include <string.h>
38 #include <stdarg.h>
39
40 static void
41 dlg_activate (GtkWidget *widget, gpointer ok_button);
42
43 /* create a button for the button row (helper for dlg_button_row_new) */
44 static GtkWidget *
45 dlg_button_new(GtkWidget *hbox, GtkWidget *button_hbox, const gchar *stock_id)
46 {
47     GtkWidget *button;
48
49     button = BUTTON_NEW_FROM_STOCK(stock_id);
50     GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
51     OBJECT_SET_DATA(hbox, stock_id, button);
52     gtk_box_pack_end(GTK_BOX(button_hbox), button, FALSE, FALSE, 0);
53     gtk_widget_show(button);
54     return button;
55 }
56
57 /*
58  * Set the focus and default for the nth item in a button row, with
59  * 0 being the first item.
60  */
61 #define BUTTON_HBOX_KEY "button_hbox"
62 void
63 dlg_button_focus_nth(GtkWidget *hbox, gint focus_item) {
64     GtkWidget *button_hbox, *button;
65     GList *children;
66     gint cur_item = 0;
67
68     if (!hbox)
69         return;
70
71     button_hbox = OBJECT_GET_DATA(hbox, BUTTON_HBOX_KEY);
72     children = gtk_container_children(GTK_CONTAINER(button_hbox));
73
74     while (children) {
75         if (cur_item == focus_item) {
76             button = children->data;
77             gtk_widget_grab_focus(button);
78             gtk_widget_grab_default(button);
79             break;
80         }
81         children = g_list_next(children);
82         cur_item++;
83     }
84
85     g_list_free(children);
86 }
87
88 /* create a button row for a dialog */
89
90 /* The purpose of this is, to have one place available, where all button rows
91  * from all dialogs are laid out. This will:
92  *
93  * a.) keep the button layout more consistent over the different dialogs
94  * b.) being able to switch between different button layouts, e.g.:
95  *     GTK1 (e.g. win32) "OK" "Apply" "Cancel"
96  *     GTK2 (e.g. GNOME) "Apply" "Cancel" "OK"
97  */
98 GtkWidget *
99 dlg_button_row_new(const gchar *stock_id_first, ...)
100 {
101     gint        buttons = 0;
102     va_list     stock_id_list;
103     const gchar *stock_id = stock_id_first;
104     GtkWidget   *hbox;
105     GtkWidget   *button_hbox;
106     GtkWidget   *help_hbox;
107     GtkWidget   *button;
108
109     const gchar *apply        = NULL;
110     const gchar *cancel       = NULL;
111     const gchar *cap_start    = NULL;
112     const gchar *cap_stop     = NULL;
113     const gchar *clear        = NULL;
114     const gchar *close        = NULL;
115     const gchar *copy         = NULL;
116     const gchar *create_stat  = NULL;
117     const gchar *delete       = NULL;
118     const gchar *dont_save    = NULL;
119     const gchar *filter_stream= NULL;
120     const gchar *find         = NULL;
121     const gchar *help         = NULL;
122     const gchar *jump         = NULL;
123     const gchar *no           = NULL;
124     const gchar *ok           = NULL;
125     const gchar *print        = NULL;
126     const gchar *save         = NULL;
127     const gchar *stop         = NULL;
128     const gchar *yes          = NULL;
129
130
131     va_start(stock_id_list, stock_id_first);
132
133     /* get all buttons needed */
134     while(stock_id != NULL) {
135         if (strcmp(stock_id, GTK_STOCK_OK) == 0) {
136             ok = stock_id;
137         } else if (strcmp(stock_id, WIRESHARK_STOCK_CREATE_STAT) == 0) {
138             create_stat = stock_id;
139         } else if (strcmp(stock_id, GTK_STOCK_APPLY) == 0) {
140             apply = stock_id;
141         } else if (strcmp(stock_id, GTK_STOCK_SAVE) == 0) {
142             save = stock_id;
143         } else if (strcmp(stock_id, WIRESHARK_STOCK_DONT_SAVE) == 0) {
144                 dont_save = stock_id;
145         } else if (strcmp(stock_id, GTK_STOCK_CANCEL) == 0) {
146             cancel = stock_id;
147         } else if (strcmp(stock_id, GTK_STOCK_CLOSE) == 0) {
148             close = stock_id;
149         } else if (strcmp(stock_id, GTK_STOCK_CLEAR) == 0) {
150             clear = stock_id;
151 #ifdef HAVE_LIBPCAP
152         } else if (strcmp(stock_id, WIRESHARK_STOCK_CAPTURE_START) == 0) {
153             cap_start = stock_id;
154         } else if (strcmp(stock_id, WIRESHARK_STOCK_CAPTURE_STOP) == 0) {
155             cap_stop = stock_id;
156 #endif /* HAVE_LIBPCAP */
157         } else if (strcmp(stock_id, GTK_STOCK_STOP) == 0) {
158             stop = stock_id;
159         } else if (strcmp(stock_id, GTK_STOCK_HELP) == 0) {
160             help = stock_id;
161         } else if (strcmp(stock_id, GTK_STOCK_PRINT) == 0) {
162             print = stock_id;
163         } else if (strcmp(stock_id, GTK_STOCK_FIND) == 0) {
164             find = stock_id;
165         } else if (strcmp(stock_id, GTK_STOCK_JUMP_TO) == 0) {
166             jump = stock_id;
167         } else if (strcmp(stock_id, GTK_STOCK_YES) == 0) {
168             yes = stock_id;
169         } else if (strcmp(stock_id, GTK_STOCK_NO) == 0) {
170             no = stock_id;
171         } else if (strcmp(stock_id, WIRESHARK_STOCK_FILTER_OUT_STREAM) == 0) {
172             filter_stream = stock_id;
173         } else if (strcmp(stock_id, GTK_STOCK_DELETE) == 0) {
174             delete = stock_id;
175         } else if (strcmp(stock_id, GTK_STOCK_COPY) == 0) {
176             copy = stock_id;
177         } else {
178             /* we don't know that button! */
179             g_assert_not_reached();
180         }
181         buttons++;
182         stock_id = va_arg(stock_id_list, gchar *);
183     }
184     va_end(stock_id_list);
185
186     hbox = gtk_hbox_new(FALSE, 0);
187     gtk_widget_show(hbox);
188
189     button_hbox = gtk_hbutton_box_new();
190     gtk_box_pack_end(GTK_BOX(hbox), button_hbox, TRUE, TRUE, 0);
191     OBJECT_SET_DATA(hbox, BUTTON_HBOX_KEY, button_hbox);
192     gtk_widget_show(button_hbox);
193
194     help_hbox = gtk_hbutton_box_new();
195     gtk_box_pack_end(GTK_BOX(hbox), help_hbox, FALSE, FALSE, 0);
196     gtk_widget_show(help_hbox);
197
198     if (buttons == 0) {
199         /* if no buttons wanted, simply do nothing */
200         return hbox;
201     }
202
203     if (buttons == 1) {
204         /* if only one button, simply put it in the middle (default) */
205         dlg_button_new(hbox, button_hbox, stock_id_first);
206         return hbox;
207     }
208
209     /* do we have a help button? -> special handling for it */
210     if (help) {
211         button = BUTTON_NEW_FROM_STOCK(help);
212         GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
213         OBJECT_SET_DATA(hbox, help, button);
214         gtk_box_pack_start(GTK_BOX(help_hbox), button, FALSE, FALSE, 0);
215         gtk_widget_show(button);
216         buttons--;
217     }
218
219     /* if more than one button, sort buttons from left to right */
220     /* (the whole button cluster will then be right aligned) */
221     gtk_button_box_set_layout (GTK_BUTTON_BOX(button_hbox), GTK_BUTTONBOX_END);
222     gtk_button_box_set_spacing(GTK_BUTTON_BOX(button_hbox), 5);
223
224 /* GTK+ 1.3 and later - on Win32, we use 1.3[.x] or 2.x, not 1.2[.x] */
225 #if !defined(_WIN32) && GTK_MAJOR_VERSION >= 2
226     /* beware: sequence of buttons are important! */
227
228     /* XXX: this can be implemented more elegant of course, but it works as it should */
229     if (buttons == 2) {
230         if (ok && cancel) {
231             dlg_button_new(hbox, button_hbox, cancel);
232             dlg_button_new(hbox, button_hbox, ok);
233             return hbox;
234         }
235         if (print && cancel) {
236             dlg_button_new(hbox, button_hbox, cancel);
237             dlg_button_new(hbox, button_hbox, print);
238             return hbox;
239         }
240         if (find && cancel) {
241             dlg_button_new(hbox, button_hbox, cancel);
242             dlg_button_new(hbox, button_hbox, find);
243             return hbox;
244         }
245         if (jump && cancel) {
246             dlg_button_new(hbox, button_hbox, cancel);
247             dlg_button_new(hbox, button_hbox, jump);
248             return hbox;
249         }
250         if (save && cancel) {
251             dlg_button_new(hbox, button_hbox, cancel);
252             dlg_button_new(hbox, button_hbox, save);
253             return hbox;
254         }
255         if (ok && clear) {
256             dlg_button_new(hbox, button_hbox, clear);
257             dlg_button_new(hbox, button_hbox, ok);
258             return hbox;
259         }
260         if (save && close) {
261             dlg_button_new(hbox, button_hbox, close);
262             dlg_button_new(hbox, button_hbox, save);
263             return hbox;
264         }
265         if (create_stat && cancel) {
266             dlg_button_new(hbox, button_hbox, cancel);
267             dlg_button_new(hbox, button_hbox, create_stat);
268             return hbox;
269         }
270         if (cap_start && cancel) {
271             dlg_button_new(hbox, button_hbox, cancel);
272             dlg_button_new(hbox, button_hbox, cap_start);
273             return hbox;
274         }
275         if (cap_stop && cancel) {
276             dlg_button_new(hbox, button_hbox, cancel);
277             dlg_button_new(hbox, button_hbox, cap_stop);
278             return hbox;
279         }
280         if (delete && cancel) {
281             dlg_button_new(hbox, button_hbox, cancel);
282             dlg_button_new(hbox, button_hbox, delete);
283             return hbox;
284         }
285     }
286     if (buttons == 3) {
287         if (ok && save && close) {
288             dlg_button_new(hbox, button_hbox, save);
289             dlg_button_new(hbox, button_hbox, close);
290             dlg_button_new(hbox, button_hbox, ok);
291             return hbox;
292         }
293         if (ok && apply && cancel) {
294             dlg_button_new(hbox, button_hbox, apply);
295             dlg_button_new(hbox, button_hbox, cancel);
296             dlg_button_new(hbox, button_hbox, ok);
297             return hbox;
298         }
299         if (apply && save && close) {
300             dlg_button_new(hbox, button_hbox, save);
301             dlg_button_new(hbox, button_hbox, close);
302             dlg_button_new(hbox, button_hbox, apply);
303             return hbox;
304         }
305         if (yes && no && cancel) {
306             dlg_button_new(hbox, button_hbox, no);
307             dlg_button_new(hbox, button_hbox, cancel);
308             dlg_button_new(hbox, button_hbox, yes);
309             return hbox;
310         }
311         if (save && dont_save && cancel) {
312                 dlg_button_new(hbox, button_hbox, dont_save);
313                 dlg_button_new(hbox, button_hbox, cancel);
314                 dlg_button_new(hbox, button_hbox, save);
315                 return hbox;
316         }
317     }
318     if (buttons == 4) {
319         if (ok && apply && save && cancel) {
320             dlg_button_new(hbox, button_hbox, save);
321             dlg_button_new(hbox, button_hbox, apply);
322             dlg_button_new(hbox, button_hbox, cancel);
323             dlg_button_new(hbox, button_hbox, ok);
324             return hbox;
325         }
326         if (ok && apply && save && close) {
327             dlg_button_new(hbox, button_hbox, save);
328             dlg_button_new(hbox, button_hbox, apply);
329             dlg_button_new(hbox, button_hbox, close);
330             dlg_button_new(hbox, button_hbox, ok);
331             return hbox;
332         }
333     }
334 #endif
335
336     /* beware: sequence of buttons is important! */
337     if (ok      != NULL) dlg_button_new(hbox, button_hbox, ok);
338     if (delete  != NULL) dlg_button_new(hbox, button_hbox, delete);
339     if (jump    != NULL) dlg_button_new(hbox, button_hbox, jump);
340     if (find    != NULL) dlg_button_new(hbox, button_hbox, find);
341     if (print   != NULL) dlg_button_new(hbox, button_hbox, print);
342     if (copy    != NULL) dlg_button_new(hbox, button_hbox, copy);
343     if (create_stat != NULL) dlg_button_new(hbox, button_hbox, create_stat);
344     if (apply   != NULL) dlg_button_new(hbox, button_hbox, apply);
345     if (yes     != NULL) dlg_button_new(hbox, button_hbox, yes);
346     if (no      != NULL) dlg_button_new(hbox, button_hbox, no);
347     if (save    != NULL) dlg_button_new(hbox, button_hbox, save);
348     if (dont_save   != NULL) dlg_button_new(hbox, button_hbox, dont_save);
349     if (cap_start   != NULL) dlg_button_new(hbox, button_hbox, cap_start);
350     if (cap_stop    != NULL) dlg_button_new(hbox, button_hbox, cap_stop);
351     if (stop    != NULL) dlg_button_new(hbox, button_hbox, stop);
352     if (close   != NULL) dlg_button_new(hbox, button_hbox, close);
353     if (clear   != NULL) dlg_button_new(hbox, button_hbox, clear);
354     if (cancel  != NULL) dlg_button_new(hbox, button_hbox, cancel);
355     if (filter_stream!= NULL) dlg_button_new(hbox, button_hbox, filter_stream);
356
357     /* GTK2: we don't know that button combination, add it to the above list! */
358     /* g_assert_not_reached(); */
359     return hbox;
360 }
361
362
363 /* this is called, when a dialog was closed */
364 static void dlg_destroy_cb(GtkWidget *dialog _U_, gpointer data _U_)
365 {
366 #if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 4
367     if(top_level) {
368         /* bring main window back to front (workaround for a bug in win32 GTK2.x)
369            XXX - do this only on Windows? */
370         gtk_window_present(GTK_WINDOW(top_level));
371     }
372 #endif
373 }
374
375
376 /* Create a dialog box window that belongs to Wireshark's main window. */
377 GtkWidget *
378 dlg_window_new(const gchar *title)
379 {
380   GtkWidget *win;
381
382 #if GTK_MAJOR_VERSION < 2
383   win = window_new(GTK_WINDOW_DIALOG, title);
384 #else
385   win = window_new(GTK_WINDOW_TOPLEVEL, title);
386 #endif
387
388   /*
389    * XXX - if we're running in the capture child process, we can't easily
390    * make this window transient for the main process's window.  We just
391    * punt here.
392    *
393    * Perhaps the child process should only capture packets, write them to
394    * a file, and somehow notify the parent process and let *it* do all
395    * the GUI work.  If we can do that efficiently (so that we don't drop
396    * more packets), perhaps we can also do so even when we're *not* doing
397    * an "Update list of packets in real time" capture.  That'd let the
398    * child process run set-UID on platforms where you need that in order
399    * to capture, and might also simplify the job of having the GUI main
400    * loop wait both for user input and packet arrival.
401    */
402   if (top_level) {
403     gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
404   }
405
406   SIGNAL_CONNECT(win, "destroy", dlg_destroy_cb, NULL);
407
408   return win;
409 }
410
411 /* Set the "activate" signal for a widget to call a routine to
412    activate the "OK" button for a dialog box.
413
414    XXX - there should be a way to specify that a GtkEntry widget
415    shouldn't itself handle the Return key, but should let it be
416    passed on to the parent, so that you don't have to do this
417    by hand for every GtkEntry widget in a dialog box, but, alas,
418    there isn't.  (Does this problem exist for other widgets?
419    I.e., are there any others that seize the Return key? */
420 void
421 dlg_set_activate(GtkWidget *widget, GtkWidget *ok_button)
422 {
423   SIGNAL_CONNECT(widget, "activate", dlg_activate, ok_button);
424 }
425
426 static void
427 dlg_activate (GtkWidget *widget _U_, gpointer ok_button)
428 {
429   gtk_widget_activate(GTK_WIDGET(ok_button));
430 }
431
432 #if GTK_MAJOR_VERSION < 2
433 /* Sigh.  GTK+ appears not to acknowledge that it should be possible
434    to attach mnemonics to anything other than menu items; provide
435    routines to create radio and check buttons with labels that
436    include mnemonics.  */
437 typedef struct {
438         GtkWidget *button;
439         GtkAccelGroup *accel_group;
440 } fix_label_args_t;
441
442 static void
443 dlg_fix_label_callback(GtkWidget *label_widget, gpointer data)
444 {
445   fix_label_args_t *args = data;
446   gchar *label;
447   guint accel_key;
448
449   gtk_label_get(GTK_LABEL(label_widget), &label);
450   accel_key = gtk_label_parse_uline(GTK_LABEL(label_widget), label);
451   if (accel_key != GDK_VoidSymbol) {
452     /* Yes, we have a mnemonic. */
453     gtk_widget_add_accelerator(args->button, "clicked", args->accel_group,
454                                 accel_key, 0, GTK_ACCEL_LOCKED);
455     gtk_widget_add_accelerator(args->button, "clicked", args->accel_group,
456                                 accel_key, GDK_MOD1_MASK, GTK_ACCEL_LOCKED);
457   }
458 }
459
460 static void
461 dlg_fix_button_label(GtkWidget *button, GtkAccelGroup *accel_group)
462 {
463   fix_label_args_t args;
464
465   args.button = button;
466   args.accel_group = accel_group;
467   gtk_container_foreach(GTK_CONTAINER(button), dlg_fix_label_callback, &args);
468 }
469
470 GtkWidget *
471 dlg_radio_button_new_with_label_with_mnemonic(GSList *group,
472                 const gchar *label, GtkAccelGroup *accel_group)
473 {
474   GtkWidget *radio_button;
475
476   radio_button = gtk_radio_button_new_with_label (group, label);
477   dlg_fix_button_label(radio_button, accel_group);
478   return radio_button;
479 }
480
481 GtkWidget *
482 dlg_check_button_new_with_label_with_mnemonic(const gchar *label,
483                         GtkAccelGroup *accel_group)
484 {
485   GtkWidget *check_button;
486
487   check_button = gtk_check_button_new_with_label (label);
488   dlg_fix_button_label(check_button, accel_group);
489   return check_button;
490 }
491
492 GtkWidget *
493 dlg_toggle_button_new_with_label_with_mnemonic(const gchar *label,
494                         GtkAccelGroup *accel_group)
495 {
496   GtkWidget *toggle_button;
497
498   toggle_button = gtk_toggle_button_new_with_label (label);
499   dlg_fix_button_label(toggle_button, accel_group);
500   return toggle_button;
501 }
502 #endif