bugfix to a bug reported by Ian Schorr:
[obnox/wireshark/wip.git] / gtk / dlg_utils.c
1 /* dlg_utils.c
2  * Utilities to use when constructing dialogs
3  *
4  * $Id: dlg_utils.c,v 1.40 2004/07/12 18:39:03 ulfl Exp $
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 *cancel       = NULL;
104     gchar *close        = NULL;
105     gchar *clear        = NULL;
106     gchar *stop         = NULL;
107     gchar *create_stat  = NULL;
108     gchar *help         = NULL;
109     gchar *print        = NULL;
110     gchar *find         = NULL;
111     gchar *jump         = NULL;
112     gchar *yes          = NULL;
113     gchar *no           = NULL;
114
115
116     va_start(stock_id_list, stock_id_first);
117
118     /* get all buttons needed */
119     while(stock_id != NULL) {
120         if (strcmp(stock_id, GTK_STOCK_OK) == 0) {
121             ok = stock_id;
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) {
125             apply = stock_id;
126         } else if (strcmp(stock_id, GTK_STOCK_SAVE) == 0) {
127             save = stock_id;
128         } else if (strcmp(stock_id, GTK_STOCK_CANCEL) == 0) {
129             cancel = stock_id;
130         } else if (strcmp(stock_id, GTK_STOCK_CLOSE) == 0) {
131             close = stock_id;
132         } else if (strcmp(stock_id, GTK_STOCK_CLEAR) == 0) {
133             clear = stock_id;
134         } else if (strcmp(stock_id, GTK_STOCK_STOP) == 0) {
135             stop = stock_id;
136         } else if (strcmp(stock_id, GTK_STOCK_HELP) == 0) {
137             help = stock_id;
138         } else if (strcmp(stock_id, GTK_STOCK_PRINT) == 0) {
139             print = stock_id;
140         } else if (strcmp(stock_id, GTK_STOCK_FIND) == 0) {
141             find = stock_id;
142         } else if (strcmp(stock_id, GTK_STOCK_JUMP_TO) == 0) {
143             jump = stock_id;
144         } else if (strcmp(stock_id, GTK_STOCK_YES) == 0) {
145             yes = stock_id;
146         } else if (strcmp(stock_id, GTK_STOCK_NO) == 0) {
147             no = stock_id;
148         } else {
149             /* we don't know that button! */
150             g_assert_not_reached();
151         }
152         buttons++;
153         stock_id = va_arg(stock_id_list, gchar *);
154     }
155     va_end(stock_id_list);
156
157     /* we should have at least one button */
158     g_assert(buttons);
159
160
161     hbox = gtk_hbox_new(FALSE, 0);
162     gtk_widget_show(hbox);
163
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);
167
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);
171
172     if (buttons == 1) {
173         /* if only one button, simply put it in the middle (default) */
174         dlg_button_new(hbox, button_hbox, stock_id_first);
175         return hbox;
176     }
177
178     /* do we have a help button? -> special handling for it */
179     if (help) {
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);
185         buttons--;
186     }
187
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);
192
193 #if !WIN32 && GTK_MAJOR_VERSION >= 2
194     /* beware: sequence of buttons are important! */
195
196     /* XXX: this can be implemented more elegant of course, but it works as it should */
197     if (buttons == 2) {
198         if (ok && cancel) {
199             dlg_button_new(hbox, button_hbox, cancel);
200             dlg_button_new(hbox, button_hbox, ok);
201             return hbox;
202         }
203         if (print && cancel) {
204             dlg_button_new(hbox, button_hbox, cancel);
205             dlg_button_new(hbox, button_hbox, print);
206             return hbox;
207         }
208         if (find && cancel) {
209             dlg_button_new(hbox, button_hbox, cancel);
210             dlg_button_new(hbox, button_hbox, find);
211             return hbox;
212         }
213         if (jump && cancel) {
214             dlg_button_new(hbox, button_hbox, cancel);
215             dlg_button_new(hbox, button_hbox, jump);
216             return hbox;
217         }
218         if (save && cancel) {
219             dlg_button_new(hbox, button_hbox, cancel);
220             dlg_button_new(hbox, button_hbox, save);
221             return hbox;
222         }
223         if (ok && clear) {
224             dlg_button_new(hbox, button_hbox, clear);
225             dlg_button_new(hbox, button_hbox, ok);
226             return hbox;
227         }
228         if (save && close) {
229             dlg_button_new(hbox, button_hbox, close);
230             dlg_button_new(hbox, button_hbox, save);
231             return hbox;
232         }
233         if (create_stat && cancel) {
234             dlg_button_new(hbox, button_hbox, cancel);
235             dlg_button_new(hbox, button_hbox, create_stat);
236             return hbox;
237         }
238     }
239     if (buttons == 3) {
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);
244             return hbox;
245         }
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);
250             return hbox;
251         }
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);
256             return hbox;
257         }
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);
262             return hbox;
263         }
264     }
265     if (buttons == 4) {
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);
271             return hbox;
272         }
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);
278             return hbox;
279         }
280     }
281 #endif
282
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);
297
298     /* GTK2: we don't know that button combination, add it to the above list! */
299     /* g_assert_not_reached(); */
300     return hbox;
301 }
302
303
304 /* this is called, when a dialog was closed */
305 static void dlg_destroy_cb(GtkWidget *dialog _U_, gpointer data _U_)
306 {
307 #if GTK_MAJOR_VERSION >= 2
308     if(top_level) {
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));
312     }
313 #endif
314 }
315
316
317 /* Create a dialog box window that belongs to Ethereal's main window. */
318 GtkWidget *
319 dlg_window_new(const gchar *title)
320 {
321   GtkWidget *win;
322
323 #if GTK_MAJOR_VERSION < 2
324   win = window_new(GTK_WINDOW_DIALOG, title);
325 #else
326   win = window_new(GTK_WINDOW_TOPLEVEL, title);
327 #endif
328
329   /*
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
332    * punt here.
333    *
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.
342    */
343   if (top_level) {
344     gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
345   }
346
347   SIGNAL_CONNECT(win, "destroy", dlg_destroy_cb, NULL);
348
349   return win;
350 }
351
352
353 /* Create a file selection dialog box window that belongs to Ethereal's
354    main window. */
355 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
356 GtkWidget *
357 file_selection_new(const gchar *title, file_selection_action_t action)
358 {
359   GtkWidget *win;
360   GtkFileChooserAction gtk_action;
361   const gchar *ok_button_text;
362
363   switch (action) {
364
365   case FILE_SELECTION_OPEN:
366     gtk_action = GTK_FILE_CHOOSER_ACTION_OPEN;
367     ok_button_text = GTK_STOCK_OPEN;
368     break;
369
370   case FILE_SELECTION_SAVE:
371     gtk_action = GTK_FILE_CHOOSER_ACTION_SAVE;
372     ok_button_text = GTK_STOCK_SAVE;
373     break;
374
375   default:
376     g_assert_not_reached();
377     gtk_action = -1;
378     ok_button_text = NULL;
379     break;
380   }
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,
384                                     NULL);
385
386   /* If we've opened a file before, start out by showing the files in the directory
387      in which that file resided. */
388   if (last_open_dir)
389     file_selection_set_current_folder(win, last_open_dir);
390
391   return win;
392 }
393 #else
394 GtkWidget *
395 file_selection_new(const gchar *title, file_selection_action_t action _U_)
396 {
397   GtkWidget *win;
398
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);
402 #endif
403   gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
404
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), "");
409
410   /* If we've opened a file before, start out by showing the files in the directory
411      in which that file resided. */
412   if (last_open_dir)
413     file_selection_set_current_folder(win, last_open_dir);
414
415   return win;
416 }
417 #endif
418
419 /* Set the current folder for a file selection dialog. */
420 gboolean
421 file_selection_set_current_folder(GtkWidget *fs, const gchar *filename)
422 {
423 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
424     gboolean ret;
425     int filename_len = strlen(filename);
426     gchar *new_filename;
427
428     /* trim filename, so gtk_file_chooser_set_current_folder() likes it, see below */
429     if (filename[filename_len -1] == G_DIR_SEPARATOR 
430 #ifdef WIN32
431         && filename_len > 3)    /* e.g. "D:\" */
432 #else
433         && filename_len > 1)    /* e.g. "/" */
434 #endif
435     {
436         new_filename = g_strdup(filename);
437             new_filename[filename_len-1] = '\0';
438     } else {
439         new_filename = g_strdup(filename);
440     }
441
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);
446     return ret;
447 #else
448     gtk_file_selection_set_filename(GTK_FILE_SELECTION(fs), filename);
449     return TRUE;
450 #endif
451 }
452
453 /* Set the "extra" widget for a file selection dialog, with user-supplied
454    options. */
455 void
456 file_selection_set_extra_widget(GtkWidget *fs, GtkWidget *extra)
457 {
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);
460 #else
461   gtk_box_pack_start(GTK_BOX(GTK_FILE_SELECTION(fs)->action_area), extra,
462                      FALSE, FALSE, 0);
463 #endif
464 }
465
466
467 /*
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. 
471  *
472  * We display the window label specified in our args.
473  */
474 void
475 file_selection_browse(GtkWidget *file_bt, GtkWidget *file_te, const char *label, file_selection_action_t action)
476 {
477   GtkWidget *caller = gtk_widget_get_toplevel(file_bt);
478   GtkWidget *fs;
479 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
480   gchar     *f_name;
481 #endif
482
483   /* Has a file selection dialog box already been opened for that top-level
484      widget? */
485   fs = OBJECT_GET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY);
486   if (fs != NULL) {
487     /* Yes.  Just re-activate that dialog box. */
488     reactivate_window(fs);
489     return;
490   }
491
492   fs = file_selection_new(label, action);
493
494   OBJECT_SET_DATA(fs, PRINT_FILE_TE_KEY, file_te);
495
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);
498
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);
501
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), 
505                  file_te);
506
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)
509   {
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);
512       g_free(f_name);
513   }
514   window_destroy(fs);
515 #else
516   SIGNAL_CONNECT(GTK_FILE_SELECTION(fs)->ok_button, "clicked", 
517                  file_selection_browse_ok_cb, fs);
518
519   window_set_cancel_button(fs, GTK_FILE_SELECTION(fs)->cancel_button, NULL);
520
521   SIGNAL_CONNECT(fs, "delete_event", window_delete_event_cb, fs);
522
523   gtk_widget_show(fs);
524   window_present(fs);
525 #endif
526 }
527
528
529 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 4) || GTK_MAJOR_VERSION < 2
530 static void
531 file_selection_browse_ok_cb(GtkWidget *w _U_, gpointer data)
532 {
533   gchar     *f_name;
534   GtkWidget *win = data;
535
536   f_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION (data)));
537
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);
543         g_free(f_name);
544         file_selection_set_current_folder(data, last_open_dir);
545         return;
546   }
547
548   gtk_entry_set_text(GTK_ENTRY(OBJECT_GET_DATA(win, PRINT_FILE_TE_KEY)),
549                      f_name);
550   window_destroy(GTK_WIDGET(win));
551
552   g_free(f_name);
553 }
554 #endif
555
556 static void
557 file_selection_browse_destroy_cb(GtkWidget *win, GtkWidget* parent_te)
558 {
559   GtkWidget *caller;
560
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);
565
566   /* Tell it we no longer exist. */
567   OBJECT_SET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY, NULL);
568
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);
572 }
573
574
575 void
576 set_last_open_dir(char *dirname)
577 {
578         int len;
579         gchar *new_last_open_dir;
580
581         if (dirname) {
582                 len = strlen(dirname);
583                 if (dirname[len-1] == G_DIR_SEPARATOR) {
584                         new_last_open_dir = g_strconcat(dirname, NULL);
585                 }
586                 else {
587                         new_last_open_dir = g_strconcat(dirname,
588                                 G_DIR_SEPARATOR_S, NULL);
589                 }
590
591                 if (last_open_dir == NULL ||
592                     strcmp(last_open_dir, new_last_open_dir) != 0)
593                         updated_last_open_dir = TRUE;
594         }
595         else {
596                 new_last_open_dir = NULL;
597                 if (last_open_dir != NULL)
598                         updated_last_open_dir = TRUE;
599         }
600
601         if (last_open_dir) {
602                 g_free(last_open_dir);
603         }
604         last_open_dir = new_last_open_dir;
605 }
606
607 char *
608 get_last_open_dir(void)
609 {
610     return last_open_dir;
611 }
612
613 /* Set the "activate" signal for a widget to call a routine to
614    activate the "OK" button for a dialog box.
615
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? */
622 void
623 dlg_set_activate(GtkWidget *widget, GtkWidget *ok_button)
624 {
625   SIGNAL_CONNECT(widget, "activate", dlg_activate, ok_button);
626 }
627
628 static void
629 dlg_activate (GtkWidget *widget _U_, gpointer ok_button)
630 {
631   gtk_widget_activate(GTK_WIDGET(ok_button));
632 }
633
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.  */
639 typedef struct {
640         GtkWidget *button;
641         GtkAccelGroup *accel_group;
642 } fix_label_args_t;
643
644 static void
645 dlg_fix_label_callback(GtkWidget *label_widget, gpointer data)
646 {
647   fix_label_args_t *args = data;
648   gchar *label;
649   guint accel_key;
650
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);
659   }
660 }
661
662 static void
663 dlg_fix_button_label(GtkWidget *button, GtkAccelGroup *accel_group)
664 {
665   fix_label_args_t args;
666
667   args.button = button;
668   args.accel_group = accel_group;
669   gtk_container_foreach(GTK_CONTAINER(button), dlg_fix_label_callback, &args);
670 }
671
672 GtkWidget *
673 dlg_radio_button_new_with_label_with_mnemonic(GSList *group,
674                 const gchar *label, GtkAccelGroup *accel_group)
675 {
676   GtkWidget *radio_button;
677
678   radio_button = gtk_radio_button_new_with_label (group, label);
679   dlg_fix_button_label(radio_button, accel_group);
680   return radio_button;
681 }
682
683 GtkWidget *
684 dlg_check_button_new_with_label_with_mnemonic(const gchar *label,
685                         GtkAccelGroup *accel_group)
686 {
687   GtkWidget *check_button;
688
689   check_button = gtk_check_button_new_with_label (label);
690   dlg_fix_button_label(check_button, accel_group);
691   return check_button;
692 }
693
694 GtkWidget *
695 dlg_toggle_button_new_with_label_with_mnemonic(const gchar *label,
696                         GtkAccelGroup *accel_group)
697 {
698   GtkWidget *toggle_button;
699
700   toggle_button = gtk_toggle_button_new_with_label (label);
701   dlg_fix_button_label(toggle_button, accel_group);
702   return toggle_button;
703 }
704 #endif