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