/* filter_autocomplete.h
 * Definitions for filter autocomplete
 *
 * Copyright 2008, Bahaa Naamneh <b.naamneh@gmail.com>
 *
 * With several usability improvements:
 * Copyright 2008, Stig Bjorlykke <stig@bjorlykke.org>
 *
 * $Id: filter_autocomplete.c 33170 2010-06-08 23:13:14Z gerald $
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */ 
 
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <string.h>

#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

#include <epan/proto.h>

#include "gtk/gui_utils.h"
#include "gtk/filter_autocomplete.h"

#define E_FILT_AUTOCOMP_TREE_KEY    "filter_autocomplete_tree"


static GtkWidget *filter_autocomplete_new(GtkWidget *filter_te, const gchar *protocol_name, 
                                          gboolean protocols_only, gboolean *stop_propagation);
static void autocomplete_protocol_string(GtkWidget  *filter_te, gchar* selected_str);
static void autoc_filter_row_activated_cb(GtkTreeView *treeview, GtkTreePath *path, 
                                          GtkTreeViewColumn *column, gpointer data);
static gint filter_te_focus_out_cb(GtkWidget *filter_te, GdkEvent *event, gpointer data);
static void init_autocompletion_list(GtkWidget *list);
static void add_to_autocompletion_list(GtkWidget *list, const gchar *str);
static gboolean autocompletion_list_lookup(GtkWidget *filter_te, GtkWidget *popup_win, GtkWidget *list,
                                           const gchar *str, gboolean *stop_propagation);
static void filter_autocomplete_handle_backspace(GtkWidget *filter_te, GtkWidget *list, GtkWidget *popup_win,
                                                 gchar *prefix, GtkWidget *main_win);
static void filter_autocomplete_win_destroy_cb(GtkWidget *win, gpointer data);
static gboolean is_protocol_name_being_typed(GtkWidget *filter_te, int str_len);


/*
 * Check if the string at the cursor position is a beginning of a protocol name.
 * Possible false positives:
 *      "NOT" at the beginning of the display filter editable text.
 *      "NOT" adjacent to another logical operation. (e.g: exp1 AND NOT exp2).
 **/
static gboolean
is_protocol_name_being_typed(GtkWidget *filter_te, int str_len)
{
  unsigned int i;
  int op_len, cursor_pos;
  gchar *start;
  gchar *pos;
  static gchar *logic_ops[] = { "!", "not",
                                "||", "or",
                                "&&", "and",
                                "^^", "xor" };
  
  /* If the cursor is located at the beginning of the filter editable text,
   * then it's _probably_ a protocol name.
   **/
  if(!(cursor_pos = gtk_editable_get_position(GTK_EDITABLE(filter_te))))
    return TRUE;
  
  start = gtk_editable_get_chars(GTK_EDITABLE(filter_te), 0, (gint) cursor_pos);
  
  /* Point to one char before the current string in the filter editable text */
  pos = start + (cursor_pos - str_len);
  
  /* Walk back through string to find last char which isn't ' ' or '(' */
  while(pos > start) {
    if(*pos != ' ' && *pos != '(') {
      /* Check if we have one of the logical operations */
      for(i = 0; i < (sizeof(logic_ops)/sizeof(logic_ops[0])); i++) {
        op_len = (int) strlen(logic_ops[i]);
        
        if(pos-start+1 < op_len)
          continue;
        
        /* If one of the logical operations is found, then the current string is _probably_ a protocol name */
        if(!strncmp(pos-op_len+1, logic_ops[i], op_len)) {
          g_free (start);
          return TRUE;
        }
      }

      /* If none of the logical operations was found, then the current string is not a protocol */
      g_free (start);
      return FALSE;
    }
    pos--;
  }
  
  /* The "str" preceded only by ' ' or '(' chars, 
   * which means that the str is _probably_ a protocol name.
   **/
  g_free (start);
  return TRUE;
}


static void
autocomplete_protocol_string(GtkWidget *filter_te, gchar *selected_str)
{
  int pos;
  gchar *filter_str;
  gchar *pch;

  /* Get the current filter string */
  pos = gtk_editable_get_position(GTK_EDITABLE(filter_te));
  filter_str = gtk_editable_get_chars(GTK_EDITABLE(filter_te), 0, pos);

  /* Start from the end */
  pch = filter_str + strlen(filter_str);

  /* Walk back through string to find last non-punctuation */
  while(pch != filter_str) {
    pch--;
    if(!g_ascii_isalnum(*pch) && (*pch) != '.' && (*pch) != '_' && (*pch) != '-') {
      pch++;
      break;
    }
  }

  if(strncmp(pch, selected_str, pos-(pch-filter_str))) {
    gtk_editable_delete_text(GTK_EDITABLE(filter_te), (gint) (pch-filter_str), pos);
    pos = (int) (pch-filter_str);
    pch = selected_str;
  } else {
    pch = (selected_str + strlen(pch));
  }

  gtk_editable_insert_text(GTK_EDITABLE(filter_te), pch, (gint) strlen(pch), &pos);
  gtk_editable_set_position(GTK_EDITABLE(filter_te), pos);
  g_free (filter_str);
}

/* On row activated signal, complete the protocol string automatically */
static void
autoc_filter_row_activated_cb(GtkTreeView *treeview, 
                              GtkTreePath *path, 
                              GtkTreeViewColumn *column _U_, 
                              gpointer data)
{
  GtkWidget *w_main;
  GtkTreeModel *model;
  GtkTreeIter iter;
  GtkWidget *win;
  gchar *proto;

  model = gtk_tree_view_get_model(treeview);

  if (gtk_tree_model_get_iter(model, &iter, path)) {

    gtk_tree_model_get(model, &iter, 0, &proto, -1);
    autocomplete_protocol_string(GTK_WIDGET(data), proto);

    g_free (proto);
  }

  w_main = gtk_widget_get_toplevel(GTK_WIDGET(data));
  win = g_object_get_data(G_OBJECT(w_main), E_FILT_AUTOCOMP_PTR_KEY);
  if(win != NULL) {
    gtk_widget_destroy(win);
    g_object_set_data(G_OBJECT(w_main), E_FILT_AUTOCOMP_PTR_KEY, NULL);
  }
}

static gint
filter_te_focus_out_cb(GtkWidget *filter_te _U_, 
                       GdkEvent *event _U_, 
                       gpointer data)
{
  GtkWidget *win;

  win = g_object_get_data(G_OBJECT(data), E_FILT_AUTOCOMP_PTR_KEY);
  if(win != NULL) {
    gtk_widget_destroy(win);
    g_object_set_data(G_OBJECT(data), E_FILT_AUTOCOMP_PTR_KEY, NULL);
  }

  return FALSE;
}

static gboolean
check_select_region (GtkWidget *filter_te, GtkWidget *popup_win, 
                     const gchar *string, unsigned int str_len)
{
  gint pos1 = gtk_editable_get_position(GTK_EDITABLE(filter_te));
  gint pos2 = pos1 + (gint) strlen(string) - str_len;
  gint pos3 = pos1;

  if (pos2 > pos1) {
    gtk_editable_insert_text(GTK_EDITABLE(filter_te), &string[str_len-1],
                             pos2-pos1+1, &pos3);
    gtk_editable_set_position(GTK_EDITABLE(filter_te), pos1+1);
    gtk_editable_select_region(GTK_EDITABLE(filter_te), pos1+1, pos2+1);
    gtk_widget_hide (popup_win);
    return TRUE;
  }

  return FALSE;
}

static void
init_autocompletion_list(GtkWidget *list)
{
  GtkCellRenderer *renderer;
  GtkTreeViewColumn *column;
  GtkListStore *store;

  renderer = gtk_cell_renderer_text_new();
  column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 0, NULL);

  gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
  gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE);

  store = gtk_list_store_new(1, G_TYPE_STRING);

  gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));

  g_object_unref(store);
}

static void
add_to_autocompletion_list(GtkWidget *list, const gchar *str)
{
  GtkListStore *store;
  GtkTreeIter iter;

  store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));

  gtk_list_store_append(store, &iter);
  gtk_list_store_set(store, &iter, 0, str, -1);
}

static gboolean
autocompletion_list_lookup(GtkWidget *filter_te, GtkWidget *popup_win, GtkWidget *list, 
			   const gchar *str, gboolean *stop_propagation)
{
  GtkRequisition requisition;
  GtkListStore *store;
  GtkTreeIter iter;
  gchar *curr_str;
  unsigned int str_len = (unsigned int) strlen(str);
  gchar *first = NULL;
  gint count = 0;
  gboolean loop = TRUE;
  gboolean exact_match = FALSE;

  store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));

  if( gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter) ) {

    do {

      gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, 0, &curr_str,  -1);

      if( !g_ascii_strncasecmp(str, curr_str, str_len) ) {
        loop = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
        if (strlen(curr_str) == str_len) {
          exact_match = TRUE;
        }
        count++;
        if (count == 1) 
          first = g_strdup (curr_str);
      } else {
        loop = gtk_list_store_remove(store, &iter);
      }

      g_free(curr_str);

    } while( loop );

    if (count == 1 && !exact_match && strncmp(str, first, str_len) == 0) {
      /* Only one match (not exact) with correct case */
      *stop_propagation = check_select_region(filter_te, popup_win, first, str_len);
    }

    /* Don't show an autocompletion-list with only one entry with exact match */
    if ((count == 1 && exact_match && strncmp(str, first, str_len) == 0) ||
        !gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter))
    {
      g_free (first);
      return FALSE;
    }

    g_free (first);

    gtk_tree_view_columns_autosize(GTK_TREE_VIEW(list));
    gtk_widget_size_request(list, &requisition);

    gtk_widget_set_size_request(popup_win, popup_win->allocation.width, (requisition.height<200? requisition.height+8:200));
    gtk_window_resize(GTK_WINDOW(popup_win), popup_win->allocation.width, (requisition.height<200? requisition.height+8:200));

    return TRUE;
  }

  return FALSE;
}

gboolean
filter_string_te_key_pressed_cb(GtkWidget *filter_te, GdkEventKey *event)
{
  GtkWidget *popup_win;
  GtkWidget *w_toplevel;
  GtkWidget *treeview;
  GtkTreeModel *model;
  GtkTreePath *path;
  GtkTreeSelection *selection;
  GtkTreeIter iter;
  gchar* prefix;
  gchar* prefix_start;
  gboolean stop_propagation = FALSE;
  guint k;
  gchar ckey;
  gint pos;

  w_toplevel = gtk_widget_get_toplevel(filter_te);

  popup_win = g_object_get_data(G_OBJECT(w_toplevel), E_FILT_AUTOCOMP_PTR_KEY);

  k = event->keyval;
  ckey = event->string[0];

  /* If the pressed key is SHIFT then we have nothing to do with the pressed key. */
  if( k == GDK_Shift_L || k == GDK_Shift_R)
    return FALSE;

  if (popup_win)
    gtk_widget_show(popup_win);

  pos = gtk_editable_get_position(GTK_EDITABLE(filter_te));
  if (g_ascii_isalnum(ckey) ||      
      k == GDK_KP_Decimal || k == GDK_period ||
      k == GDK_underscore || k == GDK_minus) 
  {
    /* Ensure we delete the selected text */
    gtk_editable_delete_selection(GTK_EDITABLE(filter_te));
  } else if (k == GDK_Return || k == GDK_KP_Enter) {
    /* Remove selection */
    gtk_editable_select_region(GTK_EDITABLE(filter_te), pos, pos);
  }
  /* get the string from filter_te, start from 0 till cursor's position */
  prefix_start = gtk_editable_get_chars(GTK_EDITABLE(filter_te), 0, pos);

  /* If the pressed key is non-alphanumeric or one of the keys specified 
   * in the condition (decimal, period...) then destroy popup window.
   **/
  if( !g_ascii_isalnum(ckey) && 
      k != GDK_KP_Decimal && k != GDK_period && 
      k != GDK_underscore && k != GDK_minus &&
      k != GDK_space && k != GDK_Return && k != GDK_KP_Enter && 
      k != GDK_Page_Down && k != GDK_Down && k != GDK_Page_Up && k != GDK_Up &&
      k != GDK_BackSpace)
  {
    if (popup_win) {
      gtk_widget_destroy(popup_win);
      g_object_set_data(G_OBJECT(w_toplevel), E_FILT_AUTOCOMP_PTR_KEY, NULL);
    }
    return FALSE;
  }
  
  /* Let prefix points to the first char that is not aphanumeric,'.', '_' or '-',
   * start from the end of filter_te_str.
   **/
  prefix = prefix_start + strlen(prefix_start);
  while(prefix != prefix_start) {
    prefix--;
    if(!g_ascii_isalnum((*prefix)) && (*prefix) != '.' && (*prefix) != '_' && (*prefix) != '-') {
      prefix++;
      break;
    }
  }

  /* Now, if the pressed key is decimal or period, and there is no period or
   * decimal before it in prefix then construct the popup window.
   *
   * If the pressed key is backspace, and there is no existing popup window
   * then construct the popup window again.
   **/
  if(k==GDK_period || k==GDK_KP_Decimal) {
    if( !strchr(prefix, '.') || !popup_win) {

      gchar* name_with_period;

      if (popup_win) {
        gtk_widget_destroy (popup_win);
      }

      name_with_period = g_strconcat(prefix, event->string, NULL);
      popup_win = filter_autocomplete_new(filter_te, name_with_period, FALSE, &stop_propagation);
      g_object_set_data(G_OBJECT(w_toplevel), E_FILT_AUTOCOMP_PTR_KEY, popup_win);

      g_free(name_with_period);
      g_free(prefix_start);

      return stop_propagation;
    }
  } else if(k==GDK_BackSpace && !popup_win) {

    if(strlen(prefix) > 1) {
      /* Delete the last character in the prefix string */
      prefix[strlen(prefix)-1] = '\0';
      if(strchr(prefix, '.')) {
        popup_win = filter_autocomplete_new(filter_te, prefix, FALSE, NULL);
        g_object_set_data(G_OBJECT(w_toplevel), E_FILT_AUTOCOMP_PTR_KEY, popup_win);
      } else if(strlen(prefix) && is_protocol_name_being_typed(filter_te, (int) strlen(prefix)+2)) {
        popup_win = filter_autocomplete_new(filter_te, prefix, TRUE, NULL);
        g_object_set_data(G_OBJECT(w_toplevel), E_FILT_AUTOCOMP_PTR_KEY, popup_win);
      }
    }

    g_free(prefix_start);

    return FALSE;
  } else if(g_ascii_isalnum(ckey) && !popup_win) {
    gchar *name = g_strconcat(prefix, event->string, NULL);

    if( !strchr(name, '.') && is_protocol_name_being_typed(filter_te, (int) strlen(name)) ) {
      popup_win = filter_autocomplete_new(filter_te, name, TRUE, &stop_propagation);
      g_object_set_data(G_OBJECT(w_toplevel), E_FILT_AUTOCOMP_PTR_KEY, popup_win);
    }

    g_free(name);
    g_free(prefix_start);

    return stop_propagation;
  }

  /* If the popup window hasn't been constructed yet then we have nothing to do */
  if( !popup_win ) {
    g_free(prefix_start);

    return FALSE;
  }


  treeview = g_object_get_data(G_OBJECT(popup_win), E_FILT_AUTOCOMP_TREE_KEY);
  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
  model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));

  switch(k)
    {
      /* a better implementation for UP/DOWN keys would be moving the control to the popup'ed window, and letting 
       * the treeview handle the up, down actions directly and return the control to the filter text once 
       * the user press Enter or any key except for UP, DOWN arrows. * I wasn't able to find a way to do that. *
       **/
    case GDK_Page_Down:
    case GDK_Down:
      if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
        if (gtk_tree_model_iter_next(model, &iter)) {
          if (k == GDK_Page_Down) {
            /* Skip up to 8 entries */
            GtkTreeIter last_iter;
            gint count = 0;
            do {
              last_iter = iter;
            } while (++count < 8 && gtk_tree_model_iter_next(model, &iter));
            iter = last_iter;
          }
          gtk_tree_selection_select_iter(GTK_TREE_SELECTION(selection), &iter);
          path = gtk_tree_model_get_path(model, &iter);
          gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(treeview), path,
                                       NULL, FALSE, 0, 0);
          gtk_tree_path_free(path);
        } else {
          gtk_tree_selection_unselect_all(selection);
        }
      } else if (gtk_tree_model_get_iter_first(model, &iter)) {
        gtk_tree_selection_select_iter(GTK_TREE_SELECTION(selection), &iter);
        path = gtk_tree_model_get_path(model, &iter);
        gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(treeview), path,
                                     NULL, FALSE, 0, 0);
        gtk_tree_path_free(path);
      }

      g_free(prefix_start);

      /* stop event propagation */
      return TRUE;

    case GDK_Page_Up:
    case GDK_Up:
    {
      GtkTreeIter last_iter;

      if (gtk_tree_selection_get_selected(selection, &model, &iter) ) {
        path = gtk_tree_model_get_path(model, &iter);

        if (gtk_tree_path_prev(path)) {
          if (k == GDK_Page_Up) {
            /* Skip up to 8 entries */
            GtkTreePath *last_path;
            gint count = 0;
            do {
              last_path = path;
            } while (++count < 8 && gtk_tree_path_prev(path));
            path = last_path;
          }
          gtk_tree_selection_select_path(GTK_TREE_SELECTION(selection), path);
          gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(treeview), path, NULL, FALSE, 0, 0);
        } else {
          gtk_tree_selection_unselect_iter(selection, &iter);
        }
        gtk_tree_path_free(path);
      } else if (gtk_tree_model_get_iter_first(model, &iter)) {
        do {
          last_iter = iter;
        } while (gtk_tree_model_iter_next(model, &iter));
        gtk_tree_selection_select_iter(GTK_TREE_SELECTION(selection), &last_iter);
        path = gtk_tree_model_get_path(model, &last_iter);
        gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(treeview), path,
                                     NULL, FALSE, 0, 0);
        gtk_tree_path_free(path);
      }

      g_free(prefix_start);

      /* stop event propagation */
      return TRUE;
    }


      /* if pressed key is Space or Enter then autocomplete protocol string */
    case GDK_space:
    case GDK_Return:
    case GDK_KP_Enter:

      if(gtk_tree_selection_get_selected(selection, &model, &iter) ) {
        gchar *value;

        /* Do not autocomplete protocols with space yet, because we can be in
         * a operator or a value field.
         **/
        if(k != GDK_space || strchr(prefix, '.')) {
          /* Use chosen string */
          gtk_tree_model_get(model, &iter, 0, &value,  -1);
          autocomplete_protocol_string(filter_te, value);
          g_free(value);
        }
      }

      /* Lose popup */
      gtk_widget_destroy(popup_win);
      g_object_set_data(G_OBJECT(w_toplevel), E_FILT_AUTOCOMP_PTR_KEY, NULL);
      break;

    case GDK_BackSpace:
      filter_autocomplete_handle_backspace(filter_te, treeview, popup_win, prefix, w_toplevel);
      break;

    default: {
      gchar* updated_str;

      updated_str = g_strconcat(prefix, event->string, NULL);
      if( !autocompletion_list_lookup(filter_te, popup_win, treeview, updated_str, &stop_propagation) ) {
        /* function returned false, ie the list is empty -> close popup  */
        gtk_widget_destroy(popup_win);
        g_object_set_data(G_OBJECT(w_toplevel), E_FILT_AUTOCOMP_PTR_KEY, NULL);
      }

      g_free(updated_str);
    }

  }

  g_free(prefix_start);

  if(k == GDK_Return || k == GDK_KP_Enter)
    return TRUE;    /* stop event propagation */

  return stop_propagation;
}

/*
 * In my implementation, I'm looking for fields that match the protocol name in the whole fields list
 * and not only restrict the process by returning all the fields of the protocol that match the prefix using
 * 'proto_get_id_by_filter_name(protocol_name)'; because I have noticed that some of the fields
 * have a prefix different than its parent protocol; for example SIP protocol had this field raw_sip.line despite
 * that there is a protocol called RAW_SIP which it should be associated with it.
 * so the unorganized fields and nonexistent of a standardized protocols and fields naming rules prevent me from
 * implementing the autocomplete in an optimized way.
 **/
static gboolean
build_autocompletion_list(GtkWidget *filter_te, GtkWidget *treeview, GtkWidget *popup_win, 
			  const gchar *protocol_name, gboolean protocols_only, gboolean *stop_propagation)
{
  void *cookie, *cookie2;
  protocol_t *protocol;
  unsigned int protocol_name_len;
  header_field_info *hfinfo;
  gint count = 0;
  gboolean exact_match = FALSE;
  const gchar *first = NULL;
  int i;

  protocol_name_len = (unsigned int) strlen(protocol_name);

  /* Force load protocol fields, if not already done */
  if(protocol_name[protocol_name_len-1] == '.')
    proto_registrar_get_byname(protocol_name);

  /* Walk protocols list */
  for (i = proto_get_first_protocol(&cookie); i != -1; i = proto_get_next_protocol(&cookie)) {

    protocol = find_protocol_by_id(i);

    if (!proto_is_protocol_enabled(protocol))
      continue;

    if (protocols_only) {
      const gchar *name = proto_get_protocol_filter_name (i);

      if (!g_ascii_strncasecmp(protocol_name, name, protocol_name_len)) {
        add_to_autocompletion_list(treeview, name);
        if (strlen(name) == protocol_name_len) {
          exact_match = TRUE;
        }
        count++;
        if (count == 1) 
          first = name;
      }
    } else {

      for (hfinfo = proto_get_first_protocol_field(i, &cookie2); 
           hfinfo != NULL;
           hfinfo = proto_get_next_protocol_field(&cookie2)) 
      {
        if (hfinfo->same_name_prev != NULL) /* ignore duplicate names */
          continue;

        if(!g_ascii_strncasecmp(protocol_name, hfinfo->abbrev, protocol_name_len)) {
          add_to_autocompletion_list(treeview, hfinfo->abbrev);
          if (strlen(hfinfo->abbrev) == protocol_name_len) {
            exact_match = TRUE;
          }
          count++;
          if (count == 1) 
            first = hfinfo->abbrev;
        }
      }
    }
  }

  if (count == 1 && !exact_match && stop_propagation && 
      strncmp(protocol_name, first, protocol_name_len) == 0)
  {
    /* Only one match (not exact) with correct case */
    *stop_propagation = check_select_region(filter_te, popup_win, first, protocol_name_len);
  }

  /* Don't show an empty autocompletion-list or
   * an autocompletion-list with only one entry with exact match 
   **/
  if (count == 0 || (count == 1 && exact_match && 
		     strncmp(protocol_name, first, protocol_name_len) == 0))
    return FALSE;

  return TRUE;
}

static void
filter_autocomplete_disable_sorting(GtkTreeModel *model)
{
#if GTK_CHECK_VERSION(2,6,0)
  gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
#else
  gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), -2, GTK_SORT_ASCENDING);
#endif
}

static void
filter_autocomplete_enable_sorting(GtkTreeModel *model)
{
  gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), 0, GTK_SORT_ASCENDING);
}

static GtkWidget *
filter_autocomplete_new(GtkWidget *filter_te, const gchar *protocol_name, gboolean protocols_only, gboolean *stop_propagation)
{
  GtkWidget *popup_win;
  GtkWidget *treeview;
  GtkWidget *filter_sc;
  gint x_pos, y_pos;
  GtkTreeModel *model;
  GtkTreeSelection *selection;
  GtkRequisition requisition;
  GtkWidget *w_toplevel;

  w_toplevel = gtk_widget_get_toplevel(filter_te);

  /* Create popup window */
  popup_win = gtk_window_new (GTK_WINDOW_POPUP); 

  /* Create scrolled window */
  filter_sc = scrolled_window_new(NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (filter_sc), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(filter_sc), GTK_SHADOW_IN);
  gtk_container_add(GTK_CONTAINER(popup_win), filter_sc);

  /* Create tree view */
  treeview = gtk_tree_view_new();
#if GTK_CHECK_VERSION(2,6,0)
  gtk_tree_view_set_hover_selection(GTK_TREE_VIEW(treeview), TRUE);
#endif
  init_autocompletion_list(treeview);
  g_object_set_data(G_OBJECT(popup_win), E_FILT_AUTOCOMP_TREE_KEY, treeview);

  /* Build list */
  if (!build_autocompletion_list(filter_te, treeview, popup_win, protocol_name, protocols_only, stop_propagation)) {
    gtk_widget_destroy(popup_win);
    return NULL;
  }

  /* Sort treeview */
  model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
  if(model)
    filter_autocomplete_enable_sorting(model);

  gtk_container_add (GTK_CONTAINER (filter_sc), treeview);

  g_signal_connect(treeview, "row-activated", G_CALLBACK(autoc_filter_row_activated_cb), filter_te);
  g_signal_connect(filter_te, "focus-out-event", G_CALLBACK(filter_te_focus_out_cb), w_toplevel);
  g_signal_connect(popup_win, "destroy", G_CALLBACK(filter_autocomplete_win_destroy_cb), NULL);

  gtk_widget_size_request(treeview, &requisition);

  gtk_widget_set_size_request(popup_win, filter_te->allocation.width, (requisition.height<200? requisition.height+8:200));
  gtk_window_resize(GTK_WINDOW(popup_win), filter_te->allocation.width, (requisition.height<200? requisition.height+8:200));

  gdk_window_get_origin(filter_te->window, &x_pos, &y_pos);
  y_pos = y_pos + filter_te->allocation.height;
  gtk_window_move(GTK_WINDOW(popup_win), x_pos, y_pos);

  gtk_widget_show_all (popup_win);

  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
  gtk_tree_selection_unselect_all(selection);

  return popup_win;
}

static void
filter_autocomplete_handle_backspace(GtkWidget *filter_te, GtkWidget *list, GtkWidget *popup_win, gchar *prefix, GtkWidget *main_win)
{
  GtkTreeModel *model;
  GtkListStore *store;
  GtkRequisition requisition;
  size_t prefix_len;
  gboolean protocols_only = FALSE;

  prefix_len = strlen(prefix);

  if (prefix_len <= 1) {
    /* Remove the popup window for protocols */
    gtk_widget_destroy(popup_win);
    g_object_set_data(G_OBJECT(main_win), E_FILT_AUTOCOMP_PTR_KEY, NULL);
    return;
  }

  /* Delete the last character in the prefix string */
  prefix_len--;
  prefix[prefix_len] = '\0';

  if(strchr(prefix, '.') == NULL) {
    protocols_only = TRUE;
  }

  /* Empty list */
  model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));
  store = GTK_LIST_STORE(model);
  gtk_list_store_clear(store);

  /* Disable sorting */
  filter_autocomplete_disable_sorting(model);

  /* Build new list */
  if (!build_autocompletion_list(filter_te, list, popup_win, prefix, protocols_only, NULL)) {
    gtk_widget_destroy(popup_win);
    g_object_set_data(G_OBJECT(main_win), E_FILT_AUTOCOMP_PTR_KEY, NULL);
    return;
  }

  /* Enable sorting */
  filter_autocomplete_enable_sorting(model);
  
  gtk_tree_view_columns_autosize(GTK_TREE_VIEW(list));
  gtk_widget_size_request(list, &requisition);

  gtk_widget_set_size_request(popup_win, popup_win->allocation.width, (requisition.height<200? requisition.height+8:200));
  gtk_window_resize(GTK_WINDOW(popup_win), popup_win->allocation.width, (requisition.height<200? requisition.height+8:200));
}

static void 
filter_autocomplete_win_destroy_cb(GtkWidget *win, gpointer data _U_)
{
  /* tell that the autocomplete window doesn't exist anymore */
  g_object_set_data(G_OBJECT(win), E_FILT_AUTOCOMP_PTR_KEY, NULL);
}

gboolean
filter_parent_dlg_key_pressed_cb(GtkWidget *win, GdkEventKey *event)
{
  GtkWidget *popup_win;

  popup_win = g_object_get_data(G_OBJECT(win), E_FILT_AUTOCOMP_PTR_KEY);

  if(popup_win && event->keyval == GDK_Escape) {
    gtk_widget_destroy(popup_win);
    g_object_set_data(G_OBJECT(win), E_FILT_AUTOCOMP_PTR_KEY, NULL);

    /* stop event propagation */
    return TRUE;
  }

  return FALSE;
}
