/* * Copyright © 2009 Benjamin Otte * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "ephy-history.h" enum { LOCATION_COLUMN_ID, LOCATION_COLUMN_FAVICON, LOCATION_COLUMN_TEXT, LOCATION_COLUMN_RELEVANCE, LOCATION_N_COLUMNS }; static gboolean return_true (GtkEntryCompletion *completion, const char *key, GtkTreeIter *iter, gpointer data) { return TRUE; } static GtkEntryCompletion * create_completion (void) { GtkEntryCompletion *completion; GtkCellRenderer *cell; completion = gtk_entry_completion_new (); #if 0 cell = gtk_cell_renderer_pixbuf_new (); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion), cell, FALSE); gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (completion), cell, "pixbuf", LOCATION_COLUMN_FAVICON); #endif cell = gtk_cell_renderer_text_new (); g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, "ellipsize-set", TRUE, NULL); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion), cell, TRUE); gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (completion), cell, "markup", LOCATION_COLUMN_TEXT); gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (cell), 2); #if 0 cell = gtk_cell_renderer_pixbuf_new (); gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (completion), cell, FALSE); gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (completion), cell, extracell_data_func, entry, NULL); #endif gtk_entry_completion_set_match_func (completion, return_true, NULL, NULL); return completion; } typedef struct { EphyHistory *history; GtkListStore *store; GCancellable *cancellable; GtkTreeIter iter; gboolean iter_valid; } ListStoreData; static void history_more_data_cb (GObject *history, GAsyncResult *res, gpointer _data) { ListStoreData *data = _data; GSList *rows; GError *error = NULL; static int columns[] = { LOCATION_COLUMN_ID, LOCATION_COLUMN_TEXT, LOCATION_COLUMN_RELEVANCE }; rows = ephy_history_select_finish (EPHY_HISTORY (history), res, &error); if (rows == NULL) { if (error) g_error_free (error); else { g_object_unref (data->cancellable); data->cancellable = NULL; while (data->iter_valid) data->iter_valid = gtk_list_store_remove (data->store, &data->iter); } return; } if (data->iter_valid) { gint64 tree_id, row_id, tree_relevance, row_relevance; GValueArray *row = rows->data; /* try to match rows, so we just do updates */ gtk_tree_model_get (GTK_TREE_MODEL (data->store), &data->iter, LOCATION_COLUMN_ID, &tree_id, LOCATION_COLUMN_RELEVANCE, &tree_relevance, -1); row_id = g_value_get_int64 (&row->values[0]); row_relevance = g_value_get_int64 (&row->values[2]); for (;;) { if (row_id == tree_id) { /* * id of columns match - update values and advance iters */ gtk_list_store_set_value (data->store, &data->iter, LOCATION_COLUMN_TEXT, &row->values[1]); data->iter_valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (data->store), &data->iter); rows = rows->next; if (rows == NULL) break; row = rows->data; row_id = g_value_get_int64 (&row->values[0]); row_relevance = g_value_get_int64 (&row->values[2]); } else if (tree_relevance >= row_relevance) { /* * The row in the tree has relevance that cannot be matched anymore. * Remove it. */ data->iter_valid = gtk_list_store_remove (data->store, &data->iter); } else { /* * Row has a higher relevance than current row in tree model. * So insert the row before the row in the tree model. */ GtkTreeIter insert; gtk_list_store_insert_before (data->store, &insert, &data->iter); gtk_list_store_set_valuesv (data->store, &insert, columns, row->values, G_N_ELEMENTS (columns)); rows = rows->next; if (rows == NULL) break; row = rows->data; row_id = g_value_get_int64 (&row->values[0]); row_relevance = g_value_get_int64 (&row->values[2]); /* no need to requery data that didn't change */ continue; } if (!data->iter_valid) break; gtk_tree_model_get (GTK_TREE_MODEL (data->store), &data->iter, LOCATION_COLUMN_ID, &tree_id, LOCATION_COLUMN_RELEVANCE, &tree_relevance, -1); } } /* add rows at the end */ for (;rows; rows = rows->next) { GValueArray *row = rows->data; GtkTreeIter insert; gtk_list_store_insert_with_valuesv (data->store, &insert, G_MAXINT, /* end */ columns, row->values, G_N_ELEMENTS (columns)); } #if 0 { GtkTreeIter iter; gint64 relevance, id; char *text; if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (data->store), &iter)) do { gtk_tree_model_get (GTK_TREE_MODEL (data->store), &iter, LOCATION_COLUMN_RELEVANCE, &relevance, LOCATION_COLUMN_ID, &id, LOCATION_COLUMN_TEXT, &text, -1); g_print ("%5lld %5lld %s\n", relevance, id, text); g_free (text); } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (data->store), &iter)); g_print ("\n"); } #endif } static void entry_changed_cb (GtkEntry *entry, ListStoreData *data) { const char *text; char *markup; if (data->cancellable) { g_cancellable_cancel (data->cancellable); g_object_unref (data->cancellable); } data->cancellable = g_cancellable_new (); text = gtk_entry_get_text (entry); markup = g_regex_escape_string (text, -1); ephy_history_select_async (data->history, 30, 100, history_more_data_cb, data, data->cancellable, "select id, markup (%Q, url), n_visits from history where url match %Q order by n_visits desc limit 300", markup, text); data->iter_valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (data->store), &data->iter); g_free (markup); } int main (int argc, char *argv[]) { static char *filename = NULL; static GOptionEntry options[] = { { "filename", 'f', 0, G_OPTION_ARG_FILENAME, &filename, "file to use as history instead of default", "FILENAME" }, { NULL } }; GError *error = NULL; GtkWidget *entry, *window; GtkEntryCompletion *completion; ListStoreData data; g_thread_init (NULL); gtk_init_with_args (&argc, &argv, (char *) "Test location entry performance", options, NULL, &error); if (error) { g_printerr ("Error parsing command line arguments: %s\n", error->message); g_error_free (error); return 1; } data.history = ephy_history_new (filename); data.store = gtk_list_store_new (LOCATION_N_COLUMNS, G_TYPE_INT64, /* LOCATION_COLUMN_ID */ GDK_TYPE_PIXBUF, /* LOCATION_COLUMN_FAVICON */ G_TYPE_STRING, /* LOCATION_COLUMN_TEXT */ G_TYPE_INT64); /* LOCATION_COLUMN_RELEVANCE */ data.cancellable = NULL; window = gtk_window_new (GTK_WINDOW_TOPLEVEL); entry = gtk_entry_new (); gtk_entry_set_width_chars (GTK_ENTRY (entry), 100); gtk_container_add (GTK_CONTAINER (window), entry); g_signal_connect (entry, "changed", G_CALLBACK (entry_changed_cb), &data); completion = create_completion (); gtk_entry_completion_set_model (completion, GTK_TREE_MODEL (data.store)); gtk_entry_set_completion (GTK_ENTRY (entry), completion); g_object_unref (completion); gtk_widget_show_all (window); gtk_main (); return 0; }