For file browsing dialog boxes, the affirmative button should be "OK",
[metze/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 "ui_util.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, 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 layouted. 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(gchar *stock_id_first, ...)
91 {
92     gint        buttons = 0;
93     va_list     stock_id_list;
94     gchar       *stock_id = stock_id_first;
95     GtkWidget   *hbox;
96     GtkWidget   *button_hbox;
97     GtkWidget   *help_hbox;
98     GtkWidget   *button;
99
100     gchar *ok           = NULL;
101     gchar *apply        = NULL;
102     gchar *save         = NULL;
103     gchar *dont_save    = NULL;
104     gchar *cancel       = NULL;
105     gchar *close        = NULL;
106     gchar *clear        = NULL;
107     gchar *stop         = NULL;
108     gchar *create_stat  = NULL;
109     gchar *help         = NULL;
110     gchar *print        = NULL;
111     gchar *find         = NULL;
112     gchar *jump         = NULL;
113     gchar *yes          = NULL;
114     gchar *no           = NULL;
115
116
117     va_start(stock_id_list, stock_id_first);
118
119     /* get all buttons needed */
120     while(stock_id != NULL) {
121         if (strcmp(stock_id, GTK_STOCK_OK) == 0) {
122             ok = stock_id;
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) {
126             apply = stock_id;
127         } else if (strcmp(stock_id, GTK_STOCK_SAVE) == 0) {
128             save = stock_id;
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) {
132             cancel = stock_id;
133         } else if (strcmp(stock_id, GTK_STOCK_CLOSE) == 0) {
134             close = stock_id;
135         } else if (strcmp(stock_id, GTK_STOCK_CLEAR) == 0) {
136             clear = stock_id;
137         } else if (strcmp(stock_id, GTK_STOCK_STOP) == 0) {
138             stop = stock_id;
139         } else if (strcmp(stock_id, GTK_STOCK_HELP) == 0) {
140             help = stock_id;
141         } else if (strcmp(stock_id, GTK_STOCK_PRINT) == 0) {
142             print = stock_id;
143         } else if (strcmp(stock_id, GTK_STOCK_FIND) == 0) {
144             find = stock_id;
145         } else if (strcmp(stock_id, GTK_STOCK_JUMP_TO) == 0) {
146             jump = stock_id;
147         } else if (strcmp(stock_id, GTK_STOCK_YES) == 0) {
148             yes = stock_id;
149         } else if (strcmp(stock_id, GTK_STOCK_NO) == 0) {
150             no = stock_id;
151         } else {
152             /* we don't know that button! */
153             g_assert_not_reached();
154         }
155         buttons++;
156         stock_id = va_arg(stock_id_list, gchar *);
157     }
158     va_end(stock_id_list);
159
160     /* we should have at least one button */
161     g_assert(buttons);
162
163
164     hbox = gtk_hbox_new(FALSE, 0);
165     gtk_widget_show(hbox);
166
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);
170
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);
174
175     if (buttons == 1) {
176         /* if only one button, simply put it in the middle (default) */
177         dlg_button_new(hbox, button_hbox, stock_id_first);
178         return hbox;
179     }
180
181     /* do we have a help button? -> special handling for it */
182     if (help) {
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);
188         buttons--;
189     }
190
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);
195
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! */
199
200     /* XXX: this can be implemented more elegant of course, but it works as it should */
201     if (buttons == 2) {
202         if (ok && cancel) {
203             dlg_button_new(hbox, button_hbox, cancel);
204             dlg_button_new(hbox, button_hbox, ok);
205             return hbox;
206         }
207         if (print && cancel) {
208             dlg_button_new(hbox, button_hbox, cancel);
209             dlg_button_new(hbox, button_hbox, print);
210             return hbox;
211         }
212         if (find && cancel) {
213             dlg_button_new(hbox, button_hbox, cancel);
214             dlg_button_new(hbox, button_hbox, find);
215             return hbox;
216         }
217         if (jump && cancel) {
218             dlg_button_new(hbox, button_hbox, cancel);
219             dlg_button_new(hbox, button_hbox, jump);
220             return hbox;
221         }
222         if (save && cancel) {
223             dlg_button_new(hbox, button_hbox, cancel);
224             dlg_button_new(hbox, button_hbox, save);
225             return hbox;
226         }
227         if (ok && clear) {
228             dlg_button_new(hbox, button_hbox, clear);
229             dlg_button_new(hbox, button_hbox, ok);
230             return hbox;
231         }
232         if (save && close) {
233             dlg_button_new(hbox, button_hbox, close);
234             dlg_button_new(hbox, button_hbox, save);
235             return hbox;
236         }
237         if (create_stat && cancel) {
238             dlg_button_new(hbox, button_hbox, cancel);
239             dlg_button_new(hbox, button_hbox, create_stat);
240             return hbox;
241         }
242     }
243     if (buttons == 3) {
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);
248             return hbox;
249         }
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);
254             return hbox;
255         }
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);
260             return hbox;
261         }
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);
266             return hbox;
267         }
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);
272                 return hbox;
273         }
274     }
275     if (buttons == 4) {
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);
281             return hbox;
282         }
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);
288             return hbox;
289         }
290     }
291 #endif
292
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);
308
309     /* GTK2: we don't know that button combination, add it to the above list! */
310     /* g_assert_not_reached(); */
311     return hbox;
312 }
313
314
315 /* this is called, when a dialog was closed */
316 static void dlg_destroy_cb(GtkWidget *dialog _U_, gpointer data _U_)
317 {
318 #if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 4
319     if(top_level) {
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));
323     }
324 #endif
325 }
326
327
328 /* Create a dialog box window that belongs to Ethereal's main window. */
329 GtkWidget *
330 dlg_window_new(const gchar *title)
331 {
332   GtkWidget *win;
333
334 #if GTK_MAJOR_VERSION < 2
335   win = window_new(GTK_WINDOW_DIALOG, title);
336 #else
337   win = window_new(GTK_WINDOW_TOPLEVEL, title);
338 #endif
339
340   /*
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
343    * punt here.
344    *
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.
353    */
354   if (top_level) {
355     gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
356   }
357
358   SIGNAL_CONNECT(win, "destroy", dlg_destroy_cb, NULL);
359
360   return win;
361 }
362
363
364 /* Create a file selection dialog box window that belongs to Ethereal's
365    main window. */
366 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
367 GtkWidget *
368 file_selection_new(const gchar *title, file_selection_action_t action)
369 {
370   GtkWidget *win;
371   GtkFileChooserAction gtk_action;
372   const gchar *ok_button_text;
373
374   switch (action) {
375
376   case FILE_SELECTION_OPEN:
377     gtk_action = GTK_FILE_CHOOSER_ACTION_OPEN;
378     ok_button_text = GTK_STOCK_OPEN;
379     break;
380
381   case FILE_SELECTION_READ_BROWSE:
382     gtk_action = GTK_FILE_CHOOSER_ACTION_OPEN;
383     ok_button_text = GTK_STOCK_OK;
384     break;
385
386   case FILE_SELECTION_SAVE:
387     gtk_action = GTK_FILE_CHOOSER_ACTION_SAVE;
388     ok_button_text = GTK_STOCK_SAVE;
389     break;
390
391   case FILE_SELECTION_WRITE_BROWSE:
392     gtk_action = GTK_FILE_CHOOSER_ACTION_SAVE;
393     ok_button_text = GTK_STOCK_OK;
394     break;
395
396   default:
397     g_assert_not_reached();
398     gtk_action = -1;
399     ok_button_text = NULL;
400     break;
401   }
402   win = gtk_file_chooser_dialog_new(title, GTK_WINDOW(top_level), gtk_action,
403 #ifndef _WIN32
404                                     GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
405                                     ok_button_text, GTK_RESPONSE_ACCEPT,
406 #else
407                                     ok_button_text, GTK_RESPONSE_ACCEPT,
408                                     GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
409 #endif
410                                     NULL);
411
412   /* If we've opened a file before, start out by showing the files in the directory
413      in which that file resided. */
414   if (last_open_dir)
415     file_selection_set_current_folder(win, last_open_dir);
416
417   return win;
418 }
419 #else
420 GtkWidget *
421 file_selection_new(const gchar *title, file_selection_action_t action _U_)
422 {
423   GtkWidget *win;
424
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);
428 #endif
429   gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
430
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), "");
435
436   /* If we've opened a file before, start out by showing the files in the directory
437      in which that file resided. */
438   if (last_open_dir)
439     file_selection_set_current_folder(win, last_open_dir);
440
441   return win;
442 }
443 #endif
444
445 /* Set the current folder for a file selection dialog. */
446 gboolean
447 file_selection_set_current_folder(GtkWidget *fs, const gchar *filename)
448 {
449 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
450     gboolean ret;
451     int filename_len = strlen(filename);
452     gchar *new_filename;
453
454     /* trim filename, so gtk_file_chooser_set_current_folder() likes it, see below */
455     if (filename[filename_len -1] == G_DIR_SEPARATOR 
456 #ifdef _WIN32
457         && filename_len > 3)    /* e.g. "D:\" */
458 #else
459         && filename_len > 1)    /* e.g. "/" */
460 #endif
461     {
462         new_filename = g_strdup(filename);
463         new_filename[filename_len-1] = '\0';
464     } else {
465         new_filename = g_strdup(filename);
466     }
467
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);
472     return ret;
473 #else
474     gtk_file_selection_set_filename(GTK_FILE_SELECTION(fs), filename);
475     return TRUE;
476 #endif
477 }
478
479 /* Set the "extra" widget for a file selection dialog, with user-supplied
480    options. */
481 void
482 file_selection_set_extra_widget(GtkWidget *fs, GtkWidget *extra)
483 {
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);
486 #else
487   gtk_box_pack_start(GTK_BOX(GTK_FILE_SELECTION(fs)->action_area), extra,
488                      FALSE, FALSE, 0);
489 #endif
490 }
491
492
493 /*
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. 
497  *
498  * We display the window label specified in our args.
499  */
500 void
501 file_selection_browse(GtkWidget *file_bt, GtkWidget *file_te, const char *label, file_selection_action_t action)
502 {
503   GtkWidget *caller = gtk_widget_get_toplevel(file_bt);
504   GtkWidget *fs;
505 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
506   gchar     *f_name;
507 #endif
508
509   /* Has a file selection dialog box already been opened for that top-level
510      widget? */
511   fs = OBJECT_GET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY);
512   if (fs != NULL) {
513     /* Yes.  Just re-activate that dialog box. */
514     reactivate_window(fs);
515     return;
516   }
517
518   fs = file_selection_new(label, action);
519
520   OBJECT_SET_DATA(fs, PRINT_FILE_TE_KEY, file_te);
521
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);
524
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);
527
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), 
531                  file_te);
532
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)
535   {
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);
538       g_free(f_name);
539   }
540   window_destroy(fs);
541 #else
542   SIGNAL_CONNECT(GTK_FILE_SELECTION(fs)->ok_button, "clicked", 
543                  file_selection_browse_ok_cb, fs);
544
545   window_set_cancel_button(fs, GTK_FILE_SELECTION(fs)->cancel_button, NULL);
546
547   SIGNAL_CONNECT(fs, "delete_event", window_delete_event_cb, fs);
548
549   gtk_widget_show(fs);
550   window_present(fs);
551 #endif
552 }
553
554
555 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 4) || GTK_MAJOR_VERSION < 2
556 static void
557 file_selection_browse_ok_cb(GtkWidget *w _U_, gpointer data)
558 {
559   gchar     *f_name;
560   GtkWidget *win = data;
561
562   f_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION (data)));
563
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);
569         g_free(f_name);
570         file_selection_set_current_folder(data, last_open_dir);
571         return;
572   }
573
574   gtk_entry_set_text(GTK_ENTRY(OBJECT_GET_DATA(win, PRINT_FILE_TE_KEY)),
575                      f_name);
576   window_destroy(GTK_WIDGET(win));
577
578   g_free(f_name);
579 }
580 #endif
581
582 static void
583 file_selection_browse_destroy_cb(GtkWidget *win, GtkWidget* parent_te)
584 {
585   GtkWidget *caller;
586
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);
591
592   /* Tell it we no longer exist. */
593   OBJECT_SET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY, NULL);
594
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);
598 }
599
600
601 void
602 set_last_open_dir(char *dirname)
603 {
604         int len;
605         gchar *new_last_open_dir;
606
607         if (dirname) {
608                 len = strlen(dirname);
609                 if (dirname[len-1] == G_DIR_SEPARATOR) {
610                         new_last_open_dir = g_strconcat(dirname, NULL);
611                 }
612                 else {
613                         new_last_open_dir = g_strconcat(dirname,
614                                 G_DIR_SEPARATOR_S, NULL);
615                 }
616
617                 if (last_open_dir == NULL ||
618                     strcmp(last_open_dir, new_last_open_dir) != 0)
619                         updated_last_open_dir = TRUE;
620         }
621         else {
622                 new_last_open_dir = NULL;
623                 if (last_open_dir != NULL)
624                         updated_last_open_dir = TRUE;
625         }
626
627         if (last_open_dir) {
628                 g_free(last_open_dir);
629         }
630         last_open_dir = new_last_open_dir;
631 }
632
633 char *
634 get_last_open_dir(void)
635 {
636     return last_open_dir;
637 }
638
639 /* Set the "activate" signal for a widget to call a routine to
640    activate the "OK" button for a dialog box.
641
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? */
648 void
649 dlg_set_activate(GtkWidget *widget, GtkWidget *ok_button)
650 {
651   SIGNAL_CONNECT(widget, "activate", dlg_activate, ok_button);
652 }
653
654 static void
655 dlg_activate (GtkWidget *widget _U_, gpointer ok_button)
656 {
657   gtk_widget_activate(GTK_WIDGET(ok_button));
658 }
659
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.  */
665 typedef struct {
666         GtkWidget *button;
667         GtkAccelGroup *accel_group;
668 } fix_label_args_t;
669
670 static void
671 dlg_fix_label_callback(GtkWidget *label_widget, gpointer data)
672 {
673   fix_label_args_t *args = data;
674   gchar *label;
675   guint accel_key;
676
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);
685   }
686 }
687
688 static void
689 dlg_fix_button_label(GtkWidget *button, GtkAccelGroup *accel_group)
690 {
691   fix_label_args_t args;
692
693   args.button = button;
694   args.accel_group = accel_group;
695   gtk_container_foreach(GTK_CONTAINER(button), dlg_fix_label_callback, &args);
696 }
697
698 GtkWidget *
699 dlg_radio_button_new_with_label_with_mnemonic(GSList *group,
700                 const gchar *label, GtkAccelGroup *accel_group)
701 {
702   GtkWidget *radio_button;
703
704   radio_button = gtk_radio_button_new_with_label (group, label);
705   dlg_fix_button_label(radio_button, accel_group);
706   return radio_button;
707 }
708
709 GtkWidget *
710 dlg_check_button_new_with_label_with_mnemonic(const gchar *label,
711                         GtkAccelGroup *accel_group)
712 {
713   GtkWidget *check_button;
714
715   check_button = gtk_check_button_new_with_label (label);
716   dlg_fix_button_label(check_button, accel_group);
717   return check_button;
718 }
719
720 GtkWidget *
721 dlg_toggle_button_new_with_label_with_mnemonic(const gchar *label,
722                         GtkAccelGroup *accel_group)
723 {
724   GtkWidget *toggle_button;
725
726   toggle_button = gtk_toggle_button_new_with_label (label);
727   dlg_fix_button_label(toggle_button, accel_group);
728   return toggle_button;
729 }
730 #endif