From Alejandro Vaquero.
[obnox/wireshark/wip.git] / gtk / dlg_utils.c
1 /* dlg_utils.c
2  * Utilities to use when constructing dialogs
3  *
4  * $Id$
5  *
6  * Ethereal - Network traffic analyzer
7  * By Gerald Combs <gerald@ethereal.com>
8  * Copyright 1998 Gerald Combs
9  *
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.
14  *
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.
19  *
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.
23  */
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include <gtk/gtk.h>
30 #include <gdk/gdkkeysyms.h>
31
32 #include <epan/filesystem.h>
33
34 #include "globals.h"
35
36 #include "gtkglobals.h"
37 #include "gui_utils.h"
38 #include "dlg_utils.h"
39 #include "keys.h"
40 #include "compat_macros.h"
41 #include "main.h"
42
43 #include <string.h>
44 #include <stdio.h>
45 #include <stdarg.h>
46
47
48 /* Keys ... */
49 #define E_FS_CALLER_PTR_KEY       "fs_caller_ptr"
50
51 static gchar *last_open_dir = NULL;
52 static gboolean updated_last_open_dir = FALSE;
53
54
55 static void
56 dlg_activate (GtkWidget *widget, gpointer ok_button);
57
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);
60 #endif
61 static void file_selection_browse_destroy_cb(GtkWidget *win, GtkWidget* file_te);
62
63
64 /* create a button for the button row (helper for dlg_button_row_new) */
65 static GtkWidget *
66 dlg_button_new(GtkWidget *hbox, GtkWidget *button_hbox, const gchar *stock_id)
67 {
68     GtkWidget *button;
69
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);
75     return button;
76 }
77
78
79 /* create a button row for a dialog */
80
81 /* The purpose of this is, to have one place available, where all button rows 
82  * from all dialogs are laid out. This will:
83  *
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"
88  */
89 GtkWidget *
90 dlg_button_row_new(const gchar *stock_id_first, ...)
91 {
92     gint        buttons = 0;
93     va_list     stock_id_list;
94     const gchar *stock_id = stock_id_first;
95     GtkWidget   *hbox;
96     GtkWidget   *button_hbox;
97     GtkWidget   *help_hbox;
98     GtkWidget   *button;
99
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;
116
117
118     va_start(stock_id_list, stock_id_first);
119
120     /* get all buttons needed */
121     while(stock_id != NULL) {
122         if (strcmp(stock_id, GTK_STOCK_OK) == 0) {
123             ok = stock_id;
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) {
127             apply = stock_id;
128         } else if (strcmp(stock_id, GTK_STOCK_SAVE) == 0) {
129             save = stock_id;
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) {
133             cancel = stock_id;
134         } else if (strcmp(stock_id, GTK_STOCK_CLOSE) == 0) {
135             close = stock_id;
136         } else if (strcmp(stock_id, GTK_STOCK_CLEAR) == 0) {
137             clear = stock_id;
138 #ifdef HAVE_LIBPCAP
139         } else if (strcmp(stock_id, ETHEREAL_STOCK_CAPTURE_START) == 0) {
140             start = stock_id;
141 #endif /* HAVE_LIBPCAP */
142         } else if (strcmp(stock_id, GTK_STOCK_STOP) == 0) {
143             stop = stock_id;
144         } else if (strcmp(stock_id, GTK_STOCK_HELP) == 0) {
145             help = stock_id;
146         } else if (strcmp(stock_id, GTK_STOCK_PRINT) == 0) {
147             print = stock_id;
148         } else if (strcmp(stock_id, GTK_STOCK_FIND) == 0) {
149             find = stock_id;
150         } else if (strcmp(stock_id, GTK_STOCK_JUMP_TO) == 0) {
151             jump = stock_id;
152         } else if (strcmp(stock_id, GTK_STOCK_YES) == 0) {
153             yes = stock_id;
154         } else if (strcmp(stock_id, GTK_STOCK_NO) == 0) {
155             no = stock_id;
156         } else {
157             /* we don't know that button! */
158             g_assert_not_reached();
159         }
160         buttons++;
161         stock_id = va_arg(stock_id_list, gchar *);
162     }
163     va_end(stock_id_list);
164
165     /* we should have at least one button */
166     g_assert(buttons);
167
168
169     hbox = gtk_hbox_new(FALSE, 0);
170     gtk_widget_show(hbox);
171
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);
175
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);
179
180     if (buttons == 1) {
181         /* if only one button, simply put it in the middle (default) */
182         dlg_button_new(hbox, button_hbox, stock_id_first);
183         return hbox;
184     }
185
186     /* do we have a help button? -> special handling for it */
187     if (help) {
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);
193         buttons--;
194     }
195
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);
200
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! */
204
205     /* XXX: this can be implemented more elegant of course, but it works as it should */
206     if (buttons == 2) {
207         if (ok && cancel) {
208             dlg_button_new(hbox, button_hbox, cancel);
209             dlg_button_new(hbox, button_hbox, ok);
210             return hbox;
211         }
212         if (print && cancel) {
213             dlg_button_new(hbox, button_hbox, cancel);
214             dlg_button_new(hbox, button_hbox, print);
215             return hbox;
216         }
217         if (find && cancel) {
218             dlg_button_new(hbox, button_hbox, cancel);
219             dlg_button_new(hbox, button_hbox, find);
220             return hbox;
221         }
222         if (jump && cancel) {
223             dlg_button_new(hbox, button_hbox, cancel);
224             dlg_button_new(hbox, button_hbox, jump);
225             return hbox;
226         }
227         if (save && cancel) {
228             dlg_button_new(hbox, button_hbox, cancel);
229             dlg_button_new(hbox, button_hbox, save);
230             return hbox;
231         }
232         if (ok && clear) {
233             dlg_button_new(hbox, button_hbox, clear);
234             dlg_button_new(hbox, button_hbox, ok);
235             return hbox;
236         }
237         if (save && close) {
238             dlg_button_new(hbox, button_hbox, close);
239             dlg_button_new(hbox, button_hbox, save);
240             return hbox;
241         }
242         if (create_stat && cancel) {
243             dlg_button_new(hbox, button_hbox, cancel);
244             dlg_button_new(hbox, button_hbox, create_stat);
245             return hbox;
246         }
247         if (start && cancel) {
248             dlg_button_new(hbox, button_hbox, cancel);
249             dlg_button_new(hbox, button_hbox, start);
250             return hbox;
251         }
252     }
253     if (buttons == 3) {
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);
258             return hbox;
259         }
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);
264             return hbox;
265         }
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);
270             return hbox;
271         }
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);
276             return hbox;
277         }
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);
282                 return hbox;
283         }
284     }
285     if (buttons == 4) {
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);
291             return hbox;
292         }
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);
298             return hbox;
299         }
300     }
301 #endif
302
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);
319
320     /* GTK2: we don't know that button combination, add it to the above list! */
321     /* g_assert_not_reached(); */
322     return hbox;
323 }
324
325
326 /* this is called, when a dialog was closed */
327 static void dlg_destroy_cb(GtkWidget *dialog _U_, gpointer data _U_)
328 {
329 #if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 4
330     if(top_level) {
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));
334     }
335 #endif
336 }
337
338
339 /* Create a dialog box window that belongs to Ethereal's main window. */
340 GtkWidget *
341 dlg_window_new(const gchar *title)
342 {
343   GtkWidget *win;
344
345 #if GTK_MAJOR_VERSION < 2
346   win = window_new(GTK_WINDOW_DIALOG, title);
347 #else
348   win = window_new(GTK_WINDOW_TOPLEVEL, title);
349 #endif
350
351   /*
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
354    * punt here.
355    *
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.
364    */
365   if (top_level) {
366     gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
367   }
368
369   SIGNAL_CONNECT(win, "destroy", dlg_destroy_cb, NULL);
370
371   return win;
372 }
373
374
375 /* Create a file selection dialog box window that belongs to Ethereal's
376    main window. */
377 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
378 GtkWidget *
379 file_selection_new(const gchar *title, file_selection_action_t action)
380 {
381   GtkWidget *win;
382   GtkFileChooserAction gtk_action;
383   const gchar *ok_button_text;
384
385   switch (action) {
386
387   case FILE_SELECTION_OPEN:
388     gtk_action = GTK_FILE_CHOOSER_ACTION_OPEN;
389     ok_button_text = GTK_STOCK_OPEN;
390     break;
391
392   case FILE_SELECTION_READ_BROWSE:
393     gtk_action = GTK_FILE_CHOOSER_ACTION_OPEN;
394     ok_button_text = GTK_STOCK_OK;
395     break;
396
397   case FILE_SELECTION_SAVE:
398     gtk_action = GTK_FILE_CHOOSER_ACTION_SAVE;
399     ok_button_text = GTK_STOCK_SAVE;
400     break;
401
402   case FILE_SELECTION_WRITE_BROWSE:
403     gtk_action = GTK_FILE_CHOOSER_ACTION_SAVE;
404     ok_button_text = GTK_STOCK_OK;
405     break;
406
407   default:
408     g_assert_not_reached();
409     gtk_action = -1;
410     ok_button_text = NULL;
411     break;
412   }
413   win = gtk_file_chooser_dialog_new(title, GTK_WINDOW(top_level), gtk_action,
414 #ifndef _WIN32
415                                     GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
416                                     ok_button_text, GTK_RESPONSE_ACCEPT,
417 #else
418                                     ok_button_text, GTK_RESPONSE_ACCEPT,
419                                     GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
420 #endif
421                                     NULL);
422
423   /* If we've opened a file before, start out by showing the files in the directory
424      in which that file resided. */
425   if (last_open_dir)
426     file_selection_set_current_folder(win, last_open_dir);
427
428   return win;
429 }
430 #else
431 GtkWidget *
432 file_selection_new(const gchar *title, file_selection_action_t action _U_)
433 {
434   GtkWidget *win;
435
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);
439 #endif
440   gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
441
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), "");
446
447   /* If we've opened a file before, start out by showing the files in the directory
448      in which that file resided. */
449   if (last_open_dir)
450     file_selection_set_current_folder(win, last_open_dir);
451
452   return win;
453 }
454 #endif
455
456 /* Set the current folder for a file selection dialog. */
457 gboolean
458 file_selection_set_current_folder(GtkWidget *fs, const gchar *filename)
459 {
460 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
461     gboolean ret;
462     int filename_len = strlen(filename);
463     gchar *new_filename;
464
465     /* trim filename, so gtk_file_chooser_set_current_folder() likes it, see below */
466     if (filename[filename_len -1] == G_DIR_SEPARATOR 
467 #ifdef _WIN32
468         && filename_len > 3)    /* e.g. "D:\" */
469 #else
470         && filename_len > 1)    /* e.g. "/" */
471 #endif
472     {
473         new_filename = g_strdup(filename);
474         new_filename[filename_len-1] = '\0';
475     } else {
476         new_filename = g_strdup(filename);
477     }
478
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);
483     return ret;
484 #else
485     gtk_file_selection_set_filename(GTK_FILE_SELECTION(fs), filename);
486     return TRUE;
487 #endif
488 }
489
490 /* Set the "extra" widget for a file selection dialog, with user-supplied
491    options. */
492 void
493 file_selection_set_extra_widget(GtkWidget *fs, GtkWidget *extra)
494 {
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);
497 #else
498   gtk_box_pack_start(GTK_BOX(GTK_FILE_SELECTION(fs)->action_area), extra,
499                      FALSE, FALSE, 0);
500 #endif
501 }
502
503
504 /*
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. 
508  *
509  * We display the window label specified in our args.
510  */
511 void
512 file_selection_browse(GtkWidget *file_bt, GtkWidget *file_te, const char *label, file_selection_action_t action)
513 {
514   GtkWidget *caller = gtk_widget_get_toplevel(file_bt);
515   GtkWidget *fs;
516 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
517   gchar     *f_name;
518 #endif
519
520   /* Has a file selection dialog box already been opened for that top-level
521      widget? */
522   fs = OBJECT_GET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY);
523   if (fs != NULL) {
524     /* Yes.  Just re-activate that dialog box. */
525     reactivate_window(fs);
526     return;
527   }
528
529   fs = file_selection_new(label, action);
530
531   OBJECT_SET_DATA(fs, PRINT_FILE_TE_KEY, file_te);
532
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);
535
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);
538
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), 
542                  file_te);
543
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)
546   {
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);
549       g_free(f_name);
550   }
551   window_destroy(fs);
552 #else
553   SIGNAL_CONNECT(GTK_FILE_SELECTION(fs)->ok_button, "clicked", 
554                  file_selection_browse_ok_cb, fs);
555
556   window_set_cancel_button(fs, GTK_FILE_SELECTION(fs)->cancel_button,
557                            window_cancel_button_cb);
558
559   SIGNAL_CONNECT(fs, "delete_event", window_delete_event_cb, fs);
560
561   gtk_widget_show(fs);
562   window_present(fs);
563 #endif
564 }
565
566
567 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 4) || GTK_MAJOR_VERSION < 2
568 static void
569 file_selection_browse_ok_cb(GtkWidget *w _U_, gpointer data)
570 {
571   gchar     *f_name;
572   GtkWidget *win = data;
573
574   f_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION (data)));
575
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);
581         g_free(f_name);
582         file_selection_set_current_folder(data, last_open_dir);
583         return;
584   }
585
586   gtk_entry_set_text(GTK_ENTRY(OBJECT_GET_DATA(win, PRINT_FILE_TE_KEY)),
587                      f_name);
588   window_destroy(GTK_WIDGET(win));
589
590   g_free(f_name);
591 }
592 #endif
593
594 static void
595 file_selection_browse_destroy_cb(GtkWidget *win, GtkWidget* parent_te)
596 {
597   GtkWidget *caller;
598
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);
603
604   /* Tell it we no longer exist. */
605   OBJECT_SET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY, NULL);
606
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);
610 }
611
612
613 void
614 set_last_open_dir(char *dirname)
615 {
616         int len;
617         gchar *new_last_open_dir;
618
619         if (dirname) {
620                 len = strlen(dirname);
621                 if (dirname[len-1] == G_DIR_SEPARATOR) {
622                         new_last_open_dir = g_strconcat(dirname, NULL);
623                 }
624                 else {
625                         new_last_open_dir = g_strconcat(dirname,
626                                 G_DIR_SEPARATOR_S, NULL);
627                 }
628
629                 if (last_open_dir == NULL ||
630                     strcmp(last_open_dir, new_last_open_dir) != 0)
631                         updated_last_open_dir = TRUE;
632         }
633         else {
634                 new_last_open_dir = NULL;
635                 if (last_open_dir != NULL)
636                         updated_last_open_dir = TRUE;
637         }
638
639         if (last_open_dir) {
640                 g_free(last_open_dir);
641         }
642         last_open_dir = new_last_open_dir;
643 }
644
645 char *
646 get_last_open_dir(void)
647 {
648     return last_open_dir;
649 }
650
651 /* Set the "activate" signal for a widget to call a routine to
652    activate the "OK" button for a dialog box.
653
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? */
660 void
661 dlg_set_activate(GtkWidget *widget, GtkWidget *ok_button)
662 {
663   SIGNAL_CONNECT(widget, "activate", dlg_activate, ok_button);
664 }
665
666 static void
667 dlg_activate (GtkWidget *widget _U_, gpointer ok_button)
668 {
669   gtk_widget_activate(GTK_WIDGET(ok_button));
670 }
671
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.  */
677 typedef struct {
678         GtkWidget *button;
679         GtkAccelGroup *accel_group;
680 } fix_label_args_t;
681
682 static void
683 dlg_fix_label_callback(GtkWidget *label_widget, gpointer data)
684 {
685   fix_label_args_t *args = data;
686   gchar *label;
687   guint accel_key;
688
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);
697   }
698 }
699
700 static void
701 dlg_fix_button_label(GtkWidget *button, GtkAccelGroup *accel_group)
702 {
703   fix_label_args_t args;
704
705   args.button = button;
706   args.accel_group = accel_group;
707   gtk_container_foreach(GTK_CONTAINER(button), dlg_fix_label_callback, &args);
708 }
709
710 GtkWidget *
711 dlg_radio_button_new_with_label_with_mnemonic(GSList *group,
712                 const gchar *label, GtkAccelGroup *accel_group)
713 {
714   GtkWidget *radio_button;
715
716   radio_button = gtk_radio_button_new_with_label (group, label);
717   dlg_fix_button_label(radio_button, accel_group);
718   return radio_button;
719 }
720
721 GtkWidget *
722 dlg_check_button_new_with_label_with_mnemonic(const gchar *label,
723                         GtkAccelGroup *accel_group)
724 {
725   GtkWidget *check_button;
726
727   check_button = gtk_check_button_new_with_label (label);
728   dlg_fix_button_label(check_button, accel_group);
729   return check_button;
730 }
731
732 GtkWidget *
733 dlg_toggle_button_new_with_label_with_mnemonic(const gchar *label,
734                         GtkAccelGroup *accel_group)
735 {
736   GtkWidget *toggle_button;
737
738   toggle_button = gtk_toggle_button_new_with_label (label);
739   dlg_fix_button_label(toggle_button, accel_group);
740   return toggle_button;
741 }
742 #endif