2 * Utilities to use when constructing dialogs
4 * $Id: dlg_utils.c,v 1.40 2004/07/12 18:39:03 ulfl Exp $
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 *cancel = NULL;
107 gchar *create_stat = NULL;
116 va_start(stock_id_list, stock_id_first);
118 /* get all buttons needed */
119 while(stock_id != NULL) {
120 if (strcmp(stock_id, GTK_STOCK_OK) == 0) {
122 } else if (strcmp(stock_id, ETHEREAL_STOCK_CREATE_STAT) == 0) {
123 create_stat = stock_id;
124 } else if (strcmp(stock_id, GTK_STOCK_APPLY) == 0) {
126 } else if (strcmp(stock_id, GTK_STOCK_SAVE) == 0) {
128 } else if (strcmp(stock_id, GTK_STOCK_CANCEL) == 0) {
130 } else if (strcmp(stock_id, GTK_STOCK_CLOSE) == 0) {
132 } else if (strcmp(stock_id, GTK_STOCK_CLEAR) == 0) {
134 } else if (strcmp(stock_id, GTK_STOCK_STOP) == 0) {
136 } else if (strcmp(stock_id, GTK_STOCK_HELP) == 0) {
138 } else if (strcmp(stock_id, GTK_STOCK_PRINT) == 0) {
140 } else if (strcmp(stock_id, GTK_STOCK_FIND) == 0) {
142 } else if (strcmp(stock_id, GTK_STOCK_JUMP_TO) == 0) {
144 } else if (strcmp(stock_id, GTK_STOCK_YES) == 0) {
146 } else if (strcmp(stock_id, GTK_STOCK_NO) == 0) {
149 /* we don't know that button! */
150 g_assert_not_reached();
153 stock_id = va_arg(stock_id_list, gchar *);
155 va_end(stock_id_list);
157 /* we should have at least one button */
161 hbox = gtk_hbox_new(FALSE, 0);
162 gtk_widget_show(hbox);
164 button_hbox = gtk_hbutton_box_new();
165 gtk_box_pack_end(GTK_BOX(hbox), button_hbox, TRUE, TRUE, 0);
166 gtk_widget_show(button_hbox);
168 help_hbox = gtk_hbutton_box_new();
169 gtk_box_pack_end(GTK_BOX(hbox), help_hbox, FALSE, FALSE, 0);
170 gtk_widget_show(help_hbox);
173 /* if only one button, simply put it in the middle (default) */
174 dlg_button_new(hbox, button_hbox, stock_id_first);
178 /* do we have a help button? -> special handling for it */
180 button = BUTTON_NEW_FROM_STOCK(help);
181 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
182 OBJECT_SET_DATA(hbox, help, button);
183 gtk_box_pack_start(GTK_BOX(help_hbox), button, FALSE, FALSE, 0);
184 gtk_widget_show(button);
188 /* if more than one button, sort buttons from left to right */
189 /* (the whole button cluster will then be right aligned) */
190 gtk_button_box_set_layout (GTK_BUTTON_BOX(button_hbox), GTK_BUTTONBOX_END);
191 gtk_button_box_set_spacing(GTK_BUTTON_BOX(button_hbox), 5);
193 #if !WIN32 && GTK_MAJOR_VERSION >= 2
194 /* beware: sequence of buttons are important! */
196 /* XXX: this can be implemented more elegant of course, but it works as it should */
199 dlg_button_new(hbox, button_hbox, cancel);
200 dlg_button_new(hbox, button_hbox, ok);
203 if (print && cancel) {
204 dlg_button_new(hbox, button_hbox, cancel);
205 dlg_button_new(hbox, button_hbox, print);
208 if (find && cancel) {
209 dlg_button_new(hbox, button_hbox, cancel);
210 dlg_button_new(hbox, button_hbox, find);
213 if (jump && cancel) {
214 dlg_button_new(hbox, button_hbox, cancel);
215 dlg_button_new(hbox, button_hbox, jump);
218 if (save && cancel) {
219 dlg_button_new(hbox, button_hbox, cancel);
220 dlg_button_new(hbox, button_hbox, save);
224 dlg_button_new(hbox, button_hbox, clear);
225 dlg_button_new(hbox, button_hbox, ok);
229 dlg_button_new(hbox, button_hbox, close);
230 dlg_button_new(hbox, button_hbox, save);
233 if (create_stat && cancel) {
234 dlg_button_new(hbox, button_hbox, cancel);
235 dlg_button_new(hbox, button_hbox, create_stat);
240 if (ok && save && close) {
241 dlg_button_new(hbox, button_hbox, save);
242 dlg_button_new(hbox, button_hbox, close);
243 dlg_button_new(hbox, button_hbox, ok);
246 if (ok && apply && cancel) {
247 dlg_button_new(hbox, button_hbox, apply);
248 dlg_button_new(hbox, button_hbox, cancel);
249 dlg_button_new(hbox, button_hbox, ok);
252 if (apply && 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, apply);
258 if (yes && no && cancel) {
259 dlg_button_new(hbox, button_hbox, yes);
260 dlg_button_new(hbox, button_hbox, no);
261 dlg_button_new(hbox, button_hbox, cancel);
266 if (ok && apply && save && cancel) {
267 dlg_button_new(hbox, button_hbox, save);
268 dlg_button_new(hbox, button_hbox, apply);
269 dlg_button_new(hbox, button_hbox, cancel);
270 dlg_button_new(hbox, button_hbox, ok);
273 if (ok && apply && save && close) {
274 dlg_button_new(hbox, button_hbox, save);
275 dlg_button_new(hbox, button_hbox, apply);
276 dlg_button_new(hbox, button_hbox, close);
277 dlg_button_new(hbox, button_hbox, ok);
283 /* beware: sequence of buttons is important! */
284 if (ok != NULL) dlg_button_new(hbox, button_hbox, ok);
285 if (jump != NULL) dlg_button_new(hbox, button_hbox, jump);
286 if (find != NULL) dlg_button_new(hbox, button_hbox, find);
287 if (print != NULL) dlg_button_new(hbox, button_hbox, print);
288 if (create_stat != NULL) dlg_button_new(hbox, button_hbox, create_stat);
289 if (apply != NULL) dlg_button_new(hbox, button_hbox, apply);
290 if (yes != NULL) dlg_button_new(hbox, button_hbox, yes);
291 if (no != NULL) dlg_button_new(hbox, button_hbox, no);
292 if (save != NULL) dlg_button_new(hbox, button_hbox, save);
293 if (stop != NULL) dlg_button_new(hbox, button_hbox, stop);
294 if (close != NULL) dlg_button_new(hbox, button_hbox, close);
295 if (clear != NULL) dlg_button_new(hbox, button_hbox, clear);
296 if (cancel != NULL) dlg_button_new(hbox, button_hbox, cancel);
298 /* GTK2: we don't know that button combination, add it to the above list! */
299 /* g_assert_not_reached(); */
304 /* this is called, when a dialog was closed */
305 static void dlg_destroy_cb(GtkWidget *dialog _U_, gpointer data _U_)
307 #if GTK_MAJOR_VERSION >= 2
309 /* bring main window back to front (workaround for a bug in win32 GTK2.x)
310 XXX - do this only on Windows? */
311 gtk_window_present(GTK_WINDOW(top_level));
317 /* Create a dialog box window that belongs to Ethereal's main window. */
319 dlg_window_new(const gchar *title)
323 #if GTK_MAJOR_VERSION < 2
324 win = window_new(GTK_WINDOW_DIALOG, title);
326 win = window_new(GTK_WINDOW_TOPLEVEL, title);
330 * XXX - if we're running in the capture child process, we can't easily
331 * make this window transient for the main process's window. We just
334 * Perhaps the child process should only capture packets, write them to
335 * a file, and somehow notify the parent process and let *it* do all
336 * the GUI work. If we can do that efficiently (so that we don't drop
337 * more packets), perhaps we can also do so even when we're *not* doing
338 * an "Update list of packets in real time" capture. That'd let the
339 * child process run set-UID on platforms where you need that in order
340 * to capture, and might also simplify the job of having the GUI main
341 * loop wait both for user input and packet arrival.
344 gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
347 SIGNAL_CONNECT(win, "destroy", dlg_destroy_cb, NULL);
353 /* Create a file selection dialog box window that belongs to Ethereal's
355 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
357 file_selection_new(const gchar *title, file_selection_action_t action)
360 GtkFileChooserAction gtk_action;
361 const gchar *ok_button_text;
365 case FILE_SELECTION_OPEN:
366 gtk_action = GTK_FILE_CHOOSER_ACTION_OPEN;
367 ok_button_text = GTK_STOCK_OPEN;
370 case FILE_SELECTION_SAVE:
371 gtk_action = GTK_FILE_CHOOSER_ACTION_SAVE;
372 ok_button_text = GTK_STOCK_SAVE;
376 g_assert_not_reached();
378 ok_button_text = NULL;
381 win = gtk_file_chooser_dialog_new(title, GTK_WINDOW(top_level), gtk_action,
382 ok_button_text, GTK_RESPONSE_ACCEPT,
383 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
386 /* If we've opened a file before, start out by showing the files in the directory
387 in which that file resided. */
389 file_selection_set_current_folder(win, last_open_dir);
395 file_selection_new(const gchar *title, file_selection_action_t action _U_)
399 win = gtk_file_selection_new(title);
400 #if GTK_MAJOR_VERSION >= 2
401 gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER_ON_PARENT);
403 gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
405 /* XXX - why are we doing this? We don't do it with the GtkFileChooser,
406 as it complains that the file name isn't being set to an absolute
407 path; does this provoke a similar complaint? */
408 gtk_file_selection_set_filename(GTK_FILE_SELECTION(win), "");
410 /* If we've opened a file before, start out by showing the files in the directory
411 in which that file resided. */
413 file_selection_set_current_folder(win, last_open_dir);
419 /* Set the current folder for a file selection dialog. */
421 file_selection_set_current_folder(GtkWidget *fs, const gchar *filename)
423 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
425 int filename_len = strlen(filename);
428 /* trim filename, so gtk_file_chooser_set_current_folder() likes it, see below */
429 if (filename[filename_len -1] == G_DIR_SEPARATOR
431 && filename_len > 3) /* e.g. "D:\" */
433 && filename_len > 1) /* e.g. "/" */
436 new_filename = g_strdup(filename);
437 new_filename[filename_len-1] = '\0';
439 new_filename = g_strdup(filename);
442 /* this function is very pedantic about it's filename parameter */
443 /* no trailing '\' allowed, unless a win32 root dir "D:\" */
444 ret = gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fs), new_filename);
445 g_free(new_filename);
448 gtk_file_selection_set_filename(GTK_FILE_SELECTION(fs), filename);
453 /* Set the "extra" widget for a file selection dialog, with user-supplied
456 file_selection_set_extra_widget(GtkWidget *fs, GtkWidget *extra)
458 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
459 gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(fs), extra);
461 gtk_box_pack_start(GTK_BOX(GTK_FILE_SELECTION(fs)->action_area), extra,
468 * A generic select_file routine that is intended to be connected to
469 * a Browse button on other dialog boxes. This allows the user to browse
470 * for a file and select it. We fill in the text_entry that is given to us.
472 * We display the window label specified in our args.
475 file_selection_browse(GtkWidget *file_bt, GtkWidget *file_te, const char *label, file_selection_action_t action)
477 GtkWidget *caller = gtk_widget_get_toplevel(file_bt);
479 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
483 /* Has a file selection dialog box already been opened for that top-level
485 fs = OBJECT_GET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY);
487 /* Yes. Just re-activate that dialog box. */
488 reactivate_window(fs);
492 fs = file_selection_new(label, action);
494 OBJECT_SET_DATA(fs, PRINT_FILE_TE_KEY, file_te);
496 /* Set the E_FS_CALLER_PTR_KEY for the new dialog to point to our caller. */
497 OBJECT_SET_DATA(fs, E_FS_CALLER_PTR_KEY, caller);
499 /* Set the E_FILE_SEL_DIALOG_PTR_KEY for the caller to point to us */
500 OBJECT_SET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY, fs);
502 /* Call a handler when the file selection box is destroyed, so we can inform
503 our caller, if any, that it's been destroyed. */
504 SIGNAL_CONNECT(fs, "destroy", GTK_SIGNAL_FUNC(file_selection_browse_destroy_cb),
507 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
508 if (gtk_dialog_run(GTK_DIALOG(fs)) == GTK_RESPONSE_ACCEPT)
510 f_name = g_strdup(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fs)));
511 gtk_entry_set_text(GTK_ENTRY(file_te), f_name);
516 SIGNAL_CONNECT(GTK_FILE_SELECTION(fs)->ok_button, "clicked",
517 file_selection_browse_ok_cb, fs);
519 window_set_cancel_button(fs, GTK_FILE_SELECTION(fs)->cancel_button, NULL);
521 SIGNAL_CONNECT(fs, "delete_event", window_delete_event_cb, fs);
529 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 4) || GTK_MAJOR_VERSION < 2
531 file_selection_browse_ok_cb(GtkWidget *w _U_, gpointer data)
534 GtkWidget *win = data;
536 f_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION (data)));
538 /* Perhaps the user specified a directory instead of a file.
539 Check whether they did. */
540 if (test_for_directory(f_name) == EISDIR) {
541 /* It's a directory - set the file selection box to display it. */
542 set_last_open_dir(f_name);
544 file_selection_set_current_folder(data, last_open_dir);
548 gtk_entry_set_text(GTK_ENTRY(OBJECT_GET_DATA(win, PRINT_FILE_TE_KEY)),
550 window_destroy(GTK_WIDGET(win));
557 file_selection_browse_destroy_cb(GtkWidget *win, GtkWidget* parent_te)
561 /* Get the widget that requested that we be popped up.
562 (It should arrange to destroy us if it's destroyed, so
563 that we don't get a pointer to a non-existent window here.) */
564 caller = OBJECT_GET_DATA(win, E_FS_CALLER_PTR_KEY);
566 /* Tell it we no longer exist. */
567 OBJECT_SET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY, NULL);
569 /* Give the focus to the file text entry widget so the user can just press
570 Return to print to the file. */
571 gtk_widget_grab_focus(parent_te);
576 set_last_open_dir(char *dirname)
579 gchar *new_last_open_dir;
582 len = strlen(dirname);
583 if (dirname[len-1] == G_DIR_SEPARATOR) {
584 new_last_open_dir = g_strconcat(dirname, NULL);
587 new_last_open_dir = g_strconcat(dirname,
588 G_DIR_SEPARATOR_S, NULL);
591 if (last_open_dir == NULL ||
592 strcmp(last_open_dir, new_last_open_dir) != 0)
593 updated_last_open_dir = TRUE;
596 new_last_open_dir = NULL;
597 if (last_open_dir != NULL)
598 updated_last_open_dir = TRUE;
602 g_free(last_open_dir);
604 last_open_dir = new_last_open_dir;
608 get_last_open_dir(void)
610 return last_open_dir;
613 /* Set the "activate" signal for a widget to call a routine to
614 activate the "OK" button for a dialog box.
616 XXX - there should be a way to specify that a GtkEntry widget
617 shouldn't itself handle the Return key, but should let it be
618 passed on to the parent, so that you don't have to do this
619 by hand for every GtkEntry widget in a dialog box, but, alas,
620 there isn't. (Does this problem exist for other widgets?
621 I.e., are there any others that seize the Return key? */
623 dlg_set_activate(GtkWidget *widget, GtkWidget *ok_button)
625 SIGNAL_CONNECT(widget, "activate", dlg_activate, ok_button);
629 dlg_activate (GtkWidget *widget _U_, gpointer ok_button)
631 gtk_widget_activate(GTK_WIDGET(ok_button));
634 #if GTK_MAJOR_VERSION < 2
635 /* Sigh. GTK+ appears not to acknowledge that it should be possible
636 to attach mnemonics to anything other than menu items; provide
637 routines to create radio and check buttons with labels that
638 include mnemonics. */
641 GtkAccelGroup *accel_group;
645 dlg_fix_label_callback(GtkWidget *label_widget, gpointer data)
647 fix_label_args_t *args = data;
651 gtk_label_get(GTK_LABEL(label_widget), &label);
652 accel_key = gtk_label_parse_uline(GTK_LABEL(label_widget), label);
653 if (accel_key != GDK_VoidSymbol) {
654 /* Yes, we have a mnemonic. */
655 gtk_widget_add_accelerator(args->button, "clicked", args->accel_group,
656 accel_key, 0, GTK_ACCEL_LOCKED);
657 gtk_widget_add_accelerator(args->button, "clicked", args->accel_group,
658 accel_key, GDK_MOD1_MASK, GTK_ACCEL_LOCKED);
663 dlg_fix_button_label(GtkWidget *button, GtkAccelGroup *accel_group)
665 fix_label_args_t args;
667 args.button = button;
668 args.accel_group = accel_group;
669 gtk_container_foreach(GTK_CONTAINER(button), dlg_fix_label_callback, &args);
673 dlg_radio_button_new_with_label_with_mnemonic(GSList *group,
674 const gchar *label, GtkAccelGroup *accel_group)
676 GtkWidget *radio_button;
678 radio_button = gtk_radio_button_new_with_label (group, label);
679 dlg_fix_button_label(radio_button, accel_group);
684 dlg_check_button_new_with_label_with_mnemonic(const gchar *label,
685 GtkAccelGroup *accel_group)
687 GtkWidget *check_button;
689 check_button = gtk_check_button_new_with_label (label);
690 dlg_fix_button_label(check_button, accel_group);
695 dlg_toggle_button_new_with_label_with_mnemonic(const gchar *label,
696 GtkAccelGroup *accel_group)
698 GtkWidget *toggle_button;
700 toggle_button = gtk_toggle_button_new_with_label (label);
701 dlg_fix_button_label(toggle_button, accel_group);
702 return toggle_button;