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"
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 layouted. 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) {
138 } else if (strcmp(stock_id, ETHEREAL_STOCK_CAPTURE_START) == 0) {
140 } else if (strcmp(stock_id, GTK_STOCK_STOP) == 0) {
142 } else if (strcmp(stock_id, GTK_STOCK_HELP) == 0) {
144 } else if (strcmp(stock_id, GTK_STOCK_PRINT) == 0) {
146 } else if (strcmp(stock_id, GTK_STOCK_FIND) == 0) {
148 } else if (strcmp(stock_id, GTK_STOCK_JUMP_TO) == 0) {
150 } else if (strcmp(stock_id, GTK_STOCK_YES) == 0) {
152 } else if (strcmp(stock_id, GTK_STOCK_NO) == 0) {
155 /* we don't know that button! */
156 g_assert_not_reached();
159 stock_id = va_arg(stock_id_list, gchar *);
161 va_end(stock_id_list);
163 /* we should have at least one button */
167 hbox = gtk_hbox_new(FALSE, 0);
168 gtk_widget_show(hbox);
170 button_hbox = gtk_hbutton_box_new();
171 gtk_box_pack_end(GTK_BOX(hbox), button_hbox, TRUE, TRUE, 0);
172 gtk_widget_show(button_hbox);
174 help_hbox = gtk_hbutton_box_new();
175 gtk_box_pack_end(GTK_BOX(hbox), help_hbox, FALSE, FALSE, 0);
176 gtk_widget_show(help_hbox);
179 /* if only one button, simply put it in the middle (default) */
180 dlg_button_new(hbox, button_hbox, stock_id_first);
184 /* do we have a help button? -> special handling for it */
186 button = BUTTON_NEW_FROM_STOCK(help);
187 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
188 OBJECT_SET_DATA(hbox, help, button);
189 gtk_box_pack_start(GTK_BOX(help_hbox), button, FALSE, FALSE, 0);
190 gtk_widget_show(button);
194 /* if more than one button, sort buttons from left to right */
195 /* (the whole button cluster will then be right aligned) */
196 gtk_button_box_set_layout (GTK_BUTTON_BOX(button_hbox), GTK_BUTTONBOX_END);
197 gtk_button_box_set_spacing(GTK_BUTTON_BOX(button_hbox), 5);
199 /* GTK+ 1.3 and later - on Win32, we use 1.3[.x] or 2.x, not 1.2[.x] */
200 #if !defined(_WIN32) && GTK_MAJOR_VERSION >= 2
201 /* beware: sequence of buttons are important! */
203 /* XXX: this can be implemented more elegant of course, but it works as it should */
206 dlg_button_new(hbox, button_hbox, cancel);
207 dlg_button_new(hbox, button_hbox, ok);
210 if (print && cancel) {
211 dlg_button_new(hbox, button_hbox, cancel);
212 dlg_button_new(hbox, button_hbox, print);
215 if (find && cancel) {
216 dlg_button_new(hbox, button_hbox, cancel);
217 dlg_button_new(hbox, button_hbox, find);
220 if (jump && cancel) {
221 dlg_button_new(hbox, button_hbox, cancel);
222 dlg_button_new(hbox, button_hbox, jump);
225 if (save && cancel) {
226 dlg_button_new(hbox, button_hbox, cancel);
227 dlg_button_new(hbox, button_hbox, save);
231 dlg_button_new(hbox, button_hbox, clear);
232 dlg_button_new(hbox, button_hbox, ok);
236 dlg_button_new(hbox, button_hbox, close);
237 dlg_button_new(hbox, button_hbox, save);
240 if (create_stat && cancel) {
241 dlg_button_new(hbox, button_hbox, cancel);
242 dlg_button_new(hbox, button_hbox, create_stat);
245 if (start && cancel) {
246 dlg_button_new(hbox, button_hbox, cancel);
247 dlg_button_new(hbox, button_hbox, start);
252 if (ok && save && close) {
253 dlg_button_new(hbox, button_hbox, save);
254 dlg_button_new(hbox, button_hbox, close);
255 dlg_button_new(hbox, button_hbox, ok);
258 if (ok && apply && cancel) {
259 dlg_button_new(hbox, button_hbox, apply);
260 dlg_button_new(hbox, button_hbox, cancel);
261 dlg_button_new(hbox, button_hbox, ok);
264 if (apply && save && close) {
265 dlg_button_new(hbox, button_hbox, save);
266 dlg_button_new(hbox, button_hbox, close);
267 dlg_button_new(hbox, button_hbox, apply);
270 if (yes && no && cancel) {
271 dlg_button_new(hbox, button_hbox, no);
272 dlg_button_new(hbox, button_hbox, cancel);
273 dlg_button_new(hbox, button_hbox, yes);
276 if (save && dont_save && cancel) {
277 dlg_button_new(hbox, button_hbox, dont_save);
278 dlg_button_new(hbox, button_hbox, cancel);
279 dlg_button_new(hbox, button_hbox, save);
284 if (ok && apply && save && cancel) {
285 dlg_button_new(hbox, button_hbox, save);
286 dlg_button_new(hbox, button_hbox, apply);
287 dlg_button_new(hbox, button_hbox, cancel);
288 dlg_button_new(hbox, button_hbox, ok);
291 if (ok && apply && save && close) {
292 dlg_button_new(hbox, button_hbox, save);
293 dlg_button_new(hbox, button_hbox, apply);
294 dlg_button_new(hbox, button_hbox, close);
295 dlg_button_new(hbox, button_hbox, ok);
301 /* beware: sequence of buttons is important! */
302 if (ok != NULL) dlg_button_new(hbox, button_hbox, ok);
303 if (jump != NULL) dlg_button_new(hbox, button_hbox, jump);
304 if (find != NULL) dlg_button_new(hbox, button_hbox, find);
305 if (print != NULL) dlg_button_new(hbox, button_hbox, print);
306 if (create_stat != NULL) dlg_button_new(hbox, button_hbox, create_stat);
307 if (apply != NULL) dlg_button_new(hbox, button_hbox, apply);
308 if (yes != NULL) dlg_button_new(hbox, button_hbox, yes);
309 if (no != NULL) dlg_button_new(hbox, button_hbox, no);
310 if (save != NULL) dlg_button_new(hbox, button_hbox, save);
311 if (dont_save != NULL) dlg_button_new(hbox, button_hbox, dont_save);
312 if (start != NULL) dlg_button_new(hbox, button_hbox, start);
313 if (stop != NULL) dlg_button_new(hbox, button_hbox, stop);
314 if (close != NULL) dlg_button_new(hbox, button_hbox, close);
315 if (clear != NULL) dlg_button_new(hbox, button_hbox, clear);
316 if (cancel != NULL) dlg_button_new(hbox, button_hbox, cancel);
318 /* GTK2: we don't know that button combination, add it to the above list! */
319 /* g_assert_not_reached(); */
324 /* this is called, when a dialog was closed */
325 static void dlg_destroy_cb(GtkWidget *dialog _U_, gpointer data _U_)
327 #if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 4
329 /* bring main window back to front (workaround for a bug in win32 GTK2.x)
330 XXX - do this only on Windows? */
331 gtk_window_present(GTK_WINDOW(top_level));
337 /* Create a dialog box window that belongs to Ethereal's main window. */
339 dlg_window_new(const gchar *title)
343 #if GTK_MAJOR_VERSION < 2
344 win = window_new(GTK_WINDOW_DIALOG, title);
346 win = window_new(GTK_WINDOW_TOPLEVEL, title);
350 * XXX - if we're running in the capture child process, we can't easily
351 * make this window transient for the main process's window. We just
354 * Perhaps the child process should only capture packets, write them to
355 * a file, and somehow notify the parent process and let *it* do all
356 * the GUI work. If we can do that efficiently (so that we don't drop
357 * more packets), perhaps we can also do so even when we're *not* doing
358 * an "Update list of packets in real time" capture. That'd let the
359 * child process run set-UID on platforms where you need that in order
360 * to capture, and might also simplify the job of having the GUI main
361 * loop wait both for user input and packet arrival.
364 gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
367 SIGNAL_CONNECT(win, "destroy", dlg_destroy_cb, NULL);
373 /* Create a file selection dialog box window that belongs to Ethereal's
375 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
377 file_selection_new(const gchar *title, file_selection_action_t action)
380 GtkFileChooserAction gtk_action;
381 const gchar *ok_button_text;
385 case FILE_SELECTION_OPEN:
386 gtk_action = GTK_FILE_CHOOSER_ACTION_OPEN;
387 ok_button_text = GTK_STOCK_OPEN;
390 case FILE_SELECTION_READ_BROWSE:
391 gtk_action = GTK_FILE_CHOOSER_ACTION_OPEN;
392 ok_button_text = GTK_STOCK_OK;
395 case FILE_SELECTION_SAVE:
396 gtk_action = GTK_FILE_CHOOSER_ACTION_SAVE;
397 ok_button_text = GTK_STOCK_SAVE;
400 case FILE_SELECTION_WRITE_BROWSE:
401 gtk_action = GTK_FILE_CHOOSER_ACTION_SAVE;
402 ok_button_text = GTK_STOCK_OK;
406 g_assert_not_reached();
408 ok_button_text = NULL;
411 win = gtk_file_chooser_dialog_new(title, GTK_WINDOW(top_level), gtk_action,
413 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
414 ok_button_text, GTK_RESPONSE_ACCEPT,
416 ok_button_text, GTK_RESPONSE_ACCEPT,
417 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
421 /* If we've opened a file before, start out by showing the files in the directory
422 in which that file resided. */
424 file_selection_set_current_folder(win, last_open_dir);
430 file_selection_new(const gchar *title, file_selection_action_t action _U_)
434 win = gtk_file_selection_new(title);
435 #if GTK_MAJOR_VERSION >= 2
436 gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER_ON_PARENT);
438 gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
440 /* XXX - why are we doing this? We don't do it with the GtkFileChooser,
441 as it complains that the file name isn't being set to an absolute
442 path; does this provoke a similar complaint? */
443 gtk_file_selection_set_filename(GTK_FILE_SELECTION(win), "");
445 /* If we've opened a file before, start out by showing the files in the directory
446 in which that file resided. */
448 file_selection_set_current_folder(win, last_open_dir);
454 /* Set the current folder for a file selection dialog. */
456 file_selection_set_current_folder(GtkWidget *fs, const gchar *filename)
458 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
460 int filename_len = strlen(filename);
463 /* trim filename, so gtk_file_chooser_set_current_folder() likes it, see below */
464 if (filename[filename_len -1] == G_DIR_SEPARATOR
466 && filename_len > 3) /* e.g. "D:\" */
468 && filename_len > 1) /* e.g. "/" */
471 new_filename = g_strdup(filename);
472 new_filename[filename_len-1] = '\0';
474 new_filename = g_strdup(filename);
477 /* this function is very pedantic about it's filename parameter */
478 /* no trailing '\' allowed, unless a win32 root dir "D:\" */
479 ret = gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fs), new_filename);
480 g_free(new_filename);
483 gtk_file_selection_set_filename(GTK_FILE_SELECTION(fs), filename);
488 /* Set the "extra" widget for a file selection dialog, with user-supplied
491 file_selection_set_extra_widget(GtkWidget *fs, GtkWidget *extra)
493 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
494 gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(fs), extra);
496 gtk_box_pack_start(GTK_BOX(GTK_FILE_SELECTION(fs)->action_area), extra,
503 * A generic select_file routine that is intended to be connected to
504 * a Browse button on other dialog boxes. This allows the user to browse
505 * for a file and select it. We fill in the text_entry that is given to us.
507 * We display the window label specified in our args.
510 file_selection_browse(GtkWidget *file_bt, GtkWidget *file_te, const char *label, file_selection_action_t action)
512 GtkWidget *caller = gtk_widget_get_toplevel(file_bt);
514 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
518 /* Has a file selection dialog box already been opened for that top-level
520 fs = OBJECT_GET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY);
522 /* Yes. Just re-activate that dialog box. */
523 reactivate_window(fs);
527 fs = file_selection_new(label, action);
529 OBJECT_SET_DATA(fs, PRINT_FILE_TE_KEY, file_te);
531 /* Set the E_FS_CALLER_PTR_KEY for the new dialog to point to our caller. */
532 OBJECT_SET_DATA(fs, E_FS_CALLER_PTR_KEY, caller);
534 /* Set the E_FILE_SEL_DIALOG_PTR_KEY for the caller to point to us */
535 OBJECT_SET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY, fs);
537 /* Call a handler when the file selection box is destroyed, so we can inform
538 our caller, if any, that it's been destroyed. */
539 SIGNAL_CONNECT(fs, "destroy", GTK_SIGNAL_FUNC(file_selection_browse_destroy_cb),
542 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
543 if (gtk_dialog_run(GTK_DIALOG(fs)) == GTK_RESPONSE_ACCEPT)
545 f_name = g_strdup(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fs)));
546 gtk_entry_set_text(GTK_ENTRY(file_te), f_name);
551 SIGNAL_CONNECT(GTK_FILE_SELECTION(fs)->ok_button, "clicked",
552 file_selection_browse_ok_cb, fs);
554 window_set_cancel_button(fs, GTK_FILE_SELECTION(fs)->cancel_button,
555 window_cancel_button_cb);
557 SIGNAL_CONNECT(fs, "delete_event", window_delete_event_cb, fs);
565 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 4) || GTK_MAJOR_VERSION < 2
567 file_selection_browse_ok_cb(GtkWidget *w _U_, gpointer data)
570 GtkWidget *win = data;
572 f_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION (data)));
574 /* Perhaps the user specified a directory instead of a file.
575 Check whether they did. */
576 if (test_for_directory(f_name) == EISDIR) {
577 /* It's a directory - set the file selection box to display it. */
578 set_last_open_dir(f_name);
580 file_selection_set_current_folder(data, last_open_dir);
584 gtk_entry_set_text(GTK_ENTRY(OBJECT_GET_DATA(win, PRINT_FILE_TE_KEY)),
586 window_destroy(GTK_WIDGET(win));
593 file_selection_browse_destroy_cb(GtkWidget *win, GtkWidget* parent_te)
597 /* Get the widget that requested that we be popped up.
598 (It should arrange to destroy us if it's destroyed, so
599 that we don't get a pointer to a non-existent window here.) */
600 caller = OBJECT_GET_DATA(win, E_FS_CALLER_PTR_KEY);
602 /* Tell it we no longer exist. */
603 OBJECT_SET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY, NULL);
605 /* Give the focus to the file text entry widget so the user can just press
606 Return to print to the file. */
607 gtk_widget_grab_focus(parent_te);
612 set_last_open_dir(char *dirname)
615 gchar *new_last_open_dir;
618 len = strlen(dirname);
619 if (dirname[len-1] == G_DIR_SEPARATOR) {
620 new_last_open_dir = g_strconcat(dirname, NULL);
623 new_last_open_dir = g_strconcat(dirname,
624 G_DIR_SEPARATOR_S, NULL);
627 if (last_open_dir == NULL ||
628 strcmp(last_open_dir, new_last_open_dir) != 0)
629 updated_last_open_dir = TRUE;
632 new_last_open_dir = NULL;
633 if (last_open_dir != NULL)
634 updated_last_open_dir = TRUE;
638 g_free(last_open_dir);
640 last_open_dir = new_last_open_dir;
644 get_last_open_dir(void)
646 return last_open_dir;
649 /* Set the "activate" signal for a widget to call a routine to
650 activate the "OK" button for a dialog box.
652 XXX - there should be a way to specify that a GtkEntry widget
653 shouldn't itself handle the Return key, but should let it be
654 passed on to the parent, so that you don't have to do this
655 by hand for every GtkEntry widget in a dialog box, but, alas,
656 there isn't. (Does this problem exist for other widgets?
657 I.e., are there any others that seize the Return key? */
659 dlg_set_activate(GtkWidget *widget, GtkWidget *ok_button)
661 SIGNAL_CONNECT(widget, "activate", dlg_activate, ok_button);
665 dlg_activate (GtkWidget *widget _U_, gpointer ok_button)
667 gtk_widget_activate(GTK_WIDGET(ok_button));
670 #if GTK_MAJOR_VERSION < 2
671 /* Sigh. GTK+ appears not to acknowledge that it should be possible
672 to attach mnemonics to anything other than menu items; provide
673 routines to create radio and check buttons with labels that
674 include mnemonics. */
677 GtkAccelGroup *accel_group;
681 dlg_fix_label_callback(GtkWidget *label_widget, gpointer data)
683 fix_label_args_t *args = data;
687 gtk_label_get(GTK_LABEL(label_widget), &label);
688 accel_key = gtk_label_parse_uline(GTK_LABEL(label_widget), label);
689 if (accel_key != GDK_VoidSymbol) {
690 /* Yes, we have a mnemonic. */
691 gtk_widget_add_accelerator(args->button, "clicked", args->accel_group,
692 accel_key, 0, GTK_ACCEL_LOCKED);
693 gtk_widget_add_accelerator(args->button, "clicked", args->accel_group,
694 accel_key, GDK_MOD1_MASK, GTK_ACCEL_LOCKED);
699 dlg_fix_button_label(GtkWidget *button, GtkAccelGroup *accel_group)
701 fix_label_args_t args;
703 args.button = button;
704 args.accel_group = accel_group;
705 gtk_container_foreach(GTK_CONTAINER(button), dlg_fix_label_callback, &args);
709 dlg_radio_button_new_with_label_with_mnemonic(GSList *group,
710 const gchar *label, GtkAccelGroup *accel_group)
712 GtkWidget *radio_button;
714 radio_button = gtk_radio_button_new_with_label (group, label);
715 dlg_fix_button_label(radio_button, accel_group);
720 dlg_check_button_new_with_label_with_mnemonic(const gchar *label,
721 GtkAccelGroup *accel_group)
723 GtkWidget *check_button;
725 check_button = gtk_check_button_new_with_label (label);
726 dlg_fix_button_label(check_button, accel_group);
731 dlg_toggle_button_new_with_label_with_mnemonic(const gchar *label,
732 GtkAccelGroup *accel_group)
734 GtkWidget *toggle_button;
736 toggle_button = gtk_toggle_button_new_with_label (label);
737 dlg_fix_button_label(toggle_button, accel_group);
738 return toggle_button;