diff options
author | Michael Catanzaro <mcatanzaro@igalia.com> | 2017-11-22 17:51:31 -0600 |
---|---|---|
committer | Michael Catanzaro <mcatanzaro@igalia.com> | 2017-11-22 17:51:31 -0600 |
commit | 513727c50807df004c985be4cc03191655d53fb3 (patch) | |
tree | 8b7e810615e6050fd1897d80b3dc12d273337a61 | |
parent | df1db5026cef1343d33e11a1bd370b41ea1eeb14 (diff) | |
download | epiphany-wip/libdazzle.tar.gz |
Revert "Stop using DzlSuggestionEntry for now"wip/libdazzle
This reverts commit df1db5026cef1343d33e11a1bd370b41ea1eeb14.
-rw-r--r-- | embed/ephy-embed-shell.c | 3 | ||||
-rw-r--r-- | embed/ephy-embed-shell.h | 5 | ||||
-rw-r--r-- | embed/meson.build | 1 | ||||
-rw-r--r-- | lib/ephy-suggestion.c | 174 | ||||
-rw-r--r-- | lib/ephy-suggestion.h | 37 | ||||
-rw-r--r-- | lib/meson.build | 2 | ||||
-rw-r--r-- | lib/widgets/contrib/gd-two-lines-renderer.c | 621 | ||||
-rw-r--r-- | lib/widgets/contrib/gd-two-lines-renderer.h | 75 | ||||
-rw-r--r-- | lib/widgets/ephy-location-entry.c | 378 | ||||
-rw-r--r-- | lib/widgets/ephy-location-entry.h | 20 | ||||
-rw-r--r-- | lib/widgets/meson.build | 2 | ||||
-rw-r--r-- | meson.build | 1 | ||||
-rw-r--r-- | org.gnome.Epiphany.json | 12 | ||||
-rw-r--r-- | src/ephy-completion-model.c | 625 | ||||
-rw-r--r-- | src/ephy-completion-model.h | 53 | ||||
-rw-r--r-- | src/ephy-location-controller.c | 133 | ||||
-rw-r--r-- | src/ephy-suggestion-model.c | 476 | ||||
-rw-r--r-- | src/ephy-suggestion-model.h | 46 | ||||
-rw-r--r-- | src/ephy-window.c | 22 | ||||
-rw-r--r-- | src/ephy-window.h | 3 | ||||
-rw-r--r-- | src/meson.build | 3 | ||||
-rw-r--r-- | src/search-provider/ephy-search-provider.c | 121 | ||||
-rw-r--r-- | tests/ephy-completion-model-test.c | 101 | ||||
-rw-r--r-- | tests/ephy-location-entry-test.c | 160 | ||||
-rw-r--r-- | tests/meson.build | 14 |
25 files changed, 868 insertions, 2220 deletions
diff --git a/embed/ephy-embed-shell.c b/embed/ephy-embed-shell.c index d0751ff33..0c1d38a1a 100644 --- a/embed/ephy-embed-shell.c +++ b/embed/ephy-embed-shell.c @@ -43,6 +43,7 @@ #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> @@ -103,7 +104,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, GTK_TYPE_APPLICATION, +G_DEFINE_TYPE_WITH_CODE (EphyEmbedShell, ephy_embed_shell, DZL_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 c933d5037..c5b5dfd97 100644 --- a/embed/ephy-embed-shell.h +++ b/embed/ephy-embed-shell.h @@ -21,6 +21,7 @@ #pragma once +#include <dazzle.h> #include <webkit2/webkit2.h> #include "ephy-downloads-manager.h" @@ -34,7 +35,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, GtkApplication) +G_DECLARE_DERIVABLE_TYPE (EphyEmbedShell, ephy_embed_shell, EPHY, EMBED_SHELL, DzlApplication) typedef enum { @@ -49,7 +50,7 @@ typedef enum struct _EphyEmbedShellClass { - GtkApplicationClass parent_class; + DzlApplicationClass parent_class; void (* restored_window) (EphyEmbedShell *shell); }; diff --git a/embed/meson.build b/embed/meson.build index 1f924f255..99315d704 100644 --- a/embed/meson.build +++ b/embed/meson.build @@ -37,6 +37,7 @@ libephyembed_deps = [ glib_dep, gtk_dep, icu_uc_dep, + libdazzle_dep, libsecret_dep, libsoup_dep, m_dep, diff --git a/lib/ephy-suggestion.c b/lib/ephy-suggestion.c new file mode 100644 index 000000000..aefbf8a8a --- /dev/null +++ b/lib/ephy-suggestion.c @@ -0,0 +1,174 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Copyright © 2017 Igalia S.L. + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" +#include "ephy-suggestion.h" + +#include "ephy-uri-helpers.h" + +#include <dazzle.h> +#include <glib.h> + +struct _EphySuggestion { + DzlSuggestion parent; + + char *unescaped_title; +}; + +G_DEFINE_TYPE (EphySuggestion, ephy_suggestion, DZL_TYPE_SUGGESTION) + +enum { + PROP_0, + PROP_UNESCAPED_TITLE, + LAST_PROP +}; + +static GParamSpec *obj_properties[LAST_PROP]; + +static void +ephy_suggestion_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EphySuggestion *self = EPHY_SUGGESTION (object); + + switch (prop_id) { + case PROP_UNESCAPED_TITLE: + self->unescaped_title = g_strdup (g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +ephy_suggestion_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EphySuggestion *self = EPHY_SUGGESTION (object); + + switch (prop_id) { + case PROP_UNESCAPED_TITLE: + g_value_set_string (value, self->unescaped_title); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +char * +ephy_suggestion_replace_typed_text (DzlSuggestion *self, + const char *typed_text) +{ + const char *url; + + g_assert (EPHY_IS_SUGGESTION (self)); + + url = ephy_suggestion_get_uri (EPHY_SUGGESTION (self)); + + return g_strdup (url); +} + +static void +ephy_suggestion_class_init (EphySuggestionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + DzlSuggestionClass *dzl_suggestion_class = DZL_SUGGESTION_CLASS (klass); + + object_class->get_property = ephy_suggestion_get_property; + object_class->set_property = ephy_suggestion_set_property; + + dzl_suggestion_class->replace_typed_text = ephy_suggestion_replace_typed_text; + + obj_properties[PROP_UNESCAPED_TITLE] = + g_param_spec_string ("unescaped-title", + "Unescaped title", + "The title of the suggestion, not XML-escaped", + "", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, LAST_PROP, obj_properties); +} + +static void +ephy_suggestion_init (EphySuggestion *self) +{ +} + +EphySuggestion * +ephy_suggestion_new (const char *title, + const char *uri) +{ + EphySuggestion *suggestion; + char *escaped_title = g_markup_escape_text (title, -1); + char *decoded_uri = ephy_uri_decode (uri); + char *escaped_uri = g_markup_escape_text (decoded_uri, -1); + + suggestion = g_object_new (EPHY_TYPE_SUGGESTION, + "icon-name", "web-browser-symbolic", + "id", uri, + "subtitle", escaped_uri, + "title", escaped_title, + "unescaped-title", title, + NULL); + + g_free (escaped_title); + g_free (decoded_uri); + g_free (escaped_uri); + + return suggestion; +} + +EphySuggestion * +ephy_suggestion_new_without_subtitle (const char *title, + const char *uri) +{ + EphySuggestion *suggestion; + char *escaped_title; + + escaped_title = g_markup_escape_text (title, -1); + suggestion = g_object_new (EPHY_TYPE_SUGGESTION, + "icon-name", "web-browser-symbolic", + "id", uri, + "title", escaped_title, + "unescaped-title", title, + NULL); + + g_free (escaped_title); + + return suggestion; +} + +const char * +ephy_suggestion_get_unescaped_title (EphySuggestion *self) +{ + g_assert (EPHY_IS_SUGGESTION (self)); + + return self->unescaped_title; +} + +const char * +ephy_suggestion_get_uri (EphySuggestion *self) +{ + g_assert (EPHY_IS_SUGGESTION (self)); + + return dzl_suggestion_get_id (DZL_SUGGESTION (self)); +} diff --git a/lib/ephy-suggestion.h b/lib/ephy-suggestion.h new file mode 100644 index 000000000..c35edcf2c --- /dev/null +++ b/lib/ephy-suggestion.h @@ -0,0 +1,37 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Copyright © 2017 Igalia S.L. + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <dazzle.h> + +G_BEGIN_DECLS + +#define EPHY_TYPE_SUGGESTION (ephy_suggestion_get_type()) + +G_DECLARE_FINAL_TYPE (EphySuggestion, ephy_suggestion, EPHY, SUGGESTION, DzlSuggestion) + +// FIXME: How about favicon? +EphySuggestion *ephy_suggestion_new (const char *title, + const char *uri); +EphySuggestion *ephy_suggestion_new_without_subtitle (const char *title, + const char *uri); +const char *ephy_suggestion_get_unescaped_title (EphySuggestion *self); +const char *ephy_suggestion_get_uri (EphySuggestion *self); + +G_END_DECLS diff --git a/lib/meson.build b/lib/meson.build index 298dd7e2d..b9f5d8e77 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -33,6 +33,7 @@ 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', @@ -61,6 +62,7 @@ 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 deleted file mode 100644 index 5a029f7f2..000000000 --- a/lib/widgets/contrib/gd-two-lines-renderer.c +++ /dev/null @@ -1,621 +0,0 @@ -/* - * 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 deleted file mode 100644 index 23bf70f08..000000000 --- a/lib/widgets/contrib/gd-two-lines-renderer.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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 74b590f9c..472d0ab66 100644 --- a/lib/widgets/ephy-location-entry.c +++ b/lib/widgets/ephy-location-entry.c @@ -31,10 +31,11 @@ #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> @@ -54,7 +55,7 @@ */ struct _EphyLocationEntry { - GtkEntry parent_instance; + DzlSuggestionEntry parent_instance; GtkTreeModel *model; @@ -91,12 +92,6 @@ 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, @@ -115,11 +110,17 @@ 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, GTK_TYPE_ENTRY, +G_DEFINE_TYPE_WITH_CODE (EphyLocationEntry, ephy_location_entry, DZL_TYPE_SUGGESTION_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; @@ -273,8 +274,12 @@ 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 @@ -361,6 +366,16 @@ 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; @@ -375,7 +390,9 @@ 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; @@ -387,6 +404,8 @@ 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"); @@ -488,103 +507,18 @@ 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_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; + ephy_location_entry_focus (location_entry); } - 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; - } - } + 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 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) { @@ -601,7 +535,7 @@ paste_received (GtkClipboard *clipboard, { if (text) { gtk_entry_set_text (GTK_ENTRY (entry), text); - g_signal_emit_by_name (entry, "activate"); + ephy_location_entry_activate (entry); } } @@ -758,11 +692,6 @@ 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 @@ -839,238 +768,6 @@ 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 @@ -1125,9 +822,8 @@ 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; @@ -1167,7 +863,7 @@ ephy_location_entry_reset (EphyLocationEntry *entry) } /** - * ephy_location_entry_activate: + * ephy_location_entry_focus: * @entry: an #EphyLocationEntry widget * * Set focus on @entry and select the text whithin. This is called when the @@ -1175,7 +871,7 @@ ephy_location_entry_reset (EphyLocationEntry *entry) * **/ void -ephy_location_entry_activate (EphyLocationEntry *entry) +ephy_location_entry_focus (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 490524a09..950d06158 100644 --- a/lib/widgets/ephy-location-entry.h +++ b/lib/widgets/ephy-location-entry.h @@ -23,6 +23,7 @@ #pragma once +#include <dazzle.h> #include <gtk/gtk.h> #include "ephy-security-levels.h" @@ -31,7 +32,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, GtkEntry) +G_DECLARE_FINAL_TYPE (EphyLocationEntry, ephy_location_entry, EPHY, LOCATION_ENTRY, DzlSuggestionEntry) typedef enum { EPHY_LOCATION_ENTRY_BOOKMARK_ICON_HIDDEN, @@ -41,21 +42,6 @@ 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); @@ -66,7 +52,7 @@ gboolean ephy_location_entry_reset (EphyLocationEntr void ephy_location_entry_undo_reset (EphyLocationEntry *entry); -void ephy_location_entry_activate (EphyLocationEntry *entry); +void ephy_location_entry_focus (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 1c5c8492d..c927648d3 100644 --- a/lib/widgets/meson.build +++ b/lib/widgets/meson.build @@ -7,7 +7,6 @@ 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', @@ -30,6 +29,7 @@ libephywidgets_deps = [ gio_dep, glib_dep, gtk_dep, + libdazzle_dep, libsoup_dep, webkit2gtk_dep ] diff --git a/meson.build b/meson.build index 535d49990..9fa9df18a 100644 --- a/meson.build +++ b/meson.build @@ -63,6 +63,7 @@ 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 0cec50c12..426b7b490 100644 --- a/org.gnome.Epiphany.json +++ b/org.gnome.Epiphany.json @@ -51,6 +51,18 @@ ] }, { + "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 deleted file mode 100644 index e1faae148..000000000 --- a/src/ephy-completion-model.c +++ /dev/null @@ -1,625 +0,0 @@ -/* -*- 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 deleted file mode 100644 index 912c3669e..000000000 --- a/src/ephy-completion-model.h +++ /dev/null @@ -1,53 +0,0 @@ -/* -*- 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 c42e31f89..f46e232e2 100644 --- a/src/ephy-location-controller.c +++ b/src/ephy-location-controller.c @@ -22,8 +22,6 @@ #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" @@ -31,10 +29,12 @@ #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,16 +79,6 @@ 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, @@ -182,33 +172,18 @@ 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; - GtkTreeModel *model; - GtkEntryCompletion *completion; + GListModel *model; address = ephy_title_widget_get_address (EPHY_TITLE_WIDGET (widget)); LOG ("user_changed_cb, address %s", address); - completion = gtk_entry_get_completion (GTK_ENTRY (widget)); - model = gtk_entry_completion_get_model (completion); + model = dzl_suggestion_entry_get_model (DZL_SUGGESTION_ENTRY (widget)); - ephy_completion_model_update_for_string (EPHY_COMPLETION_MODEL (model), address, - update_done_cb, completion); + ephy_suggestion_model_query_async (EPHY_SUGGESTION_MODEL (model), address, NULL, NULL, NULL); } static void @@ -292,84 +267,12 @@ 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; - EphyCompletionModel *model; + EphySuggestionModel *model; GtkWidget *notebook, *widget; G_OBJECT_CLASS (ephy_location_controller_parent_class)->constructed (object); @@ -389,27 +292,13 @@ 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_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); + model = ephy_suggestion_model_new (history_service, bookmarks_manager); + dzl_suggestion_entry_set_model (DZL_SUGGESTION_ENTRY (controller->title_widget), G_LIST_MODEL (model)); g_object_unref (model); - 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); +/* FIXME: What to do with search engines? Add them to the model? Probably not? + * Make the bangs mandatory? + */ g_object_bind_property (controller, "editable", controller->title_widget, "editable", diff --git a/src/ephy-suggestion-model.c b/src/ephy-suggestion-model.c new file mode 100644 index 000000000..9e12195ff --- /dev/null +++ b/src/ephy-suggestion-model.c @@ -0,0 +1,476 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Copyright © 2017 Christian Hergert <chergert@redhat.com> + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" +#include "ephy-suggestion-model.h" + +#include "ephy-embed-shell.h" +#include "ephy-search-engine-manager.h" +#include "ephy-suggestion.h" + +#include <dazzle.h> +#include <glib/gi18n.h> + +#define MAX_COMPLETION_HISTORY_URLS 8 + +struct _EphySuggestionModel { + GObject parent; + EphyHistoryService *history_service; + EphyBookmarksManager *bookmarks_manager; + GSequence *items; + GSList *search_terms; +}; + +enum { + PROP_0, + PROP_BOOKMARKS_MANAGER, + PROP_HISTORY_SERVICE, + N_PROPS +}; + +static void list_model_iface_init (GListModelInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (EphySuggestionModel, ephy_suggestion_model, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)) + +static GParamSpec *properties [N_PROPS]; + +static void +ephy_suggestion_model_finalize (GObject *object) +{ + EphySuggestionModel *self = (EphySuggestionModel *)object; + + g_clear_object (&self->bookmarks_manager); + g_clear_object (&self->history_service); + g_clear_pointer (&self->items, g_sequence_free); + + g_slist_free_full (self->search_terms, (GDestroyNotify)g_regex_unref); + + G_OBJECT_CLASS (ephy_suggestion_model_parent_class)->finalize (object); +} + +static void +ephy_suggestion_model_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EphySuggestionModel *self = EPHY_SUGGESTION_MODEL (object); + + switch (prop_id) { + case PROP_HISTORY_SERVICE: + g_value_set_object (value, self->history_service); + break; + case PROP_BOOKMARKS_MANAGER: + g_value_set_object (value, self->bookmarks_manager); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +ephy_suggestion_model_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EphySuggestionModel *self = EPHY_SUGGESTION_MODEL (object); + + switch (prop_id) { + case PROP_HISTORY_SERVICE: + self->history_service = g_value_dup_object (value); + break; + case PROP_BOOKMARKS_MANAGER: + self->bookmarks_manager = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +ephy_suggestion_model_class_init (EphySuggestionModelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = ephy_suggestion_model_finalize; + object_class->get_property = ephy_suggestion_model_get_property; + object_class->set_property = ephy_suggestion_model_set_property; + + properties [PROP_BOOKMARKS_MANAGER] = + g_param_spec_object ("bookmarks-manager", + "Bookmarks Manager", + "The bookmarks manager for suggestions", + EPHY_TYPE_BOOKMARKS_MANAGER, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_HISTORY_SERVICE] = + g_param_spec_object ("history-service", + "History Service", + "The history service for suggestions", + EPHY_TYPE_HISTORY_SERVICE, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +ephy_suggestion_model_init (EphySuggestionModel *self) +{ + self->items = g_sequence_new (g_object_unref); +} + +static GType +ephy_suggestion_model_get_item_type (GListModel *model) +{ + return EPHY_TYPE_SUGGESTION; +} + +static guint +ephy_suggestion_model_get_n_items (GListModel *model) +{ + EphySuggestionModel *self = EPHY_SUGGESTION_MODEL (model); + + return g_sequence_get_length (self->items); +} + +static gpointer +ephy_suggestion_model_get_item (GListModel *model, + guint position) +{ + EphySuggestionModel *self = EPHY_SUGGESTION_MODEL (model); + GSequenceIter *iter; + DzlSuggestion *suggestion; + + iter = g_sequence_get_iter_at_pos (self->items, position); + suggestion = g_sequence_get (iter); + + return g_object_ref (suggestion); +} + +static void +list_model_iface_init (GListModelInterface *iface) +{ + iface->get_item_type = ephy_suggestion_model_get_item_type; + iface->get_item = ephy_suggestion_model_get_item; + iface->get_n_items = ephy_suggestion_model_get_n_items; +} + +EphySuggestionModel * +ephy_suggestion_model_new (EphyHistoryService *history_service, + EphyBookmarksManager *bookmarks_manager) +{ + g_assert (EPHY_IS_HISTORY_SERVICE (history_service)); + g_assert (EPHY_IS_BOOKMARKS_MANAGER (bookmarks_manager)); + + return g_object_new (EPHY_TYPE_SUGGESTION_MODEL, + "history-service", history_service, + "bookmarks-manager", bookmarks_manager, + NULL); +} + +static void +update_search_terms (EphySuggestionModel *self, + 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; + + g_assert (EPHY_IS_SUGGESTION_MODEL (self)); + + if (self->search_terms) { + g_slist_free_full (self->search_terms, (GDestroyNotify)g_regex_unref); + self->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); + self->search_terms = g_slist_append (self->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); +} + +static gboolean +should_add_bookmark_to_model (EphySuggestionModel *self, + const char *search_string, + const char *title, + const char *location) +{ + gboolean ret = TRUE; + + if (self->search_terms) { + GSList *iter; + GRegex *current = NULL; + + for (iter = self->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, location ? location : "", G_REGEX_MATCH_NOTEMPTY, NULL))) { + ret = FALSE; + break; + } + } + } + + return ret; +} + +static guint +add_bookmarks (EphySuggestionModel *self, + const char *query) +{ + GSequence *bookmarks; + guint added = 0; + + bookmarks = ephy_bookmarks_manager_get_bookmarks (self->bookmarks_manager); + + for (GSequenceIter *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); + + url = ephy_bookmark_get_url (bookmark); + title = ephy_bookmark_get_title (bookmark); + + if (should_add_bookmark_to_model (self, query, title, url)) { + EphySuggestion *suggestion; + + suggestion = ephy_suggestion_new (title, url); + g_sequence_append (self->items, suggestion); + added++; + } + } + + return added; +} + +static guint +add_history (EphySuggestionModel *self, + GList *urls) +{ + guint added = 0; + + for (const GList *p = urls; p != NULL; p = p->next) { + EphyHistoryURL *url = (EphyHistoryURL *)p->data; + EphySuggestion *suggestion; + + suggestion = ephy_suggestion_new (url->title, url->url); + g_sequence_append (self->items, suggestion); + added++; + } + + return added; +} + +static guint +add_search_engines (EphySuggestionModel *self, + const char *query) +{ + EphyEmbedShell *shell; + EphySearchEngineManager *manager; + char **engines; + guint added = 0; + + shell = ephy_embed_shell_get_default (); + manager = ephy_embed_shell_get_search_engine_manager (shell); + engines = ephy_search_engine_manager_get_names (manager); + + for (guint i = 0; engines[i] != NULL; i++) { + EphySuggestion *suggestion; + char *address; + + address = ephy_search_engine_manager_build_search_address (manager, engines[i], query); + suggestion = ephy_suggestion_new_without_subtitle (engines[i], address); + + g_sequence_append (self->items, suggestion); + added++; + + g_free (address); + } + + g_strfreev (engines); + + return added; +} + +static void +query_completed_cb (EphyHistoryService *service, + gboolean success, + gpointer result_data, + gpointer user_data) +{ + GTask *task = user_data; + EphySuggestionModel *self; + const gchar *query; + GList *urls; + guint removed; + guint added; + + self = g_task_get_source_object (task); + query = g_task_get_task_data (task); + urls = (GList *)result_data; + + removed = g_sequence_get_length (self->items); + + g_clear_pointer (&self->items, g_sequence_free); + self->items = g_sequence_new (g_object_unref); + + added = add_bookmarks (self, query); + added += add_history (self, urls); + added += add_search_engines (self, query); + + g_list_model_items_changed (G_LIST_MODEL (self), 0, removed, added); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +} + +void +ephy_suggestion_model_query_async (EphySuggestionModel *self, + const gchar *query, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task = NULL; + char **strings; + GList *qlist = NULL; + + g_assert (EPHY_IS_SUGGESTION_MODEL (self)); + g_assert (query != NULL); + g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, ephy_suggestion_model_query_async); + g_task_set_task_data (task, g_strdup (query), g_free); + + /* Split the search string. */ + strings = g_strsplit (query, " ", -1); + for (guint i = 0; strings[i]; i++) + qlist = g_list_append (qlist, g_strdup (strings[i])); + + update_search_terms (self, query); + + ephy_history_service_find_urls (self->history_service, + 0, 0, + MAX_COMPLETION_HISTORY_URLS, 0, + qlist, + EPHY_HISTORY_SORT_MOST_VISITED, + cancellable, + (EphyHistoryJobCallback)query_completed_cb, + task); + + g_strfreev (strings); +} + +gboolean +ephy_suggestion_model_query_finish (EphySuggestionModel *self, + GAsyncResult *result, + GError **error) +{ + g_assert (EPHY_IS_SUGGESTION_MODEL (self)); + g_assert (G_IS_TASK (result)); + + return g_task_propagate_boolean (G_TASK (result), error); +} + +EphySuggestion * +ephy_suggestion_model_get_suggestion_with_uri (EphySuggestionModel *self, + const char *uri) +{ + GSequenceIter *iter; + + g_assert (EPHY_IS_SUGGESTION_MODEL (self)); + g_assert (uri != NULL && *uri != '\0'); + + for (iter = g_sequence_get_begin_iter (self->items); + !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) { + EphySuggestion *suggestion; + + suggestion = g_sequence_get (iter); + if (strcmp (ephy_suggestion_get_uri (suggestion), uri) == 0) + return suggestion; + } + + return NULL; +} diff --git a/src/ephy-suggestion-model.h b/src/ephy-suggestion-model.h new file mode 100644 index 000000000..ba1ff06d1 --- /dev/null +++ b/src/ephy-suggestion-model.h @@ -0,0 +1,46 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Copyright © 2017 Christian Hergert <chergert@redhat.com> + * + * 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 3 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, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <gio/gio.h> + +#include "ephy-bookmarks-manager.h" +#include "ephy-history-service.h" +#include "ephy-suggestion.h" + +G_BEGIN_DECLS + +#define EPHY_TYPE_SUGGESTION_MODEL (ephy_suggestion_model_get_type()) + +G_DECLARE_FINAL_TYPE (EphySuggestionModel, ephy_suggestion_model, EPHY, SUGGESTION_MODEL, GObject) + +EphySuggestionModel *ephy_suggestion_model_new (EphyHistoryService *history_service, + EphyBookmarksManager *bookmarks_manager); +void ephy_suggestion_model_query_async (EphySuggestionModel *self, + const gchar *query, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean ephy_suggestion_model_query_finish (EphySuggestionModel *self, + GAsyncResult *result, + GError **error); +EphySuggestion *ephy_suggestion_model_get_suggestion_with_uri (EphySuggestionModel *self, + const char *uri); + +G_END_DECLS diff --git a/src/ephy-window.c b/src/ephy-window.c index 96af17078..2883c0d02 100644 --- a/src/ephy-window.c +++ b/src/ephy-window.c @@ -53,6 +53,7 @@ #include "popup-commands.h" #include "window-commands.h" +#include <dazzle.h> #include <gdk/gdkkeysyms.h> #include <gdk/gdkx.h> #include <gio/gio.h> @@ -74,6 +75,10 @@ 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]; @@ -138,7 +143,7 @@ const struct { #define SETTINGS_CONNECTION_DATA_KEY "EphyWindowSettings" struct _EphyWindow { - GtkApplicationWindow parent_instance; + DzlApplicationWindow parent_instance; GtkWidget *header_bar; EphyBookmarksManager *bookmarks_manager; @@ -185,6 +190,13 @@ 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, @@ -393,12 +405,6 @@ 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) { @@ -3316,7 +3322,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_activate (EPHY_LOCATION_ENTRY (title_widget)); + ephy_location_entry_focus (EPHY_LOCATION_ENTRY (title_widget)); } /** diff --git a/src/ephy-window.h b/src/ephy-window.h index ff01c3ffd..edec1a98f 100644 --- a/src/ephy-window.h +++ b/src/ephy-window.h @@ -25,13 +25,14 @@ #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, GtkApplicationWindow) +G_DECLARE_FINAL_TYPE (EphyWindow, ephy_window, EPHY, WINDOW, DzlApplicationWindow) typedef enum { diff --git a/src/meson.build b/src/meson.build index 2d810a6c2..1373b58ab 100644 --- a/src/meson.build +++ b/src/meson.build @@ -22,7 +22,6 @@ 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', @@ -34,6 +33,7 @@ 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,7 +102,6 @@ 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 daa04f36b..076c86b4d 100644 --- a/src/search-provider/ephy-search-provider.c +++ b/src/search-provider/ephy-search-provider.c @@ -23,11 +23,12 @@ #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> @@ -43,7 +44,7 @@ struct _EphySearchProvider { GSettings *settings; EphyBookmarksManager *bookmarks_manager; - EphyCompletionModel *model; + EphySuggestionModel *model; }; struct _EphySearchProviderClass { @@ -55,29 +56,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 (EphyHistoryService *service, - gboolean success, - gpointer result_data, - gpointer user_data) +on_model_updated (GObject *source_object, + GAsyncResult *result, + GTask *task) { - GTask *task = user_data; EphySearchProvider *self = g_task_get_source_object (task); - GtkTreeModel *model = GTK_TREE_MODEL (self->model); - GtkTreeIter iter; + EphySuggestion *suggestion; GPtrArray *results; const char *search_string; - gboolean ok; - + guint n_items; + GError *error = NULL; results = g_ptr_array_new (); - 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); + 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); } search_string = g_task_get_task_data (task); @@ -106,16 +107,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_completion_model_update_for_string (self->model, - search_string, - on_model_updated, - task); + ephy_suggestion_model_query_async (self->model, + search_string, + cancellable, + (GAsyncReadyCallback)on_model_updated, + task); } static void @@ -126,7 +127,6 @@ complete_request (GObject *object, EphySearchProvider *self = EPHY_SEARCH_PROVIDER (object); char **results; GError *error; - error = NULL; results = gather_results_finish (self, result, &error); @@ -177,14 +177,8 @@ 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); @@ -201,54 +195,28 @@ 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); - continue; - } - - if (!gtk_tree_model_get_iter_from_string (model, &iter, results[i])) - continue; - - 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); + } else { + EphySuggestion *suggestion; + const char *title; + const char *uri; + char *decoded_uri; - 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)); + 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 (favicon == NULL) { - char *type; - - type = g_content_type_from_mime_type ("text/html"); - favicon = g_content_type_get_icon (type); - - 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_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); - g_object_unref (emblem); - g_object_unref (emblem_icon); - g_object_unref (favicon); - favicon = emblemed; - } + g_free (decoded_uri); } - - 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, @@ -265,7 +233,6 @@ 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); @@ -370,7 +337,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_completion_model_new (ephy_embed_shell_get_global_history_service (shell), + self->model = ephy_suggestion_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 deleted file mode 100644 index 869d16b56..000000000 --- a/tests/ephy-completion-model-test.c +++ /dev/null @@ -1,101 +0,0 @@ -/* -*- 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 deleted file mode 100644 index bcd6bebaa..000000000 --- a/tests/ephy-location-entry-test.c +++ /dev/null @@ -1,160 +0,0 @@ -/* -*- 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 8ced8ebb5..b86fc41a2 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -9,13 +9,7 @@ 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', @@ -61,12 +55,6 @@ 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 |