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