Handle -I in the options processing.
[obnox/wireshark/wip.git] / gtk / filter_autocomplete.c
1 /* filter_autocomplete.h
2  * Definitions for filter autocomplete
3  *
4  * Copyright 2008, Bahaa Naamneh <b.naamneh@gmail.com>
5  *
6  * With several usability improvements:
7  * Copyright 2008, Stig Bjorlykke <stig@bjorlykke.org>
8  *
9  * $Id$
10  *
11  * Wireshark - Network traffic analyzer
12  * By Gerald Combs <gerald@wireshark.org>
13  * Copyright 1998 Gerald Combs
14  *
15  * This program is free software; you can redistribute it and/or
16  * modify it under the terms of the GNU General Public License
17  * as published by the Free Software Foundation; either version 2
18  * of the License, or (at your option) any later version.
19  *
20  * This program is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU General Public License for more details.
24  *
25  * You should have received a copy of the GNU General Public License
26  * along with this program; if not, write to the Free Software
27  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
28  */ 
29  
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
33
34 #include <string.h>
35
36 #include <gtk/gtk.h>
37 #include <gdk/gdkkeysyms.h>
38
39 #include <epan/proto.h>
40
41 #include "gtk/gui_utils.h"
42 #include "gtk/filter_autocomplete.h"
43
44 #define E_FILT_AUTOCOMP_TREE_KEY    "filter_autocomplete_tree"
45
46
47 static GtkWidget *filter_autocomplete_new(GtkWidget *filter_te, const gchar *protocol_name, 
48                                           gboolean protocols_only, gboolean *stop_propagation);
49 static void autocomplete_protocol_string(GtkWidget  *filter_te, gchar* selected_str);
50 static void autoc_filter_row_activated_cb(GtkTreeView *treeview, GtkTreePath *path, 
51                                           GtkTreeViewColumn *column, gpointer data);
52 static gint filter_te_focus_out_cb(GtkWidget *filter_te, GdkEvent *event, gpointer data);
53 static void init_autocompletion_list(GtkWidget *list);
54 static void add_to_autocompletion_list(GtkWidget *list, const gchar *str);
55 static gboolean autocompletion_list_lookup(GtkWidget *filter_te, GtkWidget *popup_win, GtkWidget *list,
56                                            const gchar *str, gboolean *stop_propagation);
57 static void filter_autocomplete_handle_backspace(GtkWidget *filter_te, GtkWidget *list, GtkWidget *popup_win,
58                                                  gchar *prefix, GtkWidget *main_win);
59 static void filter_autocomplete_win_destroy_cb(GtkWidget *win, gpointer data);
60 static gboolean is_protocol_name_being_typed(GtkWidget *filter_te, int str_len);
61
62
63 /*
64  * Check if the string at the cursor position is a beginning of a protocol name.
65  * Possible false positives:
66  *      "NOT" at the beginning of the display filter editable text.
67  *      "NOT" adjacent to another logical operation. (e.g: exp1 AND NOT exp2).
68  **/
69 static gboolean
70 is_protocol_name_being_typed(GtkWidget *filter_te, int str_len)
71 {
72   unsigned int i;
73   int op_len, cursor_pos;
74   gchar *start;
75   gchar *pos;
76   static gchar *logic_ops[] = { "!", "not",
77                                 "||", "or",
78                                 "&&", "and",
79                                 "^^", "xor" };
80   
81   /* If the cursor is located at the beginning of the filter editable text,
82    * then it's _probably_ a protocol name.
83    **/
84   if(!(cursor_pos = gtk_editable_get_position(GTK_EDITABLE(filter_te))))
85     return TRUE;
86   
87   start = gtk_editable_get_chars(GTK_EDITABLE(filter_te), 0, (gint) cursor_pos);
88   
89   /* Point to one char before the current string in the filter editable text */
90   pos = start + (cursor_pos - str_len);
91   
92   /* Walk back through string to find last char which isn't ' ' or '(' */
93   while(pos > start) {
94     if(*pos != ' ' && *pos != '(') {
95       /* Check if we have one of the logical operations */
96       for(i = 0; i < (sizeof(logic_ops)/sizeof(logic_ops[0])); i++) {
97         op_len = (int) strlen(logic_ops[i]);
98         
99         if(pos-start+1 < op_len)
100           continue;
101         
102         /* If one of the logical operations is found, then the current string is _probably_ a protocol name */
103         if(!strncmp(pos-op_len+1, logic_ops[i], op_len)) {
104           g_free (start);
105           return TRUE;
106         }
107       }
108
109       /* If none of the logical operations was found, then the current string is not a protocol */
110       g_free (start);
111       return FALSE;
112     }
113     pos--;
114   }
115   
116   /* The "str" preceded only by ' ' or '(' chars, 
117    * which means that the str is _probably_ a protocol name.
118    **/
119   g_free (start);
120   return TRUE;
121 }
122
123
124 static void
125 autocomplete_protocol_string(GtkWidget *filter_te, gchar *selected_str)
126 {
127   int pos;
128   gchar *filter_str;
129   gchar *pch;
130
131   /* Get the current filter string */
132   pos = gtk_editable_get_position(GTK_EDITABLE(filter_te));
133   filter_str = gtk_editable_get_chars(GTK_EDITABLE(filter_te), 0, pos);
134
135   /* Start from the end */
136   pch = filter_str + strlen(filter_str);
137
138   /* Walk back through string to find last non-punctuation */
139   while(pch != filter_str) {
140     pch--;
141     if(!g_ascii_isalnum(*pch) && (*pch) != '.' && (*pch) != '_' && (*pch) != '-') {
142       pch++;
143       break;
144     }
145   }
146
147   if(strncmp(pch, selected_str, pos-(pch-filter_str))) {
148     gtk_editable_delete_text(GTK_EDITABLE(filter_te), (gint) (pch-filter_str), pos);
149     pos = (int) (pch-filter_str);
150     pch = selected_str;
151   } else {
152     pch = (selected_str + strlen(pch));
153   }
154
155   gtk_editable_insert_text(GTK_EDITABLE(filter_te), pch, (gint) strlen(pch), &pos);
156   gtk_editable_set_position(GTK_EDITABLE(filter_te), pos);
157   g_free (filter_str);
158 }
159
160 /* On row activated signal, complete the protocol string automatically */
161 static void
162 autoc_filter_row_activated_cb(GtkTreeView *treeview, 
163                               GtkTreePath *path, 
164                               GtkTreeViewColumn *column _U_, 
165                               gpointer data)
166 {
167   GtkWidget *w_main;
168   GtkTreeModel *model;
169   GtkTreeIter iter;
170   GtkWidget *win;
171   gchar *proto;
172
173   model = gtk_tree_view_get_model(treeview);
174
175   if (gtk_tree_model_get_iter(model, &iter, path)) {
176
177     gtk_tree_model_get(model, &iter, 0, &proto, -1);
178     autocomplete_protocol_string(GTK_WIDGET(data), proto);
179
180     g_free (proto);
181   }
182
183   w_main = gtk_widget_get_toplevel(GTK_WIDGET(data));
184   win = g_object_get_data(G_OBJECT(w_main), E_FILT_AUTOCOMP_PTR_KEY);
185   if(win != NULL) {
186     gtk_widget_destroy(win);
187     g_object_set_data(G_OBJECT(w_main), E_FILT_AUTOCOMP_PTR_KEY, NULL);
188   }
189 }
190
191 static gint
192 filter_te_focus_out_cb(GtkWidget *filter_te _U_, 
193                        GdkEvent *event _U_, 
194                        gpointer data)
195 {
196   GtkWidget *win;
197
198   win = g_object_get_data(G_OBJECT(data), E_FILT_AUTOCOMP_PTR_KEY);
199   if(win != NULL) {
200     gtk_widget_destroy(win);
201     g_object_set_data(G_OBJECT(data), E_FILT_AUTOCOMP_PTR_KEY, NULL);
202   }
203
204   return FALSE;
205 }
206
207 static gboolean
208 check_select_region (GtkWidget *filter_te, GtkWidget *popup_win, 
209                      const gchar *string, unsigned int str_len)
210 {
211   gint pos1 = gtk_editable_get_position(GTK_EDITABLE(filter_te));
212   gint pos2 = pos1 + (gint) strlen(string) - str_len;
213   gint pos3 = pos1;
214
215   if (pos2 > pos1) {
216     gtk_editable_insert_text(GTK_EDITABLE(filter_te), &string[str_len-1],
217                              pos2-pos1+1, &pos3);
218     gtk_editable_set_position(GTK_EDITABLE(filter_te), pos1+1);
219     gtk_editable_select_region(GTK_EDITABLE(filter_te), pos1+1, pos2+1);
220     gtk_widget_hide (popup_win);
221     return TRUE;
222   }
223
224   return FALSE;
225 }
226
227 static void
228 init_autocompletion_list(GtkWidget *list)
229 {
230   GtkCellRenderer *renderer;
231   GtkTreeViewColumn *column;
232   GtkListStore *store;
233
234   renderer = gtk_cell_renderer_text_new();
235   column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 0, NULL);
236
237   gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
238   gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE);
239
240   store = gtk_list_store_new(1, G_TYPE_STRING);
241
242   gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));
243
244   g_object_unref(store);
245 }
246
247 static void
248 add_to_autocompletion_list(GtkWidget *list, const gchar *str)
249 {
250   GtkListStore *store;
251   GtkTreeIter iter;
252
253   store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
254
255   gtk_list_store_append(store, &iter);
256   gtk_list_store_set(store, &iter, 0, str, -1);
257 }
258
259 static gboolean
260 autocompletion_list_lookup(GtkWidget *filter_te, GtkWidget *popup_win, GtkWidget *list, 
261                            const gchar *str, gboolean *stop_propagation)
262 {
263   GtkRequisition requisition;
264   GtkListStore *store;
265   GtkTreeIter iter;
266   gchar *curr_str;
267   unsigned int str_len = (unsigned int) strlen(str);
268   gchar *first = NULL;
269   gint count = 0;
270   gboolean loop = TRUE;
271   gboolean exact_match = FALSE;
272
273   store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
274
275   if( gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter) ) {
276
277     do {
278
279       gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, 0, &curr_str,  -1);
280
281       if( !g_ascii_strncasecmp(str, curr_str, str_len) ) {
282         loop = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
283         if (strlen(curr_str) == str_len) {
284           exact_match = TRUE;
285         }
286         count++;
287         if (count == 1) 
288           first = g_strdup (curr_str);
289       } else {
290         loop = gtk_list_store_remove(store, &iter);
291       }
292
293       g_free(curr_str);
294
295     } while( loop );
296
297     if (count == 1 && !exact_match && strncmp(str, first, str_len) == 0) {
298       /* Only one match (not exact) with correct case */
299       *stop_propagation = check_select_region(filter_te, popup_win, first, str_len);
300     }
301
302     /* Don't show an autocompletion-list with only one entry with exact match */
303     if ((count == 1 && exact_match && strncmp(str, first, str_len) == 0) ||
304         !gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter))
305     {
306       g_free (first);
307       return FALSE;
308     }
309
310     g_free (first);
311
312     gtk_tree_view_columns_autosize(GTK_TREE_VIEW(list));
313     gtk_widget_size_request(list, &requisition);
314
315     gtk_widget_set_size_request(popup_win, popup_win->allocation.width, (requisition.height<200? requisition.height+8:200));
316     gtk_window_resize(GTK_WINDOW(popup_win), popup_win->allocation.width, (requisition.height<200? requisition.height+8:200));
317
318     return TRUE;
319   }
320
321   return FALSE;
322 }
323
324 gboolean
325 filter_string_te_key_pressed_cb(GtkWidget *filter_te, GdkEventKey *event)
326 {
327   GtkWidget *popup_win;
328   GtkWidget *w_toplevel;
329   GtkWidget *treeview;
330   GtkTreeModel *model;
331   GtkTreePath *path;
332   GtkTreeSelection *selection;
333   GtkTreeIter iter;
334   gchar* prefix;
335   gchar* prefix_start;
336   gboolean stop_propagation = FALSE;
337   guint k;
338   gchar ckey;
339   gint pos;
340
341   w_toplevel = gtk_widget_get_toplevel(filter_te);
342
343   popup_win = g_object_get_data(G_OBJECT(w_toplevel), E_FILT_AUTOCOMP_PTR_KEY);
344
345   k = event->keyval;
346   ckey = event->string[0];
347
348   /* If the pressed key is SHIFT then we have nothing to do with the pressed key. */
349   if( k == GDK_Shift_L || k == GDK_Shift_R)
350     return FALSE;
351
352   if (popup_win)
353     gtk_widget_show(popup_win);
354
355   pos = gtk_editable_get_position(GTK_EDITABLE(filter_te));
356   if (g_ascii_isalnum(ckey) ||      
357       k == GDK_KP_Decimal || k == GDK_period ||
358       k == GDK_underscore || k == GDK_minus) 
359   {
360     /* Ensure we delete the selected text */
361     gtk_editable_delete_selection(GTK_EDITABLE(filter_te));
362   } else if (k == GDK_Return || k == GDK_KP_Enter) {
363     /* Remove selection */
364     gtk_editable_select_region(GTK_EDITABLE(filter_te), pos, pos);
365   }
366   /* get the string from filter_te, start from 0 till cursor's position */
367   prefix_start = gtk_editable_get_chars(GTK_EDITABLE(filter_te), 0, pos);
368
369   /* If the pressed key is non-alphanumeric or one of the keys specified 
370    * in the condition (decimal, period...) then destroy popup window.
371    **/
372   if( !g_ascii_isalnum(ckey) && 
373       k != GDK_KP_Decimal && k != GDK_period && 
374       k != GDK_underscore && k != GDK_minus &&
375       k != GDK_space && k != GDK_Return && k != GDK_KP_Enter && 
376       k != GDK_Page_Down && k != GDK_Down && k != GDK_Page_Up && k != GDK_Up &&
377       k != GDK_BackSpace)
378   {
379     if (popup_win) {
380       gtk_widget_destroy(popup_win);
381       g_object_set_data(G_OBJECT(w_toplevel), E_FILT_AUTOCOMP_PTR_KEY, NULL);
382     }
383     return FALSE;
384   }
385
386   /* Let prefix points to the first char that is not aphanumeric,'.', '_' or '-',
387    * start from the end of filter_te_str.
388    **/
389   prefix = prefix_start + strlen(prefix_start);
390   while(prefix != prefix_start) {
391     prefix--;
392     if(!g_ascii_isalnum((*prefix)) && (*prefix) != '.' && (*prefix) != '_' && (*prefix) != '-') {
393       prefix++;
394       break;
395     }
396   }
397
398   /* Now, if the pressed key is decimal or period, and there is no period or
399    * decimal before it in prefix then construct the popup window.
400    *
401    * If the pressed key is backspace, and there is no existing popup window
402    * then construct the popup window again.
403    **/
404   if(k==GDK_period || k==GDK_KP_Decimal) {
405     if( !strchr(prefix, '.') || !popup_win) {
406
407       gchar* name_with_period;
408
409       if (popup_win) {
410         gtk_widget_destroy (popup_win);
411       }
412
413       name_with_period = g_strconcat(prefix, event->string, NULL);
414       popup_win = filter_autocomplete_new(filter_te, name_with_period, FALSE, &stop_propagation);
415       g_object_set_data(G_OBJECT(w_toplevel), E_FILT_AUTOCOMP_PTR_KEY, popup_win);
416
417       g_free(name_with_period);
418       g_free(prefix_start);
419
420       return stop_propagation;
421     }
422   } else if(k==GDK_BackSpace && !popup_win) {
423
424     if(strlen(prefix) > 1) {
425       /* Delete the last character in the prefix string */
426       prefix[strlen(prefix)-1] = '\0';
427       if(strchr(prefix, '.')) {
428         popup_win = filter_autocomplete_new(filter_te, prefix, FALSE, NULL);
429         g_object_set_data(G_OBJECT(w_toplevel), E_FILT_AUTOCOMP_PTR_KEY, popup_win);
430       } else if(strlen(prefix) && is_protocol_name_being_typed(filter_te, (int) strlen(prefix)+2)) {
431         popup_win = filter_autocomplete_new(filter_te, prefix, TRUE, NULL);
432         g_object_set_data(G_OBJECT(w_toplevel), E_FILT_AUTOCOMP_PTR_KEY, popup_win);
433       }
434     }
435
436     g_free(prefix_start);
437
438     return FALSE;
439   } else if(g_ascii_isalnum(ckey) && !popup_win) {
440     gchar *name = g_strconcat(prefix, event->string, NULL);
441
442     if( !strchr(name, '.') && is_protocol_name_being_typed(filter_te, (int) strlen(name)) ) {
443       popup_win = filter_autocomplete_new(filter_te, name, TRUE, &stop_propagation);
444       g_object_set_data(G_OBJECT(w_toplevel), E_FILT_AUTOCOMP_PTR_KEY, popup_win);
445     }
446
447     g_free(name);
448     g_free(prefix_start);
449
450     return stop_propagation;
451   }
452
453   /* If the popup window hasn't been constructed yet then we have nothing to do */
454   if( !popup_win ) {
455     g_free(prefix_start);
456
457     return FALSE;
458   }
459
460
461   treeview = g_object_get_data(G_OBJECT(popup_win), E_FILT_AUTOCOMP_TREE_KEY);
462   selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
463   model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
464
465   switch(k)
466     {
467       /* a better implementation for UP/DOWN keys would be moving the control to the popup'ed window, and letting 
468        * the treeview handle the up, down actions directly and return the control to the filter text once 
469        * the user press Enter or any key except for UP, DOWN arrows. * I wasn't able to find a way to do that. *
470        **/
471     case GDK_Page_Down:
472     case GDK_Down:
473       if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
474         if (gtk_tree_model_iter_next(model, &iter)) {
475           if (k == GDK_Page_Down) {
476             /* Skip up to 8 entries */
477             GtkTreeIter last_iter;
478             gint count = 0;
479             do {
480               last_iter = iter;
481             } while (++count < 8 && gtk_tree_model_iter_next(model, &iter));
482             iter = last_iter;
483           }
484           gtk_tree_selection_select_iter(GTK_TREE_SELECTION(selection), &iter);
485           path = gtk_tree_model_get_path(model, &iter);
486           gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(treeview), path,
487                                        NULL, FALSE, 0, 0);
488           gtk_tree_path_free(path);
489         } else {
490           gtk_tree_selection_unselect_all(selection);
491         }
492       } else if (gtk_tree_model_get_iter_first(model, &iter)) {
493         gtk_tree_selection_select_iter(GTK_TREE_SELECTION(selection), &iter);
494         path = gtk_tree_model_get_path(model, &iter);
495         gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(treeview), path,
496                                      NULL, FALSE, 0, 0);
497         gtk_tree_path_free(path);
498       }
499
500       g_free(prefix_start);
501
502       /* stop event propagation */
503       return TRUE;
504
505     case GDK_Page_Up:
506     case GDK_Up:
507     {
508       GtkTreeIter last_iter;
509
510       if (gtk_tree_selection_get_selected(selection, &model, &iter) ) {
511         path = gtk_tree_model_get_path(model, &iter);
512
513         if (gtk_tree_path_prev(path)) {
514           if (k == GDK_Page_Up) {
515             /* Skip up to 8 entries */
516             GtkTreePath *last_path;
517             gint count = 0;
518             do {
519               last_path = path;
520             } while (++count < 8 && gtk_tree_path_prev(path));
521             path = last_path;
522           }
523           gtk_tree_selection_select_path(GTK_TREE_SELECTION(selection), path);
524           gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(treeview), path, NULL, FALSE, 0, 0);
525         } else {
526           gtk_tree_selection_unselect_iter(selection, &iter);
527         }
528         gtk_tree_path_free(path);
529       } else if (gtk_tree_model_get_iter_first(model, &iter)) {
530         do {
531           last_iter = iter;
532         } while (gtk_tree_model_iter_next(model, &iter));
533         gtk_tree_selection_select_iter(GTK_TREE_SELECTION(selection), &last_iter);
534         path = gtk_tree_model_get_path(model, &last_iter);
535         gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(treeview), path,
536                                      NULL, FALSE, 0, 0);
537         gtk_tree_path_free(path);
538       }
539
540       g_free(prefix_start);
541
542       /* stop event propagation */
543       return TRUE;
544     }
545
546
547       /* if pressed key is Space or Enter then autocomplete protocol string */
548     case GDK_space:
549     case GDK_Return:
550     case GDK_KP_Enter:
551
552       if(gtk_tree_selection_get_selected(selection, &model, &iter) ) {
553         gchar *value;
554
555         /* Do not autocomplete protocols with space yet, because we can be in
556          * a operator or a value field.
557          **/
558         if(k != GDK_space || strchr(prefix, '.')) {
559           /* Use chosen string */
560           gtk_tree_model_get(model, &iter, 0, &value,  -1);
561           autocomplete_protocol_string(filter_te, value);
562           g_free(value);
563         }
564       }
565
566       /* Lose popup */
567       gtk_widget_destroy(popup_win);
568       g_object_set_data(G_OBJECT(w_toplevel), E_FILT_AUTOCOMP_PTR_KEY, NULL);
569       break;
570
571     case GDK_BackSpace:
572       filter_autocomplete_handle_backspace(filter_te, treeview, popup_win, prefix, w_toplevel);
573       break;
574
575     default: {
576       gchar* updated_str;
577
578       updated_str = g_strconcat(prefix, event->string, NULL);
579       if( !autocompletion_list_lookup(filter_te, popup_win, treeview, updated_str, &stop_propagation) ) {
580         /* function returned false, ie the list is empty -> close popup  */
581         gtk_widget_destroy(popup_win);
582         g_object_set_data(G_OBJECT(w_toplevel), E_FILT_AUTOCOMP_PTR_KEY, NULL);
583       }
584
585       g_free(updated_str);
586     }
587
588   }
589
590   g_free(prefix_start);
591
592   if(k == GDK_Return || k == GDK_KP_Enter)
593     return TRUE;    /* stop event propagation */
594
595   return stop_propagation;
596 }
597
598 /*
599  * In my implementation, I'm looking for fields that match the protocol name in the whole fields list
600  * and not only restrict the process by returning all the fields of the protocol that match the prefix using
601  * 'proto_get_id_by_filter_name(protocol_name)'; because I have noticed that some of the fields
602  * have a prefix different than its parent protocol; for example SIP protocol had this field raw_sip.line despite
603  * that there is a protocol called RAW_SIP which it should be associated with it.
604  * so the unorganized fields and nonexistent of a standardized protocols and fields naming rules prevent me from
605  * implementing the autocomplete in an optimized way.
606  **/
607 static gboolean
608 build_autocompletion_list(GtkWidget *filter_te, GtkWidget *treeview, GtkWidget *popup_win, 
609                           const gchar *protocol_name, gboolean protocols_only, gboolean *stop_propagation)
610 {
611   void *cookie, *cookie2;
612   protocol_t *protocol;
613   unsigned int protocol_name_len;
614   header_field_info *hfinfo;
615   gint count = 0;
616   gboolean exact_match = FALSE;
617   const gchar *first = NULL;
618   int i;
619
620   protocol_name_len = (unsigned int) strlen(protocol_name);
621
622   /* Force load protocol fields, if not already done */
623   if(protocol_name[protocol_name_len-1] == '.')
624     proto_registrar_get_byname(protocol_name);
625
626   /* Walk protocols list */
627   for (i = proto_get_first_protocol(&cookie); i != -1; i = proto_get_next_protocol(&cookie)) {
628
629     protocol = find_protocol_by_id(i);
630
631     if (!proto_is_protocol_enabled(protocol))
632       continue;
633
634     if (protocols_only) {
635       const gchar *name = proto_get_protocol_filter_name (i);
636
637       if (!g_ascii_strncasecmp(protocol_name, name, protocol_name_len)) {
638         add_to_autocompletion_list(treeview, name);
639         if (strlen(name) == protocol_name_len) {
640           exact_match = TRUE;
641         }
642         count++;
643         if (count == 1) 
644           first = name;
645       }
646     } else {
647
648       for (hfinfo = proto_get_first_protocol_field(i, &cookie2); 
649            hfinfo != NULL;
650            hfinfo = proto_get_next_protocol_field(&cookie2)) 
651       {
652         if (hfinfo->same_name_prev != NULL) /* ignore duplicate names */
653           continue;
654
655         if(!g_ascii_strncasecmp(protocol_name, hfinfo->abbrev, protocol_name_len)) {
656           add_to_autocompletion_list(treeview, hfinfo->abbrev);
657           if (strlen(hfinfo->abbrev) == protocol_name_len) {
658             exact_match = TRUE;
659           }
660           count++;
661           if (count == 1) 
662             first = hfinfo->abbrev;
663         }
664       }
665     }
666   }
667
668   if (count == 1 && !exact_match && stop_propagation && 
669       strncmp(protocol_name, first, protocol_name_len) == 0)
670   {
671     /* Only one match (not exact) with correct case */
672     *stop_propagation = check_select_region(filter_te, popup_win, first, protocol_name_len);
673   }
674
675   /* Don't show an empty autocompletion-list or
676    * an autocompletion-list with only one entry with exact match 
677    **/
678   if (count == 0 || (count == 1 && exact_match && 
679                      strncmp(protocol_name, first, protocol_name_len) == 0))
680     return FALSE;
681
682   return TRUE;
683 }
684
685 static void
686 filter_autocomplete_disable_sorting(GtkTreeModel *model)
687 {
688 #if GTK_CHECK_VERSION(2,6,0)
689   gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
690 #else
691   gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), -2, GTK_SORT_ASCENDING);
692 #endif
693 }
694
695 static void
696 filter_autocomplete_enable_sorting(GtkTreeModel *model)
697 {
698   gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), 0, GTK_SORT_ASCENDING);
699 }
700
701 static GtkWidget *
702 filter_autocomplete_new(GtkWidget *filter_te, const gchar *protocol_name, gboolean protocols_only, gboolean *stop_propagation)
703 {
704   GtkWidget *popup_win;
705   GtkWidget *treeview;
706   GtkWidget *filter_sc;
707   gint x_pos, y_pos;
708   GtkTreeModel *model;
709   GtkTreeSelection *selection;
710   GtkRequisition requisition;
711   GtkWidget *w_toplevel;
712
713   w_toplevel = gtk_widget_get_toplevel(filter_te);
714
715   /* Create popup window */
716   popup_win = gtk_window_new (GTK_WINDOW_POPUP); 
717
718   /* Create scrolled window */
719   filter_sc = scrolled_window_new(NULL, NULL);
720   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (filter_sc), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
721   gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(filter_sc), GTK_SHADOW_IN);
722   gtk_container_add(GTK_CONTAINER(popup_win), filter_sc);
723
724   /* Create tree view */
725   treeview = gtk_tree_view_new();
726 #if GTK_CHECK_VERSION(2,6,0)
727   gtk_tree_view_set_hover_selection(GTK_TREE_VIEW(treeview), TRUE);
728 #endif
729   init_autocompletion_list(treeview);
730   g_object_set_data(G_OBJECT(popup_win), E_FILT_AUTOCOMP_TREE_KEY, treeview);
731
732   /* Build list */
733   if (!build_autocompletion_list(filter_te, treeview, popup_win, protocol_name, protocols_only, stop_propagation)) {
734     gtk_widget_destroy(popup_win);
735     return NULL;
736   }
737
738   /* Sort treeview */
739   model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
740   if(model)
741     filter_autocomplete_enable_sorting(model);
742
743   gtk_container_add (GTK_CONTAINER (filter_sc), treeview);
744
745   g_signal_connect(treeview, "row-activated", G_CALLBACK(autoc_filter_row_activated_cb), filter_te);
746   g_signal_connect(filter_te, "focus-out-event", G_CALLBACK(filter_te_focus_out_cb), w_toplevel);
747   g_signal_connect(popup_win, "destroy", G_CALLBACK(filter_autocomplete_win_destroy_cb), NULL);
748
749   gtk_widget_size_request(treeview, &requisition);
750
751   gtk_widget_set_size_request(popup_win, filter_te->allocation.width, (requisition.height<200? requisition.height+8:200));
752   gtk_window_resize(GTK_WINDOW(popup_win), filter_te->allocation.width, (requisition.height<200? requisition.height+8:200));
753
754   gdk_window_get_origin(filter_te->window, &x_pos, &y_pos);
755   y_pos = y_pos + filter_te->allocation.height;
756   gtk_window_move(GTK_WINDOW(popup_win), x_pos, y_pos);
757
758   gtk_widget_show_all (popup_win);
759
760   selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
761   gtk_tree_selection_unselect_all(selection);
762
763   return popup_win;
764 }
765
766 static void
767 filter_autocomplete_handle_backspace(GtkWidget *filter_te, GtkWidget *list, GtkWidget *popup_win, gchar *prefix, GtkWidget *main_win)
768 {
769   GtkTreeModel *model;
770   GtkListStore *store;
771   GtkRequisition requisition;
772   size_t prefix_len;
773   gboolean protocols_only = FALSE;
774
775   /* Delete the last character in the prefix string */
776   prefix_len = strlen(prefix)-1;
777   prefix[prefix_len] = '\0';
778
779   if (prefix_len == 0) {
780     /* Remove the popup window for protocols */
781     gtk_widget_destroy(popup_win);
782     g_object_set_data(G_OBJECT(main_win), E_FILT_AUTOCOMP_PTR_KEY, NULL);
783     return;
784   } else if(strchr(prefix, '.') == NULL) {
785     protocols_only = TRUE;
786   }
787
788   /* Empty list */
789   model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
790   store = GTK_LIST_STORE(model);
791   gtk_list_store_clear(store);
792
793   /* Disable sorting */
794   filter_autocomplete_disable_sorting(model);
795
796   /* Build new list */
797   if (!build_autocompletion_list(filter_te, list, popup_win, prefix, protocols_only, NULL)) {
798     gtk_widget_destroy(popup_win);
799     g_object_set_data(G_OBJECT(main_win), E_FILT_AUTOCOMP_PTR_KEY, NULL);
800     return;
801   }
802
803   /* Enable sorting */
804   filter_autocomplete_enable_sorting(model);
805   
806   gtk_tree_view_columns_autosize(GTK_TREE_VIEW(list));
807   gtk_widget_size_request(list, &requisition);
808
809   gtk_widget_set_size_request(popup_win, popup_win->allocation.width, (requisition.height<200? requisition.height+8:200));
810   gtk_window_resize(GTK_WINDOW(popup_win), popup_win->allocation.width, (requisition.height<200? requisition.height+8:200));
811 }
812
813 static void 
814 filter_autocomplete_win_destroy_cb(GtkWidget *win, gpointer data _U_)
815 {
816   /* tell that the autocomplete window doesn't exist anymore */
817   g_object_set_data(G_OBJECT(win), E_FILT_AUTOCOMP_PTR_KEY, NULL);
818 }
819
820 gboolean
821 filter_parent_dlg_key_pressed_cb(GtkWidget *win, GdkEventKey *event)
822 {
823   GtkWidget *popup_win;
824
825   popup_win = g_object_get_data(G_OBJECT(win), E_FILT_AUTOCOMP_PTR_KEY);
826
827   if(popup_win && event->keyval == GDK_Escape) {
828     gtk_widget_destroy(popup_win);
829     g_object_set_data(G_OBJECT(win), E_FILT_AUTOCOMP_PTR_KEY, NULL);
830
831     /* stop event propagation */
832     return TRUE;
833   }
834
835   return FALSE;
836 }