Pass the correct widget to gtk_open_file. If we're filling in a display
[metze/wireshark/wip.git] / ui / gtk / capture_file_dlg.c
1 /* capture_file_dlg.c
2  * Dialog boxes for handling capture files
3  *
4  * $Id$
5  *
6  * Wireshark - Network traffic analyzer
7  * By Gerald Combs <gerald@wireshark.org>
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23  */
24
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28
29 #ifdef HAVE_UNISTD_H
30 #include <unistd.h>
31 #endif
32
33 #include <string.h>
34
35 #include <gtk/gtk.h>
36
37 #include "packet-range.h"
38 #include <epan/filesystem.h>
39 #include <epan/addr_resolv.h>
40 #include <epan/prefs.h>
41
42 #include "../globals.h"
43 #include "../color.h"
44 #include "../color_filters.h"
45 #include "../merge.h"
46 #include "ui/util.h"
47 #include <wsutil/file_util.h>
48
49 #include "ui/alert_box.h"
50 #include "ui/recent.h"
51 #include "ui/simple_dialog.h"
52 #include "ui/ui_util.h"
53
54 #include "ui/gtk/gtkglobals.h"
55 #include "ui/gtk/keys.h"
56 #include "ui/gtk/filter_dlg.h"
57 #include "ui/gtk/gui_utils.h"
58 #include "ui/gtk/dlg_utils.h"
59 #include "ui/gtk/file_dlg.h"
60 #include "ui/gtk/capture_file_dlg.h"
61 #include "ui/gtk/drag_and_drop.h"
62 #include "ui/gtk/main.h"
63 #include "ui/gtk/color_dlg.h"
64 #include "ui/gtk/new_packet_list.h"
65 #ifdef HAVE_LIBPCAP
66 #include "ui/gtk/capture_dlg.h"
67 #endif
68 #include "ui/gtk/stock_icons.h"
69 #include "ui/gtk/range_utils.h"
70 #include "ui/gtk/filter_autocomplete.h"
71
72 #ifdef _WIN32
73 #define USE_WIN32_FILE_DIALOGS
74 #endif
75
76 #ifdef USE_WIN32_FILE_DIALOGS
77 #include <gdk/gdkwin32.h>
78 #include <windows.h>
79 #include "ui/win32/file_dlg_win32.h"
80 #endif
81
82 static void do_file_save(capture_file *cf, gboolean dont_reopen);
83 static void do_file_save_as(capture_file *cf, gboolean must_support_comments,
84                             gboolean dont_reopen);
85 static cf_write_status_t file_save_as_cb(GtkWidget *fs,
86                                          gboolean discard_comments,
87                                          gboolean dont_reopen);
88 static void file_select_file_type_cb(GtkWidget *w, gpointer data);
89 static cf_write_status_t file_export_specified_packets_cb(GtkWidget *fs, packet_range_t *range);
90 static int set_file_type_list(GtkWidget *combo_box, capture_file *cf,
91                               gboolean must_support_comments);
92
93 #define E_FILE_TYPE_COMBO_BOX_KEY "file_type_combo_box"
94 #define E_COMPRESSED_CB_KEY       "compressed_cb"
95
96 #define PREVIEW_TABLE_KEY       "preview_table_key"
97 #define PREVIEW_FILENAME_KEY    "preview_filename_key"
98 #define PREVIEW_FORMAT_KEY      "preview_format_key"
99 #define PREVIEW_SIZE_KEY        "preview_size_key"
100 #define PREVIEW_ELAPSED_KEY     "preview_elapsed_key"
101 #define PREVIEW_PACKETS_KEY     "preview_packets_key"
102 #define PREVIEW_FIRST_KEY       "preview_first_key"
103
104 /* XXX - can we make these not be static? */
105 static gboolean        color_selected;
106
107 #define PREVIEW_STR_MAX         200
108
109
110 /* set a new filename for the preview widget */
111 static wtap *
112 preview_set_filename(GtkWidget *prev, const gchar *cf_name)
113 {
114     GtkWidget  *label;
115     gchar      *display_basename;
116     wtap       *wth;
117     int         err = 0;
118     gchar      *err_info;
119     gchar       string_buff[PREVIEW_STR_MAX];
120     gint64      filesize;
121
122
123     /* init preview labels */
124     label = (GtkWidget *)g_object_get_data(G_OBJECT(prev), PREVIEW_FILENAME_KEY);
125     gtk_label_set_text(GTK_LABEL(label), "-");
126     label = (GtkWidget *)g_object_get_data(G_OBJECT(prev), PREVIEW_FORMAT_KEY);
127     gtk_label_set_text(GTK_LABEL(label), "-");
128     label = (GtkWidget *)g_object_get_data(G_OBJECT(prev), PREVIEW_SIZE_KEY);
129     gtk_label_set_text(GTK_LABEL(label), "-");
130     label = (GtkWidget *)g_object_get_data(G_OBJECT(prev), PREVIEW_ELAPSED_KEY);
131     gtk_label_set_text(GTK_LABEL(label), "-");
132     label = (GtkWidget *)g_object_get_data(G_OBJECT(prev), PREVIEW_PACKETS_KEY);
133     gtk_label_set_text(GTK_LABEL(label), "-");
134     label = (GtkWidget *)g_object_get_data(G_OBJECT(prev), PREVIEW_FIRST_KEY);
135     gtk_label_set_text(GTK_LABEL(label), "-");
136
137     if(!cf_name) {
138         return NULL;
139     }
140
141     label = (GtkWidget *)g_object_get_data(G_OBJECT(prev), PREVIEW_FILENAME_KEY);
142     display_basename = g_filename_display_basename(cf_name);
143     gtk_label_set_text(GTK_LABEL(label), display_basename);
144     g_free(display_basename);
145
146     if (test_for_directory(cf_name) == EISDIR) {
147         label = (GtkWidget *)g_object_get_data(G_OBJECT(prev), PREVIEW_FORMAT_KEY);
148         gtk_label_set_text(GTK_LABEL(label), "directory");
149         return NULL;
150     }
151
152     wth = wtap_open_offline(cf_name, &err, &err_info, TRUE);
153     if (wth == NULL) {
154         label = (GtkWidget *)g_object_get_data(G_OBJECT(prev), PREVIEW_FORMAT_KEY);
155         if(err == WTAP_ERR_FILE_UNKNOWN_FORMAT) {
156             gtk_label_set_text(GTK_LABEL(label), "unknown file format");
157         } else {
158             gtk_label_set_text(GTK_LABEL(label), "error opening file");
159         }
160         return NULL;
161     }
162
163     /* Find the size of the file. */
164     filesize = wtap_file_size(wth, &err);
165     if (filesize == -1) {
166         gtk_label_set_text(GTK_LABEL(label), "error getting file size");
167         wtap_close(wth);
168         return NULL;
169     }
170     g_snprintf(string_buff, PREVIEW_STR_MAX, "%" G_GINT64_MODIFIER "d bytes", filesize);
171     label = (GtkWidget *)g_object_get_data(G_OBJECT(prev), PREVIEW_SIZE_KEY);
172     gtk_label_set_text(GTK_LABEL(label), string_buff);
173
174     /* type */
175     g_strlcpy(string_buff, wtap_file_type_string(wtap_file_type(wth)), PREVIEW_STR_MAX);
176     label = (GtkWidget *)g_object_get_data(G_OBJECT(prev), PREVIEW_FORMAT_KEY);
177     gtk_label_set_text(GTK_LABEL(label), string_buff);
178
179     return wth;
180 }
181
182
183 /* do a preview run on the currently selected capture file */
184 static void
185 preview_do(GtkWidget *prev, wtap *wth)
186 {
187     GtkWidget  *label;
188     unsigned int elapsed_time;
189     time_t      time_preview;
190     time_t      time_current;
191     int         err = 0;
192     gchar      *err_info;
193     gint64      data_offset;
194     const struct wtap_pkthdr *phdr;
195     double      start_time = 0; /* seconds, with nsec resolution */
196     double      stop_time = 0;  /* seconds, with nsec resolution */
197     double      cur_time;
198     unsigned int packets = 0;
199     gboolean    is_breaked = FALSE;
200     gchar       string_buff[PREVIEW_STR_MAX];
201     time_t      ti_time;
202     struct tm  *ti_tm;
203
204
205     time(&time_preview);
206     while ( (wtap_read(wth, &err, &err_info, &data_offset)) ) {
207         phdr = wtap_phdr(wth);
208         cur_time = wtap_nstime_to_sec(&phdr->ts);
209         if(packets == 0) {
210             start_time = cur_time;
211             stop_time = cur_time;
212         }
213         if (cur_time < start_time) {
214             start_time = cur_time;
215         }
216         if (cur_time > stop_time){
217             stop_time = cur_time;
218         }
219
220         packets++;
221         if(packets%1000 == 0) {
222             /* do we have a timeout? */
223             time(&time_current);
224             if(time_current-time_preview >= (time_t) prefs.gui_fileopen_preview) {
225                 is_breaked = TRUE;
226                 break;
227             }
228         }
229     }
230
231     if(err != 0) {
232         g_snprintf(string_buff, PREVIEW_STR_MAX, "error after reading %u packets", packets);
233         label = (GtkWidget *)g_object_get_data(G_OBJECT(prev), PREVIEW_PACKETS_KEY);
234         gtk_label_set_text(GTK_LABEL(label), string_buff);
235         wtap_close(wth);
236         return;
237     }
238
239     /* packet count */
240     if(is_breaked) {
241         g_snprintf(string_buff, PREVIEW_STR_MAX, "more than %u packets (preview timeout)", packets);
242     } else {
243         g_snprintf(string_buff, PREVIEW_STR_MAX, "%u", packets);
244     }
245     label = g_object_get_data(G_OBJECT(prev), PREVIEW_PACKETS_KEY);
246     gtk_label_set_text(GTK_LABEL(label), string_buff);
247
248     /* first packet */
249     ti_time = (long)start_time;
250     ti_tm = localtime( &ti_time );
251     if(ti_tm) {
252         g_snprintf(string_buff, PREVIEW_STR_MAX,
253                  "%04d-%02d-%02d %02d:%02d:%02d",
254                  ti_tm->tm_year + 1900,
255                  ti_tm->tm_mon + 1,
256                  ti_tm->tm_mday,
257                  ti_tm->tm_hour,
258                  ti_tm->tm_min,
259                  ti_tm->tm_sec);
260     } else {
261         g_snprintf(string_buff, PREVIEW_STR_MAX, "?");
262     }
263     label = g_object_get_data(G_OBJECT(prev), PREVIEW_FIRST_KEY);
264     gtk_label_set_text(GTK_LABEL(label), string_buff);
265
266     /* elapsed time */
267     elapsed_time = (unsigned int)(stop_time-start_time);
268     if(elapsed_time/86400) {
269       g_snprintf(string_buff, PREVIEW_STR_MAX, "%02u days %02u:%02u:%02u",
270         elapsed_time/86400, elapsed_time%86400/3600, elapsed_time%3600/60, elapsed_time%60);
271     } else {
272       g_snprintf(string_buff, PREVIEW_STR_MAX, "%02u:%02u:%02u",
273         elapsed_time%86400/3600, elapsed_time%3600/60, elapsed_time%60);
274     }
275     if(is_breaked) {
276       g_snprintf(string_buff, PREVIEW_STR_MAX, "unknown");
277     }
278     label = (GtkWidget *)g_object_get_data(G_OBJECT(prev), PREVIEW_ELAPSED_KEY);
279     gtk_label_set_text(GTK_LABEL(label), string_buff);
280
281     wtap_close(wth);
282 }
283
284 #if 0
285 /* as the dialog layout will look very ugly when using the file chooser preview mechanism,
286    simply use the same layout as in GTK2.0 */
287 static void
288 update_preview_cb (GtkFileChooser *file_chooser, gpointer data)
289 {
290     GtkWidget *prev = GTK_WIDGET (data);
291     char *cf_name;
292     gboolean have_preview;
293
294     cf_name = gtk_file_chooser_get_preview_filename (file_chooser);
295
296     have_preview = preview_set_filename(prev, cf_name);
297
298     g_free (cf_name);
299
300     have_preview = TRUE;
301     gtk_file_chooser_set_preview_widget_active (file_chooser, have_preview);
302 }
303 #endif
304
305
306 /* the filename text entry changed */
307 static void
308 file_open_entry_changed(GtkWidget *w _U_, gpointer file_sel)
309 {
310     GtkWidget *prev = (GtkWidget *)g_object_get_data(G_OBJECT(file_sel), PREVIEW_TABLE_KEY);
311     gchar *cf_name;
312     gboolean have_preview;
313     wtap       *wth;
314
315     /* get the filename */
316     cf_name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(file_sel));
317
318     /* set the filename to the preview */
319     wth = preview_set_filename(prev, cf_name);
320     have_preview = (wth != NULL);
321
322     g_free(cf_name);
323
324     /* make the preview widget sensitive */
325     gtk_widget_set_sensitive(prev, have_preview);
326
327     /*
328      * XXX - if the Open button isn't sensitive, you can't type into
329      * the location bar and select the file or directory you've typed.
330      * See
331      *
332      *    https://bugs.wireshark.org/bugzilla/show_bug.cgi?id=1791
333      *
334      * It's not as if allowing users to click Open when they've
335      * selected a file that's not a valid capture file will cause
336      * anything worse than an error dialog, so we'll leave the Open
337      * button sensitive for now.  Perhaps making it sensitive if
338      * cf_name is NULL would also work, although I don't know whether
339      * there are any cases where it would be non-null when you've
340      * typed in the location bar.
341      *
342      * XXX - Bug 1791 also notes that, with the line removed, Bill
343      * Meier "somehow managed to get the file chooser window somewhat
344      * wedged in that neither the cancel or open buttons were responsive".
345      * That seems a bit odd, given that, without this line, we're not
346      * monkeying with the Open button's sensitivity, but...
347      */
348 #if 0
349     /* make the open/save/... dialog button sensitive */
350
351     gtk_dialog_set_response_sensitive(file_sel, GTK_RESPONSE_ACCEPT, have_preview);
352 #endif
353
354     /* do the actual preview */
355     if(have_preview)
356         preview_do(prev, wth);
357 }
358
359
360 /* copied from summary_dlg.c */
361 static GtkWidget *
362 add_string_to_table_sensitive(GtkWidget *list, guint *row, const gchar *title, const gchar *value, gboolean sensitive)
363 {
364     GtkWidget *label;
365     gchar     *indent;
366
367     if(strlen(value) != 0) {
368         indent = g_strdup_printf("   %s", title);
369     } else {
370         indent = g_strdup(title);
371     }
372     label = gtk_label_new(indent);
373     g_free(indent);
374     gtk_misc_set_alignment(GTK_MISC(label), 0.0f, 0.5f);
375     gtk_widget_set_sensitive(label, sensitive);
376     gtk_table_attach_defaults(GTK_TABLE(list), label, 0, 1, *row, *row+1);
377
378     label = gtk_label_new(value);
379     gtk_misc_set_alignment(GTK_MISC(label), 0.0f, 0.5f);
380     gtk_widget_set_sensitive(label, sensitive);
381     gtk_table_attach_defaults(GTK_TABLE(list), label, 1, 2, *row, *row+1);
382
383     *row = *row + 1;
384
385     return label;
386 }
387
388 static GtkWidget *
389 add_string_to_table(GtkWidget *list, guint *row, const gchar *title, const gchar *value)
390 {
391     return add_string_to_table_sensitive(list, row, title, value, TRUE);
392 }
393
394
395
396 static GtkWidget *
397 preview_new(void)
398 {
399     GtkWidget *table, *label;
400     guint         row;
401
402     table = gtk_table_new(1, 2, FALSE);
403     gtk_table_set_col_spacings(GTK_TABLE(table), 6);
404     gtk_table_set_row_spacings(GTK_TABLE(table), 3);
405     row = 0;
406
407     label = add_string_to_table(table, &row, "Filename:", "-");
408     gtk_widget_set_size_request(label, DEF_WIDTH/3, -1);
409     g_object_set_data(G_OBJECT(table), PREVIEW_FILENAME_KEY, label);
410     label = add_string_to_table(table, &row, "Format:", "-");
411     g_object_set_data(G_OBJECT(table), PREVIEW_FORMAT_KEY, label);
412     label = add_string_to_table(table, &row, "Size:", "-");
413     g_object_set_data(G_OBJECT(table), PREVIEW_SIZE_KEY, label);
414     label = add_string_to_table(table, &row, "Packets:", "-");
415     g_object_set_data(G_OBJECT(table), PREVIEW_PACKETS_KEY, label);
416     label = add_string_to_table(table, &row, "First Packet:", "-");
417     g_object_set_data(G_OBJECT(table), PREVIEW_FIRST_KEY, label);
418     label = add_string_to_table(table, &row, "Elapsed time:", "-");
419     g_object_set_data(G_OBJECT(table), PREVIEW_ELAPSED_KEY, label);
420
421     return table;
422 }
423
424 #ifndef USE_WIN32_FILE_DIALOGS
425 /* Open a file */
426 static gboolean
427 gtk_open_file(GtkWidget *w, GString *file_name, GString *display_filter)
428 {
429   GtkWidget     *file_open_w;
430   GtkWidget     *main_hb, *main_vb, *filter_hbox, *filter_bt, *filter_te,
431                 *m_resolv_cb, *n_resolv_cb, *t_resolv_cb, *e_resolv_cb, *prev;
432   /* No Apply button, and "OK" just sets our text widget, it doesn't
433      activate it (i.e., it doesn't cause us to try to open the file). */
434   static construct_args_t args = {
435       "Wireshark: Display Filter",
436       FALSE,
437       FALSE,
438     TRUE
439   };
440   gchar         *cf_name;
441
442   if (!file_name || !display_filter)
443     return FALSE;
444
445   file_open_w = file_selection_new("Wireshark: Open Capture File",
446                                    FILE_SELECTION_OPEN);
447   /* it's annoying, that the file chooser dialog is already shown here,
448      so we cannot use the correct gtk_window_set_default_size() to resize it */
449   gtk_widget_set_size_request(file_open_w, DEF_WIDTH, DEF_HEIGHT);
450
451   if (file_name->len > 0) {
452     file_selection_set_current_folder(file_open_w, file_name->str);
453   } else {
454     switch (prefs.gui_fileopen_style) {
455
456       case FO_STYLE_LAST_OPENED:
457         /* The user has specified that we should start out in the last directory
458            we looked in.  If we've already opened a file, use its containing
459            directory, if we could determine it, as the directory, otherwise
460            use the "last opened" directory saved in the preferences file if
461            there was one. */
462         /* This is now the default behaviour in file_selection_new() */
463         break;
464
465       case FO_STYLE_SPECIFIED:
466         /* The user has specified that we should always start out in a
467            specified directory; if they've specified that directory,
468            start out by showing the files in that dir. */
469         if (prefs.gui_fileopen_dir[0] != '\0')
470           file_selection_set_current_folder(file_open_w, prefs.gui_fileopen_dir);
471         break;
472     }
473   }
474
475   main_hb = ws_gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3, FALSE);
476   file_selection_set_extra_widget(file_open_w, main_hb);
477   gtk_widget_show(main_hb);
478
479   /* Container for each row of widgets */
480   main_vb = ws_gtk_box_new(GTK_ORIENTATION_VERTICAL, 3, FALSE);
481   gtk_container_set_border_width(GTK_CONTAINER(main_vb), 5);
482   gtk_box_pack_start(GTK_BOX(main_hb), main_vb, FALSE, FALSE, 0);
483   gtk_widget_show(main_vb);
484
485   /* filter row */
486   filter_hbox = ws_gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 1, FALSE);
487   gtk_container_set_border_width(GTK_CONTAINER(filter_hbox), 0);
488   gtk_box_pack_start(GTK_BOX(main_vb), filter_hbox, FALSE, FALSE, 0);
489   gtk_widget_show(filter_hbox);
490
491   filter_bt = gtk_button_new_from_stock(WIRESHARK_STOCK_DISPLAY_FILTER_ENTRY);
492   g_signal_connect(filter_bt, "clicked",
493                    G_CALLBACK(display_filter_construct_cb), &args);
494   g_signal_connect(filter_bt, "destroy",
495                    G_CALLBACK(filter_button_destroy_cb), NULL);
496   gtk_box_pack_start(GTK_BOX(filter_hbox), filter_bt, FALSE, TRUE, 0);
497   gtk_widget_show(filter_bt);
498   gtk_widget_set_tooltip_text(filter_bt, "Open the \"Display Filter\" dialog, to edit/apply filters");
499
500   filter_te = gtk_entry_new();
501   g_object_set_data(G_OBJECT(filter_bt), E_FILT_TE_PTR_KEY, filter_te);
502   gtk_box_pack_start(GTK_BOX(filter_hbox), filter_te, TRUE, TRUE, 3);
503   g_signal_connect(filter_te, "changed",
504                    G_CALLBACK(filter_te_syntax_check_cb), NULL);
505   g_object_set_data(G_OBJECT(filter_hbox), E_FILT_AUTOCOMP_PTR_KEY, NULL);
506   g_signal_connect(filter_te, "key-press-event", G_CALLBACK (filter_string_te_key_pressed_cb), NULL);
507   g_signal_connect(file_open_w, "key-press-event", G_CALLBACK (filter_parent_dlg_key_pressed_cb), NULL);
508   colorize_filter_te_as_empty(filter_te);
509   gtk_entry_set_text(GTK_ENTRY(filter_te), display_filter->str);
510   gtk_widget_show(filter_te);
511   gtk_widget_set_tooltip_text(filter_te, "Enter a display filter.");
512
513   g_object_set_data(G_OBJECT(file_open_w), E_RFILTER_TE_KEY, filter_te);
514
515   /* resolve buttons */
516   m_resolv_cb = gtk_check_button_new_with_mnemonic("Enable _MAC name resolution");
517   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_resolv_cb),
518                                gbl_resolv_flags.mac_name);
519   gtk_box_pack_start(GTK_BOX(main_vb), m_resolv_cb, FALSE, FALSE, 0);
520   gtk_widget_show(m_resolv_cb);
521
522   t_resolv_cb = gtk_check_button_new_with_mnemonic("Enable _transport name resolution");
523   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(t_resolv_cb),
524                                gbl_resolv_flags.transport_name);
525   gtk_box_pack_start(GTK_BOX(main_vb), t_resolv_cb, FALSE, FALSE, 0);
526   gtk_widget_show(t_resolv_cb);
527
528   n_resolv_cb = gtk_check_button_new_with_mnemonic("Enable _network name resolution");
529   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(n_resolv_cb),
530                                gbl_resolv_flags.network_name);
531   gtk_box_pack_start(GTK_BOX(main_vb), n_resolv_cb, FALSE, FALSE, 0);
532   gtk_widget_show(n_resolv_cb);
533
534   e_resolv_cb = gtk_check_button_new_with_mnemonic("Use _external network name resolver");
535   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(e_resolv_cb),
536                                gbl_resolv_flags.use_external_net_name_resolver);
537   gtk_box_pack_start(GTK_BOX(main_vb), e_resolv_cb, FALSE, FALSE, 0);
538   gtk_widget_show(e_resolv_cb);
539
540   /* preview widget */
541   prev = preview_new();
542   g_object_set_data(G_OBJECT(file_open_w), PREVIEW_TABLE_KEY, prev);
543   gtk_widget_show_all(prev);
544   gtk_box_pack_start(GTK_BOX(main_hb), prev, TRUE, TRUE, 0);
545
546   g_signal_connect(GTK_FILE_CHOOSER(file_open_w), "selection-changed",
547                    G_CALLBACK(file_open_entry_changed), file_open_w);
548   file_open_entry_changed(file_open_w, file_open_w);
549
550   g_object_set_data(G_OBJECT(file_open_w), E_DFILTER_TE_KEY,
551                     g_object_get_data(G_OBJECT(w), E_DFILTER_TE_KEY));
552
553   cf_name = file_selection_run(file_open_w);
554   if (cf_name == NULL) {
555     /* User cancelled or closed the dialog. */
556     return FALSE;
557   }
558
559   g_string_printf(file_name, "%s", cf_name);
560   g_free(cf_name);
561   g_string_printf(display_filter, "%s", gtk_entry_get_text(GTK_ENTRY(filter_te)));
562
563   /* Set the global resolving variable */
564   if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_resolv_cb)))
565     gbl_resolv_flags.mac_name = TRUE;
566   else
567     gbl_resolv_flags.mac_name = FALSE;
568   if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(n_resolv_cb)))
569    gbl_resolv_flags.network_name = TRUE;
570   else
571    gbl_resolv_flags.network_name = FALSE;
572   if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(t_resolv_cb)))
573     gbl_resolv_flags.transport_name = TRUE;
574   else
575     gbl_resolv_flags.transport_name = FALSE;
576   if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(e_resolv_cb)))
577     gbl_resolv_flags.use_external_net_name_resolver = TRUE;
578   else
579     gbl_resolv_flags.use_external_net_name_resolver = FALSE;
580
581   /* We've crossed the Rubicon; get rid of the file selection box. */
582   window_destroy(GTK_WIDGET(file_open_w));
583
584   return TRUE;
585 }
586 #endif /* USE_WIN32_FILE_DIALOGS */
587
588 /* Open a file */
589
590 /*
591  * <platform/>_open_file routines should upon entry...
592  *   Set the path and fill in the filename if the path+filename is provided.
593  *   Set the display filter if provided. Filter syntax should be checked.
594  *   Set the name resolution check boxes to match the global settings.
595  * ...and upon exit...
596  *   Return TRUE on "OK" and "FALSE" on "Cancel".
597  *   Set the global name resolution preferences on "OK".
598  *   Close the window.
599  */
600
601 static void
602 file_open_cmd(GtkWidget *w _U_)
603 {
604   GString   *file_name = g_string_new("");
605   GString   *display_filter = g_string_new("");
606   dfilter_t *rfcode = NULL;
607   int        err;
608
609   /*
610    * Loop until the user either selects a file or gives up.
611    */
612   for (;;) {
613 #ifdef USE_WIN32_FILE_DIALOGS
614     if (win32_open_file(GDK_WINDOW_HWND(gtk_widget_get_window(top_level)), file_name, display_filter)) {
615 #else /* USE_WIN32_FILE_DIALOGS */
616     if (gtk_open_file(w, file_name, display_filter)) {
617 #endif /* USE_WIN32_FILE_DIALOGS */
618
619       /* apply our filter */
620       if (dfilter_compile(display_filter->str, &rfcode)) {
621         cf_set_rfcode(&cfile, rfcode);
622       } else {
623         /* Not valid.  Tell the user, and go back and run the file
624            selection box again once they dismiss the alert. */
625         bad_dfilter_alert_box(top_level, display_filter->str);
626         continue;
627       }
628
629       /* Try to open the capture file. */
630       if (cf_open(&cfile, file_name->str, FALSE, &err) != CF_OK) {
631         /* We couldn't open it; don't dismiss the open dialog box,
632            just leave it around so that the user can, after they
633            dismiss the alert box popped up for the open error,
634            try again. */
635         if (rfcode != NULL)
636           dfilter_free(rfcode);
637           rfcode = NULL;
638         continue;
639       }
640
641       switch (cf_read(&cfile, FALSE)) {
642
643         case CF_READ_OK:
644         case CF_READ_ERROR:
645           /* Just because we got an error, that doesn't mean we were unable
646              to read any of the file; we handle what we could get from the
647              file. */
648           break;
649
650         case CF_READ_ABORTED:
651           /* The user bailed out of re-reading the capture file; the
652              capture file has been closed - just free the capture file name
653              string and return (without changing the last containing
654              directory). */
655           g_string_free(file_name, TRUE);
656           g_string_free(display_filter, TRUE);
657           return;
658       }
659       /* Save the name of the containing directory specified in the path name,
660          if any; we can write over cf_name, which is a good thing, given that
661          "get_dirname()" does write over its argument. */
662       set_last_open_dir(get_dirname(file_name->str));
663     }
664     g_string_free(file_name, TRUE);
665     g_string_free(display_filter, TRUE);
666     return;
667   }
668 }
669
670 void
671 file_open_cmd_cb(GtkWidget *widget, gpointer data _U_) {
672   /* If there's unsaved data, let the user save it first.
673      If they cancel out of it, don't quit. */
674   if (do_file_close(&cfile, FALSE, " before opening a new capture file"))
675     file_open_cmd(widget);
676 }
677
678 /* Merge existing with another file */
679 static void
680 file_merge_cmd(GtkWidget *w)
681 {
682 #ifdef USE_WIN32_FILE_DIALOGS
683   win32_merge_file(GDK_WINDOW_HWND(gtk_widget_get_window(top_level)));
684   new_packet_list_freeze();
685   new_packet_list_thaw();
686 #else /* USE_WIN32_FILE_DIALOGS */
687   GtkWidget     *file_merge_w;
688   GtkWidget     *main_hb, *main_vb, *ft_hb, *ft_lb, *ft_combo_box, *filter_hbox,
689                 *filter_bt, *filter_te, *prepend_rb, *chrono_rb,
690                 *append_rb, *prev;
691
692   /* No Apply button, and "OK" just sets our text widget, it doesn't
693      activate it (i.e., it doesn't cause us to try to open the file). */
694   static construct_args_t args = {
695     "Wireshark: Read Filter",
696     FALSE,
697     FALSE,
698     TRUE
699   };
700   gchar       *cf_name, *s;
701   const gchar *rfilter;
702   dfilter_t   *rfcode = NULL;
703   gpointer     ptr;
704   int          file_type;
705   int          err;
706   cf_status_t  merge_status;
707   char        *in_filenames[2];
708   char        *tmpname;
709
710   /* Default to saving all packets, in the file's current format. */
711
712   file_merge_w = file_selection_new("Wireshark: Merge with Capture File",
713                                    FILE_SELECTION_OPEN);
714   /* it's annoying, that the file chooser dialog is already shown here,
715      so we cannot use the correct gtk_window_set_default_size() to resize it */
716   gtk_widget_set_size_request(file_merge_w, DEF_WIDTH, DEF_HEIGHT);
717
718   switch (prefs.gui_fileopen_style) {
719
720   case FO_STYLE_LAST_OPENED:
721     /* The user has specified that we should start out in the last directory
722        we looked in.  If we've already opened a file, use its containing
723        directory, if we could determine it, as the directory, otherwise
724        use the "last opened" directory saved in the preferences file if
725        there was one. */
726     /* This is now the default behaviour in file_selection_new() */
727     break;
728
729   case FO_STYLE_SPECIFIED:
730     /* The user has specified that we should always start out in a
731        specified directory; if they've specified that directory,
732        start out by showing the files in that dir. */
733     if (prefs.gui_fileopen_dir[0] != '\0')
734       file_selection_set_current_folder(file_merge_w, prefs.gui_fileopen_dir);
735     break;
736   }
737
738   main_hb = ws_gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3, FALSE);
739   file_selection_set_extra_widget(file_merge_w, main_hb);
740   gtk_widget_show(main_hb);
741
742   /* Container for each row of widgets */
743   main_vb = ws_gtk_box_new(GTK_ORIENTATION_VERTICAL, 3, FALSE);
744   gtk_container_set_border_width(GTK_CONTAINER(main_vb), 5);
745   gtk_box_pack_start(GTK_BOX(main_hb), main_vb, FALSE, FALSE, 0);
746   gtk_widget_show(main_vb);
747
748   /* File type row */
749   ft_hb = ws_gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3, FALSE);
750   gtk_container_add(GTK_CONTAINER(main_vb), ft_hb);
751   gtk_widget_show(ft_hb);
752
753   ft_lb = gtk_label_new("Merged output file type:");
754   gtk_box_pack_start(GTK_BOX(ft_hb), ft_lb, FALSE, FALSE, 0);
755   gtk_widget_show(ft_lb);
756
757   ft_combo_box = ws_combo_box_new_text_and_pointer();
758
759   /* Generate the list of file types we can save. */
760   set_file_type_list(ft_combo_box, &cfile, FALSE);
761   gtk_box_pack_start(GTK_BOX(ft_hb), ft_combo_box, FALSE, FALSE, 0);
762   gtk_widget_show(ft_combo_box);
763   g_object_set_data(G_OBJECT(file_merge_w), E_FILE_TYPE_COMBO_BOX_KEY, ft_combo_box);
764   ws_combo_box_set_active(GTK_COMBO_BOX(ft_combo_box), 0); /* No callback */
765
766   filter_hbox = ws_gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 1, FALSE);
767   gtk_container_set_border_width(GTK_CONTAINER(filter_hbox), 0);
768   gtk_box_pack_start(GTK_BOX(main_vb), filter_hbox, FALSE, FALSE, 0);
769   gtk_widget_show(filter_hbox);
770
771   filter_bt = gtk_button_new_from_stock(WIRESHARK_STOCK_DISPLAY_FILTER_ENTRY);
772   g_signal_connect(filter_bt, "clicked",
773                    G_CALLBACK(display_filter_construct_cb), &args);
774   g_signal_connect(filter_bt, "destroy",
775                    G_CALLBACK(filter_button_destroy_cb), NULL);
776   gtk_box_pack_start(GTK_BOX(filter_hbox), filter_bt, FALSE, TRUE, 0);
777   gtk_widget_show(filter_bt);
778   gtk_widget_set_tooltip_text(filter_bt, "Open the \"Display Filter\" dialog, to edit/apply filters");
779
780   filter_te = gtk_entry_new();
781   g_object_set_data(G_OBJECT(filter_bt), E_FILT_TE_PTR_KEY, filter_te);
782   gtk_box_pack_start(GTK_BOX(filter_hbox), filter_te, TRUE, TRUE, 3);
783   g_signal_connect(filter_te, "changed",
784                    G_CALLBACK(filter_te_syntax_check_cb), NULL);
785   g_object_set_data(G_OBJECT(filter_hbox), E_FILT_AUTOCOMP_PTR_KEY, NULL);
786   g_signal_connect(filter_te, "key-press-event", G_CALLBACK (filter_string_te_key_pressed_cb), NULL);
787   g_signal_connect(file_merge_w, "key-press-event", G_CALLBACK (filter_parent_dlg_key_pressed_cb), NULL);
788   colorize_filter_te_as_empty(filter_te);
789   gtk_widget_show(filter_te);
790   gtk_widget_set_tooltip_text(filter_te, "Enter a display filter.");
791
792   g_object_set_data(G_OBJECT(file_merge_w), E_RFILTER_TE_KEY, filter_te);
793
794   prepend_rb = gtk_radio_button_new_with_mnemonic_from_widget(NULL,
795       "Prepend packets to existing file");
796   gtk_widget_set_tooltip_text(prepend_rb, "The resulting file contains the packets from the selected, followed by the packets from the currently loaded file, the packet timestamps will be ignored.");
797   gtk_box_pack_start(GTK_BOX(main_vb), prepend_rb, FALSE, FALSE, 0);
798   gtk_widget_show(prepend_rb);
799
800   chrono_rb = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(prepend_rb), "Merge packets chronologically");
801   gtk_widget_set_tooltip_text(chrono_rb, "The resulting file contains all the packets from the currently loaded and the selected file, sorted by the packet timestamps.");
802   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(chrono_rb), TRUE);
803   gtk_box_pack_start(GTK_BOX(main_vb), chrono_rb, FALSE, FALSE, 0);
804   gtk_widget_show(chrono_rb);
805
806   append_rb = gtk_radio_button_new_with_mnemonic_from_widget(GTK_RADIO_BUTTON(prepend_rb), "Append packets to existing file");
807   gtk_widget_set_tooltip_text(append_rb, "The resulting file contains the packets from the currently loaded, followed by the packets from the selected file, the packet timestamps will be ignored.");
808   gtk_box_pack_start(GTK_BOX(main_vb), append_rb, FALSE, FALSE, 0);
809   gtk_widget_show(append_rb);
810
811   /* preview widget */
812   prev = preview_new();
813   g_object_set_data(G_OBJECT(file_merge_w), PREVIEW_TABLE_KEY, prev);
814   gtk_widget_show_all(prev);
815   gtk_box_pack_start(GTK_BOX(main_hb), prev, TRUE, TRUE, 0);
816
817   g_signal_connect(GTK_FILE_CHOOSER(file_merge_w), "selection-changed",
818                    G_CALLBACK(file_open_entry_changed), file_merge_w);
819   file_open_entry_changed(file_merge_w, file_merge_w);
820
821   g_object_set_data(G_OBJECT(file_merge_w), E_DFILTER_TE_KEY,
822                     g_object_get_data(G_OBJECT(w), E_DFILTER_TE_KEY));
823
824   /*
825    * Loop until the user either selects a file or gives up.
826    */
827   for (;;) {
828     cf_name = file_selection_run(file_merge_w);
829     if (cf_name == NULL) {
830       /* User cancelled or closed the dialog. */
831       return;
832     }
833
834     /* Get the specified read filter and try to compile it. */
835     rfilter = gtk_entry_get_text(GTK_ENTRY(filter_te));
836     if (!dfilter_compile(rfilter, &rfcode)) {
837       /* Not valid.  Tell the user, and go back and run the file
838          selection box again once they dismiss the alert. */
839       bad_dfilter_alert_box(file_merge_w, rfilter);
840       g_free(cf_name);
841       continue;
842     }
843
844     if (! ws_combo_box_get_active_pointer(GTK_COMBO_BOX(ft_combo_box), &ptr)) {
845         g_assert_not_reached();  /* Programming error: somehow nothing is active */
846     }
847     file_type = GPOINTER_TO_INT(ptr);
848
849     /* Try to merge or append the two files */
850     tmpname = NULL;
851     if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(chrono_rb))) {
852       /* chronological order */
853       in_filenames[0] = cfile.filename;
854       in_filenames[1] = cf_name;
855       merge_status = cf_merge_files(&tmpname, 2, in_filenames, file_type, FALSE);
856     } else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(prepend_rb))) {
857       /* prepend file */
858       in_filenames[0] = cf_name;
859       in_filenames[1] = cfile.filename;
860       merge_status = cf_merge_files(&tmpname, 2, in_filenames, file_type,
861                                     TRUE);
862     } else {
863       /* append file */
864       in_filenames[0] = cfile.filename;
865       in_filenames[1] = cf_name;
866       merge_status = cf_merge_files(&tmpname, 2, in_filenames, file_type,
867                                     TRUE);
868     }
869
870     g_free(cf_name);
871
872     if (merge_status != CF_OK) {
873       if (rfcode != NULL)
874         dfilter_free(rfcode);
875       g_free(tmpname);
876       continue;
877     }
878
879     cf_close(&cfile);
880
881     /* We've crossed the Rubicon; get rid of the file selection box. */
882     window_destroy(GTK_WIDGET(file_merge_w));
883
884     /* Try to open the merged capture file. */
885     if (cf_open(&cfile, tmpname, TRUE /* temporary file */, &err) != CF_OK) {
886       /* We couldn't open it; fail. */
887       if (rfcode != NULL)
888         dfilter_free(rfcode);
889       g_free(tmpname);
890       return;
891     }
892     g_free(tmpname);
893
894     /* Attach the new read filter to "cf" ("cf_open()" succeeded, so
895        it closed the previous capture file, and thus destroyed any
896        previous read filter attached to "cf"). */
897     cfile.rfcode = rfcode;
898
899     switch (cf_read(&cfile, FALSE)) {
900
901     case CF_READ_OK:
902     case CF_READ_ERROR:
903       /* Just because we got an error, that doesn't mean we were unable
904          to read any of the file; we handle what we could get from the
905          file. */
906       break;
907
908     case CF_READ_ABORTED:
909       /* The user bailed out of re-reading the capture file; the
910          capture file has been closed - just free the capture file name
911          string and return (without changing the last containing
912          directory). */
913       return;
914     }
915
916     /* Save the name of the containing directory specified in the path name,
917        if any; we can write over cf_merged_name, which is a good thing, given that
918        "get_dirname()" does write over its argument. */
919     s = get_dirname(tmpname);
920     set_last_open_dir(s);
921     return;
922   }
923 #endif /* USE_WIN32_FILE_DIALOGS */
924 }
925
926 void
927 file_merge_cmd_cb(GtkWidget *widget, gpointer data _U_) {
928   /* If there's unsaved data, let the user save it first.
929      If they cancel out of it, don't merge. */
930   GtkWidget *msg_dialog;
931   gchar     *display_basename;
932   gint       response;
933
934   if (prefs.gui_ask_unsaved) {
935     if (cfile.is_tempfile || cfile.unsaved_changes) {
936       /* This is a temporary capture file or has unsaved changes; ask the
937          user whether to save the capture. */
938       if (cfile.is_tempfile) {
939         msg_dialog = gtk_message_dialog_new(GTK_WINDOW(top_level),
940                                             GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
941                                             GTK_MESSAGE_QUESTION,
942                                             GTK_BUTTONS_NONE,
943                                             "Do you want to save the captured packets before merging another capture file into it?");
944
945         gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(msg_dialog),
946              "A temporary capture file can't be merged.");
947       } else {
948         /*
949          * Format the message.
950          */
951         display_basename = g_filename_display_basename(cfile.filename);
952         msg_dialog = gtk_message_dialog_new(GTK_WINDOW(top_level),
953                                             GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
954                                             GTK_MESSAGE_QUESTION,
955                                             GTK_BUTTONS_NONE,
956                                             "Do you want to save the changes you've made "
957                                             "to the capture file \"%s\" before merging another capture file into it?",
958                                             display_basename);
959         g_free(display_basename);
960         gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(msg_dialog),
961              "The changes must be saved before the files are merged.");
962       }
963
964 #ifndef _WIN32
965       gtk_dialog_add_button(GTK_DIALOG(msg_dialog),
966                             GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
967       gtk_dialog_add_button(GTK_DIALOG(msg_dialog),
968                             GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT);
969 #else
970       gtk_dialog_add_button(GTK_DIALOG(msg_dialog),
971                             GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT);
972       gtk_dialog_add_button(GTK_DIALOG(msg_dialog),
973                             GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
974 #endif
975       gtk_dialog_set_default_response(GTK_DIALOG(msg_dialog), GTK_RESPONSE_ACCEPT);
976
977       response = gtk_dialog_run(GTK_DIALOG(msg_dialog));
978       gtk_widget_destroy(msg_dialog);
979
980       switch (response) {
981
982       case GTK_RESPONSE_ACCEPT:
983         /* Save the file but don't close it */
984         do_file_save(&cfile, FALSE);
985         break;
986
987       case GTK_RESPONSE_CANCEL:
988       case GTK_RESPONSE_NONE:
989       case GTK_RESPONSE_DELETE_EVENT:
990       default:
991         /* Don't do the merge. */
992         return;
993       }
994     }
995   }
996
997   /* Do the merge. */
998   file_merge_cmd(widget);
999 }
1000
1001 #ifdef HAVE_LIBPCAP
1002 static void
1003 do_capture_stop(capture_file *cf)
1004 {
1005   /* Stop the capture (complete with UI updates). */
1006   capture_stop_cb(NULL, NULL);
1007
1008   /* Now run the main loop until the capture stops and we finish
1009      reading it; we need to run the main loop so we respond to
1010      messages on the sync pipe and the sync pipe being closed. */
1011   while (cf->state == FILE_READ_IN_PROGRESS)
1012     gtk_main_iteration();
1013 }
1014 #endif
1015
1016 gboolean
1017 do_file_close(capture_file *cf, gboolean from_quit, const char *before_what)
1018 {
1019   GtkWidget *msg_dialog;
1020   gchar     *display_basename;
1021   gint       response;
1022   gboolean   capture_in_progress;
1023
1024   if (cf->state == FILE_CLOSED)
1025     return TRUE; /* already closed, nothing to do */
1026
1027 #ifdef HAVE_LIBPCAP
1028   if (cf->state == FILE_READ_IN_PROGRESS) {
1029     /* This is true if we're reading a capture file *or* if we're doing
1030        a live capture.  If we're reading a capture file, the main loop
1031        is busy reading packets, and only accepting input from the
1032        progress dialog, so we can't get here, so this means we're
1033        doing a capture. */
1034     capture_in_progress = TRUE;
1035   } else
1036 #endif
1037     capture_in_progress = FALSE;
1038
1039   if (prefs.gui_ask_unsaved) {
1040     if (cf->is_tempfile || capture_in_progress || cf->unsaved_changes) {
1041       /* This is a temporary capture file, or there's a capture in
1042          progress, or the file has unsaved changes; ask the user whether
1043          to save the data. */
1044       if (cf->is_tempfile) {
1045         msg_dialog = gtk_message_dialog_new(GTK_WINDOW(top_level),
1046                                             GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
1047                                             GTK_MESSAGE_QUESTION,
1048                                             GTK_BUTTONS_NONE,
1049                                             capture_in_progress ?
1050                                                 "Do you want to stop the capture and save the captured packets%s?" :
1051                                                 "Do you want to save the captured packets%s?",
1052                                             before_what);
1053
1054         gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(msg_dialog),
1055              "Your captured packets will be lost if you don't save them.");
1056       } else {
1057         /*
1058          * Format the message.
1059          */
1060         display_basename = g_filename_display_basename(cf->filename);
1061         if (capture_in_progress) {
1062           msg_dialog = gtk_message_dialog_new(GTK_WINDOW(top_level),
1063                                               GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
1064                                               GTK_MESSAGE_QUESTION,
1065                                               GTK_BUTTONS_NONE,
1066                                               "Do you want to stop the capture and save the captured packets%s?",
1067                                               before_what);
1068         } else {
1069           msg_dialog = gtk_message_dialog_new(GTK_WINDOW(top_level),
1070                                               GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
1071                                               GTK_MESSAGE_QUESTION,
1072                                               GTK_BUTTONS_NONE,
1073                                               "Do you want to save the changes you've made "
1074                                               "to the capture file \"%s\"%s?",
1075                                               display_basename, before_what);
1076         }
1077         g_free(display_basename);
1078         gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(msg_dialog),
1079                                                  capture_in_progress ?
1080              "Your captured packets will be lost if you don't save them." :
1081              "Your changes will be lost if you don't save them.");
1082       }
1083
1084 #ifndef _WIN32
1085       /* If this is from a Quit operation, use "quit and don't save"
1086          rather than just "don't save". */
1087       gtk_dialog_add_button(GTK_DIALOG(msg_dialog),
1088                             (from_quit ?
1089                                 (cf->state == FILE_READ_IN_PROGRESS ?
1090                                     WIRESHARK_STOCK_STOP_QUIT_DONT_SAVE :
1091                                     WIRESHARK_STOCK_QUIT_DONT_SAVE) :
1092                                 (capture_in_progress ?
1093                                     WIRESHARK_STOCK_STOP_DONT_SAVE :
1094                                     WIRESHARK_STOCK_DONT_SAVE)),
1095                             GTK_RESPONSE_REJECT);
1096       gtk_dialog_add_button(GTK_DIALOG(msg_dialog),
1097                             GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
1098       gtk_dialog_add_button(GTK_DIALOG(msg_dialog),
1099                             (capture_in_progress ?
1100                                 WIRESHARK_STOCK_STOP_SAVE :
1101                                 GTK_STOCK_SAVE),
1102                             GTK_RESPONSE_ACCEPT);
1103 #else
1104       gtk_dialog_add_button(GTK_DIALOG(msg_dialog),
1105                             (capture_in_progress ?
1106                                 WIRESHARK_STOCK_STOP_SAVE :
1107                                 GTK_STOCK_SAVE),
1108                             GTK_RESPONSE_ACCEPT);
1109       gtk_dialog_add_button(GTK_DIALOG(msg_dialog),
1110                             GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
1111       gtk_dialog_add_button(GTK_DIALOG(msg_dialog),
1112                             (from_quit ?
1113                                 (capture_in_progress ?
1114                                     WIRESHARK_STOCK_STOP_QUIT_DONT_SAVE :
1115                                     WIRESHARK_STOCK_QUIT_DONT_SAVE) :
1116                                 (capture_in_progress ?
1117                                     WIRESHARK_STOCK_STOP_DONT_SAVE :
1118                                     WIRESHARK_STOCK_DONT_SAVE)),
1119                             GTK_RESPONSE_REJECT);
1120 #endif
1121       gtk_dialog_set_default_response(GTK_DIALOG(msg_dialog), GTK_RESPONSE_ACCEPT);
1122
1123       response = gtk_dialog_run(GTK_DIALOG(msg_dialog));
1124       gtk_widget_destroy(msg_dialog);
1125
1126       switch (response) {
1127
1128       case GTK_RESPONSE_ACCEPT:
1129 #ifdef HAVE_LIBPCAP
1130         /* If there's a capture in progress, we have to stop the capture
1131            and then do the save. */
1132         if (capture_in_progress)
1133           do_capture_stop(cf);
1134 #endif
1135         /* Save the file and close it */
1136         do_file_save(cf, TRUE);
1137         break;
1138
1139       case GTK_RESPONSE_REJECT:
1140 #ifdef HAVE_LIBPCAP
1141         /* If there's a capture in progress; we have to stop the capture
1142            and then do the close. */
1143         if (capture_in_progress)
1144           do_capture_stop(cf);
1145 #endif
1146         /* Just close the file, discarding changes */
1147         cf_close(cf);
1148         break;
1149
1150       case GTK_RESPONSE_CANCEL:
1151       case GTK_RESPONSE_NONE:
1152       case GTK_RESPONSE_DELETE_EVENT:
1153       default:
1154         /* Don't close the file (and don't stop any capture in progress). */
1155         return FALSE; /* file not closed */
1156         break;
1157       }
1158     } else {
1159       /* unchanged file, just close it */
1160       cf_close(cf);
1161     }
1162   } else {
1163     /* User asked not to be bothered by those prompts, just close it.
1164        XXX - should that apply only to saving temporary files? */
1165 #ifdef HAVE_LIBPCAP
1166       /* If there's a capture in progress, we have to stop the capture
1167          and then do the close. */
1168     if (capture_in_progress)
1169       do_capture_stop(cf);
1170 #endif
1171     cf_close(cf);
1172   }
1173   return TRUE; /* file closed */
1174 }
1175
1176 /* Close a file */
1177 void
1178 file_close_cmd_cb(GtkWidget *widget _U_, gpointer data _U_) {
1179   do_file_close(&cfile, FALSE, "");
1180 }
1181
1182 typedef enum {
1183   SAVE,
1184   SAVE_WITHOUT_COMMENTS,
1185   SAVE_IN_ANOTHER_FORMAT,
1186   CANCELLED
1187 } check_savability_t;
1188
1189 #define RESPONSE_DISCARD_COMMENTS_AND_SAVE 1
1190 #define RESPONSE_SAVE_IN_ANOTHER_FORMAT    2
1191
1192 static check_savability_t
1193 check_save_with_comments(capture_file *cf)
1194 {
1195   GtkWidget     *msg_dialog;
1196   gint           response;
1197
1198   /* Do we have any comments? */
1199   if (!cf_has_comments(cf)) {
1200     /* No.  Let the save happen; no comments to delete. */
1201     return SAVE;
1202   }
1203
1204   /* OK, we have comments.  Can we write them out in the file's format?
1205
1206      XXX - for now, we "know" that pcap-ng is the only format for which
1207      we support comments.  We should really ask Wiretap what the
1208      format in question supports (and handle different types of
1209      comments, some but not all of which some file formats might
1210      not support). */
1211   if (cf->cd_t == WTAP_FILE_PCAPNG) {
1212     /* Yes - the file is a pcap-ng file.  Let the save happen; we can
1213        save the comments, so no need to delete them. */
1214     return SAVE;
1215   }
1216
1217   /* Is pcap-ng one of the formats in which we can write this file? */
1218   if (wtap_dump_can_write_encaps(WTAP_FILE_PCAPNG, cf->linktypes)) {
1219     /* Yes.  Ooffer the user a choice of "Save in a format that
1220        supports comments", "Discard comments and save in the
1221        file's own format", or "Cancel", meaning "don't bother
1222        saving the file at all". */
1223     msg_dialog = gtk_message_dialog_new(GTK_WINDOW(top_level),
1224                                         GTK_DIALOG_DESTROY_WITH_PARENT,
1225                                         GTK_MESSAGE_QUESTION,
1226                                         GTK_BUTTONS_NONE,
1227   "The capture has comments, but the file's format "
1228   "doesn't support comments.  Do you want to save the capture "
1229   "in a format that supports comments, or discard the comments "
1230   "and save in the file's format?");
1231 #ifndef _WIN32
1232     gtk_dialog_add_buttons(GTK_DIALOG(msg_dialog),
1233                            "Discard comments and save",
1234                            RESPONSE_DISCARD_COMMENTS_AND_SAVE,
1235                            GTK_STOCK_CANCEL,
1236                            GTK_RESPONSE_CANCEL,
1237                            "Save in another format",
1238                            RESPONSE_SAVE_IN_ANOTHER_FORMAT,
1239                            NULL);
1240 #else
1241     gtk_dialog_add_buttons(GTK_DIALOG(msg_dialog),
1242                            "Save in another format",
1243                            RESPONSE_SAVE_IN_ANOTHER_FORMAT,
1244                            GTK_STOCK_CANCEL,
1245                            GTK_RESPONSE_CANCEL,
1246                            "Discard comments and save",
1247                            RESPONSE_DISCARD_COMMENTS_AND_SAVE,
1248                            NULL);
1249 #endif
1250     gtk_dialog_set_default_response(GTK_DIALOG(msg_dialog),
1251                                     RESPONSE_SAVE_IN_ANOTHER_FORMAT);
1252   } else {
1253     /* No.  Offer the user a choice of "Discard comments and
1254        save in the file's format" or "Cancel". */
1255     msg_dialog = gtk_message_dialog_new(GTK_WINDOW(top_level),
1256                                         GTK_DIALOG_DESTROY_WITH_PARENT,
1257                                         GTK_MESSAGE_QUESTION,
1258                                         GTK_BUTTONS_NONE,
1259   "The capture has comments, but no file format in which it "
1260   "can be saved supports comments.  Do you want to discard "
1261   "the comments and save in the file's format?");
1262 #ifndef _WIN32
1263     gtk_dialog_add_buttons(GTK_DIALOG(msg_dialog),
1264                            "Discard comments and save",
1265                            RESPONSE_DISCARD_COMMENTS_AND_SAVE,
1266                            GTK_STOCK_CANCEL,
1267                            GTK_RESPONSE_CANCEL,
1268                            NULL);
1269 #else
1270     gtk_dialog_add_buttons(GTK_DIALOG(msg_dialog),
1271                            GTK_STOCK_CANCEL,
1272                            GTK_RESPONSE_CANCEL,
1273                            "Discard comments and save",
1274                            RESPONSE_DISCARD_COMMENTS_AND_SAVE,
1275                            NULL);
1276 #endif
1277     gtk_dialog_set_default_response(GTK_DIALOG(msg_dialog),
1278                                     GTK_RESPONSE_CANCEL);
1279   }
1280
1281   response = gtk_dialog_run(GTK_DIALOG(msg_dialog));
1282   gtk_widget_destroy(msg_dialog);
1283
1284   switch (response) {
1285
1286   case RESPONSE_SAVE_IN_ANOTHER_FORMAT:
1287     /* Let the user select another format. */
1288     return SAVE_IN_ANOTHER_FORMAT;
1289
1290   case RESPONSE_DISCARD_COMMENTS_AND_SAVE:
1291     /* Save without the comments and, if that succeeds, delete the
1292        comments. */
1293     return SAVE_WITHOUT_COMMENTS;
1294
1295   case GTK_RESPONSE_CANCEL:
1296   case GTK_RESPONSE_NONE:
1297   case GTK_RESPONSE_DELETE_EVENT:
1298   default:
1299     /* Just give up. */
1300     return CANCELLED;
1301   }
1302 }
1303
1304 /*
1305  * Save the capture file in question, prompting the user for a file
1306  * name to save to if necessary.
1307  */
1308 static void
1309 do_file_save(capture_file *cf, gboolean dont_reopen)
1310 {
1311   char *fname;
1312   gboolean discard_comments;
1313   cf_write_status_t status;
1314
1315   if (cf->is_tempfile) {
1316     /* This is a temporary capture file, so saving it means saving
1317        it to a permanent file.  Prompt the user for a location
1318        to which to save it.  Don't require that the file format
1319        support comments - if it's a temporary capture file, it's
1320        probably pcap-ng, which supports comments and, if it's
1321        not pcap-ng, let the user decide what they want to do
1322        if they've added comments. */
1323     do_file_save_as(cf, FALSE, dont_reopen);
1324   } else {
1325     if (cf->unsaved_changes) {
1326       /* This is not a temporary capture file, but it has unsaved
1327          changes, so saving it means doing a "safe save" on top
1328          of the existing file, in the same format - no UI needed
1329          unless the file has comments and the file's format doesn't
1330          support them.
1331
1332          If the file has comments, does the file's format support them?
1333          If not, ask the user whether they want to discard the comments
1334          or choose a different format. */
1335       switch (check_save_with_comments(cf)) {
1336
1337       case SAVE:
1338         /* The file can be saved in the specified format as is;
1339            just drive on and save in the format they selected. */
1340         discard_comments = FALSE;
1341         break;
1342
1343       case SAVE_WITHOUT_COMMENTS:
1344         /* The file can't be saved in the specified format as is,
1345            but it can be saved without the comments, and the user
1346            said "OK, discard the comments", so save it in the
1347            format they specified without the comments. */
1348         discard_comments = TRUE;
1349         break;
1350
1351       case SAVE_IN_ANOTHER_FORMAT:
1352         /* There are file formats in which we can save this that
1353            support comments, and the user said not to delete the
1354            comments.  Do a "Save As" so the user can select
1355            one of those formats and choose a file name. */
1356         do_file_save_as(cf, TRUE, dont_reopen);
1357         return;
1358
1359       case CANCELLED:
1360         /* The user said "forget it".  Just return. */
1361         return;
1362
1363       default:
1364         /* Squelch warnings that discard_comments is being used
1365            uninitialized. */
1366         g_assert_not_reached();
1367         return;
1368       }
1369
1370       /* XXX - cf->filename might get freed out from under us, because
1371          the code path through which cf_save_packets() goes currently
1372          closes the current file and then opens and reloads the saved file,
1373          so make a copy and free it later. */
1374       fname = g_strdup(cf->filename);
1375       status = cf_save_packets(cf, fname, cf->cd_t, cf->iscompressed,
1376                                discard_comments, dont_reopen);
1377       switch (status) {
1378
1379       case CF_WRITE_OK:
1380         /* The save succeeded; we're done.
1381            If we discarded comments, redraw the packet list to reflect
1382            any packets that no longer have comments. */
1383         if (discard_comments)
1384           new_packet_list_queue_draw();
1385         break;
1386
1387       case CF_WRITE_ERROR:
1388         /* The write failed.
1389            XXX - OK, what do we do now?  Let them try a
1390            "Save As", in case they want to try to save to a
1391            different directory r file system? */
1392         break;
1393
1394       case CF_WRITE_ABORTED:
1395         /* The write was aborted; just drive on. */
1396         break;
1397       }
1398       g_free(fname);
1399     }
1400     /* Otherwise just do nothing. */
1401   }
1402 }
1403
1404 void
1405 file_save_cmd_cb(GtkWidget *w _U_, gpointer data _U_) {
1406   do_file_save(&cfile, FALSE);
1407 }
1408
1409 /* Attach a list of the valid 'save as' file types to a combo_box by
1410    checking what Wiretap supports.  Make the default type the first
1411    in the list.  If must_supprt_comments is true, restrict the list
1412    to those formats that support comments (currently, just pcap-ng).
1413
1414    Returns the default file type. */
1415 static int
1416 set_file_type_list(GtkWidget *combo_box, capture_file *cf,
1417                    gboolean must_support_comments)
1418 {
1419   GArray *savable_file_types;
1420   guint i;
1421   int ft;
1422   int default_ft = -1;
1423
1424   savable_file_types = wtap_get_savable_file_types(cf->cd_t, cf->linktypes);
1425
1426   if (savable_file_types != NULL) {
1427     /* OK, we have at least one file type we can save this file as.
1428        (If we didn't, we shouldn't have gotten here in the first
1429        place.)  Add them all to the combo box.  */
1430     for (i = 0; i < savable_file_types->len; i++) {
1431       ft = g_array_index(savable_file_types, int, i);
1432       if (must_support_comments) {
1433         if (ft != WTAP_FILE_PCAPNG)
1434           continue;
1435       }
1436       if (default_ft == -1)
1437         default_ft = ft; /* first file type is the default */
1438       ws_combo_box_append_text_and_pointer(GTK_COMBO_BOX(combo_box),
1439                                            wtap_file_type_string(ft),
1440                                            GINT_TO_POINTER(ft));
1441     }
1442     g_array_free(savable_file_types, TRUE);
1443   }
1444
1445   return default_ft;
1446 }
1447
1448 static void
1449 file_select_file_type_cb(GtkWidget *w, gpointer parent_arg)
1450 {
1451   GtkWidget *parent = parent_arg;
1452   int new_file_type;
1453   gpointer ptr;
1454   GtkWidget *compressed_cb;
1455
1456   compressed_cb = (GtkWidget *)g_object_get_data(G_OBJECT(parent), E_COMPRESSED_CB_KEY);
1457   if (! ws_combo_box_get_active_pointer(GTK_COMBO_BOX(w), &ptr)) {
1458     /* XXX - this can happen when we clear the list of file types
1459        and then reconstruct it. */
1460     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compressed_cb), FALSE);
1461     gtk_widget_set_sensitive(compressed_cb, FALSE);
1462     return;
1463   }
1464   new_file_type = GPOINTER_TO_INT(ptr);
1465
1466   if (!wtap_dump_can_compress(new_file_type)) {
1467     /* Can't compress this file type; turn off compression and make
1468        the compression checkbox insensitive. */
1469     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compressed_cb), FALSE);
1470     gtk_widget_set_sensitive(compressed_cb, FALSE);
1471   } else
1472     gtk_widget_set_sensitive(compressed_cb, TRUE);
1473 }
1474
1475 static check_savability_t
1476 check_save_as_with_comments(capture_file *cf, GtkWidget *file_chooser_w,
1477                             GtkWidget *ft_combo_box)
1478 {
1479   gpointer       ptr;
1480   int            selected_file_type;
1481   GtkWidget     *msg_dialog;
1482   gint           response;
1483   GtkWidget     *compressed_cb;
1484   gboolean       compressed;
1485
1486   /* Do we have any comments? */
1487   if (!cf_has_comments(cf)) {
1488     /* No.  Let the save happen; no comments to delete. */
1489     return SAVE;
1490   }
1491
1492   /* OK, we have comments.  Can we write them out in the selected
1493      format? */
1494   if (! ws_combo_box_get_active_pointer(GTK_COMBO_BOX(ft_combo_box), &ptr)) {
1495       g_assert_not_reached();  /* Programming error: somehow nothing is active */
1496   }
1497   selected_file_type = GPOINTER_TO_INT(ptr);
1498
1499   /* XXX - for now, we "know" that pcap-ng is the only format for which
1500      we support comments.  We should really ask Wiretap what the
1501      format in question supports (and handle different types of
1502      comments, some but not all of which some file formats might
1503      not support). */
1504   if (selected_file_type == WTAP_FILE_PCAPNG) {
1505     /* Yes - they selected pcap-ng.  Let the save happen; we can
1506        save the comments, so no need to delete them. */
1507     return SAVE;
1508   }
1509   /* No. Is pcap-ng one of the formats in which we can write this file? */
1510   if (wtap_dump_can_write_encaps(WTAP_FILE_PCAPNG, cf->linktypes)) {
1511     /* Yes.  Offer the user a choice of "Save in a format that
1512        supports comments", "Discard comments and save in the
1513        format you selected", or "Cancel", meaning "don't bother
1514        saving the file at all". */
1515     msg_dialog = gtk_message_dialog_new(GTK_WINDOW(file_chooser_w),
1516                                         GTK_DIALOG_DESTROY_WITH_PARENT,
1517                                         GTK_MESSAGE_QUESTION,
1518                                         GTK_BUTTONS_NONE,
1519   "The capture has comments, but the file format you chose "
1520   "doesn't support comments.  Do you want to save the capture "
1521   "in a format that supports comments, or discard the comments "
1522   "and save in the format you chose?");
1523 #ifndef _WIN32
1524     gtk_dialog_add_buttons(GTK_DIALOG(msg_dialog),
1525                            "Discard comments and save",
1526                            RESPONSE_DISCARD_COMMENTS_AND_SAVE,
1527                            GTK_STOCK_CANCEL,
1528                            GTK_RESPONSE_CANCEL,
1529                            "Save in another format",
1530                            RESPONSE_SAVE_IN_ANOTHER_FORMAT,
1531                            NULL);
1532 #else
1533     gtk_dialog_add_buttons(GTK_DIALOG(msg_dialog),
1534                            "Save in another format",
1535                            RESPONSE_SAVE_IN_ANOTHER_FORMAT,
1536                            GTK_STOCK_CANCEL,
1537                            GTK_RESPONSE_CANCEL,
1538                            "Discard comments and save",
1539                            RESPONSE_DISCARD_COMMENTS_AND_SAVE,
1540                            NULL);
1541 #endif
1542     gtk_dialog_set_default_response(GTK_DIALOG(msg_dialog),
1543                                     RESPONSE_SAVE_IN_ANOTHER_FORMAT);
1544   } else {
1545     /* No.  Offer the user a choice of "Discard comments and
1546        save in the format you selected" or "Cancel". */
1547     msg_dialog = gtk_message_dialog_new(GTK_WINDOW(file_chooser_w),
1548                                         GTK_DIALOG_DESTROY_WITH_PARENT,
1549                                         GTK_MESSAGE_QUESTION,
1550                                         GTK_BUTTONS_NONE,
1551   "The capture has comments, but no file format in which it "
1552   "can be saved supports comments.  Do you want to discard "
1553   "the comments and save in the format you chose?");
1554 #ifndef _WIN32
1555     gtk_dialog_add_buttons(GTK_DIALOG(msg_dialog),
1556                            "Discard comments and save",
1557                            RESPONSE_DISCARD_COMMENTS_AND_SAVE,
1558                            GTK_STOCK_CANCEL,
1559                            GTK_RESPONSE_CANCEL,
1560                            NULL);
1561 #else
1562     gtk_dialog_add_buttons(GTK_DIALOG(msg_dialog),
1563                            GTK_STOCK_CANCEL,
1564                            GTK_RESPONSE_CANCEL,
1565                            "Discard comments and save",
1566                            RESPONSE_DISCARD_COMMENTS_AND_SAVE,
1567                            NULL);
1568 #endif
1569     gtk_dialog_set_default_response(GTK_DIALOG(msg_dialog),
1570                                     GTK_RESPONSE_CANCEL);
1571   }
1572
1573   response = gtk_dialog_run(GTK_DIALOG(msg_dialog));
1574   gtk_widget_destroy(msg_dialog);
1575
1576   switch (response) {
1577
1578   case RESPONSE_SAVE_IN_ANOTHER_FORMAT:
1579     /* OK, the only other format we support is pcap-ng.  Make that
1580        the one and only format in the combo box, and return to
1581        let the user continue with the dialog.
1582
1583        XXX - removing all the formats from the combo box will clear
1584        the compressed checkbox; get the current value and restore
1585        it.
1586
1587        XXX - we know pcap-ng can be compressed; if we ever end up
1588        supporting saving comments in a format that *can't* be
1589        compressed, such as NetMon format, we must check this. */
1590     compressed_cb = (GtkWidget *)g_object_get_data(G_OBJECT(file_chooser_w),
1591                                                    E_COMPRESSED_CB_KEY);
1592     compressed = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compressed_cb));
1593     ws_combo_box_clear_text_and_pointer(GTK_COMBO_BOX(ft_combo_box));
1594     ws_combo_box_append_text_and_pointer(GTK_COMBO_BOX(ft_combo_box),
1595                                          wtap_file_type_string(WTAP_FILE_PCAPNG),
1596                                          GINT_TO_POINTER(WTAP_FILE_PCAPNG));
1597     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compressed_cb), compressed);
1598
1599     ws_combo_box_set_active(GTK_COMBO_BOX(ft_combo_box), 0); /* No callback */
1600     return SAVE_IN_ANOTHER_FORMAT;
1601
1602   case RESPONSE_DISCARD_COMMENTS_AND_SAVE:
1603     /* Save without the comments and, if that succeeds, delete the
1604        comments. */
1605     return SAVE_WITHOUT_COMMENTS;
1606
1607   case GTK_RESPONSE_CANCEL:
1608   case GTK_RESPONSE_NONE:
1609   case GTK_RESPONSE_DELETE_EVENT:
1610   default:
1611     /* Just give up. */
1612     return CANCELLED;
1613   }
1614 }
1615
1616 static void
1617 do_file_save_as(capture_file *cf, gboolean must_support_comments,
1618                 gboolean dont_reopen)
1619 {
1620 #ifdef USE_WIN32_FILE_DIALOGS
1621   if (win32_save_as_file(GDK_WINDOW_HWND(gtk_widget_get_window(top_level)),
1622                          cf, must_support_comments, dont_reopen)) {
1623     /* They discarded comments, so redraw the packet details window
1624        to reflect any packets that no longer have comments. */
1625     new_packet_list_queue_draw();
1626   }
1627 #else /* USE_WIN32_FILE_DIALOGS */
1628   GtkWidget     *file_save_as_w;
1629   GtkWidget     *main_vb, *ft_hb, *ft_lb, *ft_combo_box, *compressed_cb;
1630   int            default_ft;
1631   char          *cf_name;
1632   gboolean       discard_comments;
1633
1634   /* Default to saving in the file's current format. */
1635
1636   /* build the file selection */
1637   file_save_as_w = file_selection_new("Wireshark: Save Capture File As",
1638                                       FILE_SELECTION_SAVE);
1639   gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(file_save_as_w),
1640                                                  TRUE);
1641
1642   /* Container for each row of widgets */
1643
1644   main_vb = ws_gtk_box_new(GTK_ORIENTATION_VERTICAL, 5, FALSE);
1645   gtk_container_set_border_width(GTK_CONTAINER(main_vb), 5);
1646   file_selection_set_extra_widget(file_save_as_w, main_vb);
1647   gtk_widget_show(main_vb);
1648
1649   /* File type row */
1650   ft_hb = ws_gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3, FALSE);
1651   gtk_container_add(GTK_CONTAINER(main_vb), ft_hb);
1652   gtk_widget_show(ft_hb);
1653
1654   ft_lb = gtk_label_new("File type:");
1655   gtk_box_pack_start(GTK_BOX(ft_hb), ft_lb, FALSE, FALSE, 0);
1656   gtk_widget_show(ft_lb);
1657
1658   ft_combo_box = ws_combo_box_new_text_and_pointer();
1659
1660   /* Generate the list of file types we can save. */
1661   default_ft = set_file_type_list(ft_combo_box, cf, must_support_comments);
1662   gtk_box_pack_start(GTK_BOX(ft_hb), ft_combo_box, FALSE, FALSE, 0);
1663   gtk_widget_show(ft_combo_box);
1664   g_object_set_data(G_OBJECT(file_save_as_w), E_FILE_TYPE_COMBO_BOX_KEY, ft_combo_box);
1665
1666   /* compressed - if the file is currently compressed, and the default
1667      file type supports compression, turn the checkbox on */
1668   compressed_cb = gtk_check_button_new_with_label("Compress with gzip");
1669   gtk_container_add(GTK_CONTAINER(ft_hb), compressed_cb);
1670   if (cf->iscompressed && wtap_dump_can_compress(default_ft))
1671     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(compressed_cb), TRUE);
1672   gtk_widget_show(compressed_cb);
1673   g_object_set_data(G_OBJECT(file_save_as_w), E_COMPRESSED_CB_KEY, compressed_cb);
1674
1675   /* Ok: now "select" the default filetype which invokes file_select_file_type_cb */
1676   g_signal_connect(ft_combo_box, "changed", G_CALLBACK(file_select_file_type_cb), file_save_as_w);
1677   ws_combo_box_set_active(GTK_COMBO_BOX(ft_combo_box), 0);
1678
1679   /*
1680    * Loop until the user either selects a file or gives up.
1681    */
1682   for (;;) {
1683     cf_name = file_selection_run(file_save_as_w);
1684     if (cf_name == NULL) {
1685       /* User cancelled or closed the dialog. */
1686       return;
1687     }
1688
1689     /* If the file has comments, does the format the user selected
1690        support them?  If not, ask the user whether they want to
1691        discard the comments or choose a different format. */
1692     switch (check_save_as_with_comments(cf, file_save_as_w, ft_combo_box)) {
1693
1694     case SAVE:
1695       /* The file can be saved in the specified format as is;
1696          just drive on and save in the format they selected. */
1697       discard_comments = FALSE;
1698       break;
1699
1700     case SAVE_WITHOUT_COMMENTS:
1701       /* The file can't be saved in the specified format as is,
1702          but it can be saved without the comments, and the user
1703          said "OK, discard the comments", so save it in the
1704          format they specified without the comments. */
1705       discard_comments = TRUE;
1706       break;
1707
1708     case SAVE_IN_ANOTHER_FORMAT:
1709       /* There are file formats in which we can save this that
1710          support comments, and the user said not to delete the
1711          comments.  The combo box of file formats has had the
1712          formats that don't support comments trimmed from it,
1713          so run the dialog again, to let the user decide
1714          whether to save in one of those formats or give up. */
1715       g_free(cf_name);
1716       continue;
1717
1718     case CANCELLED:
1719       /* The user said "forget it".  Just get rid of the dialog box
1720          and return. */
1721       window_destroy(file_save_as_w);
1722       return;
1723     }
1724
1725 #ifndef _WIN32
1726     /* If the file exists and it's user-immutable or not writable,
1727        ask the user whether they want to override that. */
1728     if (!file_target_unwritable_ui(file_save_as_w, cf_name)) {
1729       /* They don't.  Let them try another file name or cancel. */
1730       g_free(cf_name);
1731       continue;
1732     }
1733 #endif
1734
1735     /* Attempt to save the file */
1736     g_free(cf_name);
1737     switch (file_save_as_cb(file_save_as_w, discard_comments, dont_reopen)) {
1738
1739     case CF_WRITE_OK:
1740       /* The save succeeded; we're done.
1741          If we discarded comments, redraw the packet list to reflect
1742          any packets that no longer have comments. */
1743       if (discard_comments)
1744         new_packet_list_queue_draw();
1745       return;
1746
1747     case CF_WRITE_ERROR:
1748       /* The save failed; let the user try again. */
1749       continue;
1750
1751     case CF_WRITE_ABORTED:
1752       /* The user aborted the save; just return. */
1753       return;
1754     }
1755   }
1756 #endif /* USE_WIN32_FILE_DIALOGS */
1757 }
1758
1759 void
1760 file_save_as_cmd_cb(GtkWidget *w _U_, gpointer data _U_)
1761 {
1762   do_file_save_as(&cfile, FALSE, FALSE);
1763 }
1764
1765 /* all tests ok, we only have to save the file */
1766 /* (and probably continue with a pending operation) */
1767 static cf_write_status_t
1768 file_save_as_cb(GtkWidget *fs, gboolean discard_comments,
1769                 gboolean dont_reopen)
1770 {
1771   GtkWidget *ft_combo_box;
1772   GtkWidget *compressed_cb;
1773   gchar     *cf_name;
1774   gchar     *dirname;
1775   gpointer   ptr;
1776   int        file_type;
1777   gboolean   compressed;
1778   cf_write_status_t status;
1779
1780   /* Hide the file chooser while doing the save. */
1781   gtk_widget_hide(fs);
1782
1783   cf_name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fs));
1784
1785   compressed_cb = (GtkWidget *)g_object_get_data(G_OBJECT(fs), E_COMPRESSED_CB_KEY);
1786   ft_combo_box  = (GtkWidget *)g_object_get_data(G_OBJECT(fs), E_FILE_TYPE_COMBO_BOX_KEY);
1787
1788   if (! ws_combo_box_get_active_pointer(GTK_COMBO_BOX(ft_combo_box), &ptr)) {
1789       g_assert_not_reached();  /* Programming error: somehow nothing is active */
1790   }
1791   file_type = GPOINTER_TO_INT(ptr);
1792   compressed = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compressed_cb));
1793
1794   /* Write out all the packets to the file with the specified name. */
1795   status = cf_save_packets(&cfile, cf_name, file_type, compressed,
1796                            discard_comments, dont_reopen);
1797   switch (status) {
1798
1799   case CF_WRITE_OK:
1800     /* The write succeeded; get rid of the file selection box. */
1801     /* cf_save_packets() might already closed our dialog! */
1802     window_destroy(fs);
1803
1804     /* Save the directory name for future file dialogs. */
1805     dirname = get_dirname(cf_name);  /* Overwrites cf_name */
1806     set_last_open_dir(dirname);
1807     break;
1808
1809   case CF_WRITE_ERROR:
1810     /* The write failed.
1811        just leave the file selection box around so that the user can,
1812        after they dismiss the alert box popped up for the error, try
1813        again. */
1814     break;
1815
1816   case CF_WRITE_ABORTED:
1817     /* The write was aborted; just get rid of the file selection
1818        box and return. */
1819     window_destroy(fs);
1820     break;
1821   }
1822   g_free(cf_name);
1823   return status;
1824 }
1825
1826 void
1827 file_export_specified_packets_cmd_cb(GtkWidget *widget _U_, gpointer data _U_)
1828 {
1829 #ifdef USE_WIN32_FILE_DIALOGS
1830   win32_export_specified_packets_file(GDK_WINDOW_HWND(gtk_widget_get_window(top_level)));
1831 #else /* USE_WIN32_FILE_DIALOGS */
1832   GtkWidget     *file_export_specified_packets_w;
1833   GtkWidget     *main_vb, *ft_hb, *ft_lb, *ft_combo_box, *range_fr, *range_tb,
1834                 *compressed_cb;
1835   packet_range_t range;
1836   char          *cf_name;
1837   gchar         *display_basename;
1838   GtkWidget     *msg_dialog;
1839
1840   /* Default to writing out all displayed packets, in the file's current format. */
1841
1842   /* init the packet range */
1843   packet_range_init(&range);
1844   range.process_filtered = TRUE;
1845   range.include_dependents = TRUE;
1846
1847   /* build the file selection */
1848   file_export_specified_packets_w = file_selection_new("Wireshark: Export Specified Packets",
1849                                                        FILE_SELECTION_SAVE);
1850   gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(file_export_specified_packets_w),
1851                                                  TRUE);
1852
1853   /* Container for each row of widgets */
1854
1855   main_vb = ws_gtk_box_new(GTK_ORIENTATION_VERTICAL, 5, FALSE);
1856   gtk_container_set_border_width(GTK_CONTAINER(main_vb), 5);
1857   file_selection_set_extra_widget(file_export_specified_packets_w, main_vb);
1858   gtk_widget_show(main_vb);
1859
1860   /*** Packet Range frame ***/
1861   range_fr = gtk_frame_new("Packet Range");
1862   gtk_box_pack_start(GTK_BOX(main_vb), range_fr, FALSE, FALSE, 0);
1863   gtk_widget_show(range_fr);
1864
1865   /* range table */
1866   range_tb = range_new(&range, TRUE);
1867   gtk_container_add(GTK_CONTAINER(range_fr), range_tb);
1868   gtk_widget_show(range_tb);
1869
1870   /* File type row */
1871   ft_hb = ws_gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 3, FALSE);
1872   gtk_container_add(GTK_CONTAINER(main_vb), ft_hb);
1873   gtk_widget_show(ft_hb);
1874
1875   ft_lb = gtk_label_new("File type:");
1876   gtk_box_pack_start(GTK_BOX(ft_hb), ft_lb, FALSE, FALSE, 0);
1877   gtk_widget_show(ft_lb);
1878
1879   ft_combo_box = ws_combo_box_new_text_and_pointer();
1880
1881   /* Generate the list of file types we can save. */
1882   set_file_type_list(ft_combo_box, &cfile, FALSE);
1883   gtk_box_pack_start(GTK_BOX(ft_hb), ft_combo_box, FALSE, FALSE, 0);
1884   gtk_widget_show(ft_combo_box);
1885   g_object_set_data(G_OBJECT(file_export_specified_packets_w), E_FILE_TYPE_COMBO_BOX_KEY, ft_combo_box);
1886
1887   /* dynamic values in the range frame */
1888   range_update_dynamics(range_tb);
1889
1890   /* compressed */
1891   compressed_cb = gtk_check_button_new_with_label("Compress with gzip");
1892   gtk_container_add(GTK_CONTAINER(ft_hb), compressed_cb);
1893   gtk_widget_show(compressed_cb);
1894   g_object_set_data(G_OBJECT(file_export_specified_packets_w), E_COMPRESSED_CB_KEY, compressed_cb);
1895
1896   /* Ok: now "select" the default filetype which invokes file_select_file_type_cb */
1897   g_signal_connect(ft_combo_box, "changed", G_CALLBACK(file_select_file_type_cb), file_export_specified_packets_w);
1898   ws_combo_box_set_active(GTK_COMBO_BOX(ft_combo_box), 0);
1899
1900   /*
1901    * Loop until the user either selects a file or gives up.
1902    */
1903   for (;;) {
1904     cf_name = file_selection_run(file_export_specified_packets_w);
1905     if (cf_name == NULL) {
1906       /* User cancelled or closed the dialog. */
1907       return;
1908     }
1909
1910     /* Check whether the range is valid. */
1911     if (!range_check_validity_modal(file_export_specified_packets_w, &range)) {
1912       /* The range isn't valid; the user was told that, and dismissed
1913          the dialog telling them that, so let them fix the range
1914          and try again, or cancel. */
1915       g_free(cf_name);
1916       continue;
1917     }
1918
1919     /*
1920      * Check that we're not going to save on top of the current
1921      * capture file.
1922      * We do it here so we catch all cases ...
1923      * Unfortunately, the file requester gives us an absolute file
1924      * name and the read file name may be relative (if supplied on
1925      * the command line). From Joerg Mayer.
1926      */
1927     if (files_identical(cfile.filename, cf_name)) {
1928       display_basename = g_filename_display_basename(cf_name);
1929       msg_dialog = gtk_message_dialog_new(GTK_WINDOW(file_export_specified_packets_w),
1930                                           GTK_DIALOG_DESTROY_WITH_PARENT,
1931                                           GTK_MESSAGE_ERROR,
1932                                           GTK_BUTTONS_OK,
1933   "The file \"%s\" is the capture file from which you're exporting the packets.",
1934                                           display_basename);
1935       g_free(display_basename);
1936       gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(msg_dialog),
1937            "You cannot export packets on top of the current capture file.");
1938       gtk_dialog_run(GTK_DIALOG(msg_dialog));
1939       gtk_widget_destroy(msg_dialog);
1940       g_free(cf_name);
1941       continue;
1942     }
1943
1944 #ifndef _WIN32
1945     /* If the file exists and it's user-immutable or not writable,
1946        ask the user whether they want to override that. */
1947     if (!file_target_unwritable_ui(file_export_specified_packets_w, cf_name)) {
1948       /* They don't.  Let them try another file name or cancel. */
1949       g_free(cf_name);
1950       continue;
1951     }
1952 #endif
1953
1954     /* attempt to export the packets */
1955     g_free(cf_name);
1956     switch (file_export_specified_packets_cb(file_export_specified_packets_w,
1957                                              &range)) {
1958
1959     case CF_WRITE_OK:
1960       /* The save succeeded; we're done. */
1961       return;
1962
1963     case CF_WRITE_ERROR:
1964       /* The save failed; let the user try again */
1965       continue;
1966
1967     case CF_WRITE_ABORTED:
1968       /* The user aborted the save; just return. */
1969       return;
1970     }
1971   }
1972 #endif /* USE_WIN32_FILE_DIALOGS */
1973 }
1974
1975 /* all tests ok, we only have to write out the packets */
1976 /* (and probably continue with a pending operation) */
1977 static cf_write_status_t
1978 file_export_specified_packets_cb(GtkWidget *fs, packet_range_t *range)
1979 {
1980   GtkWidget *ft_combo_box;
1981   GtkWidget *compressed_cb;
1982   gchar     *cf_name;
1983   gchar     *dirname;
1984   gpointer   ptr;
1985   int        file_type;
1986   gboolean   compressed;
1987   cf_write_status_t status;
1988
1989   /* Hide the file chooser while we're doing the export. */
1990   gtk_widget_hide(fs);
1991
1992   cf_name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fs));
1993
1994   compressed_cb = (GtkWidget *)g_object_get_data(G_OBJECT(fs), E_COMPRESSED_CB_KEY);
1995   ft_combo_box  = (GtkWidget *)g_object_get_data(G_OBJECT(fs), E_FILE_TYPE_COMBO_BOX_KEY);
1996
1997   if (! ws_combo_box_get_active_pointer(GTK_COMBO_BOX(ft_combo_box), &ptr)) {
1998       g_assert_not_reached();  /* Programming error: somehow nothing is active */
1999   }
2000   file_type = GPOINTER_TO_INT(ptr);
2001   compressed = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compressed_cb));
2002
2003   /* Write out the specified packets to the file with the specified name. */
2004   status = cf_export_specified_packets(&cfile, cf_name, range, file_type,
2005                                        compressed);
2006   switch (status) {
2007
2008   case CF_WRITE_OK:
2009     /* The write succeeded; get rid of the file selection box. */
2010     /* cf_export_specified_packets() might already closed our dialog! */
2011     window_destroy(GTK_WIDGET(fs));
2012
2013     /* Save the directory name for future file dialogs.
2014        XXX - should there be separate ones for "Save As" and
2015        "Export Specified Packets"? */
2016     dirname = get_dirname(cf_name);  /* Overwrites cf_name */
2017     set_last_open_dir(dirname);
2018     break;
2019
2020   case CF_WRITE_ERROR:
2021     /* The write failed.
2022        just leave the file selection box around so that the user can,
2023        after they dismiss the alert box popped up for the error, try
2024        again. */
2025     break;
2026
2027   case CF_WRITE_ABORTED:
2028     /* The write was aborted; just get rid of the file selection
2029        box and return. */
2030     window_destroy(fs);
2031     break;
2032   }
2033   g_free(cf_name);
2034   return status;
2035 }
2036
2037 /* Reload a file using the current read and display filters */
2038 void
2039 file_reload_cmd_cb(GtkWidget *w _U_, gpointer data _U_) {
2040   cf_reload(&cfile);
2041 }
2042
2043 /******************** Color Filters *********************************/
2044 /*
2045  * Keep a static pointer to the current "Color Export" window, if
2046  * any, so that if somebody tries to do "Export"
2047  * while there's already a "Color Export" window up, we just pop
2048  * up the existing one, rather than creating a new one.
2049  */
2050 static GtkWidget *file_color_import_w;
2051
2052 /* sets the file path to the global color filter file.
2053    WARNING: called by both the import and the export dialog.
2054 */
2055 static void
2056 color_global_cb(GtkWidget *widget _U_, gpointer data)
2057 {
2058   GtkWidget *fs_widget = (GtkWidget *)data;
2059   gchar *path;
2060
2061   /* decide what file to open (from dfilter code) */
2062   path = get_datafile_path("colorfilters");
2063
2064   gtk_file_chooser_select_filename(GTK_FILE_CHOOSER(fs_widget), path);
2065
2066   g_free(path);
2067 }
2068
2069 /* Import color filters */
2070 void
2071 file_color_import_cmd_cb(GtkWidget *color_filters, gpointer filter_list _U_)
2072 {
2073 #ifdef USE_WIN32_FILE_DIALOGS
2074   win32_import_color_file(GDK_WINDOW_HWND(gtk_widget_get_window(top_level)), color_filters);
2075 #else /* USE_WIN32_FILE_DIALOGS */
2076   GtkWidget     *main_vb, *cfglobal_but;
2077   gchar         *cf_name, *s;
2078
2079   /* No Apply button, and "OK" just sets our text widget, it doesn't
2080      activate it (i.e., it doesn't cause us to try to open the file). */
2081
2082   file_color_import_w = file_selection_new("Wireshark: Import Color Filters",
2083                                            FILE_SELECTION_OPEN);
2084
2085   /* Container for each row of widgets */
2086   main_vb = ws_gtk_box_new(GTK_ORIENTATION_VERTICAL, 3, FALSE);
2087   gtk_container_set_border_width(GTK_CONTAINER(main_vb), 5);
2088   file_selection_set_extra_widget(file_color_import_w, main_vb);
2089   gtk_widget_show(main_vb);
2090
2091   cfglobal_but = gtk_button_new_with_label("Global Color Filter File");
2092   gtk_container_add(GTK_CONTAINER(main_vb), cfglobal_but);
2093   g_signal_connect(cfglobal_but, "clicked",
2094                    G_CALLBACK(color_global_cb), file_color_import_w);
2095   gtk_widget_show(cfglobal_but);
2096
2097   /*
2098    * Loop until the user either selects a file or gives up.
2099    */
2100   for (;;) {
2101     cf_name = file_selection_run(file_color_import_w);
2102     if (cf_name == NULL) {
2103       /* User cancelled or closed the dialog. */
2104       return;
2105     }
2106
2107     /* Try to open the color filter file. */
2108     if (!color_filters_import(cf_name, color_filters)) {
2109       /* We couldn't open it; don't dismiss the open dialog box,
2110          just leave it around so that the user can, after they
2111          dismiss the alert box popped up for the open error,
2112          try again. */
2113       g_free(cf_name);
2114       continue;
2115     }
2116
2117     /* We've crossed the Rubicon; get rid of the file selection box. */
2118     window_destroy(GTK_WIDGET(file_color_import_w));
2119
2120     /* Save the name of the containing directory specified in the path name,
2121        if any; we can write over cf_name, which is a good thing, given that
2122        "get_dirname()" does write over its argument. */
2123     s = get_dirname(cf_name);
2124     set_last_open_dir(s);
2125
2126     g_free(cf_name);
2127     return;
2128   }
2129 #endif /* USE_WIN32_FILE_DIALOGS */
2130 }
2131
2132 /*
2133  * Set the "Export only selected filters" toggle button as appropriate for
2134  * the current output file type and count of selected filters.
2135  *
2136  * Called when the "Export" dialog box is created and when the selected
2137  * count changes.
2138  */
2139 static void
2140 color_set_export_selected_sensitive(GtkWidget * cfselect_cb)
2141 {
2142   /* We can request that only the selected filters be saved only if
2143         there *are* selected filters. */
2144   if (color_selected_count() != 0)
2145     gtk_widget_set_sensitive(cfselect_cb, TRUE);
2146   else {
2147     /* Force the "Export only selected filters" toggle to "false", turn
2148        off the flag it controls. */
2149     color_selected = FALSE;
2150     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfselect_cb), FALSE);
2151     gtk_widget_set_sensitive(cfselect_cb, FALSE);
2152   }
2153 }
2154
2155 static void
2156 color_toggle_selected_cb(GtkWidget *widget, gpointer data _U_)
2157 {
2158   color_selected = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (widget));
2159 }
2160
2161 void
2162 file_color_export_cmd_cb(GtkWidget *w _U_, gpointer filter_list)
2163 {
2164 #ifdef USE_WIN32_FILE_DIALOGS
2165   win32_export_color_file(GDK_WINDOW_HWND(gtk_widget_get_window(top_level)), filter_list);
2166 #else /* USE_WIN32_FILE_DIALOGS */
2167   GtkWidget *file_color_export_w;
2168   GtkWidget *main_vb, *cfglobal_but;
2169   GtkWidget *cfselect_cb;
2170   gchar     *cf_name;
2171   gchar     *dirname;
2172
2173   color_selected   = FALSE;
2174
2175   file_color_export_w = file_selection_new("Wireshark: Export Color Filters",
2176                                            FILE_SELECTION_SAVE);
2177   gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(file_color_export_w),
2178                                                  TRUE);
2179
2180   /* Container for each row of widgets */
2181   main_vb = ws_gtk_box_new(GTK_ORIENTATION_VERTICAL, 3, FALSE);
2182   gtk_container_set_border_width(GTK_CONTAINER(main_vb), 5);
2183   file_selection_set_extra_widget(file_color_export_w, main_vb);
2184   gtk_widget_show(main_vb);
2185
2186   cfselect_cb = gtk_check_button_new_with_label("Export only selected filters");
2187   gtk_container_add(GTK_CONTAINER(main_vb), cfselect_cb);
2188   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cfselect_cb), FALSE);
2189   g_signal_connect(cfselect_cb, "toggled",
2190                    G_CALLBACK(color_toggle_selected_cb), NULL);
2191   gtk_widget_show(cfselect_cb);
2192   color_set_export_selected_sensitive(cfselect_cb);
2193
2194   cfglobal_but = gtk_button_new_with_label("Global Color Filter File");
2195   gtk_container_add(GTK_CONTAINER(main_vb), cfglobal_but);
2196   g_signal_connect(cfglobal_but, "clicked",
2197                    G_CALLBACK(color_global_cb), file_color_export_w);
2198   gtk_widget_show(cfglobal_but);
2199
2200   /*
2201    * Loop until the user either selects a file or gives up.
2202    */
2203   for (;;) {
2204     cf_name = file_selection_run(file_color_export_w);
2205     if (cf_name == NULL) {
2206       /* User cancelled or closed the dialog. */
2207       return;
2208     }
2209
2210 #ifndef _WIN32
2211     /* If the file exists and it's user-immutable or not writable,
2212        ask the user whether they want to override that. */
2213     if (!file_target_unwritable_ui(file_color_export_w, cf_name)) {
2214       /* They don't.  Let them try another file name or cancel. */
2215       g_free(cf_name);
2216       continue;
2217     }
2218 #endif
2219
2220     /* Write out the filters (all, or only the ones that are currently
2221        displayed or selected) to the file with the specified name. */
2222     if (!color_filters_export(cf_name, filter_list, color_selected)) {
2223       /* The write failed; don't dismiss the open dialog box,
2224          just leave it around so that the user can, after they
2225          dismiss the alert box popped up for the error, try again. */
2226       g_free(cf_name);
2227       continue;
2228     }
2229
2230     /* The write succeeded; get rid of the file selection box. */
2231     window_destroy(GTK_WIDGET(file_color_export_w));
2232
2233     /* Save the directory name for future file dialogs. */
2234     dirname = get_dirname(cf_name);  /* Overwrites cf_name */
2235     set_last_open_dir(dirname);
2236     g_free(cf_name);
2237   }
2238 #endif /* USE_WIN32_FILE_DIALOGS */
2239 }
2240
2241 /*
2242  * Editor modelines
2243  *
2244  * Local Variables:
2245  * c-basic-offset: 2
2246  * tab-width: 8
2247  * indent-tabs-mode: nil
2248  * End:
2249  *
2250  * ex: set shiftwidth=2 tabstop=8 expandtab:
2251  * :indentSize=2:tabSize=8:noTabs=true:
2252  */