Commit fccdc31d authored by Jan-Michael Brummer's avatar Jan-Michael Brummer

URL entry: Replacing GtkEntry with DzlSuggestionEntry

Make use of dazzles suggestion entry widget to make url entry more fancy.
parent 64a92625
......@@ -32,7 +32,7 @@ libephyembed_sources = [
]
libephyembed_deps = [
dazzle_dep,
libdazzle_dep,
ephymisc_dep,
gio_dep,
glib_dep,
......
/* -*- 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;
cairo_surface_t *favicon;
};
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);
}
cairo_surface_t *
ephy_suggestion_get_icon_surface (DzlSuggestion *self,
GtkWidget *widget)
{
EphySuggestion *suggestion = EPHY_SUGGESTION (self);
return suggestion->favicon;
}
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;
dzl_suggestion_class->get_icon_surface = ephy_suggestion_get_icon_surface;
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));
}
void
ephy_suggestion_set_favicon (EphySuggestion *self,
cairo_surface_t *favicon)
{
self->favicon = favicon;
g_object_notify (G_OBJECT (self), "icon");
}
/* -*- 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)
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);
void ephy_suggestion_set_favicon (EphySuggestion *self,
cairo_surface_t *favicon);
G_END_DECLS
......@@ -34,6 +34,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,
......
This diff is collapsed.
/*
* Copyright (c) 2011 Red Hat, Inc.
* Copyright (c) 2018 Igalia S.L.
*
* 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, see <http://www.gnu.org/licenses/>.
*
* Author: Cosimo Cecchi <cosimoc@redhat.com>
*
*/
#pragma once
#include <glib-object.h>
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define GD_TYPE_TWO_LINES_RENDERER gd_two_lines_renderer_get_type ()
G_DECLARE_FINAL_TYPE (GdTwoLinesRenderer, gd_two_lines_renderer, GD, TWO_LINES_RENDERER, GtkCellRendererText)
GtkCellRenderer *gd_two_lines_renderer_new (void);
G_END_DECLS
......@@ -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>
......@@ -103,12 +104,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,
......@@ -131,6 +126,12 @@ G_DEFINE_TYPE_WITH_CODE (EphyLocationEntry, ephy_location_entry, GTK_TYPE_OVERLA
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->url_entry, "activate");
}
static void
update_address_state (EphyLocationEntry *entry)
{
......@@ -523,99 +524,14 @@ entry_key_press_cb (GtkEntry *entry,
ephy_location_entry_activate (location_entry);
}
return FALSE;
}
static gboolean
entry_key_press_after_cb (GtkEntry *entry,
GdkEventKey *event,
EphyLocationEntry *lentry)
{
guint state = event->state & gtk_accelerator_get_default_mod_mask ();
if ((event->keyval == GDK_KEY_Return ||
event->keyval == GDK_KEY_KP_Enter ||
event->keyval == GDK_KEY_ISO_Enter) &&
(state == GDK_CONTROL_MASK ||
state == (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) {
/* gtk_im_context_reset (entry->im_context); */
lentry->needs_reset = TRUE;
g_signal_emit_by_name (entry, "activate");
return TRUE;
}
if ((event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_KP_Down)
&& state == 0) {
/* If we are focusing the entry, with the cursor at the end of it
* we emit the changed signal, so that the completion popup appears */
const char *string;
string = gtk_entry_get_text (entry);
if (gtk_editable_get_position (GTK_EDITABLE (entry)) == (int)strlen (string)) {
g_signal_emit_by_name (entry, "changed", 0);
return TRUE;
}
}
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->url_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)
......@@ -633,7 +549,7 @@ paste_received (GtkClipboard *clipboard,
{
if (text) {
gtk_entry_set_text (GTK_ENTRY (entry->url_entry), text);
g_signal_emit_by_name (entry->url_entry, "activate");
ephy_location_entry_activate (entry);
}
}
......@@ -814,6 +730,19 @@ button_box_size_allocated_cb (GtkWidget *widget,
g_free (css);
}
static void
ephy_location_entry_suggestion_activated (DzlSuggestionEntry *entry,
DzlSuggestion *arg1,
gpointer user_data)
{
EphyLocationEntry *lentry = EPHY_LOCATION_ENTRY (user_data);
DzlSuggestion *suggestion = dzl_suggestion_entry_get_suggestion (entry);
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 (lentry));
}
static void
ephy_location_entry_construct_contents (EphyLocationEntry *entry)
{
......@@ -823,18 +752,18 @@ ephy_location_entry_construct_contents (EphyLocationEntry *entry)
LOG ("EphyLocationEntry constructing contents %p", entry);
/* URL entry */
entry->url_entry = gtk_entry_new ();
entry->url_entry = dzl_suggestion_entry_new ();
/* Add special widget css provider */
context = gtk_widget_get_style_context (GTK_WIDGET (entry->url_entry));
entry->css_provider = gtk_css_provider_new ();
gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER (entry->css_provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
gtk_style_context_add_class (gtk_widget_get_style_context (entry->url_entry), "url_entry");
gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (entry->url_entry)), "url_entry");
g_signal_connect (G_OBJECT (entry->url_entry), "copy-clipboard", G_CALLBACK (ephy_location_entry_copy_clipboard), NULL);
g_signal_connect (G_OBJECT (entry->url_entry), "cut-clipboard", G_CALLBACK (ephy_location_entry_cut_clipboard), NULL);
gtk_widget_show (entry->url_entry);
gtk_overlay_add_overlay (GTK_OVERLAY (entry), entry->url_entry);
gtk_widget_show (GTK_WIDGET (entry->url_entry));
gtk_overlay_add_overlay (GTK_OVERLAY (entry), GTK_WIDGET (entry->url_entry));
/* Button Box */
button_box = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
......@@ -874,10 +803,8 @@ ephy_location_entry_construct_contents (EphyLocationEntry *entry)
"signal::changed", G_CALLBACK (editable_changed_cb), entry,
NULL);
g_signal_connect_after (entry->url_entry, "key-press-event",
G_CALLBACK (entry_key_press_after_cb), entry);
g_signal_connect_after (entry->url_entry, "activate",
G_CALLBACK (entry_activate_after_cb), entry);
g_signal_connect (entry->url_entry, "suggestion-activated",
G_CALLBACK (ephy_location_entry_suggestion_activated), entry);
}
static void
......@@ -954,238 +881,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", "starred-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->url_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);