Make "file_selection_new()" take as its second argument an
[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.23 2004/03/29 22:40:57 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 /* Create a dialog box window that belongs to Ethereal's main window. */
290 GtkWidget *
291 dlg_window_new(const gchar *title)
292 {
293   GtkWidget *win;
294
295 #if GTK_MAJOR_VERSION < 2
296   win = window_new(GTK_WINDOW_DIALOG, title);
297 #else
298   win = window_new(GTK_WINDOW_TOPLEVEL, title);
299 #endif
300   /*
301    * XXX - if we're running in the capture child process, we can't easily
302    * make this window transient for the main process's window.  We just
303    * punt here.
304    *
305    * Perhaps the child process should only capture packets, write them to
306    * a file, and somehow notify the parent process and let *it* do all
307    * the GUI work.  If we can do that efficiently (so that we don't drop
308    * more packets), perhaps we can also do so even when we're *not* doing
309    * an "Update list of packets in real time" capture.  That'd let the
310    * child process run set-UID on platforms where you need that in order
311    * to capture, and might also simplify the job of having the GUI main
312    * loop wait both for user input and packet arrival.
313    */
314   if (top_level) {
315     gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
316   }
317 #if GTK_MAJOR_VERSION >= 2
318   gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER_ON_PARENT);
319 #endif
320   return win;
321 }
322
323 /* Create a file selection dialog box window that belongs to Ethereal's
324    main window. */
325 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
326 GtkWidget *
327 file_selection_new(const gchar *title, file_selection_action_t action)
328 {
329   GtkWidget *win;
330   GtkFileChooserAction gtk_action;
331
332   switch (action) {
333
334   case FILE_SELECTION_OPEN:
335     gtk_action = GTK_FILE_CHOOSER_ACTION_OPEN;
336     break;
337
338   case FILE_SELECTION_SAVE:
339     gtk_action = GTK_FILE_CHOOSER_ACTION_SAVE;
340     break;
341
342   default:
343     g_assert_not_reached();
344     gtk_action = -1;
345     break;
346   }
347   win = gtk_file_chooser_dialog_new(title, GTK_WINDOW(top_level), gtk_action,
348                                     GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
349                                     GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
350                                     NULL);
351   gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER_ON_PARENT);
352   return win;
353 }
354 #else
355 GtkWidget *
356 file_selection_new(const gchar *title, file_selection_action_t action _U_)
357 {
358   GtkWidget *win;
359
360   win = gtk_file_selection_new(title);
361 #if GTK_MAJOR_VERSION >= 2
362   gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER_ON_PARENT);
363 #endif
364   gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
365   return win;
366 }
367 #endif
368
369 /* Set the current folder for a file selection dialog. */
370 gboolean
371 file_selection_set_current_folder(GtkWidget *fs, const gchar *filename)
372 {
373 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
374   return gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fs), filename);
375 #else
376   gtk_file_selection_set_filename(GTK_FILE_SELECTION(fs), filename);
377   return TRUE;
378 #endif
379 }
380
381 /* Set the "activate" signal for a widget to call a routine to
382    activate the "OK" button for a dialog box.
383
384    XXX - there should be a way to specify that a GtkEntry widget
385    shouldn't itself handle the Return key, but should let it be
386    passed on to the parent, so that you don't have to do this
387    by hand for every GtkEntry widget in a dialog box, but, alas,
388    there isn't.  (Does this problem exist for other widgets?
389    I.e., are there any others that seize the Return key? */
390 void
391 dlg_set_activate(GtkWidget *widget, GtkWidget *ok_button)
392 {
393   SIGNAL_CONNECT(widget, "activate", dlg_activate, ok_button);
394 }
395
396 static void
397 dlg_activate (GtkWidget *widget _U_, gpointer ok_button)
398 {
399   gtk_widget_activate(GTK_WIDGET(ok_button));
400 }
401
402 /* Set the "key_press_event" signal for a top-level dialog window to
403    call a routine to activate the "Cancel" button for a dialog box if
404    the key being pressed is the <Esc> key.
405
406    XXX - there should be a GTK+ widget that'll do that for you, and
407    let you specify a "Cancel" button.  It should also not impose
408    a requirement that there be a separator in the dialog box, as
409    the GtkDialog widget does; the visual convention that there's
410    such a separator between the rest of the dialog boxes and buttons
411    such as "OK" and "Cancel" is, for better or worse, not universal
412    (not even in GTK+ - look at the GtkFileSelection dialog!). */
413 void
414 dlg_set_cancel(GtkWidget *widget, GtkWidget *cancel_button)
415 {
416   SIGNAL_CONNECT(widget, "key_press_event", dlg_key_press, cancel_button);
417 }
418
419 static gint
420 dlg_key_press (GtkWidget *widget, GdkEventKey *event, gpointer cancel_button)
421 {
422   g_return_val_if_fail (widget != NULL, FALSE);
423   g_return_val_if_fail (event != NULL, FALSE);
424
425   if (event->keyval == GDK_Escape) {
426     gtk_widget_activate(GTK_WIDGET(cancel_button));
427     return TRUE;
428   }
429
430   return FALSE;
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