2 * Utilities to use when constructing dialogs
6 * Ethereal - Network traffic analyzer
7 * By Gerald Combs <gerald@ethereal.com>
8 * Copyright 1998 Gerald Combs
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.
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.
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.
30 #include <gdk/gdkkeysyms.h>
32 #include <epan/filesystem.h>
36 #include "gtkglobals.h"
37 #include "gui_utils.h"
38 #include "dlg_utils.h"
40 #include "compat_macros.h"
49 #define E_FS_CALLER_PTR_KEY "fs_caller_ptr"
51 static gchar *last_open_dir = NULL;
52 static gboolean updated_last_open_dir = FALSE;
56 dlg_activate (GtkWidget *widget, gpointer ok_button);
58 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 4) || GTK_MAJOR_VERSION < 2
59 static void file_selection_browse_ok_cb(GtkWidget *w, gpointer data);
61 static void file_selection_browse_destroy_cb(GtkWidget *win, GtkWidget* file_te);
64 /* create a button for the button row (helper for dlg_button_row_new) */
66 dlg_button_new(GtkWidget *hbox, GtkWidget *button_hbox, const gchar *stock_id)
70 button = BUTTON_NEW_FROM_STOCK(stock_id);
71 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
72 OBJECT_SET_DATA(hbox, stock_id, button);
73 gtk_box_pack_end(GTK_BOX(button_hbox), button, FALSE, FALSE, 0);
74 gtk_widget_show(button);
79 /* create a button row for a dialog */
81 /* The purpose of this is, to have one place available, where all button rows
82 * from all dialogs are laid out. This will:
84 * a.) keep the button layout more consistent over the different dialogs
85 * b.) being able to switch between different button layouts, e.g.:
86 * GTK1 (e.g. win32) "OK" "Apply" "Cancel"
87 * GTK2 (e.g. GNOME) "Apply" "Cancel" "OK"
90 dlg_button_row_new(const gchar *stock_id_first, ...)
93 va_list stock_id_list;
94 const gchar *stock_id = stock_id_first;
96 GtkWidget *button_hbox;
100 const gchar *ok = NULL;
101 const gchar *apply = NULL;
102 const gchar *save = NULL;
103 const gchar *dont_save = NULL;
104 const gchar *cancel = NULL;
105 const gchar *close = NULL;
106 const gchar *clear = NULL;
107 const gchar *start = NULL;
108 const gchar *stop = NULL;
109 const gchar *create_stat = NULL;
110 const gchar *help = NULL;
111 const gchar *print = NULL;
112 const gchar *find = NULL;
113 const gchar *jump = NULL;
114 const gchar *yes = NULL;
115 const gchar *no = NULL;
118 va_start(stock_id_list, stock_id_first);
120 /* get all buttons needed */
121 while(stock_id != NULL) {
122 if (strcmp(stock_id, GTK_STOCK_OK) == 0) {
124 } else if (strcmp(stock_id, ETHEREAL_STOCK_CREATE_STAT) == 0) {
125 create_stat = stock_id;
126 } else if (strcmp(stock_id, GTK_STOCK_APPLY) == 0) {
128 } else if (strcmp(stock_id, GTK_STOCK_SAVE) == 0) {
130 } else if (strcmp(stock_id, ETHEREAL_STOCK_DONT_SAVE) == 0) {
131 dont_save = stock_id;
132 } else if (strcmp(stock_id, GTK_STOCK_CANCEL) == 0) {
134 } else if (strcmp(stock_id, GTK_STOCK_CLOSE) == 0) {
136 } else if (strcmp(stock_id, GTK_STOCK_CLEAR) == 0) {
139 } else if (strcmp(stock_id, ETHEREAL_STOCK_CAPTURE_START) == 0) {
141 #endif /* HAVE_LIBPCAP */
142 } else if (strcmp(stock_id, GTK_STOCK_STOP) == 0) {
144 } else if (strcmp(stock_id, GTK_STOCK_HELP) == 0) {
146 } else if (strcmp(stock_id, GTK_STOCK_PRINT) == 0) {
148 } else if (strcmp(stock_id, GTK_STOCK_FIND) == 0) {
150 } else if (strcmp(stock_id, GTK_STOCK_JUMP_TO) == 0) {
152 } else if (strcmp(stock_id, GTK_STOCK_YES) == 0) {
154 } else if (strcmp(stock_id, GTK_STOCK_NO) == 0) {
157 /* we don't know that button! */
158 g_assert_not_reached();
161 stock_id = va_arg(stock_id_list, gchar *);
163 va_end(stock_id_list);
165 /* we should have at least one button */
169 hbox = gtk_hbox_new(FALSE, 0);
170 gtk_widget_show(hbox);
172 button_hbox = gtk_hbutton_box_new();
173 gtk_box_pack_end(GTK_BOX(hbox), button_hbox, TRUE, TRUE, 0);
174 gtk_widget_show(button_hbox);
176 help_hbox = gtk_hbutton_box_new();
177 gtk_box_pack_end(GTK_BOX(hbox), help_hbox, FALSE, FALSE, 0);
178 gtk_widget_show(help_hbox);
181 /* if only one button, simply put it in the middle (default) */
182 dlg_button_new(hbox, button_hbox, stock_id_first);
186 /* do we have a help button? -> special handling for it */
188 button = BUTTON_NEW_FROM_STOCK(help);
189 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
190 OBJECT_SET_DATA(hbox, help, button);
191 gtk_box_pack_start(GTK_BOX(help_hbox), button, FALSE, FALSE, 0);
192 gtk_widget_show(button);
196 /* if more than one button, sort buttons from left to right */
197 /* (the whole button cluster will then be right aligned) */
198 gtk_button_box_set_layout (GTK_BUTTON_BOX(button_hbox), GTK_BUTTONBOX_END);
199 gtk_button_box_set_spacing(GTK_BUTTON_BOX(button_hbox), 5);
201 /* GTK+ 1.3 and later - on Win32, we use 1.3[.x] or 2.x, not 1.2[.x] */
202 #if !defined(_WIN32) && GTK_MAJOR_VERSION >= 2
203 /* beware: sequence of buttons are important! */
205 /* XXX: this can be implemented more elegant of course, but it works as it should */
208 dlg_button_new(hbox, button_hbox, cancel);
209 dlg_button_new(hbox, button_hbox, ok);
212 if (print && cancel) {
213 dlg_button_new(hbox, button_hbox, cancel);
214 dlg_button_new(hbox, button_hbox, print);
217 if (find && cancel) {
218 dlg_button_new(hbox, button_hbox, cancel);
219 dlg_button_new(hbox, button_hbox, find);
222 if (jump && cancel) {
223 dlg_button_new(hbox, button_hbox, cancel);
224 dlg_button_new(hbox, button_hbox, jump);
227 if (save && cancel) {
228 dlg_button_new(hbox, button_hbox, cancel);
229 dlg_button_new(hbox, button_hbox, save);
233 dlg_button_new(hbox, button_hbox, clear);
234 dlg_button_new(hbox, button_hbox, ok);
238 dlg_button_new(hbox, button_hbox, close);
239 dlg_button_new(hbox, button_hbox, save);
242 if (create_stat && cancel) {
243 dlg_button_new(hbox, button_hbox, cancel);
244 dlg_button_new(hbox, button_hbox, create_stat);
247 if (start && cancel) {
248 dlg_button_new(hbox, button_hbox, cancel);
249 dlg_button_new(hbox, button_hbox, start);
254 if (ok && save && close) {
255 dlg_button_new(hbox, button_hbox, save);
256 dlg_button_new(hbox, button_hbox, close);
257 dlg_button_new(hbox, button_hbox, ok);
260 if (ok && apply && cancel) {
261 dlg_button_new(hbox, button_hbox, apply);
262 dlg_button_new(hbox, button_hbox, cancel);
263 dlg_button_new(hbox, button_hbox, ok);
266 if (apply && save && close) {
267 dlg_button_new(hbox, button_hbox, save);
268 dlg_button_new(hbox, button_hbox, close);
269 dlg_button_new(hbox, button_hbox, apply);
272 if (yes && no && cancel) {
273 dlg_button_new(hbox, button_hbox, no);
274 dlg_button_new(hbox, button_hbox, cancel);
275 dlg_button_new(hbox, button_hbox, yes);
278 if (save && dont_save && cancel) {
279 dlg_button_new(hbox, button_hbox, dont_save);
280 dlg_button_new(hbox, button_hbox, cancel);
281 dlg_button_new(hbox, button_hbox, save);
286 if (ok && apply && save && cancel) {
287 dlg_button_new(hbox, button_hbox, save);
288 dlg_button_new(hbox, button_hbox, apply);
289 dlg_button_new(hbox, button_hbox, cancel);
290 dlg_button_new(hbox, button_hbox, ok);
293 if (ok && apply && save && close) {
294 dlg_button_new(hbox, button_hbox, save);
295 dlg_button_new(hbox, button_hbox, apply);
296 dlg_button_new(hbox, button_hbox, close);
297 dlg_button_new(hbox, button_hbox, ok);
303 /* beware: sequence of buttons is important! */
304 if (ok != NULL) dlg_button_new(hbox, button_hbox, ok);
305 if (jump != NULL) dlg_button_new(hbox, button_hbox, jump);
306 if (find != NULL) dlg_button_new(hbox, button_hbox, find);
307 if (print != NULL) dlg_button_new(hbox, button_hbox, print);
308 if (create_stat != NULL) dlg_button_new(hbox, button_hbox, create_stat);
309 if (apply != NULL) dlg_button_new(hbox, button_hbox, apply);
310 if (yes != NULL) dlg_button_new(hbox, button_hbox, yes);
311 if (no != NULL) dlg_button_new(hbox, button_hbox, no);
312 if (save != NULL) dlg_button_new(hbox, button_hbox, save);
313 if (dont_save != NULL) dlg_button_new(hbox, button_hbox, dont_save);
314 if (start != NULL) dlg_button_new(hbox, button_hbox, start);
315 if (stop != NULL) dlg_button_new(hbox, button_hbox, stop);
316 if (close != NULL) dlg_button_new(hbox, button_hbox, close);
317 if (clear != NULL) dlg_button_new(hbox, button_hbox, clear);
318 if (cancel != NULL) dlg_button_new(hbox, button_hbox, cancel);
320 /* GTK2: we don't know that button combination, add it to the above list! */
321 /* g_assert_not_reached(); */
326 /* this is called, when a dialog was closed */
327 static void dlg_destroy_cb(GtkWidget *dialog _U_, gpointer data _U_)
329 #if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 4
331 /* bring main window back to front (workaround for a bug in win32 GTK2.x)
332 XXX - do this only on Windows? */
333 gtk_window_present(GTK_WINDOW(top_level));
339 /* Create a dialog box window that belongs to Ethereal's main window. */
341 dlg_window_new(const gchar *title)
345 #if GTK_MAJOR_VERSION < 2
346 win = window_new(GTK_WINDOW_DIALOG, title);
348 win = window_new(GTK_WINDOW_TOPLEVEL, title);
352 * XXX - if we're running in the capture child process, we can't easily
353 * make this window transient for the main process's window. We just
356 * Perhaps the child process should only capture packets, write them to
357 * a file, and somehow notify the parent process and let *it* do all
358 * the GUI work. If we can do that efficiently (so that we don't drop
359 * more packets), perhaps we can also do so even when we're *not* doing
360 * an "Update list of packets in real time" capture. That'd let the
361 * child process run set-UID on platforms where you need that in order
362 * to capture, and might also simplify the job of having the GUI main
363 * loop wait both for user input and packet arrival.
366 gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
369 SIGNAL_CONNECT(win, "destroy", dlg_destroy_cb, NULL);
375 /* Create a file selection dialog box window that belongs to Ethereal's
377 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
379 file_selection_new(const gchar *title, file_selection_action_t action)
382 GtkFileChooserAction gtk_action;
383 const gchar *ok_button_text;
387 case FILE_SELECTION_OPEN:
388 gtk_action = GTK_FILE_CHOOSER_ACTION_OPEN;
389 ok_button_text = GTK_STOCK_OPEN;
392 case FILE_SELECTION_READ_BROWSE:
393 gtk_action = GTK_FILE_CHOOSER_ACTION_OPEN;
394 ok_button_text = GTK_STOCK_OK;
397 case FILE_SELECTION_SAVE:
398 gtk_action = GTK_FILE_CHOOSER_ACTION_SAVE;
399 ok_button_text = GTK_STOCK_SAVE;
402 case FILE_SELECTION_WRITE_BROWSE:
403 gtk_action = GTK_FILE_CHOOSER_ACTION_SAVE;
404 ok_button_text = GTK_STOCK_OK;
408 g_assert_not_reached();
410 ok_button_text = NULL;
413 win = gtk_file_chooser_dialog_new(title, GTK_WINDOW(top_level), gtk_action,
415 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
416 ok_button_text, GTK_RESPONSE_ACCEPT,
418 ok_button_text, GTK_RESPONSE_ACCEPT,
419 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
423 /* If we've opened a file before, start out by showing the files in the directory
424 in which that file resided. */
426 file_selection_set_current_folder(win, last_open_dir);
432 file_selection_new(const gchar *title, file_selection_action_t action _U_)
436 win = gtk_file_selection_new(title);
437 #if GTK_MAJOR_VERSION >= 2
438 gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER_ON_PARENT);
440 gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
442 /* XXX - why are we doing this? We don't do it with the GtkFileChooser,
443 as it complains that the file name isn't being set to an absolute
444 path; does this provoke a similar complaint? */
445 gtk_file_selection_set_filename(GTK_FILE_SELECTION(win), "");
447 /* If we've opened a file before, start out by showing the files in the directory
448 in which that file resided. */
450 file_selection_set_current_folder(win, last_open_dir);
456 /* Set the current folder for a file selection dialog. */
458 file_selection_set_current_folder(GtkWidget *fs, const gchar *filename)
460 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
462 int filename_len = strlen(filename);
465 /* trim filename, so gtk_file_chooser_set_current_folder() likes it, see below */
466 if (filename[filename_len -1] == G_DIR_SEPARATOR
468 && filename_len > 3) /* e.g. "D:\" */
470 && filename_len > 1) /* e.g. "/" */
473 new_filename = g_strdup(filename);
474 new_filename[filename_len-1] = '\0';
476 new_filename = g_strdup(filename);
479 /* this function is very pedantic about it's filename parameter */
480 /* no trailing '\' allowed, unless a win32 root dir "D:\" */
481 ret = gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fs), new_filename);
482 g_free(new_filename);
485 gtk_file_selection_set_filename(GTK_FILE_SELECTION(fs), filename);
490 /* Set the "extra" widget for a file selection dialog, with user-supplied
493 file_selection_set_extra_widget(GtkWidget *fs, GtkWidget *extra)
495 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
496 gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(fs), extra);
498 gtk_box_pack_start(GTK_BOX(GTK_FILE_SELECTION(fs)->action_area), extra,
505 * A generic select_file routine that is intended to be connected to
506 * a Browse button on other dialog boxes. This allows the user to browse
507 * for a file and select it. We fill in the text_entry that is given to us.
509 * We display the window label specified in our args.
512 file_selection_browse(GtkWidget *file_bt, GtkWidget *file_te, const char *label, file_selection_action_t action)
514 GtkWidget *caller = gtk_widget_get_toplevel(file_bt);
516 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
520 /* Has a file selection dialog box already been opened for that top-level
522 fs = OBJECT_GET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY);
524 /* Yes. Just re-activate that dialog box. */
525 reactivate_window(fs);
529 fs = file_selection_new(label, action);
531 OBJECT_SET_DATA(fs, PRINT_FILE_TE_KEY, file_te);
533 /* Set the E_FS_CALLER_PTR_KEY for the new dialog to point to our caller. */
534 OBJECT_SET_DATA(fs, E_FS_CALLER_PTR_KEY, caller);
536 /* Set the E_FILE_SEL_DIALOG_PTR_KEY for the caller to point to us */
537 OBJECT_SET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY, fs);
539 /* Call a handler when the file selection box is destroyed, so we can inform
540 our caller, if any, that it's been destroyed. */
541 SIGNAL_CONNECT(fs, "destroy", GTK_SIGNAL_FUNC(file_selection_browse_destroy_cb),
544 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
545 if (gtk_dialog_run(GTK_DIALOG(fs)) == GTK_RESPONSE_ACCEPT)
547 f_name = g_strdup(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fs)));
548 gtk_entry_set_text(GTK_ENTRY(file_te), f_name);
553 SIGNAL_CONNECT(GTK_FILE_SELECTION(fs)->ok_button, "clicked",
554 file_selection_browse_ok_cb, fs);
556 window_set_cancel_button(fs, GTK_FILE_SELECTION(fs)->cancel_button,
557 window_cancel_button_cb);
559 SIGNAL_CONNECT(fs, "delete_event", window_delete_event_cb, fs);
567 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 4) || GTK_MAJOR_VERSION < 2
569 file_selection_browse_ok_cb(GtkWidget *w _U_, gpointer data)
572 GtkWidget *win = data;
574 f_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION (data)));
576 /* Perhaps the user specified a directory instead of a file.
577 Check whether they did. */
578 if (test_for_directory(f_name) == EISDIR) {
579 /* It's a directory - set the file selection box to display it. */
580 set_last_open_dir(f_name);
582 file_selection_set_current_folder(data, last_open_dir);
586 gtk_entry_set_text(GTK_ENTRY(OBJECT_GET_DATA(win, PRINT_FILE_TE_KEY)),
588 window_destroy(GTK_WIDGET(win));
595 file_selection_browse_destroy_cb(GtkWidget *win, GtkWidget* parent_te)
599 /* Get the widget that requested that we be popped up.
600 (It should arrange to destroy us if it's destroyed, so
601 that we don't get a pointer to a non-existent window here.) */
602 caller = OBJECT_GET_DATA(win, E_FS_CALLER_PTR_KEY);
604 /* Tell it we no longer exist. */
605 OBJECT_SET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY, NULL);
607 /* Give the focus to the file text entry widget so the user can just press
608 Return to print to the file. */
609 gtk_widget_grab_focus(parent_te);
614 set_last_open_dir(char *dirname)
617 gchar *new_last_open_dir;
620 len = strlen(dirname);
621 if (dirname[len-1] == G_DIR_SEPARATOR) {
622 new_last_open_dir = g_strconcat(dirname, NULL);
625 new_last_open_dir = g_strconcat(dirname,
626 G_DIR_SEPARATOR_S, NULL);
629 if (last_open_dir == NULL ||
630 strcmp(last_open_dir, new_last_open_dir) != 0)
631 updated_last_open_dir = TRUE;
634 new_last_open_dir = NULL;
635 if (last_open_dir != NULL)
636 updated_last_open_dir = TRUE;
640 g_free(last_open_dir);
642 last_open_dir = new_last_open_dir;
646 get_last_open_dir(void)
648 return last_open_dir;
651 /* Set the "activate" signal for a widget to call a routine to
652 activate the "OK" button for a dialog box.
654 XXX - there should be a way to specify that a GtkEntry widget
655 shouldn't itself handle the Return key, but should let it be
656 passed on to the parent, so that you don't have to do this
657 by hand for every GtkEntry widget in a dialog box, but, alas,
658 there isn't. (Does this problem exist for other widgets?
659 I.e., are there any others that seize the Return key? */
661 dlg_set_activate(GtkWidget *widget, GtkWidget *ok_button)
663 SIGNAL_CONNECT(widget, "activate", dlg_activate, ok_button);
667 dlg_activate (GtkWidget *widget _U_, gpointer ok_button)
669 gtk_widget_activate(GTK_WIDGET(ok_button));
672 #if GTK_MAJOR_VERSION < 2
673 /* Sigh. GTK+ appears not to acknowledge that it should be possible
674 to attach mnemonics to anything other than menu items; provide
675 routines to create radio and check buttons with labels that
676 include mnemonics. */
679 GtkAccelGroup *accel_group;
683 dlg_fix_label_callback(GtkWidget *label_widget, gpointer data)
685 fix_label_args_t *args = data;
689 gtk_label_get(GTK_LABEL(label_widget), &label);
690 accel_key = gtk_label_parse_uline(GTK_LABEL(label_widget), label);
691 if (accel_key != GDK_VoidSymbol) {
692 /* Yes, we have a mnemonic. */
693 gtk_widget_add_accelerator(args->button, "clicked", args->accel_group,
694 accel_key, 0, GTK_ACCEL_LOCKED);
695 gtk_widget_add_accelerator(args->button, "clicked", args->accel_group,
696 accel_key, GDK_MOD1_MASK, GTK_ACCEL_LOCKED);
701 dlg_fix_button_label(GtkWidget *button, GtkAccelGroup *accel_group)
703 fix_label_args_t args;
705 args.button = button;
706 args.accel_group = accel_group;
707 gtk_container_foreach(GTK_CONTAINER(button), dlg_fix_label_callback, &args);
711 dlg_radio_button_new_with_label_with_mnemonic(GSList *group,
712 const gchar *label, GtkAccelGroup *accel_group)
714 GtkWidget *radio_button;
716 radio_button = gtk_radio_button_new_with_label (group, label);
717 dlg_fix_button_label(radio_button, accel_group);
722 dlg_check_button_new_with_label_with_mnemonic(const gchar *label,
723 GtkAccelGroup *accel_group)
725 GtkWidget *check_button;
727 check_button = gtk_check_button_new_with_label (label);
728 dlg_fix_button_label(check_button, accel_group);
733 dlg_toggle_button_new_with_label_with_mnemonic(const gchar *label,
734 GtkAccelGroup *accel_group)
736 GtkWidget *toggle_button;
738 toggle_button = gtk_toggle_button_new_with_label (label);
739 dlg_fix_button_label(toggle_button, accel_group);
740 return toggle_button;