Simplify the ciode a bit.
[obnox/wireshark/wip.git] / gtk / filter_dlg.c
1 /* filter_dlg.c
2  * Dialog boxes for (display and capture) filter editing
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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23  */
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include <string.h>
30
31 #include <gtk/gtk.h>
32
33 #include <epan/filesystem.h>
34 #include <epan/prefs.h>
35 #include <epan/proto.h>
36
37 #include "../filters.h"
38 #include "../simple_dialog.h"
39 #include "../main_statusbar.h"
40
41 #include "gtk/main.h"
42 #include "gtk/filter_dlg.h"
43 #include "gtk/dlg_utils.h"
44 #include "gtk/gui_utils.h"
45 #include "gtk/dfilter_expr_dlg.h"
46 #include "gtk/stock_icons.h"
47 #include "gtk/gtkglobals.h"
48 #include "gtk/help_dlg.h"
49 #include "gtk/filter_autocomplete.h"
50
51 #include "gtk/old-gtk-compat.h"
52
53 #define E_FILT_DIALOG_PTR_KEY       "filter_dialog_ptr"
54 #define E_FILT_BUTTON_PTR_KEY       "filter_button_ptr"
55 #define E_FILT_PARENT_FILTER_TE_KEY "filter_parent_filter_te"
56 #define E_FILT_CONSTRUCT_ARGS_KEY   "filter_construct_args"
57 #define E_FILT_LIST_ITEM_MODEL_KEY  "filter_list_item_model"
58 #define E_FILT_LBL_KEY              "filter_label"
59 #define E_FILT_FILTER_L_KEY         "filter_filter_l"
60 #define E_FILT_CHG_BT_KEY           "filter_chg_bt"
61 #define E_FILT_COPY_BT_KEY          "filter_copy_bt"
62 #define E_FILT_DEL_BT_KEY           "filter_del_bt"
63 #define E_FILT_NAME_TE_KEY          "filter_name_te"
64 #define E_FILT_DBLFUNC_KEY          "filter_dblfunc"
65 #define E_FILT_DBLARG_KEY           "filter_dblarg"
66 #define E_FILT_DBLACTIVATE_KEY      "filter_dblactivate"
67
68 typedef struct _filter_cb_data {
69   GList     *fl;
70   GtkWidget *win;
71 } filter_cb_data;
72
73 static GtkWidget *filter_dialog_new(GtkWidget *button, GtkWidget *filter_te,
74                                     filter_list_type_t list_type,
75                                     construct_args_t *construct_args);
76 static void filter_dlg_dclick(GtkWidget *dummy, gpointer main_w_arg,
77                   gpointer activate);
78 static void filter_dlg_ok_cb(GtkWidget *ok_bt, gpointer data);
79 static void filter_dlg_apply_cb(GtkWidget *apply_bt, gpointer data);
80 static void filter_apply(GtkWidget *main_w, gboolean destroy);
81 static void filter_dlg_save(filter_list_type_t list_type);
82 static void filter_dlg_save_cb(GtkWidget *save_bt, gpointer parent_w);
83 static void filter_dlg_destroy_cb(GtkWidget *win, gpointer data);
84
85 static gboolean
86 filter_dlg_delete_event_cb(GtkWidget *prefs_w, GdkEvent *event, gpointer data);
87 static void
88 filter_dlg_cancel_cb(GtkWidget *cancel_bt, gpointer data);
89
90 static gboolean filter_sel_list_button_cb(GtkWidget *, GdkEventButton *,
91                                       gpointer);
92 static void filter_sel_list_cb(GtkTreeSelection *, gpointer);
93 static void filter_new_bt_clicked_cb(GtkWidget *, gpointer);
94 static void filter_del_bt_clicked_cb(GtkWidget *, gpointer);
95 static void filter_name_te_changed_cb(GtkWidget *, gpointer);
96
97 #ifdef HAVE_LIBPCAP
98 /* Create a filter dialog for constructing a capture filter.
99
100    This is to be used as a callback for a button next to a text entry box,
101    which, when clicked, pops up this dialog to allow you to construct a
102    display filter by browsing the list of saved filters (the dialog
103    for constructing expressions assumes display filter syntax, not
104    capture filter syntax).  The "OK" button sets the text entry box to the
105    constructed filter and activates that text entry box (which should have
106    no effect in the main capture dialog); this dialog is then dismissed. */
107 void
108 capture_filter_construct_cb(GtkWidget *w, gpointer user_data _U_)
109 {
110     GtkWidget *filter_browse_w;
111     GtkWidget *parent_filter_te;
112     /* No Apply button, and "OK" just sets our text widget, it doesn't
113        activate it (i.e., it doesn't cause us to try to open the file). */
114     static construct_args_t args = {
115         "Wireshark: Capture Filter",
116         FALSE,
117         FALSE,
118         FALSE
119     };
120
121     /* Has a filter dialog box already been opened for that button? */
122     filter_browse_w = g_object_get_data(G_OBJECT(w), E_FILT_DIALOG_PTR_KEY);
123
124     if (filter_browse_w != NULL) {
125         /* Yes.  Just re-activate that dialog box. */
126         reactivate_window(filter_browse_w);
127         return;
128     }
129
130     /* No.  Get the text entry attached to the button. */
131     parent_filter_te = g_object_get_data(G_OBJECT(w), E_FILT_TE_PTR_KEY);
132
133     /* Now create a new dialog, without an "Add Expression..." button. */
134     filter_dialog_new(w, parent_filter_te, CFILTER_LIST, &args);
135 }
136 #endif
137
138 /* Create a filter dialog for constructing a display filter.
139
140    This is to be used as a callback for a button next to a text entry box,
141    which, when clicked, pops up this dialog to allow you to construct a
142    display filter by browsing the list of saved filters and/or by adding
143    test expressions constructed with another dialog.  The "OK" button
144    sets the text entry box to the constructed filter and activates that
145    text entry box, causing the filter to be used; this dialog is then
146    dismissed.
147
148    If "wants_apply_button" is non-null, we add an "Apply" button that
149    acts like "OK" but doesn't dismiss this dialog. */
150 void
151 display_filter_construct_cb(GtkWidget *w, gpointer construct_args_ptr)
152 {
153     construct_args_t *construct_args = construct_args_ptr;
154     GtkWidget *filter_browse_w;
155     GtkWidget *parent_filter_te;
156
157     /* Has a filter dialog box already been opened for the button? */
158     filter_browse_w = g_object_get_data(G_OBJECT(w), E_FILT_DIALOG_PTR_KEY);
159
160     if (filter_browse_w != NULL) {
161         /* Yes.  Just re-activate that dialog box. */
162         reactivate_window(filter_browse_w);
163         return;
164     }
165
166     /* No.  Get the text entry attached to the button. */
167     parent_filter_te = g_object_get_data(G_OBJECT(w), E_FILT_TE_PTR_KEY);
168
169     /* Now create a new dialog, possibly with an "Apply" button, and
170        definitely with an "Add Expression..." button. */
171     filter_dialog_new(w, parent_filter_te, DFILTER_LIST, construct_args);
172 }
173
174 /* Should be called when a button that creates filters is destroyed; it
175    destroys any filter dialog created by that button. */
176 void
177 filter_button_destroy_cb(GtkWidget *button, gpointer user_data _U_)
178 {
179     GtkWidget *filter_w;
180
181     /* Is there a filter edit/selection dialog associated with this
182        button? */
183     filter_w = g_object_get_data(G_OBJECT(button), E_FILT_DIALOG_PTR_KEY);
184
185     if (filter_w != NULL) {
186         /* Yes.  Break the association, and destroy the dialog. */
187         g_object_set_data(G_OBJECT(button), E_FILT_DIALOG_PTR_KEY, NULL);
188         window_destroy(filter_w);
189     }
190 }
191
192 #ifdef HAVE_LIBPCAP
193 static GtkWidget *global_cfilter_w;
194
195 /* Create a filter dialog for editing capture filters; this is to be used
196    as a callback for menu items, toolbars, etc.. */
197 void
198 cfilter_dialog_cb(GtkWidget *w _U_)
199 {
200     /* No Apply button, and there's no text widget to set, much less
201        activate, on "OK". */
202     static construct_args_t args = {
203         "Wireshark: Capture Filter",
204         FALSE,
205         FALSE,
206         FALSE
207     };
208
209     /* Has a filter dialog box already been opened for editing
210        capture filters? */
211     if (global_cfilter_w != NULL) {
212         /* Yes.  Just reactivate it. */
213         reactivate_window(global_cfilter_w);
214         return;
215     }
216
217     /*
218      * No.  Create one; we didn't pop this up as a result of pressing
219      * a button next to some text entry field, so don't associate it
220      * with a text entry field or button.
221      */
222     global_cfilter_w = filter_dialog_new(NULL, NULL, CFILTER_LIST, &args);
223 }
224 #endif
225
226 /* Create a filter dialog for editing display filters; this is to be used
227    as a callback for menu items, toolbars, etc.. */
228 void
229 dfilter_dialog_cb(GtkWidget *w _U_)
230 {
231     static construct_args_t args = {
232         "Wireshark: Display Filter",
233         TRUE,
234         TRUE,
235         FALSE
236     };
237
238         display_filter_construct_cb(g_object_get_data(G_OBJECT(top_level), E_FILT_BT_PTR_KEY), &args);
239 }
240
241 /* List of capture filter dialogs, so that if the list of filters changes
242   (the model, if you will), we can update all of their lists displaying
243    the filters (the views). */
244 static GList *cfilter_dialogs;
245
246 /* List of display filter dialogs, so that if the list of filters changes
247   (the model, if you will), we can update all of their lists displaying
248    the filters (the views). */
249 static GList *dfilter_dialogs;
250
251 static void
252 remember_filter_dialog(GtkWidget *main_w, GList **filter_dialogs)
253 {
254     *filter_dialogs = g_list_append(*filter_dialogs, main_w);
255 }
256
257 /* Remove a filter dialog from the specified list of filter_dialogs. */
258 static void
259 forget_filter_dialog(GtkWidget *main_w, filter_list_type_t list_type)
260 {
261     switch (list_type) {
262
263     case CFILTER_EDITED_LIST:
264         cfilter_dialogs = g_list_remove(cfilter_dialogs, main_w);
265         break;
266
267     case DFILTER_EDITED_LIST:
268         dfilter_dialogs = g_list_remove(dfilter_dialogs, main_w);
269         break;
270
271     default:
272         g_assert_not_reached();
273         break;
274     }
275 }
276
277 /* Get the dialog list corresponding to a particular filter list. */
278 static GList *
279 get_filter_dialog_list(filter_list_type_t list_type)
280 {
281     switch (list_type) {
282
283     case CFILTER_EDITED_LIST:
284         return cfilter_dialogs;
285
286     case DFILTER_EDITED_LIST:
287         return dfilter_dialogs;
288
289     default:
290         g_assert_not_reached();
291         return NULL;
292     }
293 }
294
295
296 static GtkTreeIter *
297 fill_list(GtkWidget  *main_w, filter_list_type_t list_type, const gchar *filter_te_str)
298 {
299     GList      *fl_entry;
300     filter_def *filt;
301     GtkTreeView       *filter_l;
302     GtkListStore      *store;
303     GtkTreeIter       iter;
304     GtkTreeIter       *l_select = NULL;
305
306     filter_l = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(main_w), E_FILT_FILTER_L_KEY));
307     store = GTK_LIST_STORE(gtk_tree_view_get_model(filter_l));
308
309     /* fill in data */
310     fl_entry = get_filter_list_first(list_type);
311     while (fl_entry != NULL) {
312         filt    = (filter_def *) fl_entry->data;
313         gtk_list_store_append(store, &iter);
314         gtk_list_store_set(store, &iter, 0, filt->name,
315                    1, fl_entry, -1);
316
317         if (filter_te_str && filt->strval) {
318             if (strcmp(filter_te_str, filt->strval) == 0) {
319                 /*
320                  * XXX - We're assuming that we can just copy a GtkTreeIter
321                  * and use it later without any crashes.  This may not be a
322                  * valid assumption.
323                  */
324                 l_select = g_memdup(&iter, sizeof(iter));
325             }
326         }
327
328         fl_entry = fl_entry->next;
329     }
330     return l_select;
331 }
332
333 #if 0
334 static void
335 clear_list(GtkWidget *main_w) {
336     GtkWidget    *filter_l = g_object_get_data(G_OBJECT(main_w), E_FILT_FILTER_L_KEY);
337     GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(filter_l));
338
339     gtk_list_store_clear(GTK_LIST_STORE(model));
340 }
341 #endif /* 0 */
342
343 static GtkWidget *
344 filter_dialog_new(GtkWidget *button, GtkWidget *parent_filter_te,
345                   filter_list_type_t list_type, construct_args_t *construct_args)
346 {
347     GtkWidget  *main_w,           /* main window */
348                *main_vb,          /* main container */
349                *bbox,             /* button container */
350                *ok_bt,            /* "OK" button */
351                *apply_bt,         /* "Apply" button */
352                *save_bt,          /* "Save" button */
353                *cancel_bt,        /* "Cancel" button */
354                *help_bt;          /* "Help" button */
355     GtkWidget  *filter_vb,        /* filter settings box */
356                *props_vb;
357     GtkWidget  *top_hb,
358                *list_bb,
359                *new_bt,
360                *del_bt,
361                *filter_sc,
362                *filter_l,
363                *middle_hb,
364                *name_lb,
365                *name_te,
366                *bottom_hb,
367                *filter_lb,
368                *filter_te,
369                *add_expression_bt,
370                *filter_fr,
371                *edit_fr,
372                *props_fr;
373     GdkWindow  *parent;
374     static filter_list_type_t cfilter_list_type = CFILTER_EDITED_LIST;
375     static filter_list_type_t dfilter_list_type = DFILTER_EDITED_LIST;
376     filter_list_type_t *filter_list_type_p;
377     GList       **filter_dialogs;
378     const gchar *filter_te_str = NULL;
379     GtkListStore      *store;
380     GtkCellRenderer   *renderer;
381     GtkTreeViewColumn *column;
382     GtkTreeSelection  *sel;
383     GtkTreeIter       *l_select;
384     gchar *list_name = NULL;
385
386     /* Get a pointer to a static variable holding the type of filter on
387        which we're working, so we can pass that pointer to callback
388        routines. */
389     switch (list_type) {
390
391     case CFILTER_LIST:
392         filter_dialogs = &cfilter_dialogs;
393         filter_list_type_p = &cfilter_list_type;
394         list_type = CFILTER_EDITED_LIST;
395         list_name = "Capture Filter";
396         break;
397
398     case DFILTER_LIST:
399         filter_dialogs = &dfilter_dialogs;
400         filter_list_type_p = &dfilter_list_type;
401         list_type = DFILTER_EDITED_LIST;
402         list_name = "Display Filter";
403         break;
404
405     default:
406         g_assert_not_reached();
407         filter_dialogs = NULL;
408         filter_list_type_p = NULL;
409         break;
410     }
411
412     main_w = dlg_conf_window_new(construct_args->title);
413     gtk_window_set_default_size(GTK_WINDOW(main_w), 400, 400);
414     g_object_set_data(G_OBJECT(main_w), E_FILT_CONSTRUCT_ARGS_KEY, construct_args);
415
416     main_vb = gtk_vbox_new(FALSE, 0);
417     gtk_container_set_border_width(GTK_CONTAINER(main_vb), 5);
418     gtk_container_add(GTK_CONTAINER(main_w), main_vb);
419     gtk_widget_show(main_vb);
420
421     /* Make sure everything is set up */
422     if (parent_filter_te)
423         filter_te_str = gtk_entry_get_text(GTK_ENTRY(parent_filter_te));
424
425     /* Container for each row of widgets */
426     filter_vb = gtk_vbox_new(FALSE, 0);
427     gtk_container_set_border_width(GTK_CONTAINER(filter_vb), 0);
428     gtk_container_add(GTK_CONTAINER(main_vb), filter_vb);
429     gtk_widget_show(filter_vb);
430
431     /* Top row: Buttons and filter list */
432     top_hb = gtk_hbox_new(FALSE, 0);
433     gtk_container_add(GTK_CONTAINER(filter_vb), top_hb);
434     gtk_widget_show(top_hb);
435
436     edit_fr = gtk_frame_new("Edit");
437     gtk_box_pack_start(GTK_BOX(top_hb), edit_fr, FALSE, FALSE, 0);
438     gtk_widget_show(edit_fr);
439
440     list_bb = gtk_vbox_new(TRUE, 0);
441     gtk_container_set_border_width(GTK_CONTAINER(list_bb), 5);
442     gtk_container_add(GTK_CONTAINER(edit_fr), list_bb);
443     gtk_widget_show(list_bb);
444
445     new_bt = gtk_button_new_from_stock(GTK_STOCK_NEW);
446     g_signal_connect(new_bt, "clicked", G_CALLBACK(filter_new_bt_clicked_cb), filter_list_type_p);
447     gtk_widget_show(new_bt);
448     gtk_box_pack_start (GTK_BOX (list_bb), new_bt, FALSE, FALSE, 0);
449         gtk_widget_set_tooltip_text(new_bt, "Create a new filter at the end of the list (with the current properties)");
450
451     del_bt = gtk_button_new_from_stock(GTK_STOCK_DELETE);
452     gtk_widget_set_sensitive(del_bt, FALSE);
453     g_signal_connect(del_bt, "clicked", G_CALLBACK(filter_del_bt_clicked_cb), filter_list_type_p);
454     g_object_set_data(G_OBJECT(main_w), E_FILT_DEL_BT_KEY, del_bt);
455     gtk_widget_show(del_bt);
456     gtk_box_pack_start (GTK_BOX (list_bb), del_bt, FALSE, FALSE, 0);
457         gtk_widget_set_tooltip_text(del_bt, "Delete the selected filter");
458
459     filter_fr = gtk_frame_new(list_name);
460     gtk_box_pack_start(GTK_BOX(top_hb), filter_fr, TRUE, TRUE, 0);
461     gtk_widget_show(filter_fr);
462
463     filter_sc = scrolled_window_new(NULL, NULL);
464     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(filter_sc),
465                                    GTK_SHADOW_IN);
466
467     gtk_container_set_border_width  (GTK_CONTAINER (filter_sc), 5);
468     gtk_container_add(GTK_CONTAINER(filter_fr), filter_sc);
469     gtk_widget_show(filter_sc);
470
471     store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
472     filter_l = tree_view_new(GTK_TREE_MODEL(store));
473     gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(filter_l), FALSE);
474     renderer = gtk_cell_renderer_text_new();
475     column = gtk_tree_view_column_new_with_attributes("", renderer, "text",
476                                                       0, NULL);
477     gtk_tree_view_column_set_sort_column_id(column, 0);
478     gtk_tree_view_append_column(GTK_TREE_VIEW(filter_l), column);
479     sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(filter_l));
480     gtk_tree_selection_set_mode(sel, GTK_SELECTION_SINGLE);
481     g_signal_connect(sel, "changed", G_CALLBACK(filter_sel_list_cb), NULL);
482     g_signal_connect(filter_l, "button_press_event", G_CALLBACK(filter_sel_list_button_cb),
483                    NULL);
484     g_object_set_data(G_OBJECT(main_w), E_FILT_FILTER_L_KEY, filter_l);
485     gtk_container_add(GTK_CONTAINER(filter_sc), filter_l);
486     gtk_widget_show(filter_l);
487
488     g_object_set_data(G_OBJECT(filter_l), E_FILT_DBLFUNC_KEY, filter_dlg_dclick);
489     g_object_set_data(G_OBJECT(filter_l), E_FILT_DBLARG_KEY, main_w);
490     /* This is a Boolean, but we make it a non-null pointer for TRUE
491        and a null pointer for FALSE, as object data is a pointer. */
492     g_object_set_data(G_OBJECT(filter_l), E_FILT_DBLACTIVATE_KEY,
493                     construct_args->activate_on_ok ? "" : NULL);
494
495     /* fill in data */
496     l_select = fill_list(main_w, list_type, filter_te_str);
497
498     g_object_unref(G_OBJECT(store));
499
500
501     props_fr = gtk_frame_new("Properties");
502     gtk_box_pack_start(GTK_BOX(filter_vb), props_fr, FALSE, FALSE, 0);
503     gtk_widget_show(props_fr);
504
505     props_vb = gtk_vbox_new(FALSE, 3);
506     gtk_container_set_border_width(GTK_CONTAINER(props_vb), 5);
507     gtk_container_add(GTK_CONTAINER(props_fr), props_vb);
508     gtk_widget_show(props_vb);
509
510     /* row: Filter name entry */
511     middle_hb = gtk_hbox_new(FALSE, 3);
512     gtk_container_add(GTK_CONTAINER(props_vb), middle_hb);
513     gtk_widget_show(middle_hb);
514
515     name_lb = gtk_label_new("Filter name:");
516     gtk_box_pack_start(GTK_BOX(middle_hb), name_lb, FALSE, FALSE, 0);
517     gtk_widget_show(name_lb);
518
519     name_te = gtk_entry_new();
520     gtk_box_pack_start(GTK_BOX(middle_hb), name_te, TRUE, TRUE, 0);
521     g_object_set_data(G_OBJECT(main_w), E_FILT_NAME_TE_KEY, name_te);
522     g_signal_connect(name_te, "changed", G_CALLBACK(filter_name_te_changed_cb), filter_list_type_p);
523     gtk_widget_show(name_te);
524
525     /* row: Filter text entry */
526     bottom_hb = gtk_hbox_new(FALSE, 3);
527     gtk_container_add(GTK_CONTAINER(props_vb), bottom_hb);
528     gtk_widget_show(bottom_hb);
529
530     filter_lb = gtk_label_new("Filter string:");
531     gtk_box_pack_start(GTK_BOX(bottom_hb), filter_lb, FALSE, FALSE, 0);
532     gtk_widget_show(filter_lb);
533
534     filter_te = gtk_entry_new();
535     gtk_box_pack_start(GTK_BOX(bottom_hb), filter_te, TRUE, TRUE, 0);
536     g_object_set_data(G_OBJECT(main_w), E_FILT_FILTER_TE_KEY, filter_te);
537     g_signal_connect(filter_te, "changed", G_CALLBACK(filter_name_te_changed_cb), filter_list_type_p);
538     if (list_type == DFILTER_EDITED_LIST) {
539         colorize_filter_te_as_empty(filter_te);
540
541     g_object_set_data(G_OBJECT(main_w), E_FILT_AUTOCOMP_PTR_KEY, NULL);
542     g_signal_connect(filter_te, "key-press-event", G_CALLBACK (filter_string_te_key_pressed_cb), NULL);
543     g_signal_connect(main_w, "key-press-event", G_CALLBACK (filter_parent_dlg_key_pressed_cb), NULL);
544     }
545     gtk_widget_show(filter_te);
546
547     g_object_set_data(G_OBJECT(main_w), E_FILT_PARENT_FILTER_TE_KEY, parent_filter_te);
548
549     if (list_type == DFILTER_EDITED_LIST) {
550                 gtk_widget_set_tooltip_text(filter_te,
551             "Enter a display filter. "
552             "The background color of this field is changed by a continuous syntax check"
553               " (green is valid, red is invalid, yellow may have unexpected results).");
554
555         /* Create the "Add Expression..." button, to pop up a dialog
556            for constructing filter comparison expressions. */
557         add_expression_bt = gtk_button_new_from_stock(WIRESHARK_STOCK_ADD_EXPRESSION);
558         g_signal_connect(add_expression_bt, "clicked", G_CALLBACK(filter_add_expr_bt_cb), main_w);
559         gtk_box_pack_start(GTK_BOX(bottom_hb), add_expression_bt, FALSE, FALSE, 0);
560         gtk_widget_show(add_expression_bt);
561                 gtk_widget_set_tooltip_text(add_expression_bt, "Add an expression to the filter string");
562     }
563
564
565     /* button row (create all possible buttons and hide the unrequired later - it's a lot easier) */
566     bbox = dlg_button_row_new(GTK_STOCK_OK, GTK_STOCK_APPLY, GTK_STOCK_SAVE, GTK_STOCK_CANCEL, GTK_STOCK_HELP, NULL);
567     gtk_box_pack_start(GTK_BOX(main_vb), bbox, FALSE, FALSE, 5);
568     gtk_widget_show(bbox);
569
570     ok_bt = g_object_get_data(G_OBJECT(bbox), GTK_STOCK_OK);
571     g_signal_connect(ok_bt, "clicked", G_CALLBACK(filter_dlg_ok_cb), filter_list_type_p);
572         gtk_widget_set_tooltip_text(ok_bt, "Apply the filters and close this dialog");
573
574     /* Catch the "activate" signal on the filter name and filter
575        expression text entries, so that if the user types Return
576        there, we act as if the "OK" button had been selected, as
577        happens if Return is typed if some widget that *doesn't*
578        handle the Return key has the input focus. */
579     if (parent_filter_te != NULL) {
580         dlg_set_activate(name_te, ok_bt);
581         dlg_set_activate(filter_te, ok_bt);
582     }
583
584     apply_bt = g_object_get_data(G_OBJECT(bbox), GTK_STOCK_APPLY);
585     g_signal_connect(apply_bt, "clicked", G_CALLBACK(filter_dlg_apply_cb), filter_list_type_p);
586         gtk_widget_set_tooltip_text(apply_bt, "Apply the filters and keep this dialog open");
587
588     save_bt = g_object_get_data(G_OBJECT(bbox), GTK_STOCK_SAVE);
589     g_signal_connect(save_bt, "clicked", G_CALLBACK(filter_dlg_save_cb), filter_list_type_p);
590         gtk_widget_set_tooltip_text(save_bt, "Save the filters permanently and keep this dialog open");
591
592     cancel_bt = g_object_get_data(G_OBJECT(bbox), GTK_STOCK_CANCEL);
593         gtk_widget_set_tooltip_text(cancel_bt, "Cancel the changes");
594     g_signal_connect(cancel_bt, "clicked", G_CALLBACK(filter_dlg_cancel_cb), filter_list_type_p);
595     window_set_cancel_button(main_w, cancel_bt, NULL);
596
597     help_bt = g_object_get_data(G_OBJECT(bbox), GTK_STOCK_HELP);
598     if (list_type == CFILTER_EDITED_LIST) {
599         g_signal_connect(help_bt, "clicked", G_CALLBACK(topic_cb), (gpointer)HELP_CAPTURE_FILTERS_DIALOG);
600     } else {
601         g_signal_connect(help_bt, "clicked", G_CALLBACK(topic_cb), (gpointer)HELP_DISPLAY_FILTERS_DIALOG);
602     }
603         gtk_widget_set_tooltip_text(help_bt, "Show topic specific help");
604
605     if(ok_bt) {
606         gtk_widget_grab_default(ok_bt);
607     }
608
609     remember_filter_dialog(main_w, filter_dialogs);
610
611     if (button != NULL) {
612     /* This dialog box was created by a "Filter" button.
613        Set the E_FILT_BUTTON_PTR_KEY for the new dialog to point to
614        the button. */
615     g_object_set_data(G_OBJECT(main_w), E_FILT_BUTTON_PTR_KEY, button);
616
617     /* Set the E_FILT_DIALOG_PTR_KEY for the button to point to us */
618     g_object_set_data(G_OBJECT(button), E_FILT_DIALOG_PTR_KEY, main_w);
619     }
620
621     /* DO SELECTION THINGS *AFTER* SHOWING THE DIALOG! */
622     /* otherwise the updatings can get confused */
623     if (l_select) {
624         gtk_tree_selection_select_iter(sel, l_select);
625         g_free(l_select);
626     } else if (filter_te_str && filter_te_str[0]) {
627         gtk_entry_set_text(GTK_ENTRY(name_te), "New filter");
628         gtk_entry_set_text(GTK_ENTRY(filter_te), filter_te_str);
629     }
630
631     g_signal_connect(main_w, "delete_event", G_CALLBACK(filter_dlg_delete_event_cb), filter_list_type_p);
632     g_signal_connect(main_w, "destroy", G_CALLBACK(filter_dlg_destroy_cb), filter_list_type_p);
633
634     gtk_widget_show(main_w);
635
636     if(construct_args->modal_and_transient) {
637         parent = gtk_widget_get_parent_window(parent_filter_te);
638         gdk_window_set_transient_for(gtk_widget_get_window(main_w), parent);
639         gtk_window_set_modal(GTK_WINDOW(main_w), TRUE);
640     }
641
642     /* hide the Ok button, if we don't have to apply it and our caller wants a Save button */
643     if (parent_filter_te == NULL && prefs.gui_use_pref_save) {
644         gtk_widget_hide(ok_bt);
645     }
646
647     /* hide the Apply button, if our caller don't wants one */
648     if (!construct_args->wants_apply_button) {
649         gtk_widget_hide(apply_bt);
650     }
651
652     /* hide the Save button if the user uses implicit save */
653     if (!prefs.gui_use_pref_save) {
654         gtk_widget_hide(save_bt);
655     }
656
657     window_present(main_w);
658
659     return main_w;
660 }
661
662 static void
663 filter_dlg_dclick(GtkWidget *filter_l, gpointer main_w_arg, gpointer activate)
664 {
665     GtkWidget  *main_w = GTK_WIDGET(main_w_arg);
666     GtkWidget  *parent_filter_te =
667         g_object_get_data(G_OBJECT(main_w), E_FILT_PARENT_FILTER_TE_KEY);
668     GList      *flp;
669     filter_def *filt;
670     GtkTreeSelection *sel;
671     GtkTreeModel     *model;
672     GtkTreeIter       iter;
673
674     sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(filter_l));
675
676     if (parent_filter_te != NULL) {
677         /*
678          * We have a text entry widget associated with this dialog
679          * box; is one of the filters in the list selected?
680          */
681         if (gtk_tree_selection_get_selected(sel, &model, &iter)) {
682             /*
683              * Yes.  Is there a filter definition for that filter?
684              */
685             gtk_tree_model_get(model, &iter, 1, &flp, -1);
686             if (flp) {
687                 /*
688                  * Yes - put it in the text entry widget.
689                  */
690                 filt = (filter_def *) flp->data;
691                 gtk_entry_set_text(GTK_ENTRY(parent_filter_te),
692                                    filt->strval);
693
694                 /*
695                  * Are we supposed to cause the filter we
696                  * put there to be applied?
697                  */
698                 if (activate != NULL) {
699                     /*
700                      * Yes - do so.
701                      */
702                     g_signal_emit_by_name(G_OBJECT(parent_filter_te), "activate", NULL);
703                 }
704             }
705         }
706     }
707
708     window_destroy(main_w);
709 }
710
711 static void
712 filter_dlg_ok_cb(GtkWidget *ok_bt, gpointer data)
713 {
714     filter_list_type_t list_type = *(filter_list_type_t *)data;
715
716     /*
717      * Destroy the dialog box and apply the filter.
718      */
719     filter_apply(gtk_widget_get_toplevel(ok_bt), TRUE);
720
721     /* if we don't have a Save button, just save the settings now */
722     if (!prefs.gui_use_pref_save) {
723         filter_dlg_save(list_type);
724     }
725 }
726
727 static void
728 filter_dlg_apply_cb(GtkWidget *apply_bt, gpointer data)
729 {
730     filter_list_type_t list_type = *(filter_list_type_t *)data;
731
732     /*
733      * Apply the filter, but don't destroy the dialog box.
734      */
735     filter_apply(gtk_widget_get_toplevel(apply_bt), FALSE);
736
737     /* if we don't have a Save button, just save the settings now */
738     if (!prefs.gui_use_pref_save) {
739         filter_dlg_save(list_type);
740     }
741 }
742
743 static void
744 filter_apply(GtkWidget *main_w, gboolean destroy)
745 {
746     construct_args_t *construct_args =
747         g_object_get_data(G_OBJECT(main_w), E_FILT_CONSTRUCT_ARGS_KEY);
748     GtkWidget        *parent_filter_te =
749         g_object_get_data(G_OBJECT(main_w), E_FILT_PARENT_FILTER_TE_KEY);
750     GtkWidget        *filter_te;
751     const gchar      *filter_string;
752
753     if (parent_filter_te != NULL) {
754         /*
755          * We have a text entry widget associated with this dialog
756          * box; put the filter in our text entry widget into that
757          * text entry widget.
758          */
759                 filter_te = g_object_get_data(G_OBJECT(main_w), E_FILT_FILTER_TE_KEY);
760         filter_string =
761                     (const gchar *)gtk_entry_get_text(GTK_ENTRY(filter_te));
762         gtk_entry_set_text(GTK_ENTRY(parent_filter_te), filter_string);
763
764     }
765
766     if (destroy) {
767         /*
768          * Destroy the filter dialog box.
769          */
770         window_destroy(main_w);
771     }
772
773     if (parent_filter_te != NULL) {
774         /*
775          * We have a text entry widget associated with this dialog
776          * box; activate that widget to cause the filter we put
777          * there to be applied if we're supposed to do so.
778          *
779          * We do this after dismissing the filter dialog box,
780          * as activating the widget the dialog box to which
781          * it belongs to be dismissed, and that may cause it
782          * to destroy our dialog box if the filter succeeds.
783          * This means that our subsequent attempt to destroy
784          * it will fail.
785          *
786          * We don't know whether it'll destroy our dialog box,
787          * so we can't rely on it to do so.  Instead, we
788          * destroy it ourselves, which will clear the
789          * E_FILT_DIALOG_PTR_KEY pointer for their dialog box,
790          * meaning they won't think it has one and won't try
791          * to destroy it.
792          */
793         if (construct_args->activate_on_ok) {
794             g_signal_emit_by_name(G_OBJECT(parent_filter_te), "activate", NULL);
795         }
796     }
797 }
798
799
800 static void
801 filter_dlg_save(filter_list_type_t list_type)
802 {
803     char *pf_dir_path;
804     char *f_path;
805     int f_save_errno;
806         const char *filter_type;
807
808     switch (list_type) {
809
810     case CFILTER_EDITED_LIST:
811         filter_type = "capture";
812                 list_type = CFILTER_LIST;
813                 copy_filter_list(CFILTER_LIST, CFILTER_EDITED_LIST);
814         break;
815
816     case DFILTER_EDITED_LIST:
817         filter_type = "display";
818                 list_type = DFILTER_LIST;
819                 copy_filter_list(DFILTER_LIST, DFILTER_EDITED_LIST);
820         break;
821
822     default:
823         g_assert_not_reached();
824         filter_type = NULL;
825         break;
826     }
827
828     /* Create the directory that holds personal configuration files,
829        if necessary.  */
830     if (create_persconffile_dir(&pf_dir_path) == -1) {
831         simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
832             "Can't create directory\n\"%s\"\nfor filter files: %s.",
833             pf_dir_path, g_strerror(errno));
834         g_free(pf_dir_path);
835         return;
836     }
837
838     save_filter_list(list_type, &f_path, &f_save_errno);
839     if (f_path != NULL) {
840         /* We had an error saving the filter. */
841         simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
842             "Could not save to your %s filter file\n\"%s\": %s.",
843             filter_type, f_path, g_strerror(f_save_errno));
844         g_free(f_path);
845     }
846 }
847
848
849 static void
850 filter_dlg_save_cb(GtkWidget *save_bt _U_, gpointer data)
851 {
852     filter_list_type_t list_type = *(filter_list_type_t *)data;
853
854     filter_dlg_save(list_type);
855 }
856
857 #if 0
858 /* update a remaining dialog if another one was cancelled */
859 static void
860 filter_dlg_update_list_cb(gpointer data, gpointer user_data)
861 {
862     GtkWidget  *main_w = data;
863     filter_list_type_t list_type = *(filter_list_type_t *)user_data;
864
865     /* refill the list */
866     clear_list(main_w);
867     fill_list(main_w, list_type, NULL);
868 }
869 #endif
870
871 /* cancel button pressed, revert changes and exit dialog */
872 static void
873 filter_dlg_cancel_cb(GtkWidget *cancel_bt, gpointer data)
874 {
875     filter_list_type_t list_type = *(filter_list_type_t *)data;
876     GtkWidget  *main_w = gtk_widget_get_toplevel(cancel_bt);
877     static GList *filter_list;
878
879
880     window_destroy(GTK_WIDGET(main_w));
881
882     /* if this was the last open filter dialog, revert the changes made */
883     filter_list = get_filter_dialog_list(list_type);
884     if(g_list_length(filter_list) == 0) {
885         /* revert changes in the edited list */
886         switch (list_type) {
887         case CFILTER_EDITED_LIST:
888                 copy_filter_list(CFILTER_EDITED_LIST, CFILTER_LIST);
889             break;
890         case DFILTER_EDITED_LIST:
891                 copy_filter_list(DFILTER_EDITED_LIST, DFILTER_LIST);
892             break;
893         default:
894             g_assert_not_reached();
895             break;
896         }
897     }
898
899 #if 0
900     /* update other open filter dialogs */
901     g_list_foreach(get_filter_dialog_list(list_type), filter_dlg_update_list_cb, &list_type);
902 #endif
903 }
904
905 /* Treat this as a cancel, by calling "filter_dlg_cancel_cb()" */
906 static gboolean
907 filter_dlg_delete_event_cb(GtkWidget *main_w, GdkEvent *event _U_,
908                            gpointer data)
909 {
910     filter_dlg_cancel_cb(main_w, data);
911     return FALSE;
912 }
913
914
915 static void
916 filter_dlg_destroy_cb(GtkWidget *win, gpointer data)
917 {
918     filter_list_type_t list_type = *(filter_list_type_t *)data;
919     GtkWidget *button;
920
921     /* Get the button that requested that we be popped up, if any.
922        (It should arrange to destroy us if it's destroyed, so
923        that we don't get a pointer to a non-existent window here.) */
924     button = g_object_get_data(G_OBJECT(win), E_FILT_BUTTON_PTR_KEY);
925
926     if (button != NULL) {
927         /* Tell it we no longer exist. */
928                 g_object_set_data(G_OBJECT(button), E_FILT_DIALOG_PTR_KEY, NULL);
929     } else {
930         /* This is an editing dialog popped up from, for example,
931            a menu item; note that we no longer have one. */
932         switch (list_type) {
933
934 #ifdef HAVE_LIBPCAP
935         case CFILTER_EDITED_LIST:
936             g_assert(win == global_cfilter_w);
937             global_cfilter_w = NULL;
938             break;
939 #endif
940         default:
941             g_assert_not_reached();
942             break;
943         }
944     }
945
946     /* Remove this from the list of filter dialog windows. */
947     forget_filter_dialog(win, list_type);
948 }
949
950 static gboolean
951 filter_sel_list_button_cb(GtkWidget *list, GdkEventButton *event,
952                           gpointer data _U_)
953 {
954     void (* func)(GtkWidget *, gpointer, gpointer);
955     gpointer func_arg;
956     gpointer func_activate;
957
958     if (event->type == GDK_2BUTTON_PRESS) {
959         func = g_object_get_data(G_OBJECT(list), E_FILT_DBLFUNC_KEY);
960         func_arg = g_object_get_data(G_OBJECT(list), E_FILT_DBLARG_KEY);
961         func_activate = g_object_get_data(G_OBJECT(list), E_FILT_DBLACTIVATE_KEY);
962
963         if (func)
964             (*func)(list, func_arg, func_activate);
965     }
966
967     return FALSE;
968 }
969
970 static void
971 filter_sel_list_cb(GtkTreeSelection *sel, gpointer data _U_)
972 {
973     GtkWidget    *filter_l = GTK_WIDGET(gtk_tree_selection_get_tree_view(sel));
974     GtkWidget    *main_w = gtk_widget_get_toplevel(filter_l);
975     GtkTreeModel *model;
976     GtkTreeIter   iter;
977     GtkWidget    *name_te = g_object_get_data(G_OBJECT(main_w), E_FILT_NAME_TE_KEY);
978     GtkWidget    *filter_te = g_object_get_data(G_OBJECT(main_w), E_FILT_FILTER_TE_KEY);
979     GtkWidget    *chg_bt = g_object_get_data(G_OBJECT(main_w), E_FILT_CHG_BT_KEY);
980     GtkWidget    *copy_bt = g_object_get_data(G_OBJECT(main_w), E_FILT_COPY_BT_KEY);
981     GtkWidget    *del_bt = g_object_get_data(G_OBJECT(main_w), E_FILT_DEL_BT_KEY);
982     filter_def   *filt;
983     gchar        *name = NULL, *strval = NULL;
984     GList        *flp;
985     gint          sensitivity = FALSE;
986
987     if (gtk_tree_selection_get_selected(sel, &model, &iter)) {
988         gtk_tree_model_get(model, &iter, 1, &flp, -1);
989         if (flp) {
990             filt   = (filter_def *) flp->data;
991             name   = g_strdup(filt->name);
992             strval = g_strdup(filt->strval);
993             sensitivity = TRUE;
994         }
995     }
996
997     /*
998      * Did you know that this function is called when the window is destroyed?
999      * Funny, that.
1000      * This means that we have to:
1001      *
1002      *  attach to the top-level window data items containing pointers to
1003      *  the widgets we affect here;
1004      *
1005      *  give each of those widgets their own destroy callbacks;
1006      *
1007      *  clear that pointer when the widget is destroyed;
1008      *
1009      *  don't do anything to the widget if the pointer we get back is
1010      *  null;
1011      *
1012      * so that if we're called after any of the widgets we'd affect are
1013      * destroyed, we know that we shouldn't do anything to those widgets.
1014      */
1015     if (name_te != NULL)
1016         gtk_entry_set_text(GTK_ENTRY(name_te), name ? name : "");
1017     if (filter_te != NULL)
1018         gtk_entry_set_text(GTK_ENTRY(filter_te), strval ? strval : "");
1019     if (chg_bt != NULL)
1020         gtk_widget_set_sensitive(chg_bt, sensitivity);
1021     if (copy_bt != NULL)
1022         gtk_widget_set_sensitive(copy_bt, sensitivity);
1023     if (del_bt != NULL)
1024         gtk_widget_set_sensitive(del_bt, sensitivity);
1025     g_free(name);
1026     g_free(strval);
1027 }
1028
1029 /* To do: add input checking to each of these callbacks */
1030
1031 /* Structure containing arguments to be passed to "new_filter_cb()".
1032
1033    "active_filter_l" is the list in the dialog box in which "New" or
1034    "Copy" was clicked; in that dialog box, but not in any other dialog
1035    box, we select the newly created list item.
1036
1037    "nflp" is the GList member in the model (filter list) for the new
1038    filter. */
1039 typedef struct {
1040     GtkWidget *active_filter_l;
1041     GList     *nflp;
1042 } new_filter_cb_args_t;
1043
1044 static void
1045 new_filter_cb(gpointer data, gpointer user_data)
1046 {
1047     GtkWidget    *main_w = data;
1048     GtkTreeView  *filter_l;
1049     GtkListStore *store;
1050     GtkTreeIter   iter;
1051     new_filter_cb_args_t *args = user_data;
1052     filter_def *nfilt = args->nflp->data;
1053
1054     filter_l = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(main_w), E_FILT_FILTER_L_KEY));
1055     store = GTK_LIST_STORE(gtk_tree_view_get_model(filter_l));
1056     gtk_list_store_append(store, &iter);
1057     gtk_list_store_set(store, &iter, 0, nfilt->name, 1, args->nflp, -1);
1058     if (GTK_WIDGET(filter_l) == args->active_filter_l) {
1059         /* Select the item. */
1060         gtk_tree_selection_select_iter(gtk_tree_view_get_selection(filter_l),
1061                                        &iter);
1062     }
1063 }
1064
1065 static void
1066 filter_new_bt_clicked_cb(GtkWidget *w, gpointer data)
1067 {
1068     GtkWidget  *main_w = gtk_widget_get_toplevel(w);
1069     GtkWidget  *name_te = g_object_get_data(G_OBJECT(main_w), E_FILT_NAME_TE_KEY);
1070     GtkWidget  *filter_te = g_object_get_data(G_OBJECT(main_w), E_FILT_FILTER_TE_KEY);
1071     GtkWidget  *filter_l = g_object_get_data(G_OBJECT(main_w), E_FILT_FILTER_L_KEY);
1072     filter_list_type_t list_type = *(filter_list_type_t *)data;
1073     GList      *fl_entry;
1074     const gchar *name, *strval;
1075     new_filter_cb_args_t args;
1076
1077     name   = gtk_entry_get_text(GTK_ENTRY(name_te));
1078     strval = gtk_entry_get_text(GTK_ENTRY(filter_te));
1079
1080     /* if the user didn't entered a name, set default one */
1081     if (strlen(name) == 0) {
1082         name = "new";
1083     }
1084
1085     /* if the user didn't entered a string value, set default one */
1086     if (strlen(strval) == 0) {
1087         strval = "new";
1088     }
1089
1090     /* Add a new entry to the filter list. */
1091     fl_entry = add_to_filter_list(list_type, name, strval);
1092
1093     /* Update all the filter list widgets, not just the one in
1094        the dialog box in which we clicked on "Copy". */
1095     args.active_filter_l = filter_l;
1096     args.nflp = fl_entry;
1097     g_list_foreach(get_filter_dialog_list(list_type), new_filter_cb, &args);
1098
1099 }
1100
1101 static gboolean
1102 chg_list_item_cb(GtkTreeModel *model, GtkTreePath *path _U_, GtkTreeIter *iter,
1103                  gpointer data)
1104 {
1105     GList      *flp = data;
1106     filter_def *filt = flp->data;
1107     GList      *nl_model;
1108
1109     gtk_tree_model_get(model, iter, 1, &nl_model, -1);
1110     /* Is this the item corresponding to the filter list item in question? */
1111     if (flp == nl_model) {
1112         /* Yes - change the label to correspond to the new name for the
1113          * filter. */
1114         gtk_list_store_set(GTK_LIST_STORE(model), iter, 0, filt->name, -1);
1115         return TRUE;
1116     }
1117     return FALSE;
1118 }
1119
1120 static void
1121 chg_filter_cb(gpointer data, gpointer user_data)
1122 {
1123     GtkWidget  *main_w = data;
1124     GtkWidget  *filter_l = g_object_get_data(G_OBJECT(main_w), E_FILT_FILTER_L_KEY);
1125
1126     gtk_tree_model_foreach(gtk_tree_view_get_model(GTK_TREE_VIEW(filter_l)),
1127                            chg_list_item_cb, user_data);
1128 }
1129
1130 static void
1131 filter_name_te_changed_cb(GtkWidget *w, gpointer data)
1132 {
1133     GtkWidget  *main_w = gtk_widget_get_toplevel(w);
1134     GtkWidget  *name_te = g_object_get_data(G_OBJECT(main_w), E_FILT_NAME_TE_KEY);
1135     GtkWidget  *filter_te = g_object_get_data(G_OBJECT(main_w), E_FILT_FILTER_TE_KEY);
1136     GtkWidget  *filter_l = g_object_get_data(G_OBJECT(main_w), E_FILT_FILTER_L_KEY);
1137     filter_def *filt;
1138     GList      *fl_entry;
1139     filter_list_type_t  list_type = *(filter_list_type_t *)data;
1140     const gchar         *name = "";
1141     const gchar         *strval = "";
1142
1143     GtkTreeSelection  *sel;
1144     GtkTreeModel      *model;
1145     GtkTreeIter        iter;
1146
1147     sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(filter_l));
1148     name   = gtk_entry_get_text(GTK_ENTRY(name_te));
1149     strval = gtk_entry_get_text(GTK_ENTRY(filter_te));
1150
1151     if (DFILTER_EDITED_LIST == list_type) {
1152         /* colorize filter string entry */
1153         filter_te_syntax_check_cb(filter_te, NULL);
1154     }
1155
1156     /* if something was selected */
1157     if (gtk_tree_selection_get_selected(sel, &model, &iter)) {
1158         gtk_tree_model_get(model, &iter, 1, &fl_entry, -1);
1159         if (fl_entry != NULL) {
1160             filt = (filter_def *) fl_entry->data;
1161
1162             if (strlen(name) > 0 && strlen(strval) > 0 && filt) {
1163                 g_free(filt->name);
1164                 g_free(filt->strval);
1165                 filt->name   = g_strdup(name);
1166                 filt->strval = g_strdup(strval);
1167
1168                 /* Update all the filter list widgets, not just the one in
1169                    the dialog box in which we clicked on "Copy". */
1170                 g_list_foreach(get_filter_dialog_list(list_type), chg_filter_cb,
1171                                fl_entry);
1172             }
1173         }
1174     }
1175 }
1176
1177 static void
1178 delete_filter_cb(gpointer data, gpointer user_data)
1179 {
1180     GtkWidget    *main_w = data;
1181     GtkWidget    *filter_l = g_object_get_data(G_OBJECT(main_w), E_FILT_FILTER_L_KEY);
1182     gchar        *pos = (gchar *)user_data;
1183     GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(filter_l));
1184     GtkTreeIter   iter;
1185
1186     gtk_tree_model_get_iter_from_string(model, &iter, pos);
1187     gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
1188 }
1189
1190 static void
1191 filter_del_bt_clicked_cb(GtkWidget *w, gpointer data)
1192 {
1193     GtkWidget  *main_w = gtk_widget_get_toplevel(w);
1194     GtkWidget  *filter_l = g_object_get_data(G_OBJECT(main_w), E_FILT_FILTER_L_KEY);
1195     filter_list_type_t list_type = *(filter_list_type_t *)data;
1196     GList      *fl_entry;
1197     gchar             *pos;
1198     GtkTreeSelection  *sel;
1199     GtkTreeModel      *model;
1200     GtkTreeIter        iter;
1201     GtkTreePath       *path;
1202
1203     sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(filter_l));
1204     /* If something was selected */
1205     if (gtk_tree_selection_get_selected(sel, &model, &iter)) {
1206         gtk_tree_model_get(model, &iter, 1, &fl_entry, -1);
1207         path = gtk_tree_model_get_path(model, &iter);
1208         pos = gtk_tree_path_to_string(path);
1209         gtk_tree_path_free(path);
1210         if (fl_entry != NULL) {
1211             /* Remove the entry from the filter list. */
1212             remove_from_filter_list(list_type, fl_entry);
1213
1214             /* Update all the filter list widgets, not just the one in
1215                the dialog box in which we clicked on "Delete". */
1216             g_list_foreach(get_filter_dialog_list(list_type), delete_filter_cb, pos);
1217         }
1218         g_free(pos);
1219     }
1220 }
1221
1222 void
1223 filter_add_expr_bt_cb(GtkWidget *w _U_, gpointer main_w_arg)
1224 {
1225     GtkWidget  *main_w = GTK_WIDGET(main_w_arg);
1226     GtkWidget  *filter_te, *dfilter_w;
1227
1228     filter_te = g_object_get_data(G_OBJECT(main_w), E_FILT_FILTER_TE_KEY);
1229     dfilter_w = dfilter_expr_dlg_new(filter_te);
1230
1231     /* If we're opening a series of modal dialogs (such as when going
1232      * through file->open, make the latest dialog modal also so that it
1233      * takes over "control" from the other modal dialogs.  Also set
1234      * the transient property of the new dialog so the user doesn't try
1235      * to interact with the previous window when they can't.
1236          * XXX: containing widget might be the Filter Toolbar */
1237
1238     if ( GTK_IS_WINDOW(main_w) && gtk_window_get_modal(GTK_WINDOW(main_w))) {
1239         gtk_window_set_modal(GTK_WINDOW(dfilter_w), TRUE);
1240         gtk_window_set_transient_for(GTK_WINDOW(dfilter_w),
1241                          GTK_WINDOW(main_w));
1242     }
1243 }
1244
1245 static void
1246 color_filter_te(GtkWidget *w, guint16 red, guint16 green, guint16 blue)
1247 {
1248 #if GTK_CHECK_VERSION(3,0,0)
1249     static GdkRGBA black = { 0, 0, 0, 1.0 };
1250     GdkRGBA bg;
1251
1252     bg.red      = red / 65535.0;
1253     bg.green    = green / 65535.0;
1254     bg.blue     = blue / 65535.0;
1255     bg.alpha    = 1;
1256
1257     gtk_widget_override_color(w, GTK_STATE_NORMAL, &black);
1258     gtk_widget_override_background_color(w, GTK_STATE_NORMAL, &bg);
1259     gtk_widget_override_cursor(w, &black, &black);
1260 #else
1261     static GdkColor black = { 0, 0, 0, 0 };
1262     GdkColor    bg;
1263
1264     bg.pixel    = 0;
1265     bg.red      = red;
1266     bg.green    = green;
1267     bg.blue     = blue;
1268
1269     gtk_widget_modify_text(w, GTK_STATE_NORMAL, &black);
1270     gtk_widget_modify_base(w, GTK_STATE_NORMAL, &bg);
1271     gtk_widget_modify_cursor(w, &black, &black);
1272 #endif
1273 }
1274
1275 void
1276 colorize_filter_te_as_empty(GtkWidget *w)
1277 {
1278 #if GTK_CHECK_VERSION(3,0,0)
1279     /* use defaults */
1280     gtk_widget_override_color(w, GTK_STATE_NORMAL, NULL);
1281     gtk_widget_override_background_color(w, GTK_STATE_NORMAL, NULL);
1282     gtk_widget_override_cursor(w, NULL, NULL);
1283 #else    
1284     /* use defaults */
1285     gtk_widget_modify_text(w, GTK_STATE_NORMAL, NULL);
1286     gtk_widget_modify_base(w, GTK_STATE_NORMAL, NULL);
1287     gtk_widget_modify_cursor(w, NULL, NULL);
1288 #endif
1289 }
1290
1291 void
1292 colorize_filter_te_as_invalid(GtkWidget *w)
1293 {
1294     /* light red */
1295     color_filter_te(w, 0xFFFF, 0xAFFF, 0xAFFF);
1296 }
1297
1298 static void
1299 colorize_filter_te_as_deprecated(GtkWidget *w)
1300 {
1301     /* light yellow */
1302     color_filter_te(w, 0xFFFF, 0xFFFF, 0xAFFF);
1303 }
1304
1305 void
1306 colorize_filter_te_as_valid(GtkWidget *w)
1307 {
1308     /* light green */
1309     color_filter_te(w, 0xAFFF, 0xFFFF, 0xAFFF);
1310 }
1311
1312 /*
1313  * XXX This calls dfilter_compile, which might call get_host_ipaddr or
1314  * get_host_ipaddr6. Either of of these will freeze the UI if the host
1315  * name resolution takes a long time to complete. We need to work
1316  * around this, either by disabling host name resolution or by doing
1317  * the resolution asynchronously.
1318  *
1319  * We could use a separate thread but we have be careful to only call
1320  * GTK+/GDK routines from the main thread. From the GDK threads
1321  * documentation:
1322  *
1323  * "With the Win32 backend, GDK calls should not be attempted from
1324  * multiple threads at all."
1325  */
1326
1327 void
1328 filter_te_syntax_check_cb(GtkWidget *w, gpointer user_data _U_)
1329 {
1330     const gchar *strval;
1331     dfilter_t   *dfp;
1332     GPtrArray   *depr = NULL;
1333     gboolean     use_statusbar;
1334     guchar       c;
1335
1336     strval = gtk_entry_get_text(GTK_ENTRY(w));
1337     use_statusbar = g_object_get_data(G_OBJECT(w), E_FILT_FIELD_USE_STATUSBAR_KEY) ? TRUE : FALSE;
1338
1339     if (use_statusbar) {
1340         statusbar_pop_filter_msg();
1341     }
1342
1343     /* colorize filter string entry */
1344     if (g_object_get_data(G_OBJECT(w), E_FILT_FIELD_NAME_ONLY_KEY) &&
1345         strval && (c = proto_check_field_name(strval)) != 0)
1346     {
1347         colorize_filter_te_as_invalid(w);
1348         if (use_statusbar) {
1349             statusbar_push_filter_msg(" Illegal character in field name: '%c'", c);
1350         }
1351     } else if (strval && dfilter_compile(strval, &dfp)) {
1352         if (dfp != NULL) {
1353             depr = dfilter_deprecated_tokens(dfp);
1354         }
1355         if (strlen(strval) == 0) {
1356             colorize_filter_te_as_empty(w);
1357         } else if (depr) {
1358             /* You keep using that word. I do not think it means what you think it means. */
1359             colorize_filter_te_as_deprecated(w);
1360             if (use_statusbar) {
1361                 /*
1362                  * We're being lazy and only printing the first "problem" token.
1363                  * Would it be better to print all of them?
1364                  */
1365                 statusbar_push_temporary_msg(" \"%s\" may have unexpected results (see the User's Guide)",
1366                                       (const char *) g_ptr_array_index(depr, 0));
1367             }
1368         } else {
1369             colorize_filter_te_as_valid(w);
1370         }
1371         dfilter_free(dfp);
1372     } else {
1373         colorize_filter_te_as_invalid(w);
1374         if (use_statusbar) {
1375             if (dfilter_error_msg) {
1376                 statusbar_push_filter_msg(" Invalid filter: %s", dfilter_error_msg);
1377             } else {
1378                 statusbar_push_filter_msg(" Invalid filter");
1379             }
1380         }
1381     }
1382 }
1383
1384 /*
1385  * Editor modelines
1386  *
1387  * Local Variables:
1388  * c-basic-offset: 4
1389  * tab-width: 8
1390  * indent-tabs-mode: nil
1391  * End:
1392  *
1393  * ex: set shiftwidth=4 tabstop=8 expandtab
1394  * :indentSize=4:tabSize=8:noTabs=true:
1395  */