Use _WIN32 rather than WIN32 to determine if we're compiling on Win32;
[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 "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_SAVE:
382     gtk_action = GTK_FILE_CHOOSER_ACTION_SAVE;
383     ok_button_text = GTK_STOCK_SAVE;
384     break;
385
386   default:
387     g_assert_not_reached();
388     gtk_action = -1;
389     ok_button_text = NULL;
390     break;
391   }
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,
395                                     NULL);
396
397   /* If we've opened a file before, start out by showing the files in the directory
398      in which that file resided. */
399   if (last_open_dir)
400     file_selection_set_current_folder(win, last_open_dir);
401
402   return win;
403 }
404 #else
405 GtkWidget *
406 file_selection_new(const gchar *title, file_selection_action_t action _U_)
407 {
408   GtkWidget *win;
409
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);
413 #endif
414   gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
415
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), "");
420
421   /* If we've opened a file before, start out by showing the files in the directory
422      in which that file resided. */
423   if (last_open_dir)
424     file_selection_set_current_folder(win, last_open_dir);
425
426   return win;
427 }
428 #endif
429
430 /* Set the current folder for a file selection dialog. */
431 gboolean
432 file_selection_set_current_folder(GtkWidget *fs, const gchar *filename)
433 {
434 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
435     gboolean ret;
436     int filename_len = strlen(filename);
437     gchar *new_filename;
438
439     /* trim filename, so gtk_file_chooser_set_current_folder() likes it, see below */
440     if (filename[filename_len -1] == G_DIR_SEPARATOR 
441 #ifdef _WIN32
442         && filename_len > 3)    /* e.g. "D:\" */
443 #else
444         && filename_len > 1)    /* e.g. "/" */
445 #endif
446     {
447         new_filename = g_strdup(filename);
448         new_filename[filename_len-1] = '\0';
449     } else {
450         new_filename = g_strdup(filename);
451     }
452
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);
457     return ret;
458 #else
459     gtk_file_selection_set_filename(GTK_FILE_SELECTION(fs), filename);
460     return TRUE;
461 #endif
462 }
463
464 /* Set the "extra" widget for a file selection dialog, with user-supplied
465    options. */
466 void
467 file_selection_set_extra_widget(GtkWidget *fs, GtkWidget *extra)
468 {
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);
471 #else
472   gtk_box_pack_start(GTK_BOX(GTK_FILE_SELECTION(fs)->action_area), extra,
473                      FALSE, FALSE, 0);
474 #endif
475 }
476
477
478 /*
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. 
482  *
483  * We display the window label specified in our args.
484  */
485 void
486 file_selection_browse(GtkWidget *file_bt, GtkWidget *file_te, const char *label, file_selection_action_t action)
487 {
488   GtkWidget *caller = gtk_widget_get_toplevel(file_bt);
489   GtkWidget *fs;
490 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
491   gchar     *f_name;
492 #endif
493
494   /* Has a file selection dialog box already been opened for that top-level
495      widget? */
496   fs = OBJECT_GET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY);
497   if (fs != NULL) {
498     /* Yes.  Just re-activate that dialog box. */
499     reactivate_window(fs);
500     return;
501   }
502
503   fs = file_selection_new(label, action);
504
505   OBJECT_SET_DATA(fs, PRINT_FILE_TE_KEY, file_te);
506
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);
509
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);
512
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), 
516                  file_te);
517
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)
520   {
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);
523       g_free(f_name);
524   }
525   window_destroy(fs);
526 #else
527   SIGNAL_CONNECT(GTK_FILE_SELECTION(fs)->ok_button, "clicked", 
528                  file_selection_browse_ok_cb, fs);
529
530   window_set_cancel_button(fs, GTK_FILE_SELECTION(fs)->cancel_button, NULL);
531
532   SIGNAL_CONNECT(fs, "delete_event", window_delete_event_cb, fs);
533
534   gtk_widget_show(fs);
535   window_present(fs);
536 #endif
537 }
538
539
540 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 4) || GTK_MAJOR_VERSION < 2
541 static void
542 file_selection_browse_ok_cb(GtkWidget *w _U_, gpointer data)
543 {
544   gchar     *f_name;
545   GtkWidget *win = data;
546
547   f_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION (data)));
548
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);
554         g_free(f_name);
555         file_selection_set_current_folder(data, last_open_dir);
556         return;
557   }
558
559   gtk_entry_set_text(GTK_ENTRY(OBJECT_GET_DATA(win, PRINT_FILE_TE_KEY)),
560                      f_name);
561   window_destroy(GTK_WIDGET(win));
562
563   g_free(f_name);
564 }
565 #endif
566
567 static void
568 file_selection_browse_destroy_cb(GtkWidget *win, GtkWidget* parent_te)
569 {
570   GtkWidget *caller;
571
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);
576
577   /* Tell it we no longer exist. */
578   OBJECT_SET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY, NULL);
579
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);
583 }
584
585
586 void
587 set_last_open_dir(char *dirname)
588 {
589         int len;
590         gchar *new_last_open_dir;
591
592         if (dirname) {
593                 len = strlen(dirname);
594                 if (dirname[len-1] == G_DIR_SEPARATOR) {
595                         new_last_open_dir = g_strconcat(dirname, NULL);
596                 }
597                 else {
598                         new_last_open_dir = g_strconcat(dirname,
599                                 G_DIR_SEPARATOR_S, NULL);
600                 }
601
602                 if (last_open_dir == NULL ||
603                     strcmp(last_open_dir, new_last_open_dir) != 0)
604                         updated_last_open_dir = TRUE;
605         }
606         else {
607                 new_last_open_dir = NULL;
608                 if (last_open_dir != NULL)
609                         updated_last_open_dir = TRUE;
610         }
611
612         if (last_open_dir) {
613                 g_free(last_open_dir);
614         }
615         last_open_dir = new_last_open_dir;
616 }
617
618 char *
619 get_last_open_dir(void)
620 {
621     return last_open_dir;
622 }
623
624 /* Set the "activate" signal for a widget to call a routine to
625    activate the "OK" button for a dialog box.
626
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? */
633 void
634 dlg_set_activate(GtkWidget *widget, GtkWidget *ok_button)
635 {
636   SIGNAL_CONNECT(widget, "activate", dlg_activate, ok_button);
637 }
638
639 static void
640 dlg_activate (GtkWidget *widget _U_, gpointer ok_button)
641 {
642   gtk_widget_activate(GTK_WIDGET(ok_button));
643 }
644
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.  */
650 typedef struct {
651         GtkWidget *button;
652         GtkAccelGroup *accel_group;
653 } fix_label_args_t;
654
655 static void
656 dlg_fix_label_callback(GtkWidget *label_widget, gpointer data)
657 {
658   fix_label_args_t *args = data;
659   gchar *label;
660   guint accel_key;
661
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);
670   }
671 }
672
673 static void
674 dlg_fix_button_label(GtkWidget *button, GtkAccelGroup *accel_group)
675 {
676   fix_label_args_t args;
677
678   args.button = button;
679   args.accel_group = accel_group;
680   gtk_container_foreach(GTK_CONTAINER(button), dlg_fix_label_callback, &args);
681 }
682
683 GtkWidget *
684 dlg_radio_button_new_with_label_with_mnemonic(GSList *group,
685                 const gchar *label, GtkAccelGroup *accel_group)
686 {
687   GtkWidget *radio_button;
688
689   radio_button = gtk_radio_button_new_with_label (group, label);
690   dlg_fix_button_label(radio_button, accel_group);
691   return radio_button;
692 }
693
694 GtkWidget *
695 dlg_check_button_new_with_label_with_mnemonic(const gchar *label,
696                         GtkAccelGroup *accel_group)
697 {
698   GtkWidget *check_button;
699
700   check_button = gtk_check_button_new_with_label (label);
701   dlg_fix_button_label(check_button, accel_group);
702   return check_button;
703 }
704
705 GtkWidget *
706 dlg_toggle_button_new_with_label_with_mnemonic(const gchar *label,
707                         GtkAccelGroup *accel_group)
708 {
709   GtkWidget *toggle_button;
710
711   toggle_button = gtk_toggle_button_new_with_label (label);
712   dlg_fix_button_label(toggle_button, accel_group);
713   return toggle_button;
714 }
715 #endif