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_SAVE:
382 gtk_action = GTK_FILE_CHOOSER_ACTION_SAVE;
383 ok_button_text = GTK_STOCK_SAVE;
387 g_assert_not_reached();
389 ok_button_text = NULL;
392 win = gtk_file_chooser_dialog_new(title, GTK_WINDOW(top_level), gtk_action,
393 ok_button_text, GTK_RESPONSE_ACCEPT,
394 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
397 /* If we've opened a file before, start out by showing the files in the directory
398 in which that file resided. */
400 file_selection_set_current_folder(win, last_open_dir);
406 file_selection_new(const gchar *title, file_selection_action_t action _U_)
410 win = gtk_file_selection_new(title);
411 #if GTK_MAJOR_VERSION >= 2
412 gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER_ON_PARENT);
414 gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
416 /* XXX - why are we doing this? We don't do it with the GtkFileChooser,
417 as it complains that the file name isn't being set to an absolute
418 path; does this provoke a similar complaint? */
419 gtk_file_selection_set_filename(GTK_FILE_SELECTION(win), "");
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 /* Set the current folder for a file selection dialog. */
432 file_selection_set_current_folder(GtkWidget *fs, const gchar *filename)
434 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
436 int filename_len = strlen(filename);
439 /* trim filename, so gtk_file_chooser_set_current_folder() likes it, see below */
440 if (filename[filename_len -1] == G_DIR_SEPARATOR
442 && filename_len > 3) /* e.g. "D:\" */
444 && filename_len > 1) /* e.g. "/" */
447 new_filename = g_strdup(filename);
448 new_filename[filename_len-1] = '\0';
450 new_filename = g_strdup(filename);
453 /* this function is very pedantic about it's filename parameter */
454 /* no trailing '\' allowed, unless a win32 root dir "D:\" */
455 ret = gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fs), new_filename);
456 g_free(new_filename);
459 gtk_file_selection_set_filename(GTK_FILE_SELECTION(fs), filename);
464 /* Set the "extra" widget for a file selection dialog, with user-supplied
467 file_selection_set_extra_widget(GtkWidget *fs, GtkWidget *extra)
469 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
470 gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(fs), extra);
472 gtk_box_pack_start(GTK_BOX(GTK_FILE_SELECTION(fs)->action_area), extra,
479 * A generic select_file routine that is intended to be connected to
480 * a Browse button on other dialog boxes. This allows the user to browse
481 * for a file and select it. We fill in the text_entry that is given to us.
483 * We display the window label specified in our args.
486 file_selection_browse(GtkWidget *file_bt, GtkWidget *file_te, const char *label, file_selection_action_t action)
488 GtkWidget *caller = gtk_widget_get_toplevel(file_bt);
490 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
494 /* Has a file selection dialog box already been opened for that top-level
496 fs = OBJECT_GET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY);
498 /* Yes. Just re-activate that dialog box. */
499 reactivate_window(fs);
503 fs = file_selection_new(label, action);
505 OBJECT_SET_DATA(fs, PRINT_FILE_TE_KEY, file_te);
507 /* Set the E_FS_CALLER_PTR_KEY for the new dialog to point to our caller. */
508 OBJECT_SET_DATA(fs, E_FS_CALLER_PTR_KEY, caller);
510 /* Set the E_FILE_SEL_DIALOG_PTR_KEY for the caller to point to us */
511 OBJECT_SET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY, fs);
513 /* Call a handler when the file selection box is destroyed, so we can inform
514 our caller, if any, that it's been destroyed. */
515 SIGNAL_CONNECT(fs, "destroy", GTK_SIGNAL_FUNC(file_selection_browse_destroy_cb),
518 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
519 if (gtk_dialog_run(GTK_DIALOG(fs)) == GTK_RESPONSE_ACCEPT)
521 f_name = g_strdup(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fs)));
522 gtk_entry_set_text(GTK_ENTRY(file_te), f_name);
527 SIGNAL_CONNECT(GTK_FILE_SELECTION(fs)->ok_button, "clicked",
528 file_selection_browse_ok_cb, fs);
530 window_set_cancel_button(fs, GTK_FILE_SELECTION(fs)->cancel_button, NULL);
532 SIGNAL_CONNECT(fs, "delete_event", window_delete_event_cb, fs);
540 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 4) || GTK_MAJOR_VERSION < 2
542 file_selection_browse_ok_cb(GtkWidget *w _U_, gpointer data)
545 GtkWidget *win = data;
547 f_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION (data)));
549 /* Perhaps the user specified a directory instead of a file.
550 Check whether they did. */
551 if (test_for_directory(f_name) == EISDIR) {
552 /* It's a directory - set the file selection box to display it. */
553 set_last_open_dir(f_name);
555 file_selection_set_current_folder(data, last_open_dir);
559 gtk_entry_set_text(GTK_ENTRY(OBJECT_GET_DATA(win, PRINT_FILE_TE_KEY)),
561 window_destroy(GTK_WIDGET(win));
568 file_selection_browse_destroy_cb(GtkWidget *win, GtkWidget* parent_te)
572 /* Get the widget that requested that we be popped up.
573 (It should arrange to destroy us if it's destroyed, so
574 that we don't get a pointer to a non-existent window here.) */
575 caller = OBJECT_GET_DATA(win, E_FS_CALLER_PTR_KEY);
577 /* Tell it we no longer exist. */
578 OBJECT_SET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY, NULL);
580 /* Give the focus to the file text entry widget so the user can just press
581 Return to print to the file. */
582 gtk_widget_grab_focus(parent_te);
587 set_last_open_dir(char *dirname)
590 gchar *new_last_open_dir;
593 len = strlen(dirname);
594 if (dirname[len-1] == G_DIR_SEPARATOR) {
595 new_last_open_dir = g_strconcat(dirname, NULL);
598 new_last_open_dir = g_strconcat(dirname,
599 G_DIR_SEPARATOR_S, NULL);
602 if (last_open_dir == NULL ||
603 strcmp(last_open_dir, new_last_open_dir) != 0)
604 updated_last_open_dir = TRUE;
607 new_last_open_dir = NULL;
608 if (last_open_dir != NULL)
609 updated_last_open_dir = TRUE;
613 g_free(last_open_dir);
615 last_open_dir = new_last_open_dir;
619 get_last_open_dir(void)
621 return last_open_dir;
624 /* Set the "activate" signal for a widget to call a routine to
625 activate the "OK" button for a dialog box.
627 XXX - there should be a way to specify that a GtkEntry widget
628 shouldn't itself handle the Return key, but should let it be
629 passed on to the parent, so that you don't have to do this
630 by hand for every GtkEntry widget in a dialog box, but, alas,
631 there isn't. (Does this problem exist for other widgets?
632 I.e., are there any others that seize the Return key? */
634 dlg_set_activate(GtkWidget *widget, GtkWidget *ok_button)
636 SIGNAL_CONNECT(widget, "activate", dlg_activate, ok_button);
640 dlg_activate (GtkWidget *widget _U_, gpointer ok_button)
642 gtk_widget_activate(GTK_WIDGET(ok_button));
645 #if GTK_MAJOR_VERSION < 2
646 /* Sigh. GTK+ appears not to acknowledge that it should be possible
647 to attach mnemonics to anything other than menu items; provide
648 routines to create radio and check buttons with labels that
649 include mnemonics. */
652 GtkAccelGroup *accel_group;
656 dlg_fix_label_callback(GtkWidget *label_widget, gpointer data)
658 fix_label_args_t *args = data;
662 gtk_label_get(GTK_LABEL(label_widget), &label);
663 accel_key = gtk_label_parse_uline(GTK_LABEL(label_widget), label);
664 if (accel_key != GDK_VoidSymbol) {
665 /* Yes, we have a mnemonic. */
666 gtk_widget_add_accelerator(args->button, "clicked", args->accel_group,
667 accel_key, 0, GTK_ACCEL_LOCKED);
668 gtk_widget_add_accelerator(args->button, "clicked", args->accel_group,
669 accel_key, GDK_MOD1_MASK, GTK_ACCEL_LOCKED);
674 dlg_fix_button_label(GtkWidget *button, GtkAccelGroup *accel_group)
676 fix_label_args_t args;
678 args.button = button;
679 args.accel_group = accel_group;
680 gtk_container_foreach(GTK_CONTAINER(button), dlg_fix_label_callback, &args);
684 dlg_radio_button_new_with_label_with_mnemonic(GSList *group,
685 const gchar *label, GtkAccelGroup *accel_group)
687 GtkWidget *radio_button;
689 radio_button = gtk_radio_button_new_with_label (group, label);
690 dlg_fix_button_label(radio_button, accel_group);
695 dlg_check_button_new_with_label_with_mnemonic(const gchar *label,
696 GtkAccelGroup *accel_group)
698 GtkWidget *check_button;
700 check_button = gtk_check_button_new_with_label (label);
701 dlg_fix_button_label(check_button, accel_group);
706 dlg_toggle_button_new_with_label_with_mnemonic(const gchar *label,
707 GtkAccelGroup *accel_group)
709 GtkWidget *toggle_button;
711 toggle_button = gtk_toggle_button_new_with_label (label);
712 dlg_fix_button_label(toggle_button, accel_group);
713 return toggle_button;