[epiphany/wip/libdazzle: 1/2] Stop using DzlSuggestionEntry for now
- From: Michael Catanzaro <mcatanzaro src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [epiphany/wip/libdazzle: 1/2] Stop using DzlSuggestionEntry for now
- Date: Wed, 22 Nov 2017 23:52:01 +0000 (UTC)
commit df1db5026cef1343d33e11a1bd370b41ea1eeb14
Author: Michael Catanzaro <mcatanzaro igalia com>
Date: Wed Nov 22 17:48:32 2017 -0600
Stop using DzlSuggestionEntry for now
It's very promising, but not ready for the master branch yet... poor
Carlos Garcia has been suffering this whole time, because I was not
using it regularly and did not realize how many problems it had.
It's a great widget and a great library, and we should definitely aim to
bring it back ASAP. But let's not break master in the meantime.
https://bugzilla.gnome.org/show_bug.cgi?id=790732
embed/ephy-embed-shell.c | 3 +-
embed/ephy-embed-shell.h | 5 +-
embed/meson.build | 1 -
lib/ephy-suggestion.c | 174 --------
lib/ephy-suggestion.h | 37 --
lib/meson.build | 2 -
lib/widgets/contrib/gd-two-lines-renderer.c | 621 ++++++++++++++++++++++++++
lib/widgets/contrib/gd-two-lines-renderer.h | 75 ++++
lib/widgets/ephy-location-entry.c | 378 +++++++++++++++--
lib/widgets/ephy-location-entry.h | 20 +-
lib/widgets/meson.build | 2 +-
meson.build | 1 -
org.gnome.Epiphany.json | 12 -
src/ephy-completion-model.c | 625 +++++++++++++++++++++++++++
src/ephy-completion-model.h | 53 +++
src/ephy-location-controller.c | 133 ++++++-
src/ephy-suggestion-model.c | 476 --------------------
src/ephy-suggestion-model.h | 46 --
src/ephy-window.c | 22 +-
src/ephy-window.h | 3 +-
src/meson.build | 3 +-
src/search-provider/ephy-search-provider.c | 121 ++++--
tests/ephy-completion-model-test.c | 101 +++++
tests/ephy-location-entry-test.c | 160 +++++++
tests/meson.build | 14 +-
25 files changed, 2220 insertions(+), 868 deletions(-)
---
diff --git a/embed/ephy-embed-shell.c b/embed/ephy-embed-shell.c
index 0c1d38a..d0751ff 100644
--- a/embed/ephy-embed-shell.c
+++ b/embed/ephy-embed-shell.c
@@ -43,7 +43,6 @@
#include "ephy-web-app-utils.h"
#include "ephy-web-extension-proxy.h"
-#include <dazzle.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <stdlib.h>
@@ -104,7 +103,7 @@ static EphyEmbedShell *embed_shell = NULL;
static void ephy_embed_shell_tabs_catalog_iface_init (EphyTabsCatalogInterface *iface);
-G_DEFINE_TYPE_WITH_CODE (EphyEmbedShell, ephy_embed_shell, DZL_TYPE_APPLICATION,
+G_DEFINE_TYPE_WITH_CODE (EphyEmbedShell, ephy_embed_shell, GTK_TYPE_APPLICATION,
G_ADD_PRIVATE (EphyEmbedShell)
G_IMPLEMENT_INTERFACE (EPHY_TYPE_TABS_CATALOG,
ephy_embed_shell_tabs_catalog_iface_init))
diff --git a/embed/ephy-embed-shell.h b/embed/ephy-embed-shell.h
index c5b5dfd..c933d50 100644
--- a/embed/ephy-embed-shell.h
+++ b/embed/ephy-embed-shell.h
@@ -21,7 +21,6 @@
#pragma once
-#include <dazzle.h>
#include <webkit2/webkit2.h>
#include "ephy-downloads-manager.h"
@@ -35,7 +34,7 @@ G_BEGIN_DECLS
#define EPHY_TYPE_EMBED_SHELL (ephy_embed_shell_get_type ())
-G_DECLARE_DERIVABLE_TYPE (EphyEmbedShell, ephy_embed_shell, EPHY, EMBED_SHELL, DzlApplication)
+G_DECLARE_DERIVABLE_TYPE (EphyEmbedShell, ephy_embed_shell, EPHY, EMBED_SHELL, GtkApplication)
typedef enum
{
@@ -50,7 +49,7 @@ typedef enum
struct _EphyEmbedShellClass
{
- DzlApplicationClass parent_class;
+ GtkApplicationClass parent_class;
void (* restored_window) (EphyEmbedShell *shell);
};
diff --git a/embed/meson.build b/embed/meson.build
index 99315d7..1f924f2 100644
--- a/embed/meson.build
+++ b/embed/meson.build
@@ -37,7 +37,6 @@ libephyembed_deps = [
glib_dep,
gtk_dep,
icu_uc_dep,
- libdazzle_dep,
libsecret_dep,
libsoup_dep,
m_dep,
diff --git a/lib/meson.build b/lib/meson.build
index b9f5d8e..298dd7e 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -33,7 +33,6 @@ libephymisc_sources = [
'ephy-sqlite-connection.c',
'ephy-sqlite-statement.c',
'ephy-string.c',
- 'ephy-suggestion.c',
'ephy-sync-utils.c',
'ephy-time-helpers.c',
'ephy-uri-helpers.c',
@@ -62,7 +61,6 @@ libephymisc_deps = [
gtk_dep,
icu_uc_dep,
json_glib_dep,
- libdazzle_dep,
libsecret_dep,
libsoup_dep,
libxml_dep,
diff --git a/lib/widgets/contrib/gd-two-lines-renderer.c b/lib/widgets/contrib/gd-two-lines-renderer.c
new file mode 100644
index 0000000..5a029f7
--- /dev/null
+++ b/lib/widgets/contrib/gd-two-lines-renderer.c
@@ -0,0 +1,621 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program 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 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc redhat com>
+ *
+ */
+
+#include "gd-two-lines-renderer.h"
+#include <string.h>
+
+#define SUBTITLE_DIM_PERCENTAGE 0.55
+#define SUBTITLE_SIZE_PERCENTAGE 0.82
+
+G_DEFINE_TYPE (GdTwoLinesRenderer, gd_two_lines_renderer, GTK_TYPE_CELL_RENDERER_TEXT)
+
+struct _GdTwoLinesRendererPrivate {
+ gchar *line_two;
+ gint text_lines;
+};
+
+enum {
+ PROP_TEXT_LINES = 1,
+ PROP_LINE_TWO,
+ NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+static PangoLayout *
+create_layout_with_attrs (GtkWidget *widget,
+ const GdkRectangle *cell_area,
+ GdTwoLinesRenderer *self,
+ PangoEllipsizeMode ellipsize)
+{
+ PangoLayout *layout;
+ gint wrap_width, xpad;
+ PangoWrapMode wrap_mode;
+ PangoAlignment alignment;
+
+ g_object_get (self,
+ "wrap-width", &wrap_width,
+ "wrap-mode", &wrap_mode,
+ "alignment", &alignment,
+ "xpad", &xpad,
+ NULL);
+
+ layout = pango_layout_new (gtk_widget_get_pango_context (widget));
+
+ pango_layout_set_ellipsize (layout, ellipsize);
+ pango_layout_set_alignment (layout, alignment);
+
+ if (wrap_width != -1)
+ {
+ pango_layout_set_width (layout, wrap_width * PANGO_SCALE);
+ pango_layout_set_wrap (layout, wrap_mode);
+ }
+ else
+ {
+ if (cell_area != NULL)
+ pango_layout_set_width (layout, (cell_area->width - 2 * xpad) * PANGO_SCALE);
+ else
+ pango_layout_set_width (layout, -1);
+
+ pango_layout_set_wrap (layout, PANGO_WRAP_CHAR);
+ }
+
+ return layout;
+}
+
+static void
+apply_subtitle_style_to_layout (GtkStyleContext *context,
+ PangoLayout *layout,
+ GtkStateFlags flags)
+{
+ PangoFontDescription *desc;
+ PangoAttrList *layout_attr;
+ PangoAttribute *attr_alpha;
+
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, flags);
+ gtk_style_context_get (context, gtk_style_context_get_state (context),
+ "font", &desc,
+ NULL);
+ gtk_style_context_restore (context);
+
+ /* Set the font size */
+ pango_font_description_set_size (desc, pango_font_description_get_size (desc) * SUBTITLE_SIZE_PERCENTAGE);
+ pango_layout_set_font_description (layout, desc);
+ pango_font_description_free (desc);
+
+ /* Set the font alpha */
+ layout_attr = pango_attr_list_new ();
+ attr_alpha = pango_attr_foreground_alpha_new (SUBTITLE_DIM_PERCENTAGE * 65535);
+ pango_attr_list_insert (layout_attr, attr_alpha);
+
+ pango_layout_set_attributes (layout, layout_attr);
+ pango_attr_list_unref (layout_attr);
+}
+
+static void
+gd_two_lines_renderer_prepare_layouts (GdTwoLinesRenderer *self,
+ const GdkRectangle *cell_area,
+ GtkWidget *widget,
+ PangoLayout **layout_one,
+ PangoLayout **layout_two)
+{
+ PangoLayout *line_one;
+ PangoLayout *line_two = NULL;
+ gchar *text = NULL;
+
+ g_object_get (self,
+ "text", &text,
+ NULL);
+
+ line_one = create_layout_with_attrs (widget, cell_area,
+ self, PANGO_ELLIPSIZE_MIDDLE);
+
+ if (self->priv->line_two == NULL ||
+ g_strcmp0 (self->priv->line_two, "") == 0)
+ {
+ pango_layout_set_height (line_one, - (self->priv->text_lines));
+
+ if (text != NULL)
+ pango_layout_set_text (line_one, text, -1);
+ }
+ else
+ {
+ GtkStyleContext *context;
+
+ line_two = create_layout_with_attrs (widget, cell_area,
+ self, PANGO_ELLIPSIZE_END);
+
+ context = gtk_widget_get_style_context (widget);
+ gtk_style_context_save (context);
+ apply_subtitle_style_to_layout (context, line_two, GTK_STATE_FLAG_NORMAL);
+ gtk_style_context_restore (context);
+
+ pango_layout_set_height (line_one, - (self->priv->text_lines - 1));
+ pango_layout_set_height (line_two, -1);
+ pango_layout_set_text (line_two, self->priv->line_two, -1);
+
+ if (text != NULL)
+ pango_layout_set_text (line_one, text, -1);
+ }
+
+ if (layout_one)
+ *layout_one = line_one;
+ if (layout_two)
+ *layout_two = line_two;
+
+ g_free (text);
+}
+
+static void
+gd_two_lines_renderer_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ PangoLayout *layout_1,
+ PangoLayout *layout_2,
+ gint *width,
+ gint *height,
+ const GdkRectangle *cell_area,
+ gint *x_offset_1,
+ gint *x_offset_2,
+ gint *y_offset)
+{
+ GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (cell);
+ gint xpad, ypad;
+ PangoLayout *layout_one, *layout_two;
+ GdkRectangle layout_one_rect, layout_two_rect, layout_union;
+
+ if (layout_1 == NULL)
+ {
+ gd_two_lines_renderer_prepare_layouts (self, cell_area, widget, &layout_one, &layout_two);
+ }
+ else
+ {
+ layout_one = g_object_ref (layout_1);
+
+ if (layout_2 != NULL)
+ layout_two = g_object_ref (layout_2);
+ else
+ layout_two = NULL;
+ }
+
+ gtk_cell_renderer_get_padding (cell, &xpad, &ypad);
+ pango_layout_get_pixel_extents (layout_one, NULL, (PangoRectangle *) &layout_one_rect);
+
+ if (layout_two != NULL)
+ {
+ pango_layout_get_pixel_extents (layout_two, NULL, (PangoRectangle *) &layout_two_rect);
+
+ layout_union.width = MAX (layout_one_rect.width, layout_two_rect.width);
+ layout_union.height = layout_one_rect.height + layout_two_rect.height;
+ }
+ else
+ {
+ layout_union = layout_one_rect;
+ }
+
+ if (cell_area)
+ {
+ gfloat xalign, yalign;
+
+ gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
+
+ layout_union.width = MIN (layout_union.width, cell_area->width - 2 * xpad);
+ layout_union.height = MIN (layout_union.height, cell_area->height - 2 * ypad);
+
+ if (x_offset_1)
+ {
+ if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+ *x_offset_1 = (1.0 - xalign) * (cell_area->width - (layout_one_rect.width + (2 * xpad)));
+ else
+ *x_offset_1 = xalign * (cell_area->width - (layout_one_rect.width + (2 * xpad)));
+
+ *x_offset_1 = MAX (*x_offset_1, 0);
+ }
+ if (x_offset_2)
+ {
+ if (layout_two != NULL)
+ {
+ if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+ *x_offset_2 = (1.0 - xalign) * (cell_area->width - (layout_two_rect.width + (2 * xpad)));
+ else
+ *x_offset_2 = xalign * (cell_area->width - (layout_two_rect.width + (2 * xpad)));
+
+ *x_offset_2 = MAX (*x_offset_2, 0);
+ }
+ else
+ {
+ *x_offset_2 = 0;
+ }
+ }
+
+ if (y_offset)
+ {
+ *y_offset = yalign * (cell_area->height - (layout_union.height + (2 * ypad)));
+ *y_offset = MAX (*y_offset, 0);
+ }
+ }
+ else
+ {
+ if (x_offset_1) *x_offset_1 = 0;
+ if (x_offset_2) *x_offset_2 = 0;
+ if (y_offset) *y_offset = 0;
+ }
+
+ g_clear_object (&layout_one);
+ g_clear_object (&layout_two);
+
+ if (height)
+ *height = ypad * 2 + layout_union.height;
+
+ if (width)
+ *width = xpad * 2 + layout_union.width;
+}
+
+static void
+gd_two_lines_renderer_render (GtkCellRenderer *cell,
+ cairo_t *cr,
+ GtkWidget *widget,
+ const GdkRectangle *background_area,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags)
+{
+ GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (cell);
+ GtkStyleContext *context;
+ gint line_one_height;
+ GtkStateFlags state;
+ GdkRectangle area, render_area = *cell_area;
+ gint xpad, ypad, x_offset_1, x_offset_2, y_offset;
+ PangoLayout *layout_one, *layout_two;
+ PangoRectangle layout_rect;
+
+ /* fetch common information */
+ context = gtk_widget_get_style_context (widget);
+ gd_two_lines_renderer_prepare_layouts (self, cell_area, widget, &layout_one, &layout_two);
+ gd_two_lines_renderer_get_size (cell, widget,
+ layout_one, layout_two,
+ NULL, NULL,
+ cell_area,
+ &x_offset_1, &x_offset_2, &y_offset);
+ gtk_cell_renderer_get_padding (cell, &xpad, &ypad);
+
+ area = *cell_area;
+ area.x += xpad;
+ area.y += ypad;
+
+ /* now render the first layout */
+ pango_layout_get_pixel_extents (layout_one, NULL, &layout_rect);
+
+ render_area = area;
+ render_area.x += x_offset_1 - layout_rect.x;
+
+ gtk_render_layout (context, cr,
+ render_area.x,
+ render_area.y,
+ layout_one);
+
+ /* render the second layout */
+ if (layout_two != NULL)
+ {
+ pango_layout_get_pixel_size (layout_one,
+ NULL, &line_one_height);
+
+ gtk_style_context_save (context);
+
+ apply_subtitle_style_to_layout (context, layout_two, flags);
+
+ state = gtk_cell_renderer_get_state (cell, widget, flags);
+ gtk_style_context_set_state (context, state);
+
+ pango_layout_get_pixel_extents (layout_two, NULL, &layout_rect);
+
+ render_area = area;
+ render_area.x += x_offset_2 - layout_rect.x;
+ render_area.y += line_one_height;
+
+ gtk_render_layout (context, cr,
+ render_area.x,
+ render_area.y,
+ layout_two);
+
+ gtk_style_context_restore (context);
+ }
+
+ g_clear_object (&layout_one);
+ g_clear_object (&layout_two);
+}
+
+static void
+gd_two_lines_renderer_get_preferred_width (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ PangoContext *context;
+ PangoFontMetrics *metrics;
+ PangoFontDescription *font_desc;
+ GtkStyleContext *style_context;
+ gint nat_width, min_width;
+ gint xpad, char_width, wrap_width, text_width;
+ gint width_chars, ellipsize_chars;
+
+ g_object_get (cell,
+ "xpad", &xpad,
+ "width-chars", &width_chars,
+ "wrap-width", &wrap_width,
+ NULL);
+ style_context = gtk_widget_get_style_context (widget);
+ gtk_cell_renderer_get_padding (cell, &xpad, NULL);
+
+ gd_two_lines_renderer_get_size (cell, widget,
+ NULL, NULL,
+ &text_width, NULL,
+ NULL,
+ NULL, NULL, NULL);
+
+ /* Fetch the average size of a character */
+ context = gtk_widget_get_pango_context (widget);
+ gtk_style_context_save (style_context);
+ gtk_style_context_set_state (style_context, 0);
+ gtk_style_context_get (style_context, gtk_style_context_get_state (style_context),
+ "font", &font_desc, NULL);
+ gtk_style_context_restore (style_context);
+ metrics = pango_context_get_metrics (context, font_desc,
+ pango_context_get_language (context));
+
+ char_width = pango_font_metrics_get_approximate_char_width (metrics);
+
+ pango_font_metrics_unref (metrics);
+ pango_font_description_free (font_desc);
+
+ /* enforce minimum width for ellipsized labels at ~3 chars */
+ ellipsize_chars = 3;
+
+ /* If no width-chars set, minimum for wrapping text will be the wrap-width */
+ if (wrap_width > -1)
+ min_width = xpad * 2 + MIN (text_width, wrap_width);
+ else
+ min_width = xpad * 2 +
+ MIN (text_width,
+ (PANGO_PIXELS (char_width) * MAX (width_chars, ellipsize_chars)));
+
+ if (width_chars > 0)
+ nat_width = xpad * 2 +
+ MAX ((PANGO_PIXELS (char_width) * width_chars), text_width);
+ else
+ nat_width = xpad * 2 + text_width;
+
+ nat_width = MAX (nat_width, min_width);
+
+ if (minimum_size)
+ *minimum_size = min_width;
+
+ if (natural_size)
+ *natural_size = nat_width;
+}
+
+static void
+gd_two_lines_renderer_get_preferred_height_for_width (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ gint width,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (cell);
+ PangoLayout *layout_one, *layout_two;
+ gint text_height, wrap_width;
+ gint xpad, ypad;
+
+ gtk_cell_renderer_get_padding (cell, &xpad, &ypad);
+ g_object_get (cell, "wrap-width", &wrap_width, NULL);
+ gd_two_lines_renderer_prepare_layouts (self, NULL, widget, &layout_one, &layout_two);
+
+ if (wrap_width != -1)
+ wrap_width = MIN (width - 2 * xpad, wrap_width);
+ else
+ wrap_width = width - 2 * xpad;
+
+ pango_layout_set_width (layout_one, wrap_width);
+ if (layout_two != NULL)
+ pango_layout_set_width (layout_two, wrap_width);
+
+ gd_two_lines_renderer_get_size (cell, widget,
+ layout_one, layout_two,
+ NULL, &text_height,
+ NULL,
+ NULL, NULL, NULL);
+
+ text_height += 2 * ypad;
+
+ if (minimum_size != NULL)
+ *minimum_size = text_height;
+
+ if (natural_size != NULL)
+ *natural_size = text_height;
+
+ g_clear_object (&layout_one);
+ g_clear_object (&layout_two);
+}
+
+static void
+gd_two_lines_renderer_get_preferred_height (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ gint min_width;
+
+ gtk_cell_renderer_get_preferred_width (cell, widget, &min_width, NULL);
+ gd_two_lines_renderer_get_preferred_height_for_width (cell, widget, min_width,
+ minimum_size, natural_size);
+}
+
+static void
+gd_two_lines_renderer_get_aligned_area (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ GtkCellRendererState flags,
+ const GdkRectangle *cell_area,
+ GdkRectangle *aligned_area)
+{
+ GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (cell);
+ gint x_offset, x_offset_1, x_offset_2, y_offset;
+ PangoLayout *layout_one, *layout_two;
+
+ /* fetch common information */
+ gd_two_lines_renderer_prepare_layouts (self, cell_area, widget, &layout_one, &layout_two);
+ gd_two_lines_renderer_get_size (cell, widget,
+ layout_one, layout_two,
+ &aligned_area->width, &aligned_area->height,
+ cell_area,
+ &x_offset_1, &x_offset_2, &y_offset);
+
+ x_offset = MIN (x_offset_1, x_offset_2);
+
+ aligned_area->x = cell_area->x + x_offset;
+ aligned_area->y = cell_area->y;
+
+ g_clear_object (&layout_one);
+ g_clear_object (&layout_two);
+}
+
+static void
+gd_two_lines_renderer_set_line_two (GdTwoLinesRenderer *self,
+ const gchar *line_two)
+{
+ if (g_strcmp0 (self->priv->line_two, line_two) == 0)
+ return;
+
+ g_free (self->priv->line_two);
+ self->priv->line_two = g_strdup (line_two);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LINE_TWO]);
+}
+
+static void
+gd_two_lines_renderer_set_text_lines (GdTwoLinesRenderer *self,
+ gint text_lines)
+{
+ if (self->priv->text_lines == text_lines)
+ return;
+
+ self->priv->text_lines = text_lines;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TEXT_LINES]);
+}
+
+static void
+gd_two_lines_renderer_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (object);
+
+ switch (property_id)
+ {
+ case PROP_TEXT_LINES:
+ gd_two_lines_renderer_set_text_lines (self, g_value_get_int (value));
+ break;
+ case PROP_LINE_TWO:
+ gd_two_lines_renderer_set_line_two (self, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_two_lines_renderer_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (object);
+
+ switch (property_id)
+ {
+ case PROP_TEXT_LINES:
+ g_value_set_int (value, self->priv->text_lines);
+ break;
+ case PROP_LINE_TWO:
+ g_value_set_string (value, self->priv->line_two);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_two_lines_renderer_finalize (GObject *object)
+{
+ GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (object);
+
+ g_free (self->priv->line_two);
+
+ G_OBJECT_CLASS (gd_two_lines_renderer_parent_class)->finalize (object);
+}
+
+static void
+gd_two_lines_renderer_class_init (GdTwoLinesRendererClass *klass)
+{
+ GtkCellRendererClass *cclass = GTK_CELL_RENDERER_CLASS (klass);
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ cclass->render = gd_two_lines_renderer_render;
+ cclass->get_preferred_width = gd_two_lines_renderer_get_preferred_width;
+ cclass->get_preferred_height = gd_two_lines_renderer_get_preferred_height;
+ cclass->get_preferred_height_for_width = gd_two_lines_renderer_get_preferred_height_for_width;
+ cclass->get_aligned_area = gd_two_lines_renderer_get_aligned_area;
+
+ oclass->set_property = gd_two_lines_renderer_set_property;
+ oclass->get_property = gd_two_lines_renderer_get_property;
+ oclass->finalize = gd_two_lines_renderer_finalize;
+
+ properties[PROP_TEXT_LINES] =
+ g_param_spec_int ("text-lines",
+ "Lines of text",
+ "The total number of lines to be displayed",
+ 2, G_MAXINT, 2,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_LINE_TWO] =
+ g_param_spec_string ("line-two",
+ "Second line",
+ "Second line",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_type_class_add_private (klass, sizeof (GdTwoLinesRendererPrivate));
+ g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
+}
+
+static void
+gd_two_lines_renderer_init (GdTwoLinesRenderer *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GD_TYPE_TWO_LINES_RENDERER,
+ GdTwoLinesRendererPrivate);
+}
+
+GtkCellRenderer *
+gd_two_lines_renderer_new (void)
+{
+ return g_object_new (GD_TYPE_TWO_LINES_RENDERER, NULL);
+}
diff --git a/lib/widgets/contrib/gd-two-lines-renderer.h b/lib/widgets/contrib/gd-two-lines-renderer.h
new file mode 100644
index 0000000..23bf70f
--- /dev/null
+++ b/lib/widgets/contrib/gd-two-lines-renderer.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program 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 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc redhat com>
+ *
+ */
+
+#ifndef _GD_TWO_LINES_RENDERER_H
+#define _GD_TWO_LINES_RENDERER_H
+
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_TWO_LINES_RENDERER gd_two_lines_renderer_get_type()
+
+#define GD_TWO_LINES_RENDERER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GD_TYPE_TWO_LINES_RENDERER, GdTwoLinesRenderer))
+
+#define GD_TWO_LINES_RENDERER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ GD_TYPE_TWO_LINES_RENDERER, GdTwoLinesRendererClass))
+
+#define GD_IS_TWO_LINES_RENDERER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GD_TYPE_TWO_LINES_RENDERER))
+
+#define GD_IS_TWO_LINES_RENDERER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ GD_TYPE_TWO_LINES_RENDERER))
+
+#define GD_TWO_LINES_RENDERER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GD_TYPE_TWO_LINES_RENDERER, GdTwoLinesRendererClass))
+
+typedef struct _GdTwoLinesRenderer GdTwoLinesRenderer;
+typedef struct _GdTwoLinesRendererClass GdTwoLinesRendererClass;
+typedef struct _GdTwoLinesRendererPrivate GdTwoLinesRendererPrivate;
+
+struct _GdTwoLinesRenderer
+{
+ GtkCellRendererText parent;
+
+ GdTwoLinesRendererPrivate *priv;
+};
+
+struct _GdTwoLinesRendererClass
+{
+ GtkCellRendererTextClass parent_class;
+};
+
+GType gd_two_lines_renderer_get_type (void) G_GNUC_CONST;
+
+GtkCellRenderer *gd_two_lines_renderer_new (void);
+
+G_END_DECLS
+
+#endif /* _GD_TWO_LINES_RENDERER_H */
diff --git a/lib/widgets/ephy-location-entry.c b/lib/widgets/ephy-location-entry.c
index 472d0ab..74b590f 100644
--- a/lib/widgets/ephy-location-entry.c
+++ b/lib/widgets/ephy-location-entry.c
@@ -31,11 +31,10 @@
#include "ephy-gui.h"
#include "ephy-lib-type-builtins.h"
#include "ephy-signal-accumulator.h"
-#include "ephy-suggestion.h"
#include "ephy-title-widget.h"
#include "ephy-uri-helpers.h"
+#include "gd-two-lines-renderer.h"
-#include <dazzle.h>
#include <gdk/gdkkeysyms.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
@@ -55,7 +54,7 @@
*/
struct _EphyLocationEntry {
- DzlSuggestionEntry parent_instance;
+ GtkEntry parent_instance;
GtkTreeModel *model;
@@ -92,6 +91,12 @@ struct _EphyLocationEntry {
static gboolean ephy_location_entry_reset_internal (EphyLocationEntry *, gboolean);
+static void extracell_data_func (GtkCellLayout *cell_layout,
+ GtkCellRenderer *cell,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gpointer data);
+
enum {
PROP_0,
PROP_ADDRESS,
@@ -110,17 +115,11 @@ static gint signals[LAST_SIGNAL] = { 0 };
static void ephy_location_entry_title_widget_interface_init (EphyTitleWidgetInterface *iface);
-G_DEFINE_TYPE_WITH_CODE (EphyLocationEntry, ephy_location_entry, DZL_TYPE_SUGGESTION_ENTRY,
+G_DEFINE_TYPE_WITH_CODE (EphyLocationEntry, ephy_location_entry, GTK_TYPE_ENTRY,
G_IMPLEMENT_INTERFACE (EPHY_TYPE_TITLE_WIDGET,
ephy_location_entry_title_widget_interface_init))
static void
-ephy_location_entry_activate (EphyLocationEntry *entry)
-{
- g_signal_emit_by_name (entry, "activate");
-}
-
-static void
update_address_state (EphyLocationEntry *entry)
{
const char *text;
@@ -274,12 +273,8 @@ ephy_location_entry_get_property (GObject *object,
static void
ephy_location_entry_constructed (GObject *object)
{
- EphyLocationEntry *entry = EPHY_LOCATION_ENTRY (object);
-
G_OBJECT_CLASS (ephy_location_entry_parent_class)->constructed (object);
- dzl_suggestion_entry_set_position_func (DZL_SUGGESTION_ENTRY (entry),
dzl_suggestion_entry_window_position_func, NULL, NULL);
-
#if GTK_CHECK_VERSION(3, 22, 20)
gtk_entry_set_input_hints (GTK_ENTRY (object), GTK_INPUT_HINT_NO_EMOJI);
#endif
@@ -366,16 +361,6 @@ ephy_location_entry_cut_clipboard (GtkEntry *entry)
}
static void
-ephy_location_entry_suggestion_activated (DzlSuggestionEntry *entry,
- DzlSuggestion *suggestion)
-{
- gtk_entry_set_text (GTK_ENTRY (entry), ephy_suggestion_get_uri (EPHY_SUGGESTION (suggestion)));
-
- /* Now trigger the load.... */
- ephy_location_entry_activate (EPHY_LOCATION_ENTRY (entry));
-}
-
-static void
ephy_location_entry_title_widget_interface_init (EphyTitleWidgetInterface *iface)
{
iface->get_address = ephy_location_entry_title_widget_get_address;
@@ -390,9 +375,7 @@ ephy_location_entry_class_init (EphyLocationEntryClass *klass)
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GtkEntryClass *entry_class = GTK_ENTRY_CLASS (klass);
- DzlSuggestionEntryClass *dzl_entry_class = DZL_SUGGESTION_ENTRY_CLASS (klass);
- object_class->constructed = ephy_location_entry_constructed;
object_class->get_property = ephy_location_entry_get_property;
object_class->set_property = ephy_location_entry_set_property;
object_class->constructed = ephy_location_entry_constructed;
@@ -404,8 +387,6 @@ ephy_location_entry_class_init (EphyLocationEntryClass *klass)
entry_class->copy_clipboard = ephy_location_entry_copy_clipboard;
entry_class->cut_clipboard = ephy_location_entry_cut_clipboard;
- dzl_entry_class->suggestion_activated = ephy_location_entry_suggestion_activated;
-
g_object_class_override_property (object_class, PROP_ADDRESS, "address");
g_object_class_override_property (object_class, PROP_SECURITY_LEVEL, "security-level");
@@ -507,18 +488,103 @@ entry_key_press_cb (GtkEntry *entry,
/* Make sure the location is activated on CTRL+l even when the
* completion popup is shown and have an active keyboard grab.
*/
- ephy_location_entry_focus (location_entry);
+ ephy_location_entry_activate (location_entry);
}
- if (event->keyval == GDK_KEY_Return ||
- event->keyval == GDK_KEY_KP_Enter ||
- event->keyval == GDK_KEY_ISO_Enter)
- ephy_location_entry_activate (location_entry);
+ return FALSE;
+}
+
+static gboolean
+entry_key_press_after_cb (GtkEntry *entry,
+ GdkEventKey *event,
+ EphyLocationEntry *lentry)
+{
+ guint state = event->state & gtk_accelerator_get_default_mod_mask ();
+
+ if ((event->keyval == GDK_KEY_Return ||
+ event->keyval == GDK_KEY_KP_Enter ||
+ event->keyval == GDK_KEY_ISO_Enter) &&
+ (state == GDK_CONTROL_MASK ||
+ state == (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) {
+ /* gtk_im_context_reset (entry->im_context); */
+
+ lentry->needs_reset = TRUE;
+ g_signal_emit_by_name (entry, "activate");
+
+ return TRUE;
+ }
+
+ if ((event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_KP_Down)
+ && state == 0) {
+ /* If we are focusing the entry, with the cursor at the end of it
+ * we emit the changed signal, so that the completion popup appears */
+ const char *string;
+
+ string = gtk_entry_get_text (entry);
+ if (gtk_editable_get_position (GTK_EDITABLE (entry)) == (int)strlen (string)) {
+ g_signal_emit_by_name (entry, "changed", 0);
+ return TRUE;
+ }
+ }
return FALSE;
}
static void
+entry_activate_after_cb (GtkEntry *entry,
+ EphyLocationEntry *lentry)
+{
+ lentry->user_changed = FALSE;
+
+ if (lentry->needs_reset) {
+ ephy_location_entry_reset_internal (lentry, TRUE);
+ lentry->needs_reset = FALSE;
+ }
+}
+
+static gboolean
+match_selected_cb (GtkEntryCompletion *completion,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ EphyLocationEntry *entry)
+{
+ char *item = NULL;
+ guint state;
+
+ gtk_tree_model_get (model, iter,
+ entry->action_col, &item, -1);
+ if (item == NULL) return FALSE;
+
+ ephy_gui_get_current_event (NULL, &state, NULL);
+
+ entry->needs_reset = (state == GDK_CONTROL_MASK ||
+ state == (GDK_CONTROL_MASK | GDK_SHIFT_MASK));
+
+ ephy_title_widget_set_address (EPHY_TITLE_WIDGET (entry), item);
+ /* gtk_im_context_reset (GTK_ENTRY (entry)->im_context); */
+ g_signal_emit_by_name (entry, "activate");
+
+ g_free (item);
+
+ return TRUE;
+}
+
+static void
+action_activated_after_cb (GtkEntryCompletion *completion,
+ gint index,
+ EphyLocationEntry *lentry)
+{
+ guint state, button;
+
+ ephy_gui_get_current_event (NULL, &state, &button);
+ if ((state == GDK_CONTROL_MASK ||
+ state == (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) ||
+ button == 2) {
+ ephy_location_entry_reset_internal (lentry, TRUE);
+ }
+}
+
+static void
entry_clear_activate_cb (GtkMenuItem *item,
EphyLocationEntry *entry)
{
@@ -535,7 +601,7 @@ paste_received (GtkClipboard *clipboard,
{
if (text) {
gtk_entry_set_text (GTK_ENTRY (entry), text);
- ephy_location_entry_activate (entry);
+ g_signal_emit_by_name (entry, "activate");
}
}
@@ -692,6 +758,11 @@ ephy_location_entry_construct_contents (EphyLocationEntry *lentry)
"signal::key-press-event", G_CALLBACK (entry_key_press_cb), lentry,
"signal::changed", G_CALLBACK (editable_changed_cb), lentry,
NULL);
+
+ g_signal_connect_after (entry, "key-press-event",
+ G_CALLBACK (entry_key_press_after_cb), lentry);
+ g_signal_connect_after (entry, "activate",
+ G_CALLBACK (entry_activate_after_cb), lentry);
}
static void
@@ -768,6 +839,238 @@ schedule_dns_prefetch (EphyLocationEntry *entry, guint interval, const gchar *ur
}
#endif
+static gboolean
+cursor_on_match_cb (GtkEntryCompletion *completion,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ EphyLocationEntry *le)
+{
+ char *url = NULL;
+ GtkWidget *entry;
+
+ gtk_tree_model_get (model, iter,
+ le->url_col,
+ &url, -1);
+ entry = gtk_entry_completion_get_entry (completion);
+
+ /* Prevent the update so we keep the highlight from our input.
+ * See textcell_data_func().
+ */
+ le->block_update = TRUE;
+ gtk_entry_set_text (GTK_ENTRY (entry), url);
+ gtk_editable_set_position (GTK_EDITABLE (entry), -1);
+ le->block_update = FALSE;
+
+#if 0
+/* FIXME: Refactor the DNS prefetch, this is a layering violation */
+ schedule_dns_prefetch (le, 250, (const gchar *)url);
+#endif
+
+ g_free (url);
+
+ return TRUE;
+}
+
+static void
+extracell_data_func (GtkCellLayout *cell_layout,
+ GtkCellRenderer *cell,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ EphyLocationEntry *entry = EPHY_LOCATION_ENTRY (data);
+ gboolean is_bookmark = FALSE;
+ GValue visible = { 0, };
+
+ gtk_tree_model_get (tree_model, iter,
+ entry->extra_col, &is_bookmark,
+ -1);
+
+ if (is_bookmark)
+ g_object_set (cell,
+ "icon-name", "user-bookmarks-symbolic",
+ NULL);
+
+ g_value_init (&visible, G_TYPE_BOOLEAN);
+ g_value_set_boolean (&visible, is_bookmark);
+ g_object_set_property (G_OBJECT (cell), "visible", &visible);
+ g_value_unset (&visible);
+}
+
+/**
+ * ephy_location_entry_set_match_func:
+ * @entry: an #EphyLocationEntry widget
+ * @match_func: a #GtkEntryCompletionMatchFunc
+ * @user_data: user_data to pass to the @match_func
+ * @notify: a #GDestroyNotify, like the one given to
+ * gtk_entry_completion_set_match_func
+ *
+ * Sets the match_func for the internal #GtkEntryCompletion to @match_func.
+ *
+ **/
+void
+ephy_location_entry_set_match_func (EphyLocationEntry *entry,
+ GtkEntryCompletionMatchFunc match_func,
+ gpointer user_data,
+ GDestroyNotify notify)
+{
+ GtkEntryCompletion *completion;
+
+ completion = gtk_entry_get_completion (GTK_ENTRY (entry));
+ gtk_entry_completion_set_match_func (completion, match_func, user_data, notify);
+}
+
+/**
+ * ephy_location_entry_set_completion:
+ * @entry: an #EphyLocationEntry widget
+ * @model: the #GtkModel for the completion
+ * @text_col: column id to access #GtkModel relevant data
+ * @action_col: column id to access #GtkModel relevant data
+ * @keywords_col: column id to access #GtkModel relevant data
+ * @relevance_col: column id to access #GtkModel relevant data
+ * @url_col: column id to access #GtkModel relevant data
+ * @extra_col: column id to access #GtkModel relevant data
+ * @favicon_col: column id to access #GtkModel relevant data
+ *
+ * Initializes @entry to have a #GtkEntryCompletion using @model as the
+ * internal #GtkModel. The *_col arguments are for internal data retrieval from
+ * @model, like when setting the text property of one of the #GtkCellRenderer
+ * of the completion.
+ *
+ **/
+void
+ephy_location_entry_set_completion (EphyLocationEntry *entry,
+ GtkTreeModel *model,
+ guint text_col,
+ guint action_col,
+ guint keywords_col,
+ guint relevance_col,
+ guint url_col,
+ guint extra_col,
+ guint favicon_col)
+{
+ GtkEntryCompletion *completion;
+ GtkCellRenderer *cell;
+
+ entry->text_col = text_col;
+ entry->action_col = action_col;
+ entry->keywords_col = keywords_col;
+ entry->relevance_col = relevance_col;
+ entry->url_col = url_col;
+ entry->extra_col = extra_col;
+ entry->favicon_col = favicon_col;
+
+ completion = gtk_entry_completion_new ();
+ gtk_entry_completion_set_model (completion, model);
+ g_signal_connect (completion, "match-selected",
+ G_CALLBACK (match_selected_cb), entry);
+ g_signal_connect_after (completion, "action-activated",
+ G_CALLBACK (action_activated_after_cb), entry);
+
+ 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", favicon_col);
+
+ /* Pixel-perfect aligment with the location entry favicon
+ * (16x16). Consider that this /might/ depend on the theme.
+ *
+ * The GtkEntryCompletion can not be themed so we work-around
+ * that with padding and fixed sizes.
+ * For the first cell, this is:
+ *
+ * ___+++++iiiiiiiiiiiiiiii++__ttt...bbb++++++__
+ *
+ * _ = widget spacing, can not be handled (3 px)
+ * + = padding (5 px) (ICON_PADDING_LEFT)
+ * i = the icon (16 px) (ICON_CONTENT_WIDTH)
+ * + = padding (2 px) (ICON_PADDING_RIGHT) (cut by the fixed_size)
+ * _ = spacing between cells, can not be handled (2 px)
+ * t = the text (expands)
+ * b = bookmark icon (16 px)
+ * + = padding (6 px) (BKMK_PADDING_RIGHT)
+ * _ = widget spacing, can not be handled (2 px)
+ *
+ * Each character is a pixel.
+ *
+ * The text cell and the bookmark icon cell are much more
+ * flexible in its aligment, because they do not have to align
+ * with anything in the entry.
+ */
+
+#define ROW_PADDING_VERT 4
+
+#define ICON_PADDING_LEFT 5
+#define ICON_CONTENT_WIDTH 16
+#define ICON_PADDING_RIGHT 9
+
+#define ICON_CONTENT_HEIGHT 16
+
+#define TEXT_PADDING_LEFT 0
+
+#define BKMK_PADDING_RIGHT 6
+
+ gtk_cell_renderer_set_padding
+ (cell, ICON_PADDING_LEFT, ROW_PADDING_VERT);
+ gtk_cell_renderer_set_fixed_size
+ (cell,
+ (ICON_PADDING_LEFT + ICON_CONTENT_WIDTH + ICON_PADDING_RIGHT),
+ ICON_CONTENT_HEIGHT);
+ gtk_cell_renderer_set_alignment (cell, 0.0, 0.5);
+
+ cell = gd_two_lines_renderer_new ();
+ g_object_set (cell,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ "text-lines", 2,
+ NULL);
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion),
+ cell, TRUE);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (completion),
+ cell, "text", text_col);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (completion),
+ cell, "line-two", url_col);
+
+ /* Pixel-perfect aligment with the text in the location entry.
+ * See above.
+ */
+ gtk_cell_renderer_set_padding
+ (cell, TEXT_PADDING_LEFT, ROW_PADDING_VERT);
+ gtk_cell_renderer_set_alignment (cell, 0.0, 0.5);
+
+ /*
+ * As the width of the entry completion is known in advance
+ * (as big as the entry you are completing on), we can set
+ * any fixed width (the 1 is just this random number here)
+ * Since the height is known too, we avoid computing the actual
+ * sizes of the cells, which takes a lot of CPU time and does
+ * not get used anyway.
+ */
+ gtk_cell_renderer_set_fixed_size (cell, 1, -1);
+ gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (cell), 2);
+
+ cell = gtk_cell_renderer_pixbuf_new ();
+ g_object_set (cell, "follow-state", TRUE, NULL);
+ 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);
+
+ /* Pixel-perfect aligment. This just keeps the same margin from
+ * the border than the favicon on the other side. See above. */
+ gtk_cell_renderer_set_padding
+ (cell, BKMK_PADDING_RIGHT, ROW_PADDING_VERT);
+
+ g_object_set (completion, "inline-selection", TRUE, NULL);
+ g_signal_connect (completion, "cursor-on-match",
+ G_CALLBACK (cursor_on_match_cb), entry);
+
+ gtk_entry_set_completion (GTK_ENTRY (entry), completion);
+ g_object_unref (completion);
+}
+
/**
* ephy_location_entry_get_can_undo:
* @entry: an #EphyLocationEntry widget
@@ -822,8 +1125,9 @@ ephy_location_entry_reset_internal (EphyLocationEntry *entry,
ephy_title_widget_set_address (EPHY_TITLE_WIDGET (entry), text);
g_free (url);
- if (notify)
+ if (notify) {
g_signal_emit (entry, signals[USER_CHANGED], 0);
+ }
entry->user_changed = FALSE;
@@ -863,7 +1167,7 @@ ephy_location_entry_reset (EphyLocationEntry *entry)
}
/**
- * ephy_location_entry_focus:
+ * ephy_location_entry_activate:
* @entry: an #EphyLocationEntry widget
*
* Set focus on @entry and select the text whithin. This is called when the
@@ -871,7 +1175,7 @@ ephy_location_entry_reset (EphyLocationEntry *entry)
*
**/
void
-ephy_location_entry_focus (EphyLocationEntry *entry)
+ephy_location_entry_activate (EphyLocationEntry *entry)
{
GtkWidget *toplevel, *widget = GTK_WIDGET (entry);
diff --git a/lib/widgets/ephy-location-entry.h b/lib/widgets/ephy-location-entry.h
index 950d061..490524a 100644
--- a/lib/widgets/ephy-location-entry.h
+++ b/lib/widgets/ephy-location-entry.h
@@ -23,7 +23,6 @@
#pragma once
-#include <dazzle.h>
#include <gtk/gtk.h>
#include "ephy-security-levels.h"
@@ -32,7 +31,7 @@ G_BEGIN_DECLS
#define EPHY_TYPE_LOCATION_ENTRY (ephy_location_entry_get_type())
-G_DECLARE_FINAL_TYPE (EphyLocationEntry, ephy_location_entry, EPHY, LOCATION_ENTRY, DzlSuggestionEntry)
+G_DECLARE_FINAL_TYPE (EphyLocationEntry, ephy_location_entry, EPHY, LOCATION_ENTRY, GtkEntry)
typedef enum {
EPHY_LOCATION_ENTRY_BOOKMARK_ICON_HIDDEN,
@@ -42,6 +41,21 @@ typedef enum {
GtkWidget *ephy_location_entry_new (void);
+void ephy_location_entry_set_completion (EphyLocationEntry *entry,
+ GtkTreeModel *model,
+ guint text_col,
+ guint action_col,
+ guint keywords_col,
+ guint relevance_col,
+ guint url_col,
+ guint extra_col,
+ guint favicon_col);
+
+void ephy_location_entry_set_match_func (EphyLocationEntry *entry,
+ GtkEntryCompletionMatchFunc match_func,
+ gpointer user_data,
+ GDestroyNotify notify);
+
gboolean ephy_location_entry_get_can_undo (EphyLocationEntry *entry);
gboolean ephy_location_entry_get_can_redo (EphyLocationEntry *entry);
@@ -52,7 +66,7 @@ gboolean ephy_location_entry_reset (EphyLocationEntr
void ephy_location_entry_undo_reset (EphyLocationEntry *entry);
-void ephy_location_entry_focus (EphyLocationEntry *entry);
+void ephy_location_entry_activate (EphyLocationEntry *entry);
void ephy_location_entry_set_bookmark_icon_state (EphyLocationEntry *entry,
EphyLocationEntryBookmarkIconState state);
diff --git a/lib/widgets/meson.build b/lib/widgets/meson.build
index c927648..1c5c849 100644
--- a/lib/widgets/meson.build
+++ b/lib/widgets/meson.build
@@ -7,6 +7,7 @@ enums = gnome.mkenums_simple('ephy-widgets-type-builtins',
)
libephywidgets_sources = [
+ 'contrib/gd-two-lines-renderer.c',
'contrib/nautilus-floating-bar.c',
'ephy-certificate-dialog.c',
'ephy-downloads-popover.c',
@@ -29,7 +30,6 @@ libephywidgets_deps = [
gio_dep,
glib_dep,
gtk_dep,
- libdazzle_dep,
libsoup_dep,
webkit2gtk_dep
]
diff --git a/meson.build b/meson.build
index 9fa9df1..535d499 100644
--- a/meson.build
+++ b/meson.build
@@ -63,7 +63,6 @@ hogweed_dep = dependency('hogweed', version: nettle_requirement)
icu_uc_dep = dependency('icu-uc', version: '>= 4.6')
iso_codes_dep = dependency('iso-codes', version: '>= 0.35')
json_glib_dep = dependency('json-glib-1.0', version: '>= 1.2.4')
-libdazzle_dep = dependency('libdazzle-1.0', version: '>= 3.25.90')
libnotify_dep = dependency('libnotify', version: '>= 0.5.1')
libsecret_dep = dependency('libsecret-1', version: '>= 0.14')
libsoup_dep = dependency('libsoup-2.4', version: '>= 2.48.0')
diff --git a/org.gnome.Epiphany.json b/org.gnome.Epiphany.json
index 426b7b4..0cec50c 100644
--- a/org.gnome.Epiphany.json
+++ b/org.gnome.Epiphany.json
@@ -51,18 +51,6 @@
]
},
{
- "name": "libdazzle",
- "config-opts": [ "--libdir=/app/lib" ],
- "buildsystem": "meson",
- "builddir": true,
- "sources": [
- {
- "type": "git",
- "url": "https://git.gnome.org/browse/libdazzle"
- }
- ]
- },
- {
"name": "epiphany",
"config-opts": ["-Dtech_preview=true"],
"buildsystem": "meson",
diff --git a/src/ephy-completion-model.c b/src/ephy-completion-model.c
new file mode 100644
index 0000000..e1faae1
--- /dev/null
+++ b/src/ephy-completion-model.c
@@ -0,0 +1,625 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Copyright © 2012 Igalia S.L.
+ *
+ * This file is part of Epiphany.
+ *
+ * Epiphany 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Epiphany 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 Epiphany. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "ephy-completion-model.h"
+
+#include "ephy-embed-prefs.h"
+#include "ephy-embed-shell.h"
+#include "ephy-favicon-helpers.h"
+#include "ephy-history-service.h"
+#include "ephy-shell.h"
+#include "ephy-uri-helpers.h"
+
+#include <string.h>
+
+enum {
+ PROP_0,
+ PROP_HISTORY_SERVICE,
+ PROP_BOOKMARKS_MANAGER,
+ LAST_PROP
+};
+
+struct _EphyCompletionModel {
+ GtkListStore parent_instance;
+
+ EphyHistoryService *history_service;
+ GCancellable *cancellable;
+
+ EphyBookmarksManager *bookmarks_manager;
+ GSList *search_terms;
+};
+
+static GParamSpec *obj_properties[LAST_PROP];
+
+G_DEFINE_TYPE (EphyCompletionModel, ephy_completion_model, GTK_TYPE_LIST_STORE)
+
+static void
+ephy_completion_model_constructed (GObject *object)
+{
+ GType types[N_COL] = { G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
+ G_TYPE_INT, G_TYPE_STRING, G_TYPE_BOOLEAN,
+ GDK_TYPE_PIXBUF };
+
+ G_OBJECT_CLASS (ephy_completion_model_parent_class)->constructed (object);
+
+ gtk_list_store_set_column_types (GTK_LIST_STORE (object),
+ N_COL,
+ types);
+}
+
+static void
+free_search_terms (GSList *search_terms)
+{
+ GSList *iter;
+
+ for (iter = search_terms; iter != NULL; iter = iter->next)
+ g_regex_unref ((GRegex *)iter->data);
+
+ g_slist_free (search_terms);
+}
+
+static void
+ephy_completion_model_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec
*pspec)
+{
+ EphyCompletionModel *self = EPHY_COMPLETION_MODEL (object);
+
+ switch (property_id) {
+ case PROP_HISTORY_SERVICE:
+ self->history_service = EPHY_HISTORY_SERVICE (g_value_get_pointer (value));
+ break;
+ case PROP_BOOKMARKS_MANAGER:
+ self->bookmarks_manager = EPHY_BOOKMARKS_MANAGER (g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
+ break;
+ }
+}
+
+static void
+ephy_completion_model_finalize (GObject *object)
+{
+ EphyCompletionModel *model = EPHY_COMPLETION_MODEL (object);
+
+ if (model->search_terms) {
+ free_search_terms (model->search_terms);
+ model->search_terms = NULL;
+ }
+
+ if (model->cancellable) {
+ g_cancellable_cancel (model->cancellable);
+ g_clear_object (&model->cancellable);
+ }
+
+ G_OBJECT_CLASS (ephy_completion_model_parent_class)->finalize (object);
+}
+
+static void
+ephy_completion_model_class_init (EphyCompletionModelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = ephy_completion_model_set_property;
+ object_class->constructed = ephy_completion_model_constructed;
+ object_class->finalize = ephy_completion_model_finalize;
+
+ obj_properties[PROP_HISTORY_SERVICE] =
+ g_param_spec_pointer ("history-service",
+ "History Service",
+ "The history service",
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
+
+ obj_properties[PROP_BOOKMARKS_MANAGER] =
+ g_param_spec_object ("bookmarks-manager",
+ "Bookmarks manager",
+ "The bookmarks manager",
+ EPHY_TYPE_BOOKMARKS_MANAGER,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, LAST_PROP, obj_properties);
+}
+
+static void
+ephy_completion_model_init (EphyCompletionModel *model)
+{
+}
+
+static gboolean
+is_base_address (const char *address)
+{
+ if (address == NULL)
+ return FALSE;
+
+ /* A base address is <scheme>://<host>/
+ * Neither scheme nor host contain a slash, so we can use slashes
+ * figure out if it's a base address.
+ *
+ * Note: previous code was using a GRegExp to do the same thing.
+ * While regexps are much nicer to read, they're also a lot
+ * slower.
+ */
+ address = strchr (address, '/');
+ if (address == NULL ||
+ address[1] != '/')
+ return FALSE;
+
+ address += 2;
+ address = strchr (address, '/');
+ if (address == NULL ||
+ address[1] != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static int
+get_relevance (const char *location,
+ int visit_count,
+ gboolean is_bookmark)
+{
+ /* FIXME: use frecency. */
+ int relevance = 0;
+
+ /* We have three ordered groups: history's base addresses,
+ bookmarks, deep history addresses. */
+ if (is_bookmark)
+ relevance = 1 << 5;
+ else {
+ visit_count = MIN (visit_count, (1 << 5) - 1);
+
+ if (is_base_address (location))
+ relevance = visit_count << 10;
+ else
+ relevance = visit_count;
+ }
+
+ return relevance;
+}
+
+typedef struct {
+ char *title;
+ char *location;
+ char *keywords;
+ int relevance;
+ gboolean is_bookmark;
+} PotentialRow;
+
+typedef struct {
+ GtkListStore *model;
+ GtkTreeRowReference *row_reference;
+} IconLoadData;
+
+static void
+icon_loaded_cb (GObject *source, GAsyncResult *result, gpointer user_data)
+{
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ IconLoadData *data = (IconLoadData *)user_data;
+ WebKitFaviconDatabase *database = WEBKIT_FAVICON_DATABASE (source);
+ GdkPixbuf *favicon = NULL;
+ cairo_surface_t *icon_surface = webkit_favicon_database_get_favicon_finish (database, result, NULL);
+
+ if (icon_surface) {
+ favicon = ephy_pixbuf_get_from_surface_scaled (icon_surface, FAVICON_SIZE, FAVICON_SIZE);
+ cairo_surface_destroy (icon_surface);
+ }
+
+ if (favicon) {
+ /* The completion model might have changed its contents */
+ if (gtk_tree_row_reference_valid (data->row_reference)) {
+ path = gtk_tree_row_reference_get_path (data->row_reference);
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (data->model), &iter, path);
+ gtk_list_store_set (data->model, &iter, EPHY_COMPLETION_FAVICON_COL, favicon, -1);
+ g_object_unref (favicon);
+ gtk_tree_path_free (path);
+ }
+ }
+
+ g_object_unref (data->model);
+ gtk_tree_row_reference_free (data->row_reference);
+ g_slice_free (IconLoadData, data);
+}
+
+static void
+set_row_in_model (EphyCompletionModel *model, int position, PotentialRow *row)
+{
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ IconLoadData *data;
+ WebKitFaviconDatabase *database;
+ EphyEmbedShell *shell = ephy_embed_shell_get_default ();
+
+ database = webkit_web_context_get_favicon_database (ephy_embed_shell_get_web_context (shell));
+
+ gtk_list_store_insert_with_values (GTK_LIST_STORE (model), &iter, position,
+ EPHY_COMPLETION_TEXT_COL, row->title ? row->title : "",
+ EPHY_COMPLETION_URL_COL, row->location,
+ EPHY_COMPLETION_ACTION_COL, row->location,
+ EPHY_COMPLETION_KEYWORDS_COL, row->keywords ? row->keywords : "",
+ EPHY_COMPLETION_EXTRA_COL, row->is_bookmark,
+ EPHY_COMPLETION_RELEVANCE_COL, row->relevance,
+ -1);
+
+ data = g_slice_new (IconLoadData);
+ data->model = GTK_LIST_STORE (g_object_ref (model));
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+ data->row_reference = gtk_tree_row_reference_new (GTK_TREE_MODEL (model), path);
+ gtk_tree_path_free (path);
+
+ webkit_favicon_database_get_favicon (database, row->location,
+ NULL, icon_loaded_cb, data);
+}
+
+static void
+replace_rows_in_model (EphyCompletionModel *model, GSList *new_rows)
+{
+ /* This is by far the simplest way of doing, and yet it gives
+ * basically the same result than the other methods... */
+ int i;
+
+ gtk_list_store_clear (GTK_LIST_STORE (model));
+
+ if (!new_rows)
+ return;
+
+ for (i = 0; new_rows != NULL; i++) {
+ PotentialRow *row = (PotentialRow *)new_rows->data;
+
+ set_row_in_model (model, i, row);
+ new_rows = new_rows->next;
+ }
+}
+
+static gboolean
+should_add_bookmark_to_model (EphyCompletionModel *model,
+ const char *search_string,
+ EphyBookmark *bookmark)
+{
+ gboolean ret = TRUE;
+ GSequence *tags;
+ GSequenceIter *tag_iter;
+ const char *url;
+ const char *title;
+ char *tag_string = NULL;
+ char **tag_array;
+ int i;
+
+ title = ephy_bookmark_get_title (bookmark);
+ url = ephy_bookmark_get_url (bookmark);
+ tags = ephy_bookmark_get_tags (bookmark);
+
+ tag_array = g_malloc0 ((g_sequence_get_length (tags) + 1) * sizeof (char *));
+
+ for (i = 0, tag_iter = g_sequence_get_begin_iter (tags);
+ !g_sequence_iter_is_end (tag_iter);
+ i++, tag_iter = g_sequence_iter_next (tag_iter)) {
+ tag_array[i] = g_sequence_get (tag_iter);
+ }
+
+ tag_string = g_strjoinv (" ", tag_array);
+
+ if (model->search_terms) {
+ GSList *iter;
+ GRegex *current = NULL;
+
+ for (iter = model->search_terms; iter != NULL; iter = iter->next) {
+ current = (GRegex *)iter->data;
+
+ if ((!g_regex_match (current, title ? title : "", G_REGEX_MATCH_NOTEMPTY, NULL)) &&
+ (!g_regex_match (current, url ? url : "", G_REGEX_MATCH_NOTEMPTY, NULL)) &&
+ (!g_regex_match (current, tag_string ? tag_string : "", G_REGEX_MATCH_NOTEMPTY, NULL))) {
+ ret = FALSE;
+ break;
+ }
+ }
+ }
+
+ g_free (tag_array);
+ g_free (tag_string);
+
+ return ret;
+}
+
+typedef struct {
+ EphyCompletionModel *model;
+ char *search_string;
+ EphyHistoryJobCallback callback;
+ gpointer user_data;
+} FindURLsData;
+
+static int
+find_url (gconstpointer a,
+ gconstpointer b)
+{
+ return g_strcmp0 (((PotentialRow *)a)->location,
+ ((char *)b));
+}
+
+static PotentialRow *
+potential_row_new (const char *title, const char *location,
+ const char *keywords, int visit_count,
+ gboolean is_bookmark)
+{
+ PotentialRow *row = g_slice_new0 (PotentialRow);
+
+ row->title = g_strdup (title);
+ row->location = g_strdup (location);
+ row->keywords = g_strdup (keywords);
+ row->relevance = get_relevance (location, visit_count, is_bookmark);
+ row->is_bookmark = is_bookmark;
+
+ return row;
+}
+
+static void
+free_potential_row (PotentialRow *row)
+{
+ g_free (row->title);
+ g_free (row->location);
+ g_free (row->keywords);
+
+ g_slice_free (PotentialRow, row);
+}
+
+static GSList *
+add_to_potential_rows (GSList *rows,
+ const char *title,
+ const char *location,
+ const char *keywords,
+ int visit_count,
+ gboolean is_bookmark,
+ gboolean search_for_duplicates)
+{
+ gboolean found = FALSE;
+ PotentialRow *row = potential_row_new (title, location, keywords, visit_count, is_bookmark);
+
+ if (search_for_duplicates) {
+ GSList *p;
+
+ p = g_slist_find_custom (rows, location, find_url);
+ if (p) {
+ PotentialRow *match = (PotentialRow *)p->data;
+ if (row->relevance > match->relevance)
+ match->relevance = row->relevance;
+
+ found = TRUE;
+ free_potential_row (row);
+ }
+ }
+
+ if (!found)
+ rows = g_slist_prepend (rows, row);
+
+ return rows;
+}
+
+static int
+sort_by_relevance (gconstpointer a, gconstpointer b)
+{
+ PotentialRow *r1 = (PotentialRow *)a;
+ PotentialRow *r2 = (PotentialRow *)b;
+
+ if (r1->relevance < r2->relevance)
+ return 1;
+ else if (r1->relevance > r2->relevance)
+ return -1;
+ else
+ return 0;
+}
+
+static void
+query_completed_cb (EphyHistoryService *service,
+ gboolean success,
+ gpointer result_data,
+ FindURLsData *user_data)
+{
+ EphyCompletionModel *model = user_data->model;
+ GList *p, *urls;
+ GSequence *bookmarks;
+ GSequenceIter *iter;
+ GSList *list = NULL;
+
+ /* Bookmarks */
+ bookmarks = ephy_bookmarks_manager_get_bookmarks (model->bookmarks_manager);
+
+ /* FIXME: perhaps this could be done in a service thread? There
+ * should never be a ton of bookmarks, but seems a bit cleaner and
+ * consistent with what we do for the history. */
+ for (iter = g_sequence_get_begin_iter (bookmarks);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter)) {
+ EphyBookmark *bookmark;
+ const char *url, *title;
+
+ bookmark = g_sequence_get (iter);
+
+ if (should_add_bookmark_to_model (model, user_data->search_string, bookmark)) {
+ url = ephy_bookmark_get_url (bookmark);
+ title = ephy_bookmark_get_title (bookmark);
+ list = add_to_potential_rows (list, title, url, NULL, 0, TRUE, FALSE);
+ }
+ }
+
+ /* History */
+ urls = (GList *)result_data;
+
+ for (p = urls; p != NULL; p = p->next) {
+ EphyHistoryURL *url = (EphyHistoryURL *)p->data;
+
+ list = add_to_potential_rows (list, url->title, url->url, NULL, url->visit_count, FALSE, TRUE);
+ }
+
+ /* Sort the rows by relevance. */
+ list = g_slist_sort (list, sort_by_relevance);
+
+ /* Now that we have all the rows we want to insert, replace the rows
+ * in the current model one by one, sorted by relevance. */
+ replace_rows_in_model (model, list);
+
+ /* Notify */
+ if (user_data->callback)
+ user_data->callback (service, success, result_data, user_data->user_data);
+
+ g_free (user_data->search_string);
+ g_slice_free (FindURLsData, user_data);
+ g_list_free_full (urls, (GDestroyNotify)ephy_history_url_free);
+ g_slist_free_full (list, (GDestroyNotify)free_potential_row);
+ g_clear_object (&model->cancellable);
+}
+
+static void
+update_search_terms (EphyCompletionModel *model,
+ const char *text)
+{
+ const char *current;
+ const char *ptr;
+ char *tmp;
+ char *term;
+ GRegex *term_regex;
+ GRegex *quote_regex;
+ gint count;
+ gboolean inside_quotes = FALSE;
+
+ if (model->search_terms) {
+ free_search_terms (model->search_terms);
+ model->search_terms = NULL;
+ }
+
+ quote_regex = g_regex_new ("\"", G_REGEX_OPTIMIZE,
+ G_REGEX_MATCH_NOTEMPTY, NULL);
+
+ /*
+ * This code loops through the string using pointer arythmetics.
+ * Although the string we are handling may contain UTF-8 chars
+ * this works because only ASCII chars affect what is actually
+ * copied from the string as a search term.
+ */
+ for (count = 0, current = ptr = text; ptr[0] != '\0'; ptr++, count++) {
+ /*
+ * If we found a double quote character; we will
+ * consume bytes up until the next quote, or
+ * end of line;
+ */
+ if (ptr[0] == '"')
+ inside_quotes = !inside_quotes;
+
+ /*
+ * If we found a space, and we are not looking for a
+ * closing double quote, or if the next char is the
+ * end of the string, append what we have already as
+ * a search term.
+ */
+ if (((ptr[0] == ' ') && (!inside_quotes)) || ptr[1] == '\0') {
+ /*
+ * We special-case the end of the line because
+ * we would otherwise not copy the last character
+ * of the search string, since the for loop will
+ * stop before that.
+ */
+ if (ptr[1] == '\0')
+ count++;
+
+ /*
+ * remove quotes, and quote any regex-sensitive
+ * characters
+ */
+ tmp = g_regex_escape_string (current, count);
+ term = g_regex_replace (quote_regex, tmp, -1, 0,
+ "", G_REGEX_MATCH_NOTEMPTY, NULL);
+ g_strstrip (term);
+ g_free (tmp);
+
+ /* we don't want empty search terms */
+ if (term[0] != '\0') {
+ term_regex = g_regex_new (term,
+ G_REGEX_CASELESS | G_REGEX_OPTIMIZE,
+ G_REGEX_MATCH_NOTEMPTY, NULL);
+ model->search_terms = g_slist_append (model->search_terms, term_regex);
+ }
+ g_free (term);
+
+ /* count will be incremented by the for loop */
+ count = -1;
+ current = ptr + 1;
+ }
+ }
+
+ g_regex_unref (quote_regex);
+}
+
+#define MAX_COMPLETION_HISTORY_URLS 8
+
+void
+ephy_completion_model_update_for_string (EphyCompletionModel *model,
+ const char *search_string,
+ EphyHistoryJobCallback callback,
+ gpointer data)
+{
+ char **strings;
+ int i;
+ GList *query = NULL;
+ FindURLsData *user_data;
+
+ g_return_if_fail (EPHY_IS_COMPLETION_MODEL (model));
+ g_return_if_fail (search_string != NULL);
+
+ /* Split the search string. */
+ strings = g_strsplit (search_string, " ", -1);
+ for (i = 0; strings[i]; i++)
+ query = g_list_append (query, g_strdup (strings[i]));
+ g_strfreev (strings);
+
+ update_search_terms (model, search_string);
+
+ user_data = g_slice_new (FindURLsData);
+ user_data->model = model;
+ user_data->search_string = g_strdup (search_string);
+ user_data->callback = callback;
+ user_data->user_data = data;
+
+ if (model->cancellable) {
+ g_cancellable_cancel (model->cancellable);
+ g_object_unref (model->cancellable);
+ }
+ model->cancellable = g_cancellable_new ();
+
+ ephy_history_service_find_urls (model->history_service,
+ 0, 0,
+ MAX_COMPLETION_HISTORY_URLS, 0,
+ query,
+ EPHY_HISTORY_SORT_MOST_VISITED,
+ model->cancellable,
+ (EphyHistoryJobCallback)query_completed_cb,
+ user_data);
+}
+
+EphyCompletionModel *
+ephy_completion_model_new (EphyHistoryService *history_service,
+ EphyBookmarksManager *bookmarks_manager)
+{
+ g_return_val_if_fail (EPHY_IS_HISTORY_SERVICE (history_service), NULL);
+ g_return_val_if_fail (EPHY_IS_BOOKMARKS_MANAGER (bookmarks_manager), NULL);
+
+ return g_object_new (EPHY_TYPE_COMPLETION_MODEL,
+ "history-service", history_service,
+ "bookmarks-manager", bookmarks_manager,
+ NULL);
+}
diff --git a/src/ephy-completion-model.h b/src/ephy-completion-model.h
new file mode 100644
index 0000000..912c366
--- /dev/null
+++ b/src/ephy-completion-model.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Copyright © 2012 Igalia S.L.
+ *
+ * This file is part of Epiphany.
+ *
+ * Epiphany 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Epiphany 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 Epiphany. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "ephy-bookmarks-manager.h"
+#include "ephy-history-service.h"
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_COMPLETION_MODEL (ephy_completion_model_get_type ())
+
+G_DECLARE_FINAL_TYPE (EphyCompletionModel, ephy_completion_model, EPHY, COMPLETION_MODEL, GtkListStore)
+
+typedef enum
+{
+ EPHY_COMPLETION_TEXT_COL,
+ EPHY_COMPLETION_ACTION_COL,
+ EPHY_COMPLETION_KEYWORDS_COL,
+ EPHY_COMPLETION_RELEVANCE_COL,
+ EPHY_COMPLETION_URL_COL,
+ EPHY_COMPLETION_EXTRA_COL,
+ EPHY_COMPLETION_FAVICON_COL,
+ N_COL
+} EphyCompletionColumn;
+
+EphyCompletionModel *ephy_completion_model_new (EphyHistoryService *history_service,
+ EphyBookmarksManager *bookmarks_manager);
+
+void ephy_completion_model_update_for_string (EphyCompletionModel *model,
+ const char *string,
+ EphyHistoryJobCallback callback,
+ gpointer data);
+G_END_DECLS
diff --git a/src/ephy-location-controller.c b/src/ephy-location-controller.c
index f46e232..c42e31f 100644
--- a/src/ephy-location-controller.c
+++ b/src/ephy-location-controller.c
@@ -22,6 +22,8 @@
#include "config.h"
#include "ephy-location-controller.h"
+
+#include "ephy-completion-model.h"
#include "ephy-debug.h"
#include "ephy-dnd.h"
#include "ephy-embed-container.h"
@@ -29,12 +31,10 @@
#include "ephy-link.h"
#include "ephy-location-entry.h"
#include "ephy-shell.h"
-#include "ephy-suggestion-model.h"
#include "ephy-title-widget.h"
#include "ephy-uri-helpers.h"
#include "ephy-widgets-type-builtins.h"
-#include <dazzle.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include <string.h>
@@ -79,6 +79,16 @@ G_DEFINE_TYPE_WITH_CODE (EphyLocationController, ephy_location_controller, G_TYP
G_IMPLEMENT_INTERFACE (EPHY_TYPE_LINK,
NULL))
+static gboolean
+match_func (GtkEntryCompletion *completion,
+ const char *key,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ /* We want every row in the model to show up. */
+ return TRUE;
+}
+
static void
entry_drag_data_received_cb (GtkWidget *widget,
GdkDragContext *context,
@@ -172,18 +182,33 @@ entry_activate_cb (GtkEntry *entry,
}
static void
+update_done_cb (EphyHistoryService *service,
+ gboolean success,
+ gpointer result_data,
+ gpointer user_data)
+{
+ /* FIXME: this hack is needed for the completion entry popup
+ * to resize smoothly. See:
+ * https://bugzilla.gnome.org/show_bug.cgi?id=671074 */
+ gtk_entry_completion_complete (GTK_ENTRY_COMPLETION (user_data));
+}
+
+static void
user_changed_cb (GtkWidget *widget, EphyLocationController *controller)
{
const char *address;
- GListModel *model;
+ GtkTreeModel *model;
+ GtkEntryCompletion *completion;
address = ephy_title_widget_get_address (EPHY_TITLE_WIDGET (widget));
LOG ("user_changed_cb, address %s", address);
- model = dzl_suggestion_entry_get_model (DZL_SUGGESTION_ENTRY (widget));
+ completion = gtk_entry_get_completion (GTK_ENTRY (widget));
+ model = gtk_entry_completion_get_model (completion);
- ephy_suggestion_model_query_async (EPHY_SUGGESTION_MODEL (model), address, NULL, NULL, NULL);
+ ephy_completion_model_update_for_string (EPHY_COMPLETION_MODEL (model), address,
+ update_done_cb, completion);
}
static void
@@ -267,12 +292,84 @@ switch_page_cb (GtkNotebook *notebook,
}
static void
+action_activated_cb (GtkEntryCompletion *completion,
+ int index,
+ EphyLocationController *controller)
+{
+ GtkWidget *entry;
+ char *content;
+ char *url;
+ char **engine_names;
+
+ entry = gtk_entry_completion_get_entry (completion);
+ content = gtk_editable_get_chars (GTK_EDITABLE (entry), 0, -1);
+ if (content == NULL)
+ return;
+
+ engine_names = ephy_search_engine_manager_get_names (controller->search_engine_manager);
+ url = ephy_search_engine_manager_build_search_address (controller->search_engine_manager,
+ engine_names[index],
+ content);
+ g_strfreev (engine_names);
+
+ ephy_link_open (EPHY_LINK (controller), url, NULL,
+ ephy_link_flags_from_current_event ());
+ g_free (content);
+ g_free (url);
+}
+
+static void
+fill_entry_completion_with_actions (GtkEntryCompletion *completion,
+ EphyLocationController *controller)
+{
+ char **engine_names;
+
+ engine_names = ephy_search_engine_manager_get_names (controller->search_engine_manager);
+
+ controller->num_search_engines_actions = 0;
+
+ for (guint i = 0; engine_names[i] != NULL; i++) {
+ gtk_entry_completion_insert_action_text (completion, i, engine_names[i]);
+ controller->num_search_engines_actions++;
+ }
+
+ g_strfreev (engine_names);
+}
+
+static void
+add_completion_actions (EphyLocationController *controller,
+ EphyLocationEntry *lentry)
+{
+ GtkEntryCompletion *completion = gtk_entry_get_completion (GTK_ENTRY (lentry));
+
+ fill_entry_completion_with_actions (completion, controller);
+ g_signal_connect (completion, "action_activated",
+ G_CALLBACK (action_activated_cb), controller);
+}
+
+static void
+search_engines_changed_cb (EphySearchEngineManager *manager,
+ gpointer data)
+{
+ EphyLocationController *controller;
+ GtkEntryCompletion *completion;
+
+ controller = EPHY_LOCATION_CONTROLLER (data);
+ completion = gtk_entry_get_completion (GTK_ENTRY (controller->title_widget));
+
+ for (guint i = 0; i < controller->num_search_engines_actions; i++)
+ gtk_entry_completion_delete_action (completion, 0);
+
+ fill_entry_completion_with_actions (completion, controller);
+}
+
+static void
ephy_location_controller_constructed (GObject *object)
{
EphyLocationController *controller = EPHY_LOCATION_CONTROLLER (object);
EphyHistoryService *history_service;
EphyBookmarksManager *bookmarks_manager;
- EphySuggestionModel *model;
+ EphyCompletionModel *model;
GtkWidget *notebook, *widget;
G_OBJECT_CLASS (ephy_location_controller_parent_class)->constructed (object);
@@ -292,13 +389,27 @@ ephy_location_controller_constructed (GObject *object)
history_service = ephy_embed_shell_get_global_history_service (ephy_embed_shell_get_default ());
bookmarks_manager = ephy_shell_get_bookmarks_manager (ephy_shell_get_default ());
- model = ephy_suggestion_model_new (history_service, bookmarks_manager);
- dzl_suggestion_entry_set_model (DZL_SUGGESTION_ENTRY (controller->title_widget), G_LIST_MODEL (model));
+ model = ephy_completion_model_new (history_service, bookmarks_manager);
+ ephy_location_entry_set_completion (EPHY_LOCATION_ENTRY (controller->title_widget),
+ GTK_TREE_MODEL (model),
+ EPHY_COMPLETION_TEXT_COL,
+ EPHY_COMPLETION_ACTION_COL,
+ EPHY_COMPLETION_KEYWORDS_COL,
+ EPHY_COMPLETION_RELEVANCE_COL,
+ EPHY_COMPLETION_URL_COL,
+ EPHY_COMPLETION_EXTRA_COL,
+ EPHY_COMPLETION_FAVICON_COL);
g_object_unref (model);
-/* FIXME: What to do with search engines? Add them to the model? Probably not?
- * Make the bangs mandatory?
- */
+ ephy_location_entry_set_match_func (EPHY_LOCATION_ENTRY (controller->title_widget),
+ match_func,
+ controller->title_widget,
+ NULL);
+
+ add_completion_actions (controller, EPHY_LOCATION_ENTRY (controller->title_widget));
+
+ g_signal_connect_object (controller->search_engine_manager, "changed",
+ G_CALLBACK (search_engines_changed_cb), controller, 0);
g_object_bind_property (controller, "editable",
controller->title_widget, "editable",
diff --git a/src/ephy-window.c b/src/ephy-window.c
index 2883c0d..96af170 100644
--- a/src/ephy-window.c
+++ b/src/ephy-window.c
@@ -53,7 +53,6 @@
#include "popup-commands.h"
#include "window-commands.h"
-#include <dazzle.h>
#include <gdk/gdkkeysyms.h>
#include <gdk/gdkx.h>
#include <gio/gio.h>
@@ -75,10 +74,6 @@ static void ephy_window_change_allow_popup_windows_state (GSimpleAction *action,
GVariant *state,
gpointer user_data);
-static void ephy_window_embed_container_iface_init (EphyEmbedContainerInterface *iface);
-
-static void ephy_window_link_iface_init (EphyLinkInterface *iface);
-
const struct {
const char *action_and_target;
const char *accelerators[9];
@@ -143,7 +138,7 @@ const struct {
#define SETTINGS_CONNECTION_DATA_KEY "EphyWindowSettings"
struct _EphyWindow {
- DzlApplicationWindow parent_instance;
+ GtkApplicationWindow parent_instance;
GtkWidget *header_bar;
EphyBookmarksManager *bookmarks_manager;
@@ -190,13 +185,6 @@ enum {
SENS_FLAG_IS_BLANK = 1 << 5
};
-G_DEFINE_TYPE_WITH_CODE (EphyWindow, ephy_window, DZL_TYPE_APPLICATION_WINDOW,
- G_IMPLEMENT_INTERFACE (EPHY_TYPE_LINK,
- ephy_window_link_iface_init)
- G_IMPLEMENT_INTERFACE (EPHY_TYPE_EMBED_CONTAINER,
- ephy_window_embed_container_iface_init))
-
-
static gint
impl_add_child (EphyEmbedContainer *container,
EphyEmbed *child,
@@ -405,6 +393,12 @@ ephy_window_link_iface_init (EphyLinkInterface *iface)
iface->open_link = ephy_window_open_link;
}
+G_DEFINE_TYPE_WITH_CODE (EphyWindow, ephy_window, GTK_TYPE_APPLICATION_WINDOW,
+ G_IMPLEMENT_INTERFACE (EPHY_TYPE_LINK,
+ ephy_window_link_iface_init)
+ G_IMPLEMENT_INTERFACE (EPHY_TYPE_EMBED_CONTAINER,
+ ephy_window_embed_container_iface_init))
+
static void
sync_chromes_visibility (EphyWindow *window)
{
@@ -3322,7 +3316,7 @@ ephy_window_activate_location (EphyWindow *window)
title_widget = ephy_header_bar_get_title_widget (EPHY_HEADER_BAR (window->header_bar));
if (EPHY_IS_LOCATION_ENTRY (title_widget))
- ephy_location_entry_focus (EPHY_LOCATION_ENTRY (title_widget));
+ ephy_location_entry_activate (EPHY_LOCATION_ENTRY (title_widget));
}
/**
diff --git a/src/ephy-window.h b/src/ephy-window.h
index edec1a9..ff01c3f 100644
--- a/src/ephy-window.h
+++ b/src/ephy-window.h
@@ -25,14 +25,13 @@
#include "ephy-location-controller.h"
#include "ephy-web-view.h"
-#include <dazzle.h>
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define EPHY_TYPE_WINDOW (ephy_window_get_type ())
-G_DECLARE_FINAL_TYPE (EphyWindow, ephy_window, EPHY, WINDOW, DzlApplicationWindow)
+G_DECLARE_FINAL_TYPE (EphyWindow, ephy_window, EPHY, WINDOW, GtkApplicationWindow)
typedef enum
{
diff --git a/src/meson.build b/src/meson.build
index 1373b58..2d810a6 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -22,6 +22,7 @@ libephymain_sources = [
'clear-data-dialog.c',
'cookies-dialog.c',
'ephy-action-helper.c',
+ 'ephy-completion-model.c',
'ephy-encoding-dialog.c',
'ephy-encoding-row.c',
'ephy-header-bar.c',
@@ -33,7 +34,6 @@ libephymain_sources = [
'ephy-search-engine-dialog.c',
'ephy-session.c',
'ephy-shell.c',
- 'ephy-suggestion-model.c',
'ephy-window.c',
'passwords-dialog.c',
'popup-commands.c',
@@ -102,6 +102,7 @@ executable('epiphany-search-provider',
install_rpath: pkglibdir
)
+
resource_files = files('resources/epiphany.gresource.xml')
resources = gnome.compile_resources('epiphany-resources',
resource_files,
diff --git a/src/search-provider/ephy-search-provider.c b/src/search-provider/ephy-search-provider.c
index 076c86b..daa04f3 100644
--- a/src/search-provider/ephy-search-provider.c
+++ b/src/search-provider/ephy-search-provider.c
@@ -23,12 +23,11 @@
#include "ephy-search-provider.h"
#include "ephy-bookmarks-manager.h"
+#include "ephy-completion-model.h"
#include "ephy-file-helpers.h"
#include "ephy-prefs.h"
#include "ephy-profile-utils.h"
#include "ephy-shell.h"
-#include "ephy-suggestion-model.h"
-#include "ephy-uri-helpers.h"
#include <gio/gio.h>
#include <gio/gdesktopappinfo.h>
@@ -44,7 +43,7 @@ struct _EphySearchProvider {
GSettings *settings;
EphyBookmarksManager *bookmarks_manager;
- EphySuggestionModel *model;
+ EphyCompletionModel *model;
};
struct _EphySearchProviderClass {
@@ -56,29 +55,29 @@ G_DEFINE_TYPE (EphySearchProvider, ephy_search_provider, G_TYPE_APPLICATION)
#define INACTIVITY_TIMEOUT 60 * 1000 /* One minute, in milliseconds */
static void
-on_model_updated (GObject *source_object,
- GAsyncResult *result,
- GTask *task)
+on_model_updated (EphyHistoryService *service,
+ gboolean success,
+ gpointer result_data,
+ gpointer user_data)
{
+ GTask *task = user_data;
EphySearchProvider *self = g_task_get_source_object (task);
- EphySuggestion *suggestion;
+ GtkTreeModel *model = GTK_TREE_MODEL (self->model);
+ GtkTreeIter iter;
GPtrArray *results;
const char *search_string;
- guint n_items;
- GError *error = NULL;
+ gboolean ok;
+
results = g_ptr_array_new ();
- if (ephy_suggestion_model_query_finish (self->model,
- result,
- &error)) {
- n_items = g_list_model_get_n_items (G_LIST_MODEL (self->model));
- for (guint i = 0; i < n_items; i++) {
- suggestion = g_list_model_get_item (G_LIST_MODEL (self->model), i);
- g_ptr_array_add (results, g_strdup (ephy_suggestion_get_uri (suggestion)));
- }
- } else {
- g_warning ("Failed to query suggestion model: %s", error->message);
- g_error_free (error);
+ ok = gtk_tree_model_get_iter_first (model, &iter);
+ while (ok) {
+ char *result;
+
+ result = gtk_tree_model_get_string_from_iter (model, &iter);
+ g_ptr_array_add (results, result);
+
+ ok = gtk_tree_model_iter_next (model, &iter);
}
search_string = g_task_get_task_data (task);
@@ -107,16 +106,16 @@ gather_results_async (EphySearchProvider *self,
{
GTask *task;
char *search_string;
+
task = g_task_new (self, cancellable, callback, user_data);
search_string = g_strjoinv (" ", terms);
g_task_set_task_data (task, search_string, g_free);
- ephy_suggestion_model_query_async (self->model,
- search_string,
- cancellable,
- (GAsyncReadyCallback)on_model_updated,
- task);
+ ephy_completion_model_update_for_string (self->model,
+ search_string,
+ on_model_updated,
+ task);
}
static void
@@ -127,6 +126,7 @@ complete_request (GObject *object,
EphySearchProvider *self = EPHY_SEARCH_PROVIDER (object);
char **results;
GError *error;
+
error = NULL;
results = gather_results_finish (self, result, &error);
@@ -177,8 +177,14 @@ handle_get_result_metas (EphyShellSearchProvider2 *skeleton,
char **results,
EphySearchProvider *self)
{
+ GtkTreeModel *model = GTK_TREE_MODEL (self->model);
+ GtkTreeIter iter;
int i;
GVariantBuilder builder;
+ GIcon *favicon;
+ char *name, *url;
+ gboolean is_bookmark;
+
g_application_hold (G_APPLICATION (self));
g_cancellable_cancel (self->cancellable);
@@ -195,28 +201,54 @@ handle_get_result_metas (EphyShellSearchProvider2 *skeleton,
g_variant_builder_add (&builder, "{sv}",
"gicon", g_variant_new_string ("org.gnome.Epiphany"));
g_variant_builder_close (&builder);
- } else {
- EphySuggestion *suggestion;
- const char *title;
- const char *uri;
- char *decoded_uri;
+ continue;
+ }
- suggestion = ephy_suggestion_model_get_suggestion_with_uri (self->model, results[i]);
- title = ephy_suggestion_get_unescaped_title (suggestion);
- uri = ephy_suggestion_get_uri (suggestion);
- decoded_uri = ephy_uri_decode (uri);
+ if (!gtk_tree_model_get_iter_from_string (model, &iter, results[i]))
+ continue;
- g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sv}"));
- g_variant_builder_add (&builder, "{sv}",
- "id", g_variant_new_string (decoded_uri));
- g_variant_builder_add (&builder, "{sv}",
- "name", g_variant_new_string (title));
- g_variant_builder_add (&builder, "{sv}",
- "gicon", g_variant_new_string ("text-html"));
- g_variant_builder_close (&builder);
+ gtk_tree_model_get (model, &iter,
+ EPHY_COMPLETION_TEXT_COL, &name,
+ EPHY_COMPLETION_URL_COL, &url,
+ EPHY_COMPLETION_FAVICON_COL, &favicon,
+ EPHY_COMPLETION_EXTRA_COL, &is_bookmark,
+ -1);
+
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sv}"));
+ g_variant_builder_add (&builder, "{sv}",
+ "id", g_variant_new_string (url));
+ g_variant_builder_add (&builder, "{sv}",
+ "name", g_variant_new_string (name));
+
+ if (favicon == NULL) {
+ char *type;
+
+ type = g_content_type_from_mime_type ("text/html");
+ favicon = g_content_type_get_icon (type);
- g_free (decoded_uri);
+ if (is_bookmark) {
+ GEmblem *emblem;
+ GIcon *emblem_icon, *emblemed;
+
+ emblem_icon = g_themed_icon_new ("emblem-favorite");
+ emblem = g_emblem_new (emblem_icon);
+
+ emblemed = g_emblemed_icon_new (favicon, emblem);
+
+ g_object_unref (emblem);
+ g_object_unref (emblem_icon);
+ g_object_unref (favicon);
+ favicon = emblemed;
+ }
}
+
+ g_variant_builder_add (&builder, "{sv}",
+ "icon", g_icon_serialize (favicon));
+ g_variant_builder_close (&builder);
+
+ g_object_unref (favicon);
+ g_free (name);
+ g_free (url);
}
ephy_shell_search_provider2_complete_get_result_metas (skeleton,
@@ -233,6 +265,7 @@ launch_uri (const char *uri,
guint timestamp)
{
char *str;
+
/* TODO: Handle the timestamp */
str = g_strdup_printf ("epiphany %s", uri);
g_spawn_command_line_async (str, NULL);
@@ -337,7 +370,7 @@ ephy_search_provider_init (EphySearchProvider *self)
filename = g_build_filename (ephy_dot_dir (), EPHY_HISTORY_FILE, NULL);
self->bookmarks_manager = ephy_bookmarks_manager_new ();
- self->model = ephy_suggestion_model_new (ephy_embed_shell_get_global_history_service (shell),
+ self->model = ephy_completion_model_new (ephy_embed_shell_get_global_history_service (shell),
self->bookmarks_manager);
g_free (filename);
diff --git a/tests/ephy-completion-model-test.c b/tests/ephy-completion-model-test.c
new file mode 100644
index 0000000..869d16b
--- /dev/null
+++ b/tests/ephy-completion-model-test.c
@@ -0,0 +1,101 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Copyright © 2012 Igalia S.L.
+ *
+ * This file is part of Epiphany.
+ *
+ * Epiphany 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Epiphany 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 Epiphany. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "ephy-completion-model.h"
+
+#include "ephy-debug.h"
+#include "ephy-embed-prefs.h"
+#include "ephy-file-helpers.h"
+#include "ephy-shell.h"
+
+static void
+test_ephy_completion_model_create (void)
+{
+ EphyCompletionModel *model;
+ model = ephy_completion_model_new (ephy_embed_shell_get_global_history_service
(ephy_embed_shell_get_default ()),
+ ephy_shell_get_bookmarks_manager (ephy_shell_get_default ()));
+ g_assert (model);
+ g_object_unref (model);
+}
+
+static void
+update_empty_cb (EphyHistoryService *service,
+ gboolean success,
+ gpointer result_data,
+ GMainLoop *loop)
+{
+ GList *results = (GList *)result_data;
+
+ g_assert (success);
+ g_assert (results == NULL);
+
+ g_main_loop_quit (loop);
+}
+
+static void
+test_ephy_completion_model_update_empty (void)
+{
+ EphyCompletionModel *model;
+ GMainLoop *loop = NULL;
+
+ model = ephy_completion_model_new (ephy_embed_shell_get_global_history_service
(ephy_embed_shell_get_default ()),
+ ephy_shell_get_bookmarks_manager (ephy_shell_get_default ()));
+ g_assert (model);
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ ephy_completion_model_update_for_string (model, "hello",
+ (EphyHistoryJobCallback)update_empty_cb,
+ loop);
+
+ g_main_loop_run (loop);
+
+ g_object_unref (model);
+ g_main_loop_unref (loop);
+}
+
+int
+main (int argc, char *argv[])
+{
+ gboolean ret;
+
+ gtk_test_init (&argc, &argv);
+ ephy_debug_init ();
+
+ if (!ephy_file_helpers_init (NULL,
+ EPHY_FILE_HELPERS_PRIVATE_PROFILE | EPHY_FILE_HELPERS_ENSURE_EXISTS,
+ NULL)) {
+ g_debug ("Something wrong happened with ephy_file_helpers_init()");
+ return -1;
+ }
+
+ _ephy_shell_create_instance (EPHY_EMBED_SHELL_MODE_TEST);
+
+ g_test_add_func ("/src/ephy-completion-model/create",
+ test_ephy_completion_model_create);
+
+ g_test_add_func ("/src/ephy-completion-model/update_empty",
+ test_ephy_completion_model_update_empty);
+
+ ret = g_test_run ();
+
+ return ret;
+}
diff --git a/tests/ephy-location-entry-test.c b/tests/ephy-location-entry-test.c
new file mode 100644
index 0000000..bcd6beb
--- /dev/null
+++ b/tests/ephy-location-entry-test.c
@@ -0,0 +1,160 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Copyright © 2008 Diego Escalante Urrelo
+ *
+ * This file is part of Epiphany.
+ *
+ * Epiphany 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Epiphany 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 Epiphany. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "ephy-debug.h"
+#include "ephy-location-entry.h"
+#include "ephy-title-widget.h"
+#include <glib.h>
+#include <gtk/gtk.h>
+
+static void
+test_entry_new (void)
+{
+ GtkWidget *entry;
+ entry = ephy_location_entry_new ();
+
+ g_assert (GTK_IS_WIDGET (entry));
+ g_assert (EPHY_IS_LOCATION_ENTRY (entry));
+}
+
+static void
+test_entry_get_entry (void)
+{
+ EphyLocationEntry *entry;
+ entry = EPHY_LOCATION_ENTRY (ephy_location_entry_new ());
+
+ g_assert (GTK_IS_ENTRY (entry));
+}
+
+static void
+test_entry_set_location (void)
+{
+ const char *set = "test";
+ const char *get;
+
+ EphyTitleWidget *widget;
+ widget = EPHY_TITLE_WIDGET (ephy_location_entry_new ());
+
+ ephy_title_widget_set_address (widget, set);
+ get = ephy_title_widget_get_address (widget);
+ g_assert_cmpstr (set, ==, get);
+}
+
+static void
+test_entry_set_location_null (void)
+{
+ const char *set = "test";
+ const char *get;
+
+ EphyTitleWidget *widget;
+ widget = EPHY_TITLE_WIDGET (ephy_location_entry_new ());
+
+ ephy_title_widget_set_address (widget, NULL);
+ get = ephy_title_widget_get_address (widget);
+ g_assert_cmpstr (set, !=, get);
+}
+
+static void
+test_entry_get_location (void)
+{
+ const char *set = "test";
+ const char *get;
+
+ EphyTitleWidget *widget;
+ widget = EPHY_TITLE_WIDGET (ephy_location_entry_new ());
+
+ ephy_title_widget_set_address (widget, set);
+ get = ephy_title_widget_get_address (widget);
+ g_assert_cmpstr (set, ==, get);
+}
+
+static void
+test_entry_get_location_empty (void)
+{
+ const char *get;
+
+ EphyTitleWidget *widget;
+ widget = EPHY_TITLE_WIDGET (ephy_location_entry_new ());
+
+ get = ephy_title_widget_get_address (widget);
+ g_assert_cmpstr ("", ==, get);
+}
+
+static void
+test_entry_can_undo (void)
+{
+ const char *test = "test";
+
+ EphyLocationEntry *entry;
+ entry = EPHY_LOCATION_ENTRY (ephy_location_entry_new ());
+
+ g_assert_cmpint (ephy_location_entry_get_can_undo (entry), ==, FALSE);
+
+ /* Use gtk_* function or otherwise user_changed won't be correctly handled
+ * internally by the location entry (see editable_changed_cb and
+ * block_update) */
+ gtk_entry_set_text (GTK_ENTRY (entry), test);
+ g_assert_cmpint (ephy_location_entry_get_can_undo (entry), ==, TRUE);
+}
+
+static void
+test_entry_can_redo (void)
+{
+ const char *test = "test";
+
+ EphyLocationEntry *entry;
+ entry = EPHY_LOCATION_ENTRY (ephy_location_entry_new ());
+ g_assert_cmpint (ephy_location_entry_get_can_redo (entry), ==, FALSE);
+
+ /* Can't redo, in this point we can undo */
+ ephy_title_widget_set_address (EPHY_TITLE_WIDGET (entry), test);
+ g_assert_cmpint (ephy_location_entry_get_can_redo (entry), ==, FALSE);
+
+ /* Reset should set redo to TRUE */
+ ephy_location_entry_reset (entry);
+ g_assert_cmpint (ephy_location_entry_get_can_redo (entry), ==, TRUE);
+}
+
+int
+main (int argc, char *argv[])
+{
+ gtk_test_init (&argc, &argv);
+ ephy_debug_init ();
+
+ g_test_add_func ("/lib/widgets/ephy-location-entry/new",
+ test_entry_new);
+ g_test_add_func ("/lib/widgets/ephy-location-entry/get_entry",
+ test_entry_get_entry);
+ g_test_add_func ("/lib/widgets/ephy-location-entry/set_location",
+ test_entry_set_location);
+ g_test_add_func ("/lib/widgets/ephy-location-entry/get_location",
+ test_entry_get_location);
+ g_test_add_func ("/lib/widgets/ephy-location-entry/set_location_null",
+ test_entry_set_location_null);
+ g_test_add_func ("/lib/widgets/ephy-location-entry/get_location_empty",
+ test_entry_get_location_empty);
+ g_test_add_func ("/lib/widgets/ephy-location-entry/can_undo",
+ test_entry_can_undo);
+ g_test_add_func ("/lib/widgets/ephy-location-entry/can_redo",
+ test_entry_can_redo);
+
+ return g_test_run ();
+}
diff --git a/tests/meson.build b/tests/meson.build
index b86fc41..8ced8eb 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -9,7 +9,13 @@ if get_option('unit_tests')
# ephytestutils_dep = declare_dependency(
# link_with: libephytestutils
# )
- #
+
+ completion_model_test = executable('test-ephy-completion-model',
+ 'ephy-completion-model-test.c',
+ dependencies: ephymain_dep
+ )
+ test('Completion model test', completion_model_test)
+
# FIXME: https://bugzilla.gnome.org/show_bug.cgi?id=778153
# download_test = executable('test-ephy-download',
# 'ephy-download-test.c',
@@ -55,6 +61,12 @@ if get_option('unit_tests')
)
test('History test', history_test)
+ location_entry_test = executable('test-location-entry',
+ 'ephy-location-entry-test.c',
+ dependencies: ephymain_dep
+ )
+ test('Location entry test', location_entry_test)
+
migration_test = executable('test-ephy-migration',
'ephy-migration-test.c',
dependencies: ephymain_dep
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]