Move the stats.[ch] stuff into epan, so plugins can use it.
[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, 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 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(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         } else if (strcmp(stock_id, ETHEREAL_STOCK_CAPTURE_START) == 0) {
139             start = stock_id;
140         } else if (strcmp(stock_id, GTK_STOCK_STOP) == 0) {
141             stop = stock_id;
142         } else if (strcmp(stock_id, GTK_STOCK_HELP) == 0) {
143             help = stock_id;
144         } else if (strcmp(stock_id, GTK_STOCK_PRINT) == 0) {
145             print = stock_id;
146         } else if (strcmp(stock_id, GTK_STOCK_FIND) == 0) {
147             find = stock_id;
148         } else if (strcmp(stock_id, GTK_STOCK_JUMP_TO) == 0) {
149             jump = stock_id;
150         } else if (strcmp(stock_id, GTK_STOCK_YES) == 0) {
151             yes = stock_id;
152         } else if (strcmp(stock_id, GTK_STOCK_NO) == 0) {
153             no = stock_id;
154         } else {
155             /* we don't know that button! */
156             g_assert_not_reached();
157         }
158         buttons++;
159         stock_id = va_arg(stock_id_list, gchar *);
160     }
161     va_end(stock_id_list);
162
163     /* we should have at least one button */
164     g_assert(buttons);
165
166
167     hbox = gtk_hbox_new(FALSE, 0);
168     gtk_widget_show(hbox);
169
170     button_hbox = gtk_hbutton_box_new();
171     gtk_box_pack_end(GTK_BOX(hbox), button_hbox, TRUE, TRUE, 0);
172     gtk_widget_show(button_hbox);
173
174     help_hbox = gtk_hbutton_box_new();
175     gtk_box_pack_end(GTK_BOX(hbox), help_hbox, FALSE, FALSE, 0);
176     gtk_widget_show(help_hbox);
177
178     if (buttons == 1) {
179         /* if only one button, simply put it in the middle (default) */
180         dlg_button_new(hbox, button_hbox, stock_id_first);
181         return hbox;
182     }
183
184     /* do we have a help button? -> special handling for it */
185     if (help) {
186         button = BUTTON_NEW_FROM_STOCK(help);
187         GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
188         OBJECT_SET_DATA(hbox, help, button);
189         gtk_box_pack_start(GTK_BOX(help_hbox), button, FALSE, FALSE, 0);
190         gtk_widget_show(button);
191         buttons--;
192     }
193
194     /* if more than one button, sort buttons from left to right */
195     /* (the whole button cluster will then be right aligned) */
196     gtk_button_box_set_layout (GTK_BUTTON_BOX(button_hbox), GTK_BUTTONBOX_END);
197     gtk_button_box_set_spacing(GTK_BUTTON_BOX(button_hbox), 5);
198
199 /* GTK+ 1.3 and later - on Win32, we use 1.3[.x] or 2.x, not 1.2[.x] */
200 #if !defined(_WIN32) && GTK_MAJOR_VERSION >= 2
201     /* beware: sequence of buttons are important! */
202
203     /* XXX: this can be implemented more elegant of course, but it works as it should */
204     if (buttons == 2) {
205         if (ok && cancel) {
206             dlg_button_new(hbox, button_hbox, cancel);
207             dlg_button_new(hbox, button_hbox, ok);
208             return hbox;
209         }
210         if (print && cancel) {
211             dlg_button_new(hbox, button_hbox, cancel);
212             dlg_button_new(hbox, button_hbox, print);
213             return hbox;
214         }
215         if (find && cancel) {
216             dlg_button_new(hbox, button_hbox, cancel);
217             dlg_button_new(hbox, button_hbox, find);
218             return hbox;
219         }
220         if (jump && cancel) {
221             dlg_button_new(hbox, button_hbox, cancel);
222             dlg_button_new(hbox, button_hbox, jump);
223             return hbox;
224         }
225         if (save && cancel) {
226             dlg_button_new(hbox, button_hbox, cancel);
227             dlg_button_new(hbox, button_hbox, save);
228             return hbox;
229         }
230         if (ok && clear) {
231             dlg_button_new(hbox, button_hbox, clear);
232             dlg_button_new(hbox, button_hbox, ok);
233             return hbox;
234         }
235         if (save && close) {
236             dlg_button_new(hbox, button_hbox, close);
237             dlg_button_new(hbox, button_hbox, save);
238             return hbox;
239         }
240         if (create_stat && cancel) {
241             dlg_button_new(hbox, button_hbox, cancel);
242             dlg_button_new(hbox, button_hbox, create_stat);
243             return hbox;
244         }
245         if (start && cancel) {
246             dlg_button_new(hbox, button_hbox, cancel);
247             dlg_button_new(hbox, button_hbox, start);
248             return hbox;
249         }
250     }
251     if (buttons == 3) {
252         if (ok && 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, ok);
256             return hbox;
257         }
258         if (ok && apply && cancel) {
259             dlg_button_new(hbox, button_hbox, apply);
260             dlg_button_new(hbox, button_hbox, cancel);
261             dlg_button_new(hbox, button_hbox, ok);
262             return hbox;
263         }
264         if (apply && save && close) {
265             dlg_button_new(hbox, button_hbox, save);
266             dlg_button_new(hbox, button_hbox, close);
267             dlg_button_new(hbox, button_hbox, apply);
268             return hbox;
269         }
270         if (yes && no && cancel) {
271             dlg_button_new(hbox, button_hbox, no);
272             dlg_button_new(hbox, button_hbox, cancel);
273             dlg_button_new(hbox, button_hbox, yes);
274             return hbox;
275         }
276         if (save && dont_save && cancel) {
277                 dlg_button_new(hbox, button_hbox, dont_save);
278                 dlg_button_new(hbox, button_hbox, cancel);
279                 dlg_button_new(hbox, button_hbox, save);
280                 return hbox;
281         }
282     }
283     if (buttons == 4) {
284         if (ok && apply && save && cancel) {
285             dlg_button_new(hbox, button_hbox, save);
286             dlg_button_new(hbox, button_hbox, apply);
287             dlg_button_new(hbox, button_hbox, cancel);
288             dlg_button_new(hbox, button_hbox, ok);
289             return hbox;
290         }
291         if (ok && apply && save && close) {
292             dlg_button_new(hbox, button_hbox, save);
293             dlg_button_new(hbox, button_hbox, apply);
294             dlg_button_new(hbox, button_hbox, close);
295             dlg_button_new(hbox, button_hbox, ok);
296             return hbox;
297         }
298     }
299 #endif
300
301     /* beware: sequence of buttons is important! */
302     if (ok      != NULL) dlg_button_new(hbox, button_hbox, ok);
303     if (jump    != NULL) dlg_button_new(hbox, button_hbox, jump);
304     if (find    != NULL) dlg_button_new(hbox, button_hbox, find);
305     if (print   != NULL) dlg_button_new(hbox, button_hbox, print);
306     if (create_stat != NULL) dlg_button_new(hbox, button_hbox, create_stat);
307     if (apply   != NULL) dlg_button_new(hbox, button_hbox, apply);
308     if (yes     != NULL) dlg_button_new(hbox, button_hbox, yes);
309     if (no      != NULL) dlg_button_new(hbox, button_hbox, no);
310     if (save    != NULL) dlg_button_new(hbox, button_hbox, save);
311     if (dont_save != NULL) dlg_button_new(hbox, button_hbox, dont_save);
312     if (start   != NULL) dlg_button_new(hbox, button_hbox, start);
313     if (stop    != NULL) dlg_button_new(hbox, button_hbox, stop);
314     if (close   != NULL) dlg_button_new(hbox, button_hbox, close);
315     if (clear   != NULL) dlg_button_new(hbox, button_hbox, clear);
316     if (cancel  != NULL) dlg_button_new(hbox, button_hbox, cancel);
317
318     /* GTK2: we don't know that button combination, add it to the above list! */
319     /* g_assert_not_reached(); */
320     return hbox;
321 }
322
323
324 /* this is called, when a dialog was closed */
325 static void dlg_destroy_cb(GtkWidget *dialog _U_, gpointer data _U_)
326 {
327 #if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 4
328     if(top_level) {
329         /* bring main window back to front (workaround for a bug in win32 GTK2.x)
330            XXX - do this only on Windows? */
331         gtk_window_present(GTK_WINDOW(top_level));
332     }
333 #endif
334 }
335
336
337 /* Create a dialog box window that belongs to Ethereal's main window. */
338 GtkWidget *
339 dlg_window_new(const gchar *title)
340 {
341   GtkWidget *win;
342
343 #if GTK_MAJOR_VERSION < 2
344   win = window_new(GTK_WINDOW_DIALOG, title);
345 #else
346   win = window_new(GTK_WINDOW_TOPLEVEL, title);
347 #endif
348
349   /*
350    * XXX - if we're running in the capture child process, we can't easily
351    * make this window transient for the main process's window.  We just
352    * punt here.
353    *
354    * Perhaps the child process should only capture packets, write them to
355    * a file, and somehow notify the parent process and let *it* do all
356    * the GUI work.  If we can do that efficiently (so that we don't drop
357    * more packets), perhaps we can also do so even when we're *not* doing
358    * an "Update list of packets in real time" capture.  That'd let the
359    * child process run set-UID on platforms where you need that in order
360    * to capture, and might also simplify the job of having the GUI main
361    * loop wait both for user input and packet arrival.
362    */
363   if (top_level) {
364     gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
365   }
366
367   SIGNAL_CONNECT(win, "destroy", dlg_destroy_cb, NULL);
368
369   return win;
370 }
371
372
373 /* Create a file selection dialog box window that belongs to Ethereal's
374    main window. */
375 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
376 GtkWidget *
377 file_selection_new(const gchar *title, file_selection_action_t action)
378 {
379   GtkWidget *win;
380   GtkFileChooserAction gtk_action;
381   const gchar *ok_button_text;
382
383   switch (action) {
384
385   case FILE_SELECTION_OPEN:
386     gtk_action = GTK_FILE_CHOOSER_ACTION_OPEN;
387     ok_button_text = GTK_STOCK_OPEN;
388     break;
389
390   case FILE_SELECTION_READ_BROWSE:
391     gtk_action = GTK_FILE_CHOOSER_ACTION_OPEN;
392     ok_button_text = GTK_STOCK_OK;
393     break;
394
395   case FILE_SELECTION_SAVE:
396     gtk_action = GTK_FILE_CHOOSER_ACTION_SAVE;
397     ok_button_text = GTK_STOCK_SAVE;
398     break;
399
400   case FILE_SELECTION_WRITE_BROWSE:
401     gtk_action = GTK_FILE_CHOOSER_ACTION_SAVE;
402     ok_button_text = GTK_STOCK_OK;
403     break;
404
405   default:
406     g_assert_not_reached();
407     gtk_action = -1;
408     ok_button_text = NULL;
409     break;
410   }
411   win = gtk_file_chooser_dialog_new(title, GTK_WINDOW(top_level), gtk_action,
412 #ifndef _WIN32
413                                     GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
414                                     ok_button_text, GTK_RESPONSE_ACCEPT,
415 #else
416                                     ok_button_text, GTK_RESPONSE_ACCEPT,
417                                     GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
418 #endif
419                                     NULL);
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 #else
429 GtkWidget *
430 file_selection_new(const gchar *title, file_selection_action_t action _U_)
431 {
432   GtkWidget *win;
433
434   win = gtk_file_selection_new(title);
435 #if GTK_MAJOR_VERSION >= 2
436   gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER_ON_PARENT);
437 #endif
438   gtk_window_set_transient_for(GTK_WINDOW(win), GTK_WINDOW(top_level));
439
440   /* XXX - why are we doing this?  We don't do it with the GtkFileChooser,
441      as it complains that the file name isn't being set to an absolute
442      path; does this provoke a similar complaint? */
443   gtk_file_selection_set_filename(GTK_FILE_SELECTION(win), "");
444
445   /* If we've opened a file before, start out by showing the files in the directory
446      in which that file resided. */
447   if (last_open_dir)
448     file_selection_set_current_folder(win, last_open_dir);
449
450   return win;
451 }
452 #endif
453
454 /* Set the current folder for a file selection dialog. */
455 gboolean
456 file_selection_set_current_folder(GtkWidget *fs, const gchar *filename)
457 {
458 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
459     gboolean ret;
460     int filename_len = strlen(filename);
461     gchar *new_filename;
462
463     /* trim filename, so gtk_file_chooser_set_current_folder() likes it, see below */
464     if (filename[filename_len -1] == G_DIR_SEPARATOR 
465 #ifdef _WIN32
466         && filename_len > 3)    /* e.g. "D:\" */
467 #else
468         && filename_len > 1)    /* e.g. "/" */
469 #endif
470     {
471         new_filename = g_strdup(filename);
472         new_filename[filename_len-1] = '\0';
473     } else {
474         new_filename = g_strdup(filename);
475     }
476
477     /* this function is very pedantic about it's filename parameter */
478     /* no trailing '\' allowed, unless a win32 root dir "D:\" */
479     ret = gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fs), new_filename);
480     g_free(new_filename);
481     return ret;
482 #else
483     gtk_file_selection_set_filename(GTK_FILE_SELECTION(fs), filename);
484     return TRUE;
485 #endif
486 }
487
488 /* Set the "extra" widget for a file selection dialog, with user-supplied
489    options. */
490 void
491 file_selection_set_extra_widget(GtkWidget *fs, GtkWidget *extra)
492 {
493 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
494   gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(fs), extra);
495 #else
496   gtk_box_pack_start(GTK_BOX(GTK_FILE_SELECTION(fs)->action_area), extra,
497                      FALSE, FALSE, 0);
498 #endif
499 }
500
501
502 /*
503  * A generic select_file routine that is intended to be connected to
504  * a Browse button on other dialog boxes. This allows the user to browse
505  * for a file and select it. We fill in the text_entry that is given to us. 
506  *
507  * We display the window label specified in our args.
508  */
509 void
510 file_selection_browse(GtkWidget *file_bt, GtkWidget *file_te, const char *label, file_selection_action_t action)
511 {
512   GtkWidget *caller = gtk_widget_get_toplevel(file_bt);
513   GtkWidget *fs;
514 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
515   gchar     *f_name;
516 #endif
517
518   /* Has a file selection dialog box already been opened for that top-level
519      widget? */
520   fs = OBJECT_GET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY);
521   if (fs != NULL) {
522     /* Yes.  Just re-activate that dialog box. */
523     reactivate_window(fs);
524     return;
525   }
526
527   fs = file_selection_new(label, action);
528
529   OBJECT_SET_DATA(fs, PRINT_FILE_TE_KEY, file_te);
530
531   /* Set the E_FS_CALLER_PTR_KEY for the new dialog to point to our caller. */
532   OBJECT_SET_DATA(fs, E_FS_CALLER_PTR_KEY, caller);
533
534   /* Set the E_FILE_SEL_DIALOG_PTR_KEY for the caller to point to us */
535   OBJECT_SET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY, fs);
536
537   /* Call a handler when the file selection box is destroyed, so we can inform
538      our caller, if any, that it's been destroyed. */
539   SIGNAL_CONNECT(fs, "destroy", GTK_SIGNAL_FUNC(file_selection_browse_destroy_cb), 
540                  file_te);
541
542 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 4) || GTK_MAJOR_VERSION > 2
543   if (gtk_dialog_run(GTK_DIALOG(fs)) == GTK_RESPONSE_ACCEPT)
544   {
545       f_name = g_strdup(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fs)));
546       gtk_entry_set_text(GTK_ENTRY(file_te), f_name);
547       g_free(f_name);
548   }
549   window_destroy(fs);
550 #else
551   SIGNAL_CONNECT(GTK_FILE_SELECTION(fs)->ok_button, "clicked", 
552                  file_selection_browse_ok_cb, fs);
553
554   window_set_cancel_button(fs, GTK_FILE_SELECTION(fs)->cancel_button,
555                            window_cancel_button_cb);
556
557   SIGNAL_CONNECT(fs, "delete_event", window_delete_event_cb, fs);
558
559   gtk_widget_show(fs);
560   window_present(fs);
561 #endif
562 }
563
564
565 #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 4) || GTK_MAJOR_VERSION < 2
566 static void
567 file_selection_browse_ok_cb(GtkWidget *w _U_, gpointer data)
568 {
569   gchar     *f_name;
570   GtkWidget *win = data;
571
572   f_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION (data)));
573
574   /* Perhaps the user specified a directory instead of a file.
575      Check whether they did. */
576   if (test_for_directory(f_name) == EISDIR) {
577         /* It's a directory - set the file selection box to display it. */
578         set_last_open_dir(f_name);
579         g_free(f_name);
580         file_selection_set_current_folder(data, last_open_dir);
581         return;
582   }
583
584   gtk_entry_set_text(GTK_ENTRY(OBJECT_GET_DATA(win, PRINT_FILE_TE_KEY)),
585                      f_name);
586   window_destroy(GTK_WIDGET(win));
587
588   g_free(f_name);
589 }
590 #endif
591
592 static void
593 file_selection_browse_destroy_cb(GtkWidget *win, GtkWidget* parent_te)
594 {
595   GtkWidget *caller;
596
597   /* Get the widget that requested that we be popped up.
598      (It should arrange to destroy us if it's destroyed, so
599      that we don't get a pointer to a non-existent window here.) */
600   caller = OBJECT_GET_DATA(win, E_FS_CALLER_PTR_KEY);
601
602   /* Tell it we no longer exist. */
603   OBJECT_SET_DATA(caller, E_FILE_SEL_DIALOG_PTR_KEY, NULL);
604
605   /* Give the focus to the file text entry widget so the user can just press
606      Return to print to the file. */
607   gtk_widget_grab_focus(parent_te);
608 }
609
610
611 void
612 set_last_open_dir(char *dirname)
613 {
614         int len;
615         gchar *new_last_open_dir;
616
617         if (dirname) {
618                 len = strlen(dirname);
619                 if (dirname[len-1] == G_DIR_SEPARATOR) {
620                         new_last_open_dir = g_strconcat(dirname, NULL);
621                 }
622                 else {
623                         new_last_open_dir = g_strconcat(dirname,
624                                 G_DIR_SEPARATOR_S, NULL);
625                 }
626
627                 if (last_open_dir == NULL ||
628                     strcmp(last_open_dir, new_last_open_dir) != 0)
629                         updated_last_open_dir = TRUE;
630         }
631         else {
632                 new_last_open_dir = NULL;
633                 if (last_open_dir != NULL)
634                         updated_last_open_dir = TRUE;
635         }
636
637         if (last_open_dir) {
638                 g_free(last_open_dir);
639         }
640         last_open_dir = new_last_open_dir;
641 }
642
643 char *
644 get_last_open_dir(void)
645 {
646     return last_open_dir;
647 }
648
649 /* Set the "activate" signal for a widget to call a routine to
650    activate the "OK" button for a dialog box.
651
652    XXX - there should be a way to specify that a GtkEntry widget
653    shouldn't itself handle the Return key, but should let it be
654    passed on to the parent, so that you don't have to do this
655    by hand for every GtkEntry widget in a dialog box, but, alas,
656    there isn't.  (Does this problem exist for other widgets?
657    I.e., are there any others that seize the Return key? */
658 void
659 dlg_set_activate(GtkWidget *widget, GtkWidget *ok_button)
660 {
661   SIGNAL_CONNECT(widget, "activate", dlg_activate, ok_button);
662 }
663
664 static void
665 dlg_activate (GtkWidget *widget _U_, gpointer ok_button)
666 {
667   gtk_widget_activate(GTK_WIDGET(ok_button));
668 }
669
670 #if GTK_MAJOR_VERSION < 2
671 /* Sigh.  GTK+ appears not to acknowledge that it should be possible
672    to attach mnemonics to anything other than menu items; provide
673    routines to create radio and check buttons with labels that
674    include mnemonics.  */
675 typedef struct {
676         GtkWidget *button;
677         GtkAccelGroup *accel_group;
678 } fix_label_args_t;
679
680 static void
681 dlg_fix_label_callback(GtkWidget *label_widget, gpointer data)
682 {
683   fix_label_args_t *args = data;
684   gchar *label;
685   guint accel_key;
686
687   gtk_label_get(GTK_LABEL(label_widget), &label);
688   accel_key = gtk_label_parse_uline(GTK_LABEL(label_widget), label);
689   if (accel_key != GDK_VoidSymbol) {
690     /* Yes, we have a mnemonic. */
691     gtk_widget_add_accelerator(args->button, "clicked", args->accel_group,
692                                 accel_key, 0, GTK_ACCEL_LOCKED);
693     gtk_widget_add_accelerator(args->button, "clicked", args->accel_group,
694                                 accel_key, GDK_MOD1_MASK, GTK_ACCEL_LOCKED);
695   }
696 }
697
698 static void
699 dlg_fix_button_label(GtkWidget *button, GtkAccelGroup *accel_group)
700 {
701   fix_label_args_t args;
702
703   args.button = button;
704   args.accel_group = accel_group;
705   gtk_container_foreach(GTK_CONTAINER(button), dlg_fix_label_callback, &args);
706 }
707
708 GtkWidget *
709 dlg_radio_button_new_with_label_with_mnemonic(GSList *group,
710                 const gchar *label, GtkAccelGroup *accel_group)
711 {
712   GtkWidget *radio_button;
713
714   radio_button = gtk_radio_button_new_with_label (group, label);
715   dlg_fix_button_label(radio_button, accel_group);
716   return radio_button;
717 }
718
719 GtkWidget *
720 dlg_check_button_new_with_label_with_mnemonic(const gchar *label,
721                         GtkAccelGroup *accel_group)
722 {
723   GtkWidget *check_button;
724
725   check_button = gtk_check_button_new_with_label (label);
726   dlg_fix_button_label(check_button, accel_group);
727   return check_button;
728 }
729
730 GtkWidget *
731 dlg_toggle_button_new_with_label_with_mnemonic(const gchar *label,
732                         GtkAccelGroup *accel_group)
733 {
734   GtkWidget *toggle_button;
735
736   toggle_button = gtk_toggle_button_new_with_label (label);
737   dlg_fix_button_label(toggle_button, accel_group);
738   return toggle_button;
739 }
740 #endif