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