Updating branch refs/heads/master
         to bd1d4f8cc00cc62b826e2d786aed75b0275020dd (commit)
       from 2ffca48f81a5d4ed19a449428ba4c472f8317455 (commit)

commit bd1d4f8cc00cc62b826e2d786aed75b0275020dd
Author: Christian Dywan <christ...@twotoasts.de>
Date:   Thu Sep 27 20:34:57 2012 +0200

    Introduce Completion API with search and history classes
    
    The new API makes completion independent from the database and
    uses pluggable, asynchronous backends. No new functionality.

 extensions/wscript_build             |    2 +-
 midori/midori-completion.vala        |  152 ++++++++++++++++
 midori/midori-historycompletion.vala |  113 ++++++++++++
 midori/midori-locationaction.c       |  331 ++++++++--------------------------
 midori/midori-searchcompletion.vala  |   77 ++++++++
 midori/wscript_build                 |    2 +-
 po/POTFILES.in                       |    2 +
 tests/completion.vala                |   85 +++++++++
 tests/extensions.c                   |    1 +
 tests/wscript_build                  |    2 +-
 10 files changed, 509 insertions(+), 258 deletions(-)

diff --git a/extensions/wscript_build b/extensions/wscript_build
index c09a92b..cf556c9 100644
--- a/extensions/wscript_build
+++ b/extensions/wscript_build
@@ -34,7 +34,7 @@ for extension in extensions:
     obj.source = source
     obj.uselib = 'UNIQUE LIBSOUP GIO GTK SQLITE WEBKIT LIBXML HILDON'
     obj.vapi_dirs = '../midori ../katze'
-    obj.packages = 'glib-2.0 gio-2.0 libsoup-2.4 midori midori-core katze'
+    obj.packages = 'glib-2.0 gio-2.0 libsoup-2.4 sqlite3 midori midori-core 
katze'
     if bld.env['HAVE_GTK3']:
         obj.packages += ' gtk+-3.0 webkitgtk-3.0'
     else:
diff --git a/midori/midori-completion.vala b/midori/midori-completion.vala
new file mode 100644
index 0000000..f3327f5
--- /dev/null
+++ b/midori/midori-completion.vala
@@ -0,0 +1,152 @@
+/*
+ Copyright (C) 2012 Christian Dywan <christ...@twotoasts.de>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ See the file COPYING for the full license text.
+*/
+
+namespace Midori {
+    public class Suggestion : GLib.Object {
+        public string? uri { get; set; }
+        public string? markup { get; set; }
+        public bool use_markup { get; set; }
+        public string? background { get; set; }
+        public GLib.Icon? icon {  get; set; }
+        public bool action { get; set; default = false; }
+
+        public Suggestion (string? uri, string? markup, bool use_markup=false,
+            string? background=null, GLib.Icon? icon=null) {
+
+            GLib.Object (uri: uri, markup: markup, use_markup: use_markup,
+                         background: background, icon: icon);
+        }
+    }
+
+    public abstract class Completion : GLib.Object {
+        public string? description { get; set; }
+        public int max_items { get; internal set; default = 25; }
+        internal int position { get; set; }
+
+        public abstract void prepare (GLib.Object app);
+        public abstract bool can_complete (string prefix);
+        public abstract bool can_action (string action);
+        public abstract async List<Suggestion>? complete (string text, string? 
action, Cancellable cancellable);
+    }
+
+    public class Autocompleter : GLib.Object {
+        private GLib.Object app;
+        private List<Completion> completions;
+        private int next_position;
+        public Gtk.ListStore model { get; private set; }
+        private bool need_to_clear = false;
+        private uint current_count = 0;
+        private Cancellable? cancellable = null;
+
+        public enum Columns {
+            ICON,
+            URI,
+            MARKUP,
+            BACKGROUND,
+            YALIGN,
+            N
+        }
+
+        public Autocompleter (GLib.Object app) {
+            this.app = app;
+            completions = new List<Completion> ();
+            next_position = 0;
+            model = new Gtk.ListStore (Columns.N,
+                typeof (Gdk.Pixbuf), typeof (string), typeof (string),
+                typeof (string), typeof (float));
+        }
+
+        public void add (Completion completion) {
+            completion.prepare (app);
+            completion.position = next_position;
+            next_position += completion.max_items;
+            completions.append (completion);
+        }
+
+        public bool can_complete (string text) {
+            foreach (var completion in completions)
+                if (completion.can_complete (text))
+                    return true;
+            return false;
+        }
+
+        private void fill_model (GLib.Object? object, AsyncResult result) {
+            var completion = object as Completion;
+            List<Suggestion>? suggestions = completion.complete.end (result);
+            if (suggestions == null)
+                return;
+
+            if (need_to_clear) {
+                model.clear ();
+                need_to_clear = false;
+                current_count = 0;
+            }
+
+            uint count = 0;
+            foreach (var suggestion in suggestions) {
+                model.insert_with_values (null, completion.position,
+                    Columns.URI, suggestion.uri,
+                    Columns.MARKUP, suggestion.use_markup
+                    ? suggestion.markup : Markup.escape_text 
(suggestion.markup),
+                    Columns.ICON, suggestion.icon,
+                    Columns.BACKGROUND, suggestion.background,
+                    Columns.YALIGN, 0.25);
+
+                count++;
+                if (count > completion.max_items)
+                    break;
+            }
+
+            current_count += count;
+            populated (current_count);
+        }
+
+        public signal void populated (uint count);
+
+        public async void complete (string text) {
+            if (cancellable != null)
+                cancellable.cancel ();
+            cancellable = new Cancellable ();
+            need_to_clear = true;
+
+            foreach (var completion in completions) {
+                if (completion.can_complete (text))
+                    completion.complete.begin (text, null, cancellable, 
fill_model);
+
+                uint src = Idle.add (complete.callback);
+                yield;
+                Source.remove (src);
+
+                if (cancellable.is_cancelled ())
+                    break;
+            }
+        }
+
+        public bool can_action (string action) {
+            foreach (var completion in completions)
+                if (completion.can_action (action))
+                    return true;
+            return false;
+        }
+
+        public async void action (string action, string text) {
+            if (cancellable != null)
+                cancellable.cancel ();
+            cancellable = new Cancellable ();
+            need_to_clear = true;
+
+            foreach (var completion in completions) {
+                if (completion.can_action (action))
+                    completion.complete.begin (text, action, cancellable, 
fill_model);
+            }
+        }
+    }
+}
diff --git a/midori/midori-historycompletion.vala 
b/midori/midori-historycompletion.vala
new file mode 100644
index 0000000..5a194d9
--- /dev/null
+++ b/midori/midori-historycompletion.vala
@@ -0,0 +1,113 @@
+/*
+ Copyright (C) 2012 Christian Dywan <christ...@twotoasts.de>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ See the file COPYING for the full license text.
+*/
+
+namespace Katze {
+    extern static Gdk.Pixbuf? load_cached_icon (string uri, Gtk.Widget? proxy);
+}
+
+namespace Midori {
+    public class HistoryCompletion : Completion {
+        unowned Sqlite.Database db;
+
+        public HistoryCompletion () {
+            GLib.Object (description: "Bookmarks and history");
+        }
+
+        public override void prepare (GLib.Object app) {
+            GLib.Object history;
+            app.get ("history", out history);
+            return_if_fail (history != null);
+            db = history.get_data<Sqlite.Database?> ("db");
+            return_if_fail (db != null);
+        }
+
+        public override bool can_complete (string text) {
+            return db != null;
+        }
+
+        public override bool can_action (string action) {
+            return false;
+        }
+
+        public override async List<Suggestion>? complete (string text, string? 
action, Cancellable cancellable) {
+            return_val_if_fail (db != null, null);
+
+            Sqlite.Statement stmt;
+            unowned string sqlcmd = """
+                SELECT type, uri, title FROM (
+                SELECT 1 AS type, uri, title, count() AS ct FROM history
+                WHERE uri LIKE ?1 OR title LIKE ?1 GROUP BY uri
+                UNION ALL
+                SELECT 2 AS type, replace(uri, '%s', keywords) AS uri,
+                       keywords AS title, count() AS ct FROM search
+                WHERE uri LIKE ?1 OR title LIKE ?1 GROUP BY uri
+                UNION ALL
+                SELECT 1 AS type, uri, title, 50 AS ct FROM bookmarks
+                WHERE title LIKE ?1 OR uri LIKE ?1 AND uri !=''
+                ) GROUP BY uri ORDER BY ct DESC LIMIT ?2
+                """;
+            if (db.prepare_v2 (sqlcmd, -1, out stmt, null) != Sqlite.OK) {
+                critical (_("Failed to initialize history: %s"), db.errmsg ());
+                return null;
+            }
+
+            string query = "%" + text.replace (" ", "%") + "%";
+            stmt.bind_text (1, query);
+            stmt.bind_int64 (2, max_items);
+
+            int result = stmt.step ();
+            if (result != Sqlite.ROW) {
+                if (result == Sqlite.ERROR)
+                    critical (_("Failed to select from history: %s"), 
db.errmsg ());
+                return null;
+            }
+
+            var suggestions = new List<Suggestion> ();
+            while (result == Sqlite.ROW) {
+                int64 type = stmt.column_int64 (0);
+                unowned string uri = stmt.column_text (1);
+                unowned string title = stmt.column_text (2);
+                Gdk.Pixbuf? icon = Katze.load_cached_icon (uri, null);
+
+                switch (type) {
+                    case 1: /* history_view */
+                        var suggestion = new Suggestion (uri, title, false, 
null, icon);
+                        suggestions.append (suggestion);
+                        break;
+                    case 2: /* search_view */
+                        string desc = _("Search for %s").printf (title) + "\n" 
+ uri;
+                        /* FIXME: Theming? Win32? */
+                        string background = "gray";
+                        var suggestion = new Suggestion (uri, desc, false, 
background, icon);
+                        suggestions.append (suggestion);
+                        break;
+                    default:
+                        warn_if_reached ();
+                        break;
+                }
+
+                uint src = Idle.add (complete.callback);
+                yield;
+                Source.remove (src);
+
+                if (cancellable.is_cancelled ())
+                    return null;
+
+                result = stmt.step ();
+            }
+
+            if (cancellable.is_cancelled ())
+                return null;
+
+            return suggestions;
+        }
+    }
+}
diff --git a/midori/midori-locationaction.c b/midori/midori-locationaction.c
index 49f3295..ad13677 100644
--- a/midori/midori-locationaction.c
+++ b/midori/midori-locationaction.c
@@ -15,6 +15,7 @@
 #include "marshal.h"
 #include "midori-browser.h"
 #include "midori-searchaction.h"
+#include "midori-app.h"
 #include "midori-platform.h"
 #include <midori/midori-core.h>
 
@@ -25,9 +26,6 @@
 
 #include <sqlite3.h>
 
-#define COMPLETION_DELAY 200
-#define MAX_ITEMS 25
-
 struct _MidoriLocationAction
 {
     GtkAction parent_instance;
@@ -37,14 +35,13 @@ struct _MidoriLocationAction
     gdouble progress;
     gchar* secondary_icon;
 
-    guint completion_timeout;
     gchar* key;
+    MidoriAutocompleter* autocompleter;
     GtkWidget* popup;
     GtkWidget* treeview;
     GtkTreeModel* completion_model;
     gint completion_index;
     GtkWidget* entry;
-    GdkPixbuf* default_icon;
     KatzeArray* history;
 };
 
@@ -77,19 +74,6 @@ enum
 
 static guint signals[LAST_SIGNAL];
 
-enum
-{
-    FAVICON_COL,
-    URI_COL,
-    TITLE_COL,
-    VISITS_COL,
-    VISIBLE_COL,
-    YALIGN_COL,
-    BACKGROUND_COL,
-    STYLE_COL,
-    N_COLS
-};
-
 static void
 midori_location_action_finalize (GObject* object);
 
@@ -265,16 +249,6 @@ midori_location_action_class_init 
(MidoriLocationActionClass* class)
                                      G_PARAM_READWRITE | 
G_PARAM_STATIC_STRINGS));
 }
 
-static GtkTreeModel*
-midori_location_action_create_model (void)
-{
-    GtkTreeModel* model = (GtkTreeModel*) gtk_list_store_new (N_COLS,
-        GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING,
-        G_TYPE_INT, G_TYPE_BOOLEAN, G_TYPE_FLOAT,
-        GDK_TYPE_COLOR, G_TYPE_BOOLEAN);
-    return model;
-}
-
 static void
 midori_location_action_popup_position (MidoriLocationAction* action,
                                        gint                  matches)
@@ -290,10 +264,8 @@ midori_location_action_popup_position 
(MidoriLocationAction* action,
     GdkRectangle monitor;
     GtkAllocation alloc;
     gint height, sep, width, toplevel_height;
-    gboolean above;
     GtkWidget* scrolled = gtk_widget_get_parent (action->treeview);
     GtkWidget* toplevel;
-    GtkTreePath* path;
 
     if (!window)
         return;
@@ -350,20 +322,9 @@ midori_location_action_popup_position 
(MidoriLocationAction* action,
 
     if (wy + widget_req.height + menu_req.height <= monitor.y + monitor.height 
||
         wy - monitor.y < (monitor.y + monitor.height) - (wy + 
widget_req.height))
-    {
         wy += widget_req.height;
-        above = FALSE;
-    }
     else
-    {
         wy -= menu_req.height;
-        above = TRUE;
-    }
-
-    path = gtk_tree_path_new_from_indices (above ? matches - 1 : 0, -1);
-    gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (action->treeview), path,
-                                  NULL, FALSE, 0.0, 0.0);
-    gtk_tree_path_free (path);
 
     gtk_window_move (GTK_WINDOW (popup), wx, wy);
 }
@@ -394,77 +355,13 @@ midori_location_action_entry_set_text (GtkWidget*   entry,
     }
 }
 
-static int
-midori_location_action_add_search_engines (MidoriLocationAction* action,
-                                           GtkListStore*         store,
-                                           gint                  matches)
-{
-    KatzeItem* item;
-    gint i = 0;
-    #ifndef G_OS_WIN32
-    GtkStyle* style;
-    #endif
-
-    gtk_widget_realize (action->treeview);
-    #ifndef G_OS_WIN32
-    style = gtk_widget_get_style (action->treeview);
-    #endif
-
-    /* FIXME: choose 3 most frequently except for default */
-    KATZE_ARRAY_FOREACH_ITEM (item, action->search_engines)
-    {
-        gchar* uri;
-        gchar* title;
-        const gchar* text;
-        gchar* desc;
-        GdkPixbuf* icon;
-
-        uri = midori_uri_for_search (katze_item_get_uri (item), action->key);
-        title = g_strdup_printf (_("Search with %s"), katze_item_get_name 
(item));
-        text = katze_item_get_text (item);
-        desc = g_strdup_printf ("%s\n%s", title, text ? text : uri);
-        icon = midori_search_action_get_icon (item, action->treeview, NULL, 
FALSE);
-        gtk_list_store_insert_with_values (store, NULL, matches + i,
-            URI_COL, uri, TITLE_COL, desc, YALIGN_COL, 0.25,
-            #ifndef G_OS_WIN32
-            BACKGROUND_COL, style ? &style->bg[GTK_STATE_NORMAL] : NULL,
-            #endif
-            STYLE_COL, 1, FAVICON_COL, icon, -1);
-        g_free (uri);
-        g_free (title);
-        g_free (desc);
-        if (icon != NULL)
-            g_object_unref (icon);
-        i++;
-
-        if (i > 2 && matches > 0)
-        {
-            gtk_list_store_insert_with_values (store, NULL, matches + i,
-                URI_COL, "about:search", TITLE_COL, _("Search with…"),
-                YALIGN_COL, 0.25,
-                #ifndef G_OS_WIN32
-                BACKGROUND_COL, style ? &style->bg[GTK_STATE_NORMAL] : NULL,
-                #endif
-                STYLE_COL, 1, FAVICON_COL, NULL, -1);
-            i++;
-            break;
-        }
-    }
-    return i;
-}
-
 static void
 midori_location_action_complete (MidoriLocationAction* action,
                                  gboolean              new_tab,
                                  const gchar*          uri)
 {
-    if (!strcmp (uri, "about:search"))
-    {
-        GtkListStore* store = GTK_LIST_STORE (action->completion_model);
-        gtk_list_store_clear (store);
-        midori_location_action_popup_position (action,
-            midori_location_action_add_search_engines (action, store, 0));
-    }
+    if (midori_autocompleter_can_action (action->autocompleter, uri))
+        midori_autocompleter_action (action->autocompleter, uri, action->key, 
NULL, NULL);
     else
     {
         midori_location_action_popdown_completion (action);
@@ -488,9 +385,10 @@ midori_location_action_treeview_button_press_cb 
(GtkWidget*            treeview,
 
         gtk_tree_model_get_iter (action->completion_model, &iter, path);
         gtk_tree_path_free (path);
-        gtk_tree_model_get (action->completion_model, &iter, URI_COL, &uri, 
-1);
+        gtk_tree_model_get (action->completion_model, &iter,
+            MIDORI_AUTOCOMPLETER_COLUMNS_URI, &uri, -1);
         midori_location_action_complete (action,
-                MIDORI_MOD_NEW_TAB (event->state), uri);
+            MIDORI_MOD_NEW_TAB (event->state), uri);
         g_free (uri);
 
         return TRUE;
@@ -499,29 +397,26 @@ midori_location_action_treeview_button_press_cb 
(GtkWidget*            treeview,
     return FALSE;
 }
 
+static void
+midori_location_action_populated_suggestions_cb (MidoriAutocompleter*  
autocompleter,
+                                                 guint                 count,
+                                                 MidoriLocationAction* action)
+{
+    GtkTreePath* path = gtk_tree_path_new_first ();
+    gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (action->treeview), path, NULL,
+        FALSE, 0.0, 0.0);
+    gtk_tree_path_free (path);
+    midori_location_action_popup_position (action, count);
+}
+
 static gboolean
 midori_location_action_popup_timeout_cb (gpointer data)
 {
     MidoriLocationAction* action = data;
     GtkTreeViewColumn* column;
-    GtkListStore* store;
-    gchar* effective_key;
-    gint i;
-    gint result;
-    static sqlite3_stmt* stmt;
-    const gchar* sqlcmd;
-    gint matches, searches;
-
-    if (!action->entry || !gtk_widget_has_focus (action->entry) || 
!action->history)
-        return FALSE;
 
-    /* No completion when typing a search token */
-    if (action->search_engines
-     && katze_array_find_token (action->search_engines, action->key))
-    {
-        midori_location_action_popdown_completion (action);
+    if (!gtk_widget_has_focus (action->entry))
         return FALSE;
-    }
 
     /* Empty string or starting with a space means: no completion */
     if (!(action->key && *action->key && *action->key != ' '))
@@ -530,61 +425,40 @@ midori_location_action_popup_timeout_cb (gpointer data)
         return FALSE;
     }
 
-    if (!stmt)
-    {
-        sqlite3* db;
-        db = g_object_get_data (G_OBJECT (action->history), "db");
-
-        if (!db)
-            return FALSE;
-
-        sqlcmd = "SELECT type, uri, title FROM ("
-                 "  SELECT 1 AS type, uri, title, count() AS ct FROM history "
-                 "      WHERE uri LIKE ?1 OR title LIKE ?1 GROUP BY uri "
-                 "  UNION ALL "
-                 "  SELECT 2 AS type, replace(uri, '%s', keywords) AS uri, "
-                 "      keywords AS title, count() AS ct FROM search "
-                 "      WHERE uri LIKE ?1 OR title LIKE ?1 GROUP BY uri "
-                 "  UNION ALL "
-                 "  SELECT 1 AS type, uri, title, 50 AS ct FROM bookmarks "
-                 "      WHERE title LIKE ?1 OR uri LIKE ?1 AND uri !='' "
-                 ") GROUP BY uri ORDER BY ct DESC LIMIT ?2";
-        sqlite3_prepare_v2 (db, sqlcmd, strlen (sqlcmd) + 1, &stmt, NULL);
-    }
-    effective_key = g_strdup_printf ("%%%s%%", action->key);
-    i = 0;
-    do
+    if (action->autocompleter == NULL)
     {
-        if (effective_key[i] == ' ')
-            effective_key[i] = '%';
-        i++;
+        /* FIXME: use a real app here */
+        GObject* app = g_object_new (MIDORI_TYPE_APP,
+            "history", action->history,
+            "search-engines", action->search_engines,
+            NULL);
+        action->autocompleter = midori_autocompleter_new (app);
+        g_signal_connect (action->autocompleter, "populated",
+            G_CALLBACK (midori_location_action_populated_suggestions_cb), 
action);
+        g_object_unref (app);
+        midori_autocompleter_add (action->autocompleter,
+            MIDORI_COMPLETION (midori_history_completion_new ()));
+        midori_autocompleter_add (action->autocompleter,
+            MIDORI_COMPLETION (midori_search_completion_new ()));
     }
-    while (effective_key[i] != '\0');
-    sqlite3_bind_text (stmt, 1, effective_key, -1, g_free);
-    sqlite3_bind_int64 (stmt, 2, MAX_ITEMS);
 
-    result = sqlite3_step (stmt);
-    if (result != SQLITE_ROW && !action->search_engines)
+    if (!midori_autocompleter_can_complete (action->autocompleter, 
action->key))
     {
-        if (result == SQLITE_ERROR)
-            g_print (_("Failed to select from history\n"));
-        sqlite3_reset (stmt);
-        sqlite3_clear_bindings (stmt);
         midori_location_action_popdown_completion (action);
         return FALSE;
     }
 
+    midori_autocompleter_complete (action->autocompleter, action->key, NULL, 
NULL);
+
     if (G_UNLIKELY (!action->popup))
     {
-        GtkTreeModel* model = NULL;
         GtkWidget* popup;
         GtkWidget* popup_frame;
         GtkWidget* scrolled;
         GtkWidget* treeview;
         GtkCellRenderer* renderer;
 
-        model = midori_location_action_create_model ();
-        action->completion_model = model;
+        action->completion_model = 
(GtkTreeModel*)midori_autocompleter_get_model (action->autocompleter);
 
         popup = gtk_window_new (GTK_WINDOW_POPUP);
         gtk_window_set_type_hint (GTK_WINDOW (popup), 
GDK_WINDOW_TYPE_HINT_COMBO);
@@ -597,7 +471,7 @@ midori_location_action_popup_timeout_cb (gpointer data)
             "hscrollbar-policy", GTK_POLICY_NEVER,
             "vscrollbar-policy", GTK_POLICY_AUTOMATIC, NULL);
         gtk_container_add (GTK_CONTAINER (popup_frame), scrolled);
-        treeview = gtk_tree_view_new_with_model (model);
+        treeview = gtk_tree_view_new_with_model (action->completion_model);
         gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
         gtk_tree_view_set_hover_selection (GTK_TREE_VIEW (treeview), TRUE);
         gtk_container_add (GTK_CONTAINER (scrolled), treeview);
@@ -607,22 +481,25 @@ midori_location_action_popup_timeout_cb (gpointer data)
         gtk_widget_set_size_request (gtk_scrolled_window_get_vscrollbar (
             GTK_SCROLLED_WINDOW (scrolled)), -1, 0);
         action->treeview = treeview;
+        #if !GTK_CHECK_VERSION (3, 4, 0)
+        gtk_widget_realize (action->treeview);
+        #endif
 
         column = gtk_tree_view_column_new ();
         renderer = gtk_cell_renderer_pixbuf_new ();
         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (column), renderer, FALSE);
         gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (column), renderer,
-            "pixbuf", FAVICON_COL, "yalign", YALIGN_COL,
-            "cell-background-gdk", BACKGROUND_COL,
+            "pixbuf", MIDORI_AUTOCOMPLETER_COLUMNS_ICON,
+            "yalign", MIDORI_AUTOCOMPLETER_COLUMNS_YALIGN,
+            "cell-background", MIDORI_AUTOCOMPLETER_COLUMNS_BACKGROUND,
             NULL);
         renderer = gtk_cell_renderer_text_new ();
-        g_object_set_data (G_OBJECT (renderer), "location-action", action);
         gtk_cell_renderer_set_fixed_size (renderer, 1, -1);
         gtk_cell_renderer_text_set_fixed_height_from_font (
             GTK_CELL_RENDERER_TEXT (renderer), 2);
         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (column), renderer, TRUE);
         gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (column), renderer,
-            "cell-background-gdk", BACKGROUND_COL,
+            "cell-background", MIDORI_AUTOCOMPLETER_COLUMNS_BACKGROUND,
             NULL);
         gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (column), renderer,
                                             
midori_location_entry_render_text_cb,
@@ -632,63 +509,21 @@ midori_location_action_popup_timeout_cb (gpointer data)
         action->popup = popup;
         g_signal_connect (popup, "destroy",
             G_CALLBACK (gtk_widget_destroyed), &action->popup);
+        gtk_widget_show_all (popup_frame);
     }
 
-    store = GTK_LIST_STORE (action->completion_model);
-    gtk_list_store_clear (store);
-
-    matches = searches = 0;
-    while (result == SQLITE_ROW)
-    {
-        sqlite3_int64 type = sqlite3_column_int64 (stmt, 0);
-        const unsigned char* uri = sqlite3_column_text (stmt, 1);
-        const unsigned char* title = sqlite3_column_text (stmt, 2);
-        GdkPixbuf* icon = katze_load_cached_icon ((gchar*)uri, NULL);
-        if (!icon)
-            icon = g_object_ref (action->default_icon);
-        if (type == 1 /* history_view */)
-        {
-            gtk_list_store_insert_with_values (store, NULL, matches,
-                URI_COL, uri, TITLE_COL, title, YALIGN_COL, 0.25,
-                FAVICON_COL, icon, -1);
-        }
-        else if (type == 2 /* search_view */)
-        {
-            gchar* search_title = g_strdup_printf (_("Search for %s"), title);
-            gchar* search_desc = g_strdup_printf ("%s\n%s", search_title, uri);
-            gtk_list_store_insert_with_values (store, NULL, matches,
-                URI_COL, uri, TITLE_COL, search_desc, YALIGN_COL, 0.25,
-                STYLE_COL, 1, FAVICON_COL, icon, -1);
-            g_free (search_desc);
-            g_free (search_title);
-        }
-        if (icon != NULL)
-            g_object_unref (icon);
-
-        matches++;
-        result = sqlite3_step (stmt);
-    }
-
-    if (stmt)
-    {
-        sqlite3_reset (stmt);
-        sqlite3_clear_bindings (stmt);
-    }
-
-    if (action->search_engines)
-        searches += midori_location_action_add_search_engines (action, store, 
matches);
-
     if (!gtk_widget_get_visible (action->popup))
     {
         GtkWidget* toplevel = gtk_widget_get_toplevel (action->entry);
         gtk_window_set_screen (GTK_WINDOW (action->popup),
                                gtk_widget_get_screen (action->entry));
         gtk_window_set_transient_for (GTK_WINDOW (action->popup), GTK_WINDOW 
(toplevel));
-        gtk_widget_show_all (action->popup);
+        #if GTK_CHECK_VERSION (3, 4, 0)
+        gtk_window_set_attached_to (GTK_WINDOW (action->popup), action->entry);
+        #endif
+        gtk_widget_show (action->popup);
     }
 
-    midori_location_action_popup_position (action, matches + searches);
-
     return FALSE;
 }
 
@@ -697,14 +532,14 @@ midori_location_action_popup_completion 
(MidoriLocationAction* action,
                                          GtkWidget*            entry,
                                          gchar*                key)
 {
-    if (action->completion_timeout)
-        g_source_remove (action->completion_timeout);
     katze_assign (action->key, key);
-    action->entry = entry;
-    g_signal_connect (entry, "destroy",
-        G_CALLBACK (gtk_widget_destroyed), &action->entry);
-    action->completion_timeout = g_timeout_add (COMPLETION_DELAY,
-        midori_location_action_popup_timeout_cb, action);
+    if (action->entry != entry)
+    {
+        action->entry = entry;
+        g_signal_connect (entry, "destroy",
+            G_CALLBACK (gtk_widget_destroyed), &action->entry);
+    }
+    g_idle_add (midori_location_action_popup_timeout_cb, action);
 }
 
 static void
@@ -717,11 +552,6 @@ midori_location_action_popdown_completion 
(MidoriLocationAction* location_action
         gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (
             GTK_TREE_VIEW (location_action->treeview)));
     }
-    if (location_action->completion_timeout)
-    {
-        g_source_remove (location_action->completion_timeout);
-        location_action->completion_timeout = 0;
-    }
     location_action->completion_index = -1;
 }
 
@@ -737,17 +567,8 @@ midori_location_action_entry_for_proxy (GtkWidget* proxy)
 static void
 midori_location_action_init (MidoriLocationAction* location_action)
 {
-    location_action->text = NULL;
-    location_action->search_engines = NULL;
     location_action->progress = 0.0;
-    location_action->secondary_icon = NULL;
-    location_action->default_icon = NULL;
-    location_action->completion_timeout = 0;
     location_action->completion_index = -1;
-    location_action->key = NULL;
-    location_action->popup = NULL;
-    location_action->entry = NULL;
-    location_action->history = NULL;
 }
 
 static void
@@ -756,7 +577,8 @@ midori_location_action_finalize (GObject* object)
     MidoriLocationAction* location_action = MIDORI_LOCATION_ACTION (object);
 
     katze_assign (location_action->text, NULL);
-    katze_assign (location_action->search_engines, NULL);
+    katze_object_assign (location_action->search_engines, NULL);
+    katze_assign (location_action->autocompleter, NULL);
 
     katze_assign (location_action->key, NULL);
     if (location_action->popup)
@@ -764,7 +586,6 @@ midori_location_action_finalize (GObject* object)
         gtk_widget_destroy (location_action->popup);
         location_action->popup = NULL;
     }
-    katze_object_assign (location_action->default_icon, NULL);
     katze_object_assign (location_action->history, NULL);
 
     G_OBJECT_CLASS (midori_location_action_parent_class)->finalize (object);
@@ -981,7 +802,8 @@ midori_location_action_key_press_event_cb (GtkEntry*    
entry,
                 gtk_tree_model_iter_nth_child (model, &iter, NULL, selected))
             {
                 gchar* uri;
-                gtk_tree_model_get (model, &iter, URI_COL, &uri, -1);
+                gtk_tree_model_get (model, &iter,
+                    MIDORI_AUTOCOMPLETER_COLUMNS_URI, &uri, -1);
 
                 if (is_enter)
                     midori_location_action_complete (location_action,
@@ -1032,7 +854,8 @@ midori_location_action_key_press_event_cb (GtkEntry*    
entry,
                 gchar* errmsg;
                 gint result;
 
-                gtk_tree_model_get (model, &iter, URI_COL, &uri, -1);
+                gtk_tree_model_get (model, &iter,
+                    MIDORI_AUTOCOMPLETER_COLUMNS_URI, &uri, -1);
                 sqlcmd = sqlite3_mprintf ("DELETE FROM history "
                                           "WHERE uri = '%q'", uri);
                 g_free (uri);
@@ -1042,7 +865,7 @@ midori_location_action_key_press_event_cb (GtkEntry*    
entry,
                 if (result == SQLITE_ERROR)
                 {
                     gtk_list_store_set (GTK_LIST_STORE (model), &iter,
-                                        URI_COL, errmsg, -1);
+                        MIDORI_AUTOCOMPLETER_COLUMNS_URI, errmsg, -1);
                     sqlite3_free (errmsg);
                     break;
                 }
@@ -1126,7 +949,8 @@ midori_location_action_key_press_event_cb (GtkEntry*    
entry,
                 if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 
selected))
                 {
                     gchar* uri;
-                    gtk_tree_model_get (model, &iter, URI_COL, &uri, -1);
+                    gtk_tree_model_get (model, &iter,
+                        MIDORI_AUTOCOMPLETER_COLUMNS_URI, &uri, -1);
                     /* Update the layout without actually changing the text */
                     pango_layout_set_text (gtk_entry_get_layout (entry), uri, 
-1);
                     g_free (uri);
@@ -1449,7 +1273,7 @@ midori_location_entry_render_text_cb (GtkCellLayout*   
layout,
     gchar* uri_temp;
     gchar* uri;
     gchar* title;
-    gboolean style;
+    gchar* background;
     gchar* desc;
     gchar* desc_uri;
     gchar* desc_iter;
@@ -1467,15 +1291,19 @@ midori_location_entry_render_text_cb (GtkCellLayout*   
layout,
     gchar** parts;
     size_t offset;
 
-    gtk_tree_model_get (model, iter, URI_COL, &uri_escaped, TITLE_COL, &title,
-        STYLE_COL, &style, -1);
+    gtk_tree_model_get (model, iter,
+        MIDORI_AUTOCOMPLETER_COLUMNS_URI, &uri_escaped,
+        MIDORI_AUTOCOMPLETER_COLUMNS_MARKUP, &title,
+        MIDORI_AUTOCOMPLETER_COLUMNS_BACKGROUND, &background,
+        -1);
 
-    if (style) /* A search engine action */
+    if (background != NULL) /* A search engine or action suggestion */
     {
-        g_object_set (renderer, "text", title,
+        g_object_set (renderer, "markup", title,
             "ellipsize-set", TRUE, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
         g_free (uri_escaped);
         g_free (title);
+        g_free (background);
         return;
     }
 
@@ -1685,16 +1513,9 @@ static void
 midori_location_action_connect_proxy (GtkAction* action,
                                       GtkWidget* proxy)
 {
-    MidoriLocationAction* location_action;
-
     GTK_ACTION_CLASS (midori_location_action_parent_class)->connect_proxy (
         action, proxy);
 
-    location_action = MIDORI_LOCATION_ACTION (action);
-    katze_object_assign (location_action->default_icon,
-                         gtk_widget_render_icon (proxy, GTK_STOCK_FILE,
-                                                 GTK_ICON_SIZE_MENU, NULL));
-
     if (GTK_IS_TOOL_ITEM (proxy))
     {
         GtkWidget* entry = midori_location_action_entry_for_proxy (proxy);
diff --git a/midori/midori-searchcompletion.vala 
b/midori/midori-searchcompletion.vala
new file mode 100644
index 0000000..0a5c44e
--- /dev/null
+++ b/midori/midori-searchcompletion.vala
@@ -0,0 +1,77 @@
+/*
+ Copyright (C) 2012 Christian Dywan <christ...@twotoasts.de>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ See the file COPYING for the full license text.
+*/
+namespace Katze {
+    extern static unowned List<GLib.Object> array_peek_items (GLib.Object 
array);
+}
+
+namespace Midori {
+    extern static Gdk.Pixbuf? search_action_get_icon (GLib.Object item,
+        Gtk.Widget? widget, out string[] icon_name, bool in_entry);
+
+    public class SearchCompletion : Completion {
+        GLib.Object search_engines;
+
+        public SearchCompletion () {
+            GLib.Object (description: "Search engines");
+        }
+
+        public override void prepare (GLib.Object app) {
+            app.get ("search-engines", out search_engines);
+        }
+
+        public override bool can_complete (string text) {
+            return search_engines != null;
+        }
+
+        public override bool can_action (string action) {
+            return action == "about:search";
+        }
+
+        public override async List<Suggestion>? complete (string text, string? 
action, Cancellable cancellable) {
+            return_val_if_fail (search_engines != null, null);
+
+            unowned List<GLib.Object> items = Katze.array_peek_items 
(search_engines);
+            var suggestions = new List<Suggestion> ();
+            uint n = 0;
+            foreach (var item in items) {
+                string uri, title, desc;
+                item.get ("uri", out uri);
+                item.get ("name", out title);
+                item.get ("text", out desc);
+                string search_uri = URI.for_search (uri, text);
+                string search_title = _("Search with %s").printf (title);
+                var icon = Midori.search_action_get_icon (item, null, null, 
false);
+                string search_desc = search_title + "\n" + desc ?? uri;
+                /* FIXME: Theming? Win32? */
+                string background = "gray";
+                var suggestion = new Suggestion (search_uri, search_desc, 
false, background, icon);
+                suggestions.append (suggestion);
+
+                n++;
+                if (n == 3 && action == null) {
+                    suggestion = new Suggestion ("about:search", _("Search 
with…"), false, background);
+                    suggestion.action = true;
+                    suggestions.append (suggestion);
+                    break;
+                }
+
+                uint src = Idle.add (complete.callback);
+                yield;
+                Source.remove (src);
+            }
+
+            if (cancellable.is_cancelled ())
+                return null;
+
+            return suggestions;
+        }
+    }
+}
diff --git a/midori/wscript_build b/midori/wscript_build
index 52c3337..6c8c0e8 100644
--- a/midori/wscript_build
+++ b/midori/wscript_build
@@ -19,7 +19,7 @@ if progressive:
     obj.add_marshal_file ('marshal.list', 'midori_cclosure_marshal')
     obj.install_path = None
     obj.vapi_dirs = '../midori ../katze'
-    obj.packages = 'glib-2.0 gio-2.0 libsoup-2.4 posix'
+    obj.packages = 'glib-2.0 gio-2.0 libsoup-2.4 posix sqlite3'
     if bld.env['HAVE_GTK3']:
         obj.packages += ' gtk+-3.0 webkitgtk-3.0'
     else:
diff --git a/po/POTFILES.in b/po/POTFILES.in
index d0688b6..9f4b2d9 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -17,6 +17,8 @@ midori/midori-download.vala
 midori/midori-speeddial.vala
 midori/midori-preferences.c
 midori/midori-searchaction.c
+midori/midori-historycompletion.vala
+midori/midori-searchcompletion.vala
 midori/sokoke.c
 toolbars/midori-findbar.c
 toolbars/midori-transferbar.c
diff --git a/tests/completion.vala b/tests/completion.vala
new file mode 100644
index 0000000..98d7d99
--- /dev/null
+++ b/tests/completion.vala
@@ -0,0 +1,85 @@
+/*
+ Copyright (C) 2012 Christian Dywan <christ...@twotoasts.de>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ See the file COPYING for the full license text.
+*/
+
+class TestCompletion : Midori.Completion {
+    public bool test_can_complete { get; set; }
+
+    public TestCompletion () {
+    }
+
+    public override void prepare (GLib.Object app) {
+    }
+
+    public override bool can_complete (string text) {
+        return test_can_complete;
+    }
+
+    public override bool can_action (string action) {
+        return false;
+    }
+
+    public override async List<Midori.Suggestion>? complete (string text, 
string? action, Cancellable cancellable) {
+        return null;
+    }
+}
+
+void completion_autocompleter () {
+    var app = new Midori.App ();
+    var autocompleter = new Midori.Autocompleter (app);
+    assert (!autocompleter.can_complete (""));
+    var completion = new TestCompletion ();
+    autocompleter.add (completion);
+    completion.test_can_complete = false;
+    assert (!autocompleter.can_complete (""));
+    completion.test_can_complete = true;
+    assert (autocompleter.can_complete (""));
+}
+
+struct TestCaseCompletion {
+    public string prefix;
+    public string text;
+    public int expected_count;
+}
+
+const TestCaseCompletion[] completions = {
+    { "history", "example", 1 }
+};
+
+async void complete_spec (Midori.Completion completion, TestCaseCompletion 
spec) {
+    assert (completion.can_complete (spec.text));
+    var cancellable = new Cancellable ();
+    var suggestions = yield completion.complete (spec.text, null, cancellable);
+    if (spec.expected_count != suggestions.length ())
+        error ("%u expected for %s/ %s but got %u",
+            spec.expected_count, spec.prefix, spec.text, suggestions.length 
());
+}
+
+void completion_history () {
+    /* TODO: mock history database
+    var completion = new Midori.HistoryCompletion ();
+    var app = new Midori.App ();
+    var history = new Katze.Array (typeof (Katze.Item));
+    app.set ("history", history);
+    Sqlite.Database db;
+    Sqlite.Database.open_v2 (":memory:", out db);
+    history.set_data<unowned Sqlite.Database?> ("db", db);
+    completion.prepare (app);
+    foreach (var spec in completions)
+        complete_spec (completion, spec); */
+}
+
+void main (string[] args) {
+    Test.init (ref args);
+    Test.add_func ("/completion/autocompleter", completion_autocompleter);
+    Test.add_func ("/completion/history", completion_history);
+    Test.run ();
+}
+
diff --git a/tests/extensions.c b/tests/extensions.c
index 97f85af..56bec50 100644
--- a/tests/extensions.c
+++ b/tests/extensions.c
@@ -169,6 +169,7 @@ extension_activate (gconstpointer data)
     MidoriApp* app = midori_app_new ();
     g_object_set (app, "settings", midori_web_settings_new (), NULL);
     midori_extension_activate (G_OBJECT (data), NULL, TRUE, app);
+    /* TODO: MidoriCompletion */
     g_object_unref (app);
 }
 
diff --git a/tests/wscript_build b/tests/wscript_build
index 28b8c0c..85b0016 100644
--- a/tests/wscript_build
+++ b/tests/wscript_build
@@ -34,7 +34,7 @@ for test in tests:
     obj.cflags = ['-DEXTENSION_PATH="' + os.path.abspath 
('_build/default/extensions') + '"']
     obj.source = source
     obj.vapi_dirs = '../midori ../katze'
-    obj.packages = 'glib-2.0 gio-2.0 libsoup-2.4 katze midori midori-core'
+    obj.packages = 'glib-2.0 gio-2.0 libsoup-2.4 sqlite3 katze midori 
midori-core'
     if bld.env['HAVE_GTK3']:
         obj.packages += ' gtk+-3.0 webkitgtk-3.0'
     else:
_______________________________________________
Xfce4-commits mailing list
Xfce4-commits@xfce.org
https://mail.xfce.org/mailman/listinfo/xfce4-commits

Reply via email to