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