Patch from Ben Fowler to rename the global variable "cf" to "cfile", to
[obnox/wireshark/wip.git] / gtk / file_dlg.c
1 /* file_dlg.c
2  * Dialog boxes for handling files
3  *
4  * $Id: file_dlg.c,v 1.25 2000/06/27 04:36:00 guy Exp $
5  *
6  * Ethereal - Network traffic analyzer
7  * By Gerald Combs <gerald@zing.org>
8  * Copyright 1998 Gerald Combs
9  *
10  * 
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  * 
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  * 
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif
29
30 #ifdef HAVE_UNISTD_H
31 #include <unistd.h>
32 #endif
33
34 #ifdef HAVE_DIRECT_H
35 #include <direct.h>
36 #endif
37
38 #include <string.h>
39
40 #ifndef __GLOBALS_H__
41 #include "globals.h"
42 #endif
43 #ifndef __GTKGLOBALS_H__
44 #include "gtkglobals.h"
45 #endif
46
47 #ifndef __KEYS_H__
48 #include "keys.h"
49 #endif
50
51 #include "filter_prefs.h"
52 #include "ui_util.h"
53
54 #ifndef __DIALOG_H__
55 #include "simple_dialog.h"
56 #endif
57
58 #ifndef __MENU_H__
59 #include "menu.h"
60 #endif
61
62 #include "file_dlg.h"
63 #include "dlg_utils.h"
64
65 #ifndef __UTIL_H__
66 #include "util.h"
67 #endif
68
69 static void file_open_ok_cb(GtkWidget *w, GtkFileSelection *fs);
70 static void file_open_destroy_cb(GtkWidget *win, gpointer user_data);
71 static void select_file_type_cb(GtkWidget *w, gpointer data);
72 static void file_save_as_ok_cb(GtkWidget *w, GtkFileSelection *fs);
73 static void file_save_as_destroy_cb(GtkWidget *win, gpointer user_data);
74
75 #define E_FILE_RESOLVE_KEY        "file_dlg_resolve_key"
76
77 /*
78  * Keep a static pointer to the current "Open Capture File" window, if
79  * any, so that if somebody tries to do "File:Open" while there's already
80  * an "Open Capture File" window up, we just pop up the existing one,
81  * rather than creating a new one.
82  */
83 static GtkWidget *file_open_w;
84
85 /* Open a file */
86 void
87 file_open_cmd_cb(GtkWidget *w, gpointer data) {
88   GtkWidget     *filter_hbox, *filter_bt, *filter_te,
89                 *resolv_cb;
90
91   if (file_open_w != NULL) {
92     /* There's already an "Open Capture File" dialog box; reactivate it. */
93     reactivate_window(file_open_w);
94     return;
95   }
96
97   /* XXX - GTK+'s file selection dialog box doesn't let you set the
98      initial directory it should show; it always uses the current
99      directory.  We want to start out by showing the user the files
100      in the last directory in which they looked, so we have to "chdir()"
101      there.
102
103      This means means that if Ethereal dumps core, the core file will be
104      dumped in whatever directory it last "chdir()"red to, rather
105      than in the directory in which you started it.
106
107      It also means, for better or worse, that *all* file selection
108      dialog boxes will start in that directory. */
109   if (last_open_dir)
110           chdir(last_open_dir);
111
112   file_open_w = gtk_file_selection_new ("Ethereal: Open Capture File");
113   gtk_signal_connect(GTK_OBJECT(file_open_w), "destroy",
114         GTK_SIGNAL_FUNC(file_open_destroy_cb), NULL);
115
116   resolv_cb = dlg_check_button_new_with_label_with_mnemonic(
117                   "Enable name resolution", NULL);
118   gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(resolv_cb), g_resolving_actif);
119   gtk_box_pack_end(GTK_BOX(GTK_FILE_SELECTION(file_open_w)->main_vbox),
120                   resolv_cb, FALSE, FALSE, 5);
121   gtk_widget_show(resolv_cb);
122   gtk_object_set_data(GTK_OBJECT(GTK_FILE_SELECTION(file_open_w)->ok_button),
123                   E_FILE_RESOLVE_KEY,  resolv_cb);
124
125   
126   /* Connect the ok_button to file_open_ok_cb function and pass along a
127      pointer to the file selection box widget */
128   gtk_signal_connect(GTK_OBJECT (GTK_FILE_SELECTION (file_open_w)->ok_button),
129     "clicked", (GtkSignalFunc) file_open_ok_cb, file_open_w);
130
131   gtk_object_set_data(GTK_OBJECT(GTK_FILE_SELECTION(file_open_w)->ok_button),
132       E_DFILTER_TE_KEY, gtk_object_get_data(GTK_OBJECT(w), E_DFILTER_TE_KEY));
133
134   filter_hbox = gtk_hbox_new(FALSE, 1);
135   gtk_container_border_width(GTK_CONTAINER(filter_hbox), 0);
136   gtk_box_pack_start(GTK_BOX(GTK_FILE_SELECTION(file_open_w)->action_area),
137     filter_hbox, FALSE, FALSE, 0);
138   gtk_widget_show(filter_hbox);
139
140   filter_bt = gtk_button_new_with_label("Filter:");
141   gtk_signal_connect(GTK_OBJECT(filter_bt), "clicked",
142     GTK_SIGNAL_FUNC(filter_browse_cb), NULL);
143   gtk_box_pack_start(GTK_BOX(filter_hbox), filter_bt, FALSE, TRUE, 0);
144   gtk_widget_show(filter_bt);
145   
146   filter_te = gtk_entry_new();
147   gtk_object_set_data(GTK_OBJECT(filter_bt), E_FILT_TE_PTR_KEY, filter_te);
148   gtk_box_pack_start(GTK_BOX(filter_hbox), filter_te, TRUE, TRUE, 3);
149   gtk_widget_show(filter_te);
150
151   gtk_object_set_data(GTK_OBJECT(GTK_FILE_SELECTION(file_open_w)->ok_button),
152     E_RFILTER_TE_KEY, filter_te);
153
154   /* Connect the cancel_button to destroy the widget */
155   gtk_signal_connect_object(GTK_OBJECT (GTK_FILE_SELECTION
156     (file_open_w)->cancel_button), "clicked", (GtkSignalFunc)
157     gtk_widget_destroy, GTK_OBJECT (file_open_w));
158
159   /* Catch the "key_press_event" signal in the window, so that we can catch
160      the ESC key being pressed and act as if the "Cancel" button had
161      been selected. */
162   dlg_set_cancel(file_open_w, GTK_FILE_SELECTION(file_open_w)->cancel_button);
163
164   gtk_widget_show(file_open_w);
165 }
166
167 static void
168 file_open_ok_cb(GtkWidget *w, GtkFileSelection *fs) {
169   gchar     *cf_name, *rfilter, *s;
170   GtkWidget *filter_te, *resolv_cb;
171   dfilter   *rfcode = NULL;
172   int        err;
173
174   cf_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION (fs)));
175   filter_te = gtk_object_get_data(GTK_OBJECT(w), E_RFILTER_TE_KEY);
176   rfilter = gtk_entry_get_text(GTK_ENTRY(filter_te));
177   if (dfilter_compile(rfilter, &rfcode) != 0) {
178     simple_dialog(ESD_TYPE_WARN, NULL, dfilter_error_msg);
179     return;
180   }
181
182   /* Perhaps the user specified a directory instead of a file.
183    * See if we can chdir to it. */
184   if (chdir(cf_name) == 0) {
185         int     cf_name_len;
186
187         g_free(last_open_dir);
188
189         /* Append a slash, even if not needed */
190         cf_name_len = strlen(cf_name);
191         last_open_dir = g_malloc(cf_name_len + 2);
192         strcpy(last_open_dir, cf_name);
193         strcat(last_open_dir, "/");
194         g_free(cf_name);
195         gtk_file_selection_set_filename(GTK_FILE_SELECTION(fs), last_open_dir);
196         return;
197   }
198
199   /* Try to open the capture file. */
200   if ((err = open_cap_file(cf_name, FALSE, &cfile)) != 0) {
201     /* We couldn't open it; don't dismiss the open dialog box,
202        just leave it around so that the user can, after they
203        dismiss the alert box popped up for the open error,
204        try again. */
205     if (rfcode != NULL)
206       dfilter_destroy(rfcode);
207     g_free(cf_name);
208     return;
209   }
210
211   /* Attach the new read filter to "cf" ("open_cap_file()" succeeded, so
212      it closed the previous capture file, and thus destroyed any
213      previous read filter attached to "cf"). */
214   cfile.rfcode = rfcode;
215
216   /* Set the global resolving variable */
217   resolv_cb = gtk_object_get_data(GTK_OBJECT(w), E_FILE_RESOLVE_KEY);
218   g_resolving_actif = GTK_TOGGLE_BUTTON (resolv_cb)->active;
219
220   /* We've crossed the Rubicon; get rid of the file selection box. */
221   gtk_widget_hide(GTK_WIDGET (fs));
222   gtk_widget_destroy(GTK_WIDGET (fs));
223
224   err = read_cap_file(&cfile);
225   /* Save the name of the containing directory specified in the path name,
226      if any; we can write over cf_name, which is a good thing, given that
227      "get_dirname()" does write over its argument. */
228   s = get_dirname(cf_name);
229   if (s != NULL) {
230     /* Well, there is a directory in there... */
231     if (last_open_dir != NULL) {
232       /* ...and we already have one saved... */
233       if (strcmp(last_open_dir, s) != 0) {
234         /* ...and it's not the same as this one, so free the old one
235            and assign a copy of the new one to it. */
236         g_free(last_open_dir);
237         last_open_dir = g_strdup(s);
238       }
239     } else {
240       /* ...and we don't already have one saved, so just save this one. */
241       last_open_dir = g_strdup(s);
242     }
243   } else {
244     /* There was no directory in there. */
245     last_open_dir = NULL;
246   }
247
248   g_free(cf_name);
249 }
250
251 static void
252 file_open_destroy_cb(GtkWidget *win, gpointer user_data)
253 {
254   GtkWidget *file_open_filter_w;
255
256   /* Is there a filter edit/selection dialog associated with this
257      Open Capture File dialog? */
258   file_open_filter_w = gtk_object_get_data(GTK_OBJECT(win), E_FILT_DIALOG_PTR_KEY);
259
260   if (file_open_filter_w != NULL) {
261     /* Yes.  Destroy it. */
262     gtk_widget_destroy(file_open_filter_w);
263   }
264
265   /* Note that we no longer have a "Open Capture File" dialog box. */
266   file_open_w = NULL;
267 }
268
269 /* Close a file */
270 void
271 file_close_cmd_cb(GtkWidget *widget, gpointer data) {
272   close_cap_file(&cfile, info_bar);
273 }
274
275 void
276 file_save_cmd_cb(GtkWidget *w, gpointer data) {
277   /* If the file's already been saved, do nothing.  */
278   if (cfile.user_saved)
279     return;
280
281   /* Do a "Save As". */
282   file_save_as_cmd_cb(w, data);
283 }
284
285 /* XXX - can we make these not be static? */
286 static gboolean filtered;
287 static int filetype;
288 static GtkWidget *filter_cb;
289 static GtkWidget *ft_om;
290
291 static gboolean
292 can_save_with_wiretap(int ft)
293 {
294   /* To save a file with Wiretap, Wiretap has to handle that format,
295      and its code to handle that format must be able to write a file
296      with this file's encapsulation type. */
297   return wtap_dump_can_open(ft) && wtap_dump_can_write_encap(ft, cfile.lnk_t);
298 }
299
300 /* Generate a list of the file types we can save this file as.
301
302    "filetype" is the type it has now.
303
304    "encap" is the encapsulation for its packets (which could be
305    "unknown" or "per-packet").
306
307    "filtered" is TRUE if we're to save only the packets that passed
308    the display filter (in which case we have to save it using Wiretap)
309    and FALSE if we're to save the entire file (in which case, if we're
310    saving it in the type it has already, we can just copy it). */
311 static void
312 set_file_type_list(GtkWidget *option_menu)
313 {
314   GtkWidget *ft_menu, *ft_menu_item;
315   int ft;
316   guint index;
317   guint item_to_select;
318
319   /* Default to the first supported file type, if the file's current
320      type isn't supported. */
321   item_to_select = 0;
322
323   ft_menu = gtk_menu_new();
324
325   /* Check all file types. */
326   index = 0;
327   for (ft = 0; ft < WTAP_NUM_FILE_TYPES; ft++) {
328     if (filtered || ft != cfile.cd_t) {
329       /* Filtered, or a different file type.  We have to use Wiretap. */
330       if (!can_save_with_wiretap(ft))
331         continue;       /* We can't. */
332     }
333
334     /* OK, we can write it out in this type. */
335     ft_menu_item = gtk_menu_item_new_with_label(wtap_file_type_string(ft));
336     if (ft == filetype) {
337       /* Default to the same format as the file, if it's supported. */
338       item_to_select = index;
339     }
340     gtk_signal_connect(GTK_OBJECT(ft_menu_item), "activate",
341       GTK_SIGNAL_FUNC(select_file_type_cb), (gpointer)ft);
342     gtk_menu_append(GTK_MENU(ft_menu), ft_menu_item);
343     gtk_widget_show(ft_menu_item);
344     index++;
345   }
346
347   gtk_option_menu_set_menu(GTK_OPTION_MENU(option_menu), ft_menu);
348   gtk_option_menu_set_history(GTK_OPTION_MENU(option_menu), item_to_select);
349 }
350
351 static void
352 select_file_type_cb(GtkWidget *w, gpointer data)
353 {
354   int new_filetype = (int)data;
355
356   if (filetype != new_filetype) {
357     /* We can select only the filtered packets to be saved iff we can
358        use Wiretap to save the file. */
359     gtk_widget_set_sensitive(filter_cb, can_save_with_wiretap(new_filetype));
360     filetype = new_filetype;
361   }
362 }
363
364 static void
365 toggle_filtered_cb(GtkWidget *widget, gpointer data)
366 {
367   gboolean new_filtered;
368
369   new_filtered = GTK_TOGGLE_BUTTON (widget)->active;
370
371   if (filtered != new_filtered) {
372     /* They changed the state of the "filtered" button. */
373     filtered = new_filtered;
374     set_file_type_list(ft_om);
375   }
376 }
377
378 /*
379  * Keep a static pointer to the current "Save Capture File As" window, if
380  * any, so that if somebody tries to do "File:Save" or "File:Save As"
381  * while there's already a "Save Capture File As" window up, we just pop
382  * up the existing one, rather than creating a new one.
383  */
384 static GtkWidget *file_save_as_w;
385
386 void
387 file_save_as_cmd_cb(GtkWidget *w, gpointer data)
388 {
389   GtkWidget *ok_bt, *main_vb, *ft_hb, *ft_lb;
390
391   if (file_save_as_w != NULL) {
392     /* There's already an "Save Capture File As" dialog box; reactivate it. */
393     reactivate_window(file_save_as_w);
394     return;
395   }
396
397   /* Default to saving all packets, in the file's current format. */
398   filtered = FALSE;
399   filetype = cfile.cd_t;
400
401   file_save_as_w = gtk_file_selection_new ("Ethereal: Save Capture File As");
402   gtk_signal_connect(GTK_OBJECT(file_save_as_w), "destroy",
403         GTK_SIGNAL_FUNC(file_save_as_destroy_cb), NULL);
404
405   /* Connect the ok_button to file_save_as_ok_cb function and pass along a
406      pointer to the file selection box widget */
407   ok_bt = GTK_FILE_SELECTION (file_save_as_w)->ok_button;
408   gtk_signal_connect(GTK_OBJECT (ok_bt), "clicked",
409     (GtkSignalFunc) file_save_as_ok_cb, file_save_as_w);
410
411   /* Container for each row of widgets */
412   main_vb = gtk_vbox_new(FALSE, 3);
413   gtk_container_border_width(GTK_CONTAINER(main_vb), 5);
414   gtk_box_pack_start(GTK_BOX(GTK_FILE_SELECTION(file_save_as_w)->action_area),
415     main_vb, FALSE, FALSE, 0);
416   gtk_widget_show(main_vb);
417   
418   filter_cb = gtk_check_button_new_with_label("Save only packets currently being displayed");
419   gtk_container_add(GTK_CONTAINER(main_vb), filter_cb);
420   gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(filter_cb), FALSE);
421   gtk_signal_connect(GTK_OBJECT(filter_cb), "toggled",
422                         GTK_SIGNAL_FUNC(toggle_filtered_cb), NULL);
423   gtk_widget_set_sensitive(filter_cb, can_save_with_wiretap(filetype));
424   gtk_widget_show(filter_cb);
425
426   /* File type row */
427   ft_hb = gtk_hbox_new(FALSE, 3);
428   gtk_container_add(GTK_CONTAINER(main_vb), ft_hb);
429   gtk_widget_show(ft_hb);
430   
431   ft_lb = gtk_label_new("File type:");
432   gtk_box_pack_start(GTK_BOX(ft_hb), ft_lb, FALSE, FALSE, 0);
433   gtk_widget_show(ft_lb);
434
435   ft_om = gtk_option_menu_new();
436
437   /* Generate the list of file types we can save. */
438   set_file_type_list(ft_om);
439   gtk_box_pack_start(GTK_BOX(ft_hb), ft_om, FALSE, FALSE, 0);
440   gtk_widget_show(ft_om);
441
442   /* Connect the cancel_button to destroy the widget */
443   gtk_signal_connect_object(GTK_OBJECT (GTK_FILE_SELECTION
444     (file_save_as_w)->cancel_button), "clicked", (GtkSignalFunc)
445     gtk_widget_destroy, GTK_OBJECT (file_save_as_w));
446
447   /* Catch the "key_press_event" signal in the window, so that we can catch
448      the ESC key being pressed and act as if the "Cancel" button had
449      been selected. */
450   dlg_set_cancel(file_save_as_w, GTK_FILE_SELECTION(file_save_as_w)->cancel_button);
451
452   gtk_file_selection_set_filename(GTK_FILE_SELECTION(file_save_as_w), "");
453   gtk_widget_show(file_save_as_w);
454 }
455
456 static void
457 file_save_as_ok_cb(GtkWidget *w, GtkFileSelection *fs) {
458   gchar *cf_name;
459
460   cf_name = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION(fs)));
461   gtk_widget_hide(GTK_WIDGET (fs));
462   gtk_widget_destroy(GTK_WIDGET (fs));
463
464   /* Write out the packets (all, or only the ones that are currently
465      displayed) to the file with the specified name. */
466   save_cap_file(cf_name, &cfile, filtered, filetype);
467
468   /* If "save_cap_file()" saved the file name we handed it, it saved
469      a copy, so we should free up our copy. */
470   g_free(cf_name);
471 }
472
473 static void
474 file_save_as_destroy_cb(GtkWidget *win, gpointer user_data)
475 {
476   /* Note that we no longer have a "Save Capture File As" dialog box. */
477   file_save_as_w = NULL;
478 }
479
480 /* Reload a file using the current read and display filters */
481 void
482 file_reload_cmd_cb(GtkWidget *w, gpointer data) {
483   /*GtkWidget *filter_te = gtk_object_get_data(GTK_OBJECT(w), E_DFILTER_TE_KEY);*/
484   GtkWidget *filter_te;
485   gchar *filename;
486   gboolean is_tempfile;
487
488   filter_te = gtk_object_get_data(GTK_OBJECT(w), E_DFILTER_TE_KEY);
489
490   if (cfile.dfilter)
491     g_free(cfile.dfilter);
492   cfile.dfilter = g_strdup(gtk_entry_get_text(GTK_ENTRY(filter_te)));
493
494   /* If the file could be opened, "open_cap_file()" calls "close_cap_file()"
495      to get rid of state for the old capture file before filling in state
496      for the new capture file.  "close_cap_file()" will remove the file if
497      it's a temporary file; we don't want that to happen (for one thing,
498      it'd prevent subsequent reopens from working).  Remember whether it's
499      a temporary file, mark it as not being a temporary file, and then
500      reopen it as the type of file it was.
501
502      Also, "close_cap_file()" will free "cfile.filename", so we must make
503      a copy of it first. */
504   filename = strdup(cfile.filename);
505   is_tempfile = cfile.is_tempfile;
506   cfile.is_tempfile = FALSE;
507   if (open_cap_file(filename, is_tempfile, &cfile) == 0)
508     read_cap_file(&cfile);
509   else {
510     /* The open failed, so "cfile.is_tempfile" wasn't set to "is_tempfile".
511        Instead, the file was left open, so we should restore "cfile.is_tempfile"
512        ourselves.
513
514        XXX - change the menu?  Presumably "open_cap_file()" will do that;
515        make sure it does! */
516     cfile.is_tempfile = is_tempfile;
517   }
518   /* "open_cap_file()" made a copy of the file name we handed it, so
519      we should free up our copy. */
520   g_free(filename);
521 }