1 /* filter_autocomplete.h
2 * Definitions for filter autocomplete
4 * Copyright 2008, Bahaa Naamneh <b.naamneh@gmail.com>
6 * With several usability improvements:
7 * Copyright 2008, Stig Bjorlykke <stig@bjorlykke.org>
11 * Wireshark - Network traffic analyzer
12 * By Gerald Combs <gerald@wireshark.org>
13 * Copyright 1998 Gerald Combs
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.
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.
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.
37 #include <gdk/gdkkeysyms.h>
38 #if GTK_CHECK_VERSION(3,0,0)
39 # include <gdk/gdkkeysyms-compat.h>
42 #include <epan/proto.h>
44 #include "ui/gtk/gui_utils.h"
45 #include "ui/gtk/filter_autocomplete.h"
47 #include "ui/gtk/old-gtk-compat.h"
49 #define E_FILT_AUTOCOMP_TREE_KEY "filter_autocomplete_tree"
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);
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).
75 is_protocol_name_being_typed(GtkWidget *filter_te, int str_len)
78 int op_len, cursor_pos;
81 static gchar *logic_ops[] = { "!", "not",
86 /* If the cursor is located at the beginning of the filter editable text,
87 * then it's _probably_ a protocol name.
89 if(!(cursor_pos = gtk_editable_get_position(GTK_EDITABLE(filter_te))))
92 start = gtk_editable_get_chars(GTK_EDITABLE(filter_te), 0, (gint) cursor_pos);
94 /* Point to one char before the current string in the filter editable text */
95 pos = start + (cursor_pos - str_len);
97 /* Walk back through string to find last char which isn't ' ' or '(' */
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]);
104 if(pos-start+1 < op_len)
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)) {
114 /* If none of the logical operations was found, then the current string is not a protocol */
121 /* The "str" preceded only by ' ' or '(' chars,
122 * which means that the str is _probably_ a protocol name.
130 autocomplete_protocol_string(GtkWidget *filter_te, gchar *selected_str)
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);
140 /* Start from the end */
141 pch = filter_str + strlen(filter_str);
143 /* Walk back through string to find last non-punctuation */
144 while(pch != filter_str) {
146 if(!g_ascii_isalnum(*pch) && (*pch) != '.' && (*pch) != '_' && (*pch) != '-') {
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);
157 pch = (selected_str + strlen(pch));
160 gtk_editable_insert_text(GTK_EDITABLE(filter_te), pch, (gint) strlen(pch), &pos);
161 gtk_editable_set_position(GTK_EDITABLE(filter_te), pos);
165 /* On row activated signal, complete the protocol string automatically */
167 autoc_filter_row_activated_cb(GtkTreeView *treeview,
169 GtkTreeViewColumn *column _U_,
178 model = gtk_tree_view_get_model(treeview);
180 if (gtk_tree_model_get_iter(model, &iter, path)) {
182 gtk_tree_model_get(model, &iter, 0, &proto, -1);
183 autocomplete_protocol_string(GTK_WIDGET(data), proto);
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);
191 gtk_widget_destroy(win);
192 g_object_set_data(G_OBJECT(w_main), E_FILT_AUTOCOMP_PTR_KEY, NULL);
197 filter_te_focus_out_cb(GtkWidget *filter_te _U_,
203 win = g_object_get_data(G_OBJECT(data), E_FILT_AUTOCOMP_PTR_KEY);
205 gtk_widget_destroy(win);
206 g_object_set_data(G_OBJECT(data), E_FILT_AUTOCOMP_PTR_KEY, NULL);
213 check_select_region (GtkWidget *filter_te, GtkWidget *popup_win,
214 const gchar *string, unsigned int str_len)
216 gint pos1 = gtk_editable_get_position(GTK_EDITABLE(filter_te));
217 gint pos2 = pos1 + (gint) strlen(string) - str_len;
221 gtk_editable_insert_text(GTK_EDITABLE(filter_te), &string[str_len-1],
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);
233 init_autocompletion_list(GtkWidget *list)
235 GtkCellRenderer *renderer;
236 GtkTreeViewColumn *column;
239 renderer = gtk_cell_renderer_text_new();
240 column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 0, NULL);
242 gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
243 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE);
245 store = gtk_list_store_new(1, G_TYPE_STRING);
247 gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));
249 g_object_unref(store);
253 add_to_autocompletion_list(GtkWidget *list, const gchar *str)
258 store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
260 gtk_list_store_append(store, &iter);
261 gtk_list_store_set(store, &iter, 0, str, -1);
265 autocompletion_list_lookup(GtkWidget *filter_te, GtkWidget *popup_win, GtkWidget *list,
266 const gchar *str, gboolean *stop_propagation)
268 GtkRequisition requisition;
271 GtkAllocation popup_win_alloc;
273 unsigned int str_len = (unsigned int) strlen(str);
276 gboolean loop = TRUE;
277 gboolean exact_match = FALSE;
279 store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
281 if( gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter) ) {
285 gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, 0, &curr_str, -1);
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) {
294 first = g_strdup (curr_str);
296 loop = gtk_list_store_remove(store, &iter);
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);
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))
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);
322 gtk_widget_size_request(list, &requisition);
324 #if GTK_CHECK_VERSION(2,18,0)
325 gtk_widget_get_allocation(popup_win, &popup_win_alloc);
327 popup_win_alloc = popup_win->allocation;
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));
342 filter_string_te_key_pressed_cb(GtkWidget *filter_te, GdkEventKey *event, gpointer user_data _U_)
344 GtkWidget *popup_win;
345 GtkWidget *w_toplevel;
349 GtkTreeSelection *selection;
353 gboolean stop_propagation = FALSE;
358 w_toplevel = gtk_widget_get_toplevel(filter_te);
360 popup_win = g_object_get_data(G_OBJECT(w_toplevel), E_FILT_AUTOCOMP_PTR_KEY);
363 ckey = event->string[0];
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)
370 gtk_widget_show(popup_win);
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)
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);
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);
386 /* If the pressed key is non-alphanumeric or one of the keys specified
387 * in the condition (decimal, period...) then destroy popup window.
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 &&
397 gtk_widget_destroy(popup_win);
398 g_object_set_data(G_OBJECT(w_toplevel), E_FILT_AUTOCOMP_PTR_KEY, NULL);
403 /* Let prefix points to the first char that is not aphanumeric,'.', '_' or '-',
404 * start from the end of filter_te_str.
406 prefix = prefix_start + strlen(prefix_start);
407 while(prefix != prefix_start) {
409 if(!g_ascii_isalnum((*prefix)) && (*prefix) != '.' && (*prefix) != '_' && (*prefix) != '-') {
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.
418 * If the pressed key is backspace, and there is no existing popup window
419 * then construct the popup window again.
421 if(k==GDK_period || k==GDK_KP_Decimal) {
422 if( !strchr(prefix, '.') || !popup_win) {
424 gchar* name_with_period;
427 gtk_widget_destroy (popup_win);
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);
434 g_free(name_with_period);
435 g_free(prefix_start);
437 return stop_propagation;
439 } else if(k==GDK_BackSpace && !popup_win) {
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);
453 g_free(prefix_start);
456 } else if(g_ascii_isalnum(ckey) && !popup_win) {
457 gchar *name = g_strconcat(prefix, event->string, NULL);
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);
465 g_free(prefix_start);
467 return stop_propagation;
470 /* If the popup window hasn't been constructed yet then we have nothing to do */
472 g_free(prefix_start);
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));
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. *
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;
498 } while (++count < 8 && gtk_tree_model_iter_next(model, &iter));
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,
505 gtk_tree_path_free(path);
507 gtk_tree_selection_unselect_all(selection);
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,
514 gtk_tree_path_free(path);
517 g_free(prefix_start);
519 /* stop event propagation */
525 GtkTreeIter last_iter;
527 if (gtk_tree_selection_get_selected(selection, &model, &iter) ) {
528 path = gtk_tree_model_get_path(model, &iter);
530 if (gtk_tree_path_prev(path)) {
531 if (k == GDK_Page_Up) {
532 /* Skip up to 8 entries */
533 GtkTreePath *last_path;
537 } while (++count < 8 && gtk_tree_path_prev(path));
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);
543 gtk_tree_selection_unselect_iter(selection, &iter);
545 gtk_tree_path_free(path);
546 } else if (gtk_tree_model_get_iter_first(model, &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,
554 gtk_tree_path_free(path);
557 g_free(prefix_start);
559 /* stop event propagation */
564 /* if pressed key is Space or Enter then autocomplete protocol string */
569 if(gtk_tree_selection_get_selected(selection, &model, &iter) ) {
572 /* Do not autocomplete protocols with space yet, because we can be in
573 * a operator or a value field.
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);
582 stop_propagation = TRUE; /* stop event propagation */
587 gtk_widget_destroy(popup_win);
588 g_object_set_data(G_OBJECT(w_toplevel), E_FILT_AUTOCOMP_PTR_KEY, NULL);
592 filter_autocomplete_handle_backspace(filter_te, treeview, popup_win, prefix, w_toplevel);
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);
610 g_free(prefix_start);
612 return stop_propagation;
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.
625 build_autocompletion_list(GtkWidget *filter_te, GtkWidget *treeview, GtkWidget *popup_win,
626 const gchar *protocol_name, gboolean protocols_only, gboolean *stop_propagation)
628 void *cookie, *cookie2;
629 protocol_t *protocol;
630 unsigned int protocol_name_len;
631 header_field_info *hfinfo;
633 gboolean exact_match = FALSE;
634 const gchar *first = NULL;
637 protocol_name_len = (unsigned int) strlen(protocol_name);
639 /* Force load protocol fields, if not already done */
640 if(protocol_name[protocol_name_len-1] == '.')
641 proto_registrar_get_byname(protocol_name);
643 /* Walk protocols list */
644 for (i = proto_get_first_protocol(&cookie); i != -1; i = proto_get_next_protocol(&cookie)) {
646 protocol = find_protocol_by_id(i);
648 if (!proto_is_protocol_enabled(protocol))
651 if (protocols_only) {
652 const gchar *name = proto_get_protocol_filter_name (i);
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) {
665 for (hfinfo = proto_get_first_protocol_field(i, &cookie2);
667 hfinfo = proto_get_next_protocol_field(&cookie2))
669 if (hfinfo->same_name_prev != NULL) /* ignore duplicate names */
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) {
679 first = hfinfo->abbrev;
685 if (count == 1 && !exact_match && stop_propagation &&
686 strncmp(protocol_name, first, protocol_name_len) == 0)
688 /* Only one match (not exact) with correct case */
689 *stop_propagation = check_select_region(filter_te, popup_win, first, protocol_name_len);
692 /* Don't show an empty autocompletion-list or
693 * an autocompletion-list with only one entry with exact match
695 if (count == 0 || (count == 1 && exact_match &&
696 strncmp(protocol_name, first, protocol_name_len) == 0))
703 filter_autocomplete_disable_sorting(GtkTreeModel *model)
705 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model),
706 GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
710 filter_autocomplete_enable_sorting(GtkTreeModel *model)
712 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), 0, GTK_SORT_ASCENDING);
716 filter_autocomplete_new(GtkWidget *filter_te, const gchar *protocol_name,
717 gboolean protocols_only, gboolean *stop_propagation)
719 GtkWidget *popup_win;
721 GtkWidget *filter_sc;
724 GtkTreeSelection *selection;
725 GtkRequisition requisition;
726 GtkWidget *w_toplevel;
727 GtkAllocation filter_te_alloc;
729 w_toplevel = gtk_widget_get_toplevel(filter_te);
731 /* Create popup window */
732 popup_win = gtk_window_new (GTK_WINDOW_POPUP);
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);
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);
747 if (!build_autocompletion_list(filter_te, treeview, popup_win, protocol_name, protocols_only, stop_propagation)) {
748 gtk_widget_destroy(popup_win);
753 model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
755 filter_autocomplete_enable_sorting(model);
757 gtk_container_add (GTK_CONTAINER (filter_sc), treeview);
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);
763 #if GTK_CHECK_VERSION(3,0,0)
764 gtk_widget_get_preferred_size(treeview, &requisition, NULL);
766 gtk_widget_size_request(treeview, &requisition);
768 #if GTK_CHECK_VERSION(2,18,0)
769 gtk_widget_get_allocation(filter_te, &filter_te_alloc);
771 filter_te_alloc = filter_te->allocation;
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));
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);
784 y_pos += filter_te_alloc.height;
786 gtk_window_move(GTK_WINDOW(popup_win), x_pos, y_pos);
788 gtk_widget_show_all (popup_win);
790 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
791 gtk_tree_selection_unselect_all(selection);
797 filter_autocomplete_handle_backspace(GtkWidget *filter_te, GtkWidget *list, GtkWidget *popup_win,
798 gchar *prefix, GtkWidget *main_win)
802 GtkRequisition requisition;
804 gboolean protocols_only = FALSE;
805 GtkAllocation popup_win_alloc;
807 prefix_len = strlen(prefix);
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);
816 /* Delete the last character in the prefix string */
818 prefix[prefix_len] = '\0';
820 if(strchr(prefix, '.') == NULL) {
821 protocols_only = TRUE;
825 model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
826 store = GTK_LIST_STORE(model);
827 gtk_list_store_clear(store);
829 /* Disable sorting */
830 filter_autocomplete_disable_sorting(model);
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);
840 filter_autocomplete_enable_sorting(model);
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);
846 gtk_widget_size_request(list, &requisition);
849 #if GTK_CHECK_VERSION(2,18,0)
850 gtk_widget_get_allocation(popup_win, &popup_win_alloc);
852 popup_win_alloc = popup_win->allocation;
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));
863 filter_autocomplete_win_destroy_cb(GtkWidget *win, gpointer data _U_)
865 /* tell that the autocomplete window doesn't exist anymore */
866 g_object_set_data(G_OBJECT(win), E_FILT_AUTOCOMP_PTR_KEY, NULL);
870 filter_parent_dlg_key_pressed_cb(GtkWidget *win, GdkEventKey *event, gpointer user_data _U_)
872 GtkWidget *popup_win;
874 popup_win = g_object_get_data(G_OBJECT(win), E_FILT_AUTOCOMP_PTR_KEY);
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);
880 /* stop event propagation */