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, 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(gchar *stock_id_first, ...)
93 va_list stock_id_list;
94 gchar *stock_id = stock_id_first;
96 GtkWidget *button_hbox;
103 gchar *dont_save = NULL;
104 gchar *cancel = NULL;
108 gchar *create_stat = NULL;
117 va_start(stock_id_list, stock_id_first);
119 /* get all buttons needed */
120 while(stock_id != NULL) {
121 if (strcmp(stock_id, GTK_STOCK_OK) == 0) {
123 } else if (strcmp(stock_id, ETHEREAL_STOCK_CREATE_STAT) == 0) {
124 create_stat = stock_id;
125 } else if (strcmp(stock_id, GTK_STOCK_APPLY) == 0) {
127 } else if (strcmp(stock_id, GTK_STOCK_SAVE) == 0) {
129 } else if (strcmp(stock_id, ETHEREAL_STOCK_DONT_SAVE) == 0) {
130 dont_save = stock_id;
131 } else if (strcmp(stock_id, GTK_STOCK_CANCEL) == 0) {
133 } else if (strcmp(stock_id, GTK_STOCK_CLOSE) == 0) {
135 } else if (strcmp(stock_id, GTK_STOCK_CLEAR) == 0) {
137 } else if (strcmp(stock_id, GTK_STOCK_STOP) == 0) {
139 } else if (strcmp(stock_id, GTK_STOCK_HELP) == 0) {
141 } else if (strcmp(stock_id, GTK_STOCK_PRINT) == 0) {
143 } else if (strcmp(stock_id, GTK_STOCK_FIND) == 0) {
145 } else if (strcmp(stock_id, GTK_STOCK_JUMP_TO) == 0) {
147 } else if (strcmp(stock_id, GTK_STOCK_YES) == 0) {
149 } else if (strcmp(stock_id, GTK_STOCK_NO) == 0) {
152 /* we don't know that button! */
153 g_assert_not_reached();
156 stock_id = va_arg(stock_id_list, gchar *);
158 va_end(stock_id_list);
160 /* we should have at least one button */
164 hbox = gtk_hbox_new(FALSE, 0);
165 gtk_widget_show(hbox);
167 button_hbox = gtk_hbutton_box_new();
168 gtk_box_pack_end(GTK_BOX(hbox), button_hbox, TRUE, TRUE, 0);
169 gtk_widget_show(button_hbox);
171 help_hbox = gtk_hbutton_box_new();
172 gtk_box_pack_end(GTK_BOX(hbox), help_hbox, FALSE, FALSE, 0);
173 gtk_widget_show(help_hbox);
176 /* if only one button, simply put it in the middle (default) */
177 dlg_button_new(hbox, button_hbox, stock_id_first);
181 /* do we have a help button? -> special handling for it */
183 button = BUTTON_NEW_FROM_STOCK(help);
184 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
185 OBJECT_SET_DATA(hbox, help, button);
186 gtk_box_pack_start(GTK_BOX(help_hbox), button, FALSE, FALSE, 0);
187 gtk_widget_show(button);
191 /* if more than one button, sort buttons from left to right */
192 /* (the whole button cluster will then be right aligned) */
193 gtk_button_box_set_layout (GTK_BUTTON_BOX(button_hbox), GTK_BUTTONBOX_END);
194 gtk_button_box_set_spacing(GTK_BUTTON_BOX(button_hbox), 5);
196 /* GTK+ 1.3 and later - on Win32, we use 1.3[.x] or 2.x, not 1.2[.x] */
197 #if !defined(_WIN32) && GTK_MAJOR_VERSION >= 2
198 /* beware: sequence of buttons are important! */
200 /* XXX: this can be implemented more elegant of course, but it works as it should */
203 dlg_button_new(hbox, button_hbox, cancel);
204 dlg_button_new(hbox, button_hbox, ok);
207 if (print && cancel) {
208 dlg_button_new(hbox, button_hbox, cancel);
209 dlg_button_new(hbox, button_hbox, print);
212 if (find && cancel) {
213 dlg_button_new(hbox, button_hbox, cancel);
214 dlg_button_new(hbox, button_hbox, find);
217 if (jump && cancel) {
218 dlg_button_new(hbox, button_hbox, cancel);
219 dlg_button_new(hbox, button_hbox, jump);
222 if (save && cancel) {
223 dlg_button_new(hbox, button_hbox, cancel);
224 dlg_button_new(hbox, button_hbox, save);
228 dlg_button_new(hbox, button_hbox, clear);
229 dlg_button_new(hbox, button_hbox, ok);
233 dlg_button_new(hbox, button_hbox, close);
234 dlg_button_new(hbox, button_hbox, save);
237 if (create_stat && cancel) {
238 dlg_button_new(hbox, button_hbox, cancel);
239 dlg_button_new(hbox, button_hbox, create_stat);
244 if (ok && save && close) {
245 dlg_button_new(hbox, button_hbox, save);
246 dlg_button_new(hbox, button_hbox, close);
247 dlg_button_new(hbox, button_hbox, ok);
250 if (ok && apply && cancel) {
251 dlg_button_new(hbox, button_hbox, apply);
252 dlg_button_new(hbox, button_hbox, cancel);
253 dlg_button_new(hbox, button_hbox, ok);
256 if (apply && save && close) {
257 dlg_button_new(hbox, button_hbox, save);
258 dlg_button_new(hbox, button_hbox, close);
259 dlg_button_new(hbox, button_hbox, apply);
262 if (yes && no && cancel) {
263 dlg_button_new(hbox, button_hbox, no);
264 dlg_button_new(hbox, button_hbox, cancel);
265 dlg_button_new(hbox, button_hbox, yes);
268 if (save && dont_save && cancel) {
269 dlg_button_new(hbox, button_hbox, dont_save);
270 dlg_button_new(hbox, button_hbox, cancel);
271 dlg_button_new(hbox, button_hbox, save);
276 if (ok && apply && save && cancel) {
277 dlg_button_new(hbox, button_hbox, save);
278 dlg_button_new(hbox, button_hbox, apply);
279 dlg_button_new(hbox, button_hbox, cancel);
280 dlg_button_new(hbox, button_hbox, ok);
283 if (ok && apply && save && close) {
284 dlg_button_new(hbox, button_hbox, save);
285 dlg_button_new(hbox, button_hbox, apply);
286 dlg_button_new(hbox, button_hbox, close);
287 dlg_button_new(hbox, button_hbox, ok);
293 /* beware: sequence of buttons is important! */
294 if (ok != NULL) dlg_button_new(hbox, button_hbox, ok);
295 if (jump != NULL) dlg_button_new(hbox, button_hbox, jump);
296 if (find != NULL) dlg_button_new(hbox, button_hbox, find);
297 if (print != NULL) dlg_button_new(hbox, button_hbox, print);
298 if (create_stat != NULL) dlg_button_new(hbox, button_hbox, create_stat);
299 if (apply != NULL) dlg_button_new(hbox, button_hbox, apply);
300 if (yes != NULL) dlg_button_new(hbox, button_hbox, yes);
301 if (no != NULL) dlg_button_new(hbox, button_hbox, no);
302 if (save != NULL) dlg_button_new(hbox, button_hbox, save);
303 if (dont_save != NULL) dlg_button_new(hbox, button_hbox, dont_save);
304 if (stop != NULL) dlg_button_new(hbox, button_hbox, stop);
305 if (close != NULL) dlg_button_new(hbox, button_hbox, close);
306 if (clear != NULL) dlg_button_new(hbox, button_hbox, clear);
307 if (cancel != NULL) dlg_button_new(hbox, button_hbox, cancel);
309 /* GTK2: we don't know that button combination, add it to the above list! */
310 /* g_assert_not_reached(); */
315 /* this is called, when a dialog was closed */
316 static void dlg_destroy_cb(GtkWidget *dialog _U_, gpointer data _U_)
318 #if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 4
320 /* bring main window back to front (workaround for a bug in win32 GTK2.x)
321 XXX - do this only on Windows? */
322 gtk_window_present(GTK_WINDOW(top_level));
328 /* Create a dialog box window that belongs to Ethereal's main window. */
330 dlg_window_new(const gchar *title)
334 #if GTK_MAJOR_VERSION < 2
335 win = window_new(GTK_WINDOW_DIALOG, title);
337 win = window_new(GTK_WINDOW_TOPLEVEL, title);
341 * XXX - if we're running in the capture child process, we can't easily
342 * make this window transient for the main process's window. We just
345 * Perhaps the child process should only capture packets, write them to
346 * a file, and somehow notify the parent process and let *it* do all
347 * the GUI work. If we can do that efficiently (so that we don't drop
348 * more packets), perhaps we can also do so even when we're *not* doing
349 * an "Update list of packets in real time" capture. That'd let the
350 * child process run set-UID on platforms where you need that in order
351 * to capture, and might also simplify the job of having the GUI main
352 * loop wait both for user input and packet arrival.
355 gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
358 SIGNAL_CONNECT(win, "destroy", dlg_destroy_cb, NULL);
364 /* Create a file selection dialog box window that belongs to Ethereal's
366 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
368 file_selection_new(const gchar *title, file_selection_action_t action)
371 GtkFileChooserAction gtk_action;
372 const gchar *ok_button_text;
376 case FILE_SELECTION_OPEN:
377 gtk_action = GTK_FILE_CHOOSER_ACTION_OPEN;
378 ok_button_text = GTK_STOCK_OPEN;
381 case FILE_SELECTION_READ_BROWSE:
382 gtk_action = GTK_FILE_CHOOSER_ACTION_OPEN;
383 ok_button_text = GTK_STOCK_OK;
386 case FILE_SELECTION_SAVE:
387 gtk_action = GTK_FILE_CHOOSER_ACTION_SAVE;
388 ok_button_text = GTK_STOCK_SAVE;
391 case FILE_SELECTION_WRITE_BROWSE:
392 gtk_action = GTK_FILE_CHOOSER_ACTION_SAVE;
393 ok_button_text = GTK_STOCK_OK;
397 g_assert_not_reached();
399 ok_button_text = NULL;
402 win = gtk_file_chooser_dialog_new(title, GTK_WINDOW(top_level), gtk_action,
404 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
405 ok_button_text, GTK_RESPONSE_ACCEPT,
407 ok_button_text, GTK_RESPONSE_ACCEPT,
408 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
412 /* If we've opened a file before, start out by showing the files in the directory
413 in which that file resided. */
415 file_selection_set_current_folder(win, last_open_dir);
421 file_selection_new(const gchar *title, file_selection_action_t action _U_)
425 win = gtk_file_selection_new(title);
426 #if GTK_MAJOR_VERSION >= 2
427 gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER_ON_PARENT);
429 gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
431 /* XXX - why are we doing this? We don't do it with the GtkFileChooser,
432 as it complains that the file name isn't being set to an absolute
433 path; does this provoke a similar complaint? */
434 gtk_file_selection_set_filename(GTK_FILE_SELECTION(win), "");
436 /* If we've opened a file before, start out by showing the files in the directory
437 in which that file resided. */
439 file_selection_set_current_folder(win, last_open_dir);
445 /* Set the current folder for a file selection dialog. */
447 file_selection_set_current_folder(GtkWidget *fs, const gchar *filename)
449 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
451 int filename_len = strlen(filename);
454 /* trim filename, so gtk_file_chooser_set_current_folder() likes it, see below */
455 if (filename[filename_len -1] == G_DIR_SEPARATOR
457 && filename_len > 3) /* e.g. "D:\" */
459 && filename_len > 1) /* e.g. "/" */
462 new_filename = g_strdup(filename);
463 new_filename[filename_len-1] = '\0';
465 new_filename = g_strdup(filename);
468 /* this function is very pedantic about it's filename parameter */
469 /* no trailing '\' allowed, unless a win32 root dir "D:\" */
470 ret = gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fs), new_filename);
471 g_free(new_filename);
474 gtk_file_selection_set_filename(GTK_FILE_SELECTION(fs), filename);
479 /* Set the "extra" widget for a file selection dialog, with user-supplied
482 file_selection_set_extra_widget(GtkWidget *fs, GtkWidget *extra)
484 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
485 gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(fs), extra);
487 gtk_box_pack_start(GTK_BOX(GTK_FILE_SELECTION(fs)->action_area), extra,
494 * A generic select_file routine that is intended to be connected to
495 * a Browse button on other dialog boxes. This allows the user to browse
496 * for a file and select it. We fill in the text_entry that is given to us.
498 * We display the window label specified in our args.
501 file_selection_browse(GtkWidget *file_bt, GtkWidget *file_te, const char *label, file_selection_action_t action)
503 GtkWidget *caller = gtk_widget_get_toplevel(file_bt);
505 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
509 /* Has a file selection dialog box already been opened for that top-level
511 fs = OBJECT_GET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY);
513 /* Yes. Just re-activate that dialog box. */
514 reactivate_window(fs);
518 fs = file_selection_new(label, action);
520 OBJECT_SET_DATA(fs, PRINT_FILE_TE_KEY, file_te);
522 /* Set the E_FS_CALLER_PTR_KEY for the new dialog to point to our caller. */
523 OBJECT_SET_DATA(fs, E_FS_CALLER_PTR_KEY, caller);
525 /* Set the E_FILE_SEL_DIALOG_PTR_KEY for the caller to point to us */
526 OBJECT_SET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY, fs);
528 /* Call a handler when the file selection box is destroyed, so we can inform
529 our caller, if any, that it's been destroyed. */
530 SIGNAL_CONNECT(fs, "destroy", GTK_SIGNAL_FUNC(file_selection_browse_destroy_cb),
533 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
534 if (gtk_dialog_run(GTK_DIALOG(fs)) == GTK_RESPONSE_ACCEPT)
536 f_name = g_strdup(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fs)));
537 gtk_entry_set_text(GTK_ENTRY(file_te), f_name);
542 SIGNAL_CONNECT(GTK_FILE_SELECTION(fs)->ok_button, "clicked",
543 file_selection_browse_ok_cb, fs);
545 window_set_cancel_button(fs, GTK_FILE_SELECTION(fs)->cancel_button, NULL);
547 SIGNAL_CONNECT(fs, "delete_event", window_delete_event_cb, fs);
555 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 4) || GTK_MAJOR_VERSION < 2
557 file_selection_browse_ok_cb(GtkWidget *w _U_, gpointer data)
560 GtkWidget *win = data;
562 f_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION (data)));
564 /* Perhaps the user specified a directory instead of a file.
565 Check whether they did. */
566 if (test_for_directory(f_name) == EISDIR) {
567 /* It's a directory - set the file selection box to display it. */
568 set_last_open_dir(f_name);
570 file_selection_set_current_folder(data, last_open_dir);
574 gtk_entry_set_text(GTK_ENTRY(OBJECT_GET_DATA(win, PRINT_FILE_TE_KEY)),
576 window_destroy(GTK_WIDGET(win));
583 file_selection_browse_destroy_cb(GtkWidget *win, GtkWidget* parent_te)
587 /* Get the widget that requested that we be popped up.
588 (It should arrange to destroy us if it's destroyed, so
589 that we don't get a pointer to a non-existent window here.) */
590 caller = OBJECT_GET_DATA(win, E_FS_CALLER_PTR_KEY);
592 /* Tell it we no longer exist. */
593 OBJECT_SET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY, NULL);
595 /* Give the focus to the file text entry widget so the user can just press
596 Return to print to the file. */
597 gtk_widget_grab_focus(parent_te);
602 set_last_open_dir(char *dirname)
605 gchar *new_last_open_dir;
608 len = strlen(dirname);
609 if (dirname[len-1] == G_DIR_SEPARATOR) {
610 new_last_open_dir = g_strconcat(dirname, NULL);
613 new_last_open_dir = g_strconcat(dirname,
614 G_DIR_SEPARATOR_S, NULL);
617 if (last_open_dir == NULL ||
618 strcmp(last_open_dir, new_last_open_dir) != 0)
619 updated_last_open_dir = TRUE;
622 new_last_open_dir = NULL;
623 if (last_open_dir != NULL)
624 updated_last_open_dir = TRUE;
628 g_free(last_open_dir);
630 last_open_dir = new_last_open_dir;
634 get_last_open_dir(void)
636 return last_open_dir;
639 /* Set the "activate" signal for a widget to call a routine to
640 activate the "OK" button for a dialog box.
642 XXX - there should be a way to specify that a GtkEntry widget
643 shouldn't itself handle the Return key, but should let it be
644 passed on to the parent, so that you don't have to do this
645 by hand for every GtkEntry widget in a dialog box, but, alas,
646 there isn't. (Does this problem exist for other widgets?
647 I.e., are there any others that seize the Return key? */
649 dlg_set_activate(GtkWidget *widget, GtkWidget *ok_button)
651 SIGNAL_CONNECT(widget, "activate", dlg_activate, ok_button);
655 dlg_activate (GtkWidget *widget _U_, gpointer ok_button)
657 gtk_widget_activate(GTK_WIDGET(ok_button));
660 #if GTK_MAJOR_VERSION < 2
661 /* Sigh. GTK+ appears not to acknowledge that it should be possible
662 to attach mnemonics to anything other than menu items; provide
663 routines to create radio and check buttons with labels that
664 include mnemonics. */
667 GtkAccelGroup *accel_group;
671 dlg_fix_label_callback(GtkWidget *label_widget, gpointer data)
673 fix_label_args_t *args = data;
677 gtk_label_get(GTK_LABEL(label_widget), &label);
678 accel_key = gtk_label_parse_uline(GTK_LABEL(label_widget), label);
679 if (accel_key != GDK_VoidSymbol) {
680 /* Yes, we have a mnemonic. */
681 gtk_widget_add_accelerator(args->button, "clicked", args->accel_group,
682 accel_key, 0, GTK_ACCEL_LOCKED);
683 gtk_widget_add_accelerator(args->button, "clicked", args->accel_group,
684 accel_key, GDK_MOD1_MASK, GTK_ACCEL_LOCKED);
689 dlg_fix_button_label(GtkWidget *button, GtkAccelGroup *accel_group)
691 fix_label_args_t args;
693 args.button = button;
694 args.accel_group = accel_group;
695 gtk_container_foreach(GTK_CONTAINER(button), dlg_fix_label_callback, &args);
699 dlg_radio_button_new_with_label_with_mnemonic(GSList *group,
700 const gchar *label, GtkAccelGroup *accel_group)
702 GtkWidget *radio_button;
704 radio_button = gtk_radio_button_new_with_label (group, label);
705 dlg_fix_button_label(radio_button, accel_group);
710 dlg_check_button_new_with_label_with_mnemonic(const gchar *label,
711 GtkAccelGroup *accel_group)
713 GtkWidget *check_button;
715 check_button = gtk_check_button_new_with_label (label);
716 dlg_fix_button_label(check_button, accel_group);
721 dlg_toggle_button_new_with_label_with_mnemonic(const gchar *label,
722 GtkAccelGroup *accel_group)
724 GtkWidget *toggle_button;
726 toggle_button = gtk_toggle_button_new_with_label (label);
727 dlg_fix_button_label(toggle_button, accel_group);
728 return toggle_button;