[Xfce4-commits] <midori:master> Introduce Completion API with search and history classes
Christian Dywan
noreply at xfce.org
Fri Oct 5 00:36:01 CEST 2012
Updating branch refs/heads/master
to bd1d4f8cc00cc62b826e2d786aed75b0275020dd (commit)
from 2ffca48f81a5d4ed19a449428ba4c472f8317455 (commit)
commit bd1d4f8cc00cc62b826e2d786aed75b0275020dd
Author: Christian Dywan <christian at 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 <christian at 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 <christian at 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 <christian at 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 <christian at 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:
More information about the Xfce4-commits
mailing list