[epiphany] search-engines: Port whole EphySearchEngineManager code to GListModel
- From: Marge Bot <marge-bot src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [epiphany] search-engines: Port whole EphySearchEngineManager code to GListModel
- Date: Fri, 11 Feb 2022 22:02:21 +0000 (UTC)
commit 33179c39c5e5e20d625ead2cabcd8c8d01a825af
Author: vanadiae <vanadiae35 gmail com>
Date: Sat Jan 29 15:16:49 2022 +0100
search-engines: Port whole EphySearchEngineManager code to GListModel
This commit ports EphySearchEngineManager to being a proper GListModel,
with a separate EphySearchEngine object for each search engines. That makes
the code overall way cleaner and easier to work with, as there's no need
to keep track of the old_name and saved_name or whatever horrible thing
that I needed to do, since now we just need to keep around the EphySearchEngine
object we're displaying in this preferences row, and do changes on it directly.
That did require quite a few changes all around the code base to adapt to all
API changes, but it is definitely worth it.
Part-of: <https://gitlab.gnome.org/GNOME/epiphany/-/merge_requests/1055>
embed/ephy-embed-utils.c | 88 +--
lib/ephy-search-engine-manager.c | 795 ++++++++++++++++-----------
lib/ephy-search-engine-manager.h | 51 +-
lib/ephy-search-engine.c | 244 ++++++++
lib/ephy-search-engine.h | 51 ++
lib/meson.build | 1 +
src/ephy-suggestion-model.c | 27 +-
src/ephy-window.c | 7 +-
src/preferences/ephy-prefs-dialog.c | 11 +-
src/preferences/ephy-search-engine-listbox.c | 383 ++++++++++---
src/preferences/ephy-search-engine-listbox.h | 5 -
src/preferences/ephy-search-engine-row.c | 287 +++-------
src/preferences/ephy-search-engine-row.h | 6 +-
src/resources/gtk/search-engine-listbox.ui | 14 -
src/resources/gtk/search-engine-row.ui | 2 +-
src/search-provider/ephy-search-provider.c | 17 +-
tests/ephy-web-view-test.c | 28 +-
17 files changed, 1262 insertions(+), 755 deletions(-)
---
diff --git a/embed/ephy-embed-utils.c b/embed/ephy-embed-utils.c
index c3a915984..a21319f31 100644
--- a/embed/ephy-embed-utils.c
+++ b/embed/ephy-embed-utils.c
@@ -25,8 +25,10 @@
#include "ephy-embed-utils.h"
#include "ephy-about-handler.h"
+#include "ephy-embed-shell.h"
#include "ephy-prefs.h"
#include "ephy-reader-handler.h"
+#include "ephy-search-engine-manager.h"
#include "ephy-settings.h"
#include "ephy-string.h"
#include "ephy-view-source-handler.h"
@@ -197,34 +199,6 @@ is_public_domain (const char *address)
return retval;
}
-static gboolean
-is_bang_search (const char *address)
-{
- EphyEmbedShell *shell;
- EphySearchEngineManager *search_engine_manager;
- char **bangs;
- GString *buffer;
-
- shell = ephy_embed_shell_get_default ();
- search_engine_manager = ephy_embed_shell_get_search_engine_manager (shell);
- bangs = ephy_search_engine_manager_get_bangs (search_engine_manager);
-
- for (uint i = 0; bangs[i] != NULL; i++) {
- buffer = g_string_new (bangs[i]);
- g_string_append (buffer, " ");
-
- if (strstr (address, buffer->str) == address) {
- g_string_free (buffer, TRUE);
- g_free (bangs);
- return TRUE;
- }
- g_string_free (buffer, TRUE);
- }
- g_free (bangs);
-
- return FALSE;
-}
-
static gboolean
is_host_with_port (const char *address)
{
@@ -241,6 +215,13 @@ is_host_with_port (const char *address)
return port != 0;
}
+/* This function checks whether @address can point to a web page.
+ * It accepts as potential sources for web page not only full URI/URLs, but also:
+ * - local absolute file path
+ * - IP address
+ * - host:port
+ * - localhost
+ */
gboolean
ephy_embed_utils_address_is_valid (const char *address)
{
@@ -262,7 +243,6 @@ ephy_embed_utils_address_is_valid (const char *address)
ephy_embed_utils_address_is_existing_absolute_filename (address) ||
g_regex_match (get_non_search_regex (), address, 0, NULL) ||
is_public_domain (address) ||
- is_bang_search (address) ||
is_host_with_port (address);
g_clear_object (&info);
@@ -287,6 +267,11 @@ ensure_host_name_is_lowercase (const char *address)
return g_strdup (address);
}
+/* Does various normalization rules to make sure @input_address ends up
+ * with a URI scheme (e.g. absolute filenames or "localhost"), changes
+ * the URI scheme to something more appropriate when needed and lowercases
+ * the hostname.
+ */
char *
ephy_embed_utils_normalize_address (const char *input_address)
{
@@ -294,19 +279,6 @@ ephy_embed_utils_normalize_address (const char *input_address)
g_autofree gchar *address = NULL;
g_assert (input_address);
- /* We don't want to lowercase the host name if it's a bang search, as it's not a URI.
- * It would otherwise lowercase the entire search string, bang included, which is not
- * what we want. So use input_address directly.
- */
- if (is_bang_search (input_address)) {
- EphyEmbedShell *shell;
- EphySearchEngineManager *search_engine_manager;
-
- shell = ephy_embed_shell_get_default ();
- search_engine_manager = ephy_embed_shell_get_search_engine_manager (shell);
- return ephy_search_engine_manager_parse_bang_search (search_engine_manager,
- input_address);
- }
address = ensure_host_name_is_lowercase (input_address);
@@ -342,38 +314,34 @@ ephy_embed_utils_normalize_address (const char *input_address)
return effective_address ? effective_address : g_strdup (address);
}
+/* Searches @search_key with the default search engine. */
char *
ephy_embed_utils_autosearch_address (const char *search_key)
{
- char *query_param;
- const char *address_search;
- char *effective_address;
EphyEmbedShell *shell;
- EphySearchEngineManager *search_engine_manager;
+ EphySearchEngineManager *manager;
+ EphySearchEngine *engine;
if (!g_settings_get_boolean (EPHY_SETTINGS_WEB, EPHY_PREFS_WEB_ENABLE_AUTOSEARCH))
return g_strdup (search_key);
shell = ephy_embed_shell_get_default ();
- search_engine_manager = ephy_embed_shell_get_search_engine_manager (shell);
- address_search = ephy_search_engine_manager_get_default_search_address (search_engine_manager);
-
- query_param = soup_form_encode ("q", search_key, NULL);
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wformat-nonliteral"
- /* Format string under control of user input... but gsettings is trusted input. */
- /* + 2 here is getting rid of 'q=' */
- effective_address = g_strdup_printf (address_search, query_param + 2);
-#pragma GCC diagnostic pop
- g_free (query_param);
-
- return effective_address;
+ manager = ephy_embed_shell_get_search_engine_manager (shell);
+ engine = ephy_search_engine_manager_get_default_engine (manager);
+ g_assert (engine != NULL);
+
+ return ephy_search_engine_build_search_address (engine, search_key);
}
char *
ephy_embed_utils_normalize_or_autosearch_address (const char *address)
{
- if (ephy_embed_utils_address_is_valid (address))
+ EphySearchEngineManager *manager = ephy_embed_shell_get_search_engine_manager
(ephy_embed_shell_get_default ());
+ char *bang_search = ephy_search_engine_manager_parse_bang_search (manager, address);
+
+ if (bang_search)
+ return bang_search;
+ else if (ephy_embed_utils_address_is_valid (address))
return ephy_embed_utils_normalize_address (address);
else
return ephy_embed_utils_autosearch_address (address);
diff --git a/lib/ephy-search-engine-manager.c b/lib/ephy-search-engine-manager.c
index 96285a721..3729090d2 100644
--- a/lib/ephy-search-engine-manager.c
+++ b/lib/ephy-search-engine-manager.c
@@ -1,6 +1,7 @@
/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
* Copyright © 2017 Cedric Le Moigne <cedlemo gmx com>
+ * Copyright 2021 vanadiae <vanadiae35 gmail com>
*
* This file is part of Epiphany.
*
@@ -19,6 +20,7 @@
*/
#include "config.h"
+
#include "ephy-search-engine-manager.h"
#include "ephy-file-helpers.h"
@@ -27,437 +29,612 @@
#include "ephy-settings.h"
#include "ephy-prefs.h"
-#include <libsoup/soup.h>
-
-#define FALLBACK_ADDRESS "https://duckduckgo.com/?q=%s&t=epiphany"
+struct _EphySearchEngineManager {
+ GObject parent_instance;
-enum {
- SEARCH_ENGINES_CHANGED,
- LAST_SIGNAL
-};
+ GPtrArray *engines;
-static guint signals[LAST_SIGNAL];
+ EphySearchEngine *default_engine; /* unowned */
-struct _EphySearchEngineManager {
- GObject parent_instance;
- GHashTable *search_engines;
+ /* This is just to speed things up. It updates based on each search engine's
+ * notify::bang signal, so it is never out of sync because signal callbacks
+ * are called synchronously. The key is the bang, and the value is the
+ * corresponding EphySearchEngine.
+ */
+ GHashTable *bangs;
};
-typedef struct {
- char *address;
- char *bang;
-} EphySearchEngineInfo;
+static void list_model_iface_init (GListModelInterface *iface,
+ gpointer iface_data);
-G_DEFINE_TYPE (EphySearchEngineManager, ephy_search_engine_manager, G_TYPE_OBJECT)
+G_DEFINE_TYPE_WITH_CODE (EphySearchEngineManager, ephy_search_engine_manager,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
-static void
-ephy_search_engine_info_free (EphySearchEngineInfo *info)
-{
- g_free (info->address);
- g_free (info->bang);
- g_free (info);
-}
+enum {
+ PROP_0,
+ PROP_DEFAULT_ENGINE,
+ N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
-static EphySearchEngineInfo *
-ephy_search_engine_info_new (const char *address,
- const char *bang)
+static int
+search_engine_compare_func (EphySearchEngine **a,
+ EphySearchEngine **b)
{
- EphySearchEngineInfo *info;
- info = g_malloc (sizeof (EphySearchEngineInfo));
- info->address = g_strdup (address);
- info->bang = g_strdup (bang);
- return info;
+ return g_strcmp0 (ephy_search_engine_get_name (*a),
+ ephy_search_engine_get_name (*b));
}
static void
-search_engines_changed_cb (GSettings *settings,
- char *key,
- gpointer user_data)
+on_search_engine_bang_changed_cb (EphySearchEngine *engine,
+ GParamSpec *pspec,
+ EphySearchEngineManager *manager)
{
- g_signal_emit (EPHY_SEARCH_ENGINE_MANAGER (user_data),
- signals[SEARCH_ENGINES_CHANGED], 0);
+ GHashTableIter iter;
+ const char *bang;
+ EphySearchEngine *old_bang_engine;
+
+ g_hash_table_iter_init (&iter, manager->bangs);
+
+ /* We have no way of knowing what bang @engine was previously using, so
+ * we must iterate the whole bangs hash table to find @engine, remove its
+ * bang-engine pair and finally insert it back with its new bang.
+ */
+ while (g_hash_table_iter_next (&iter, (gpointer *)&bang, (gpointer *)&old_bang_engine)) {
+ if (old_bang_engine == engine) {
+ /* We found the engine by its pointer (not bang), so we remove it from the hash table. */
+ g_hash_table_iter_remove (&iter);
+ }
+ }
+
+ bang = ephy_search_engine_get_bang (engine);
+
+ /* Now that we've removed the engine from the hash table (with its old bang),
+ * we can add it back with its new value, in case its bang isn't empty.
+ */
+ if (*bang != '\0')
+ g_hash_table_insert (manager->bangs, (gpointer)bang, engine);
}
static void
-ephy_search_engine_manager_init (EphySearchEngineManager *manager)
+load_search_engines_from_settings (EphySearchEngineManager *manager)
{
g_autoptr (GVariantIter) iter = NULL;
- GVariant *search_engine;
-
- manager->search_engines = g_hash_table_new_full (g_str_hash,
- g_str_equal,
- g_free,
- (GDestroyNotify)ephy_search_engine_info_free);
+ GVariant *variant;
+ g_autofree char *default_engine_name = g_settings_get_string (EPHY_SETTINGS_MAIN,
EPHY_PREFS_DEFAULT_SEARCH_ENGINE);
g_settings_get (EPHY_SETTINGS_MAIN, EPHY_PREFS_SEARCH_ENGINES, "aa{sv}", &iter);
- while ((search_engine = g_variant_iter_next_value (iter))) {
- const char *address;
- const char *bang;
- char *name = NULL;
+ while ((variant = g_variant_iter_next_value (iter))) {
GVariantDict dict;
- EphySearchEngineInfo *info;
+ const char *name, *url, *bang;
+ g_autoptr (EphySearchEngine) search_engine = NULL;
+
+ g_variant_dict_init (&dict, variant);
+
+ /* All of those checks are just to make sure we keep our state clean and
+ * respect the non-NULL expectations.
+ */
+ if (!g_variant_dict_lookup (&dict, "name", "&s", &name))
+ name = "";
+ if (!g_variant_dict_lookup (&dict, "url", "&s", &url))
+ url = "";
+ if (!g_variant_dict_lookup (&dict, "bang", "&s", &bang))
+ bang = "";
+
+ search_engine = g_object_new (EPHY_TYPE_SEARCH_ENGINE,
+ "name", name,
+ "url", url,
+ "bang", bang,
+ NULL);
+ g_assert (EPHY_IS_SEARCH_ENGINE (search_engine));
+
+ /* Bangs are assumed to be unique, so this shouldn't happen unless GSettings
+ * are wrongly modified or we messed up input validation in the UI.
+ */
+ if (g_hash_table_lookup (manager->bangs, bang)) {
+ g_warning ("Found bang %s assigned to several search engines in GSettings."
+ "The bang for %s is hence reset to avoid collision.",
+ bang, name);
+ ephy_search_engine_set_bang (search_engine, "");
+ }
- g_variant_dict_init (&dict, search_engine);
- g_variant_dict_lookup (&dict, "url", "&s", &address);
- g_variant_dict_lookup (&dict, "bang", "&s", &bang);
- g_variant_dict_lookup (&dict, "name", "s", &name);
+ ephy_search_engine_manager_add_engine (manager, search_engine);
+ if (g_strcmp0 (ephy_search_engine_get_name (search_engine), default_engine_name) == 0)
+ ephy_search_engine_manager_set_default_engine (manager, search_engine);
- info = ephy_search_engine_info_new (address, bang);
+ g_variant_unref (variant);
+ }
- g_hash_table_insert (manager->search_engines, name, info);
+ /* Both of these conditions should never actually be encountered, unless someone
+ * messed up with GSettings manually or we did something wrong in the UI
+ * (i.e. validation code has an issue in the prefs).
+ */
+ if (G_UNLIKELY (manager->engines->len == 0)) {
+ g_settings_reset (EPHY_SETTINGS_MAIN, EPHY_PREFS_SEARCH_ENGINES);
+ g_settings_reset (EPHY_SETTINGS_MAIN, EPHY_PREFS_DEFAULT_SEARCH_ENGINE);
+ load_search_engines_from_settings (manager);
- g_variant_unref (search_engine);
+ g_warning ("Having no search engine is forbidden. Resetting to default ones instead.");
}
+ g_assert (manager->engines->len > 0);
- g_signal_connect (EPHY_SETTINGS_MAIN,
- "changed::search-engines",
- G_CALLBACK (search_engines_changed_cb), manager);
+ if (G_UNLIKELY (!manager->default_engine)) {
+ g_warning ("Could not find default search engine set in the gsettings within all available search
engines! Setting the first one as fallback.");
+ ephy_search_engine_manager_set_default_engine (manager, manager->engines->pdata[0]);
+ }
}
static void
-ephy_search_engine_manager_dispose (GObject *object)
+ephy_search_engine_manager_init (EphySearchEngineManager *manager)
{
- EphySearchEngineManager *manager = EPHY_SEARCH_ENGINE_MANAGER (object);
+ /* We don't use _new_full(), as we'll directly insert unowned bangs from
+ * ephy_search_engine_get_bang(), and the value belongs to us anyway (as part
+ * of the list store), so both don't need to be freed.
+ */
+ manager->bangs = g_hash_table_new (g_str_hash, g_str_equal);
- g_clear_pointer (&manager->search_engines, g_hash_table_destroy);
+ manager->engines = g_ptr_array_new_with_free_func (g_object_unref);
- G_OBJECT_CLASS (ephy_search_engine_manager_parent_class)->dispose (object);
+ load_search_engines_from_settings (manager);
}
static void
-ephy_search_engine_manager_class_init (EphySearchEngineManagerClass *klass)
+ephy_search_engine_manager_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
- object_class->dispose = ephy_search_engine_manager_dispose;
-
- signals[SEARCH_ENGINES_CHANGED] = g_signal_new ("changed",
- EPHY_TYPE_SEARCH_ENGINE_MANAGER,
- G_SIGNAL_RUN_LAST,
- 0,
- NULL, NULL, NULL,
- G_TYPE_NONE, 0);
+ EphySearchEngineManager *self = EPHY_SEARCH_ENGINE_MANAGER (object);
+
+ switch (prop_id) {
+ case PROP_DEFAULT_ENGINE:
+ g_value_take_object (value, ephy_search_engine_manager_get_default_engine (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
}
-EphySearchEngineManager *
-ephy_search_engine_manager_new (void)
+static void
+ephy_search_engine_manager_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
{
- return EPHY_SEARCH_ENGINE_MANAGER (g_object_new (EPHY_TYPE_SEARCH_ENGINE_MANAGER, NULL));
+ EphySearchEngineManager *self = EPHY_SEARCH_ENGINE_MANAGER (object);
+
+ switch (prop_id) {
+ case PROP_DEFAULT_ENGINE:
+ ephy_search_engine_manager_set_default_engine (self, g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
}
-const char *
-ephy_search_engine_manager_get_address (EphySearchEngineManager *manager,
- const char *name)
+static void
+ephy_search_engine_manager_finalize (GObject *object)
{
- EphySearchEngineInfo *info;
-
- info = (EphySearchEngineInfo *)g_hash_table_lookup (manager->search_engines, name);
+ EphySearchEngineManager *manager = EPHY_SEARCH_ENGINE_MANAGER (object);
- if (info)
- return info->address;
+ g_clear_pointer (&manager->bangs, g_hash_table_destroy);
+ g_clear_pointer (&manager->engines, g_ptr_array_unref);
- return NULL;
+ G_OBJECT_CLASS (ephy_search_engine_manager_parent_class)->finalize (object);
}
-const char *
-ephy_search_engine_manager_get_default_search_address (EphySearchEngineManager *manager)
+static void
+ephy_search_engine_manager_class_init (EphySearchEngineManagerClass *klass)
{
- char *name;
- const char *address;
-
- name = ephy_search_engine_manager_get_default_engine (manager);
- address = ephy_search_engine_manager_get_address (manager, name);
- g_free (name);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
- return address ? address : FALLBACK_ADDRESS;
+ object_class->finalize = ephy_search_engine_manager_finalize;
+ object_class->get_property = ephy_search_engine_manager_get_property;
+ object_class->set_property = ephy_search_engine_manager_set_property;
+
+ properties [PROP_DEFAULT_ENGINE] =
+ g_param_spec_object ("default-engine",
+ "Default search engine",
+ "The default search engine for this manager.",
+ EPHY_TYPE_SEARCH_ENGINE,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_EXPLICIT_NOTIFY));
+ g_object_class_install_properties (object_class, N_PROPS, properties);
}
-const char *
-ephy_search_engine_manager_get_bang (EphySearchEngineManager *manager,
- const char *name)
+static GType
+list_model_get_item_type (GListModel *list)
{
- EphySearchEngineInfo *info;
-
- info = (EphySearchEngineInfo *)g_hash_table_lookup (manager->search_engines, name);
-
- if (info)
- return info->bang;
-
- return NULL;
+ return EPHY_TYPE_SEARCH_ENGINE;
}
-char *
-ephy_search_engine_manager_get_default_engine (EphySearchEngineManager *manager)
+static guint
+list_model_get_n_items (GListModel *list)
{
- return g_settings_get_string (EPHY_SETTINGS_MAIN, EPHY_PREFS_DEFAULT_SEARCH_ENGINE);
+ EphySearchEngineManager *manager = EPHY_SEARCH_ENGINE_MANAGER (list);
+
+ return manager->engines->len;
}
-gboolean
-ephy_search_engine_manager_set_default_engine (EphySearchEngineManager *manager,
- const char *name)
+static gpointer
+list_model_get_item (GListModel *list,
+ guint position)
{
- if (!g_hash_table_contains (manager->search_engines, name))
- return FALSE;
+ EphySearchEngineManager *manager = EPHY_SEARCH_ENGINE_MANAGER (list);
- return g_settings_set_string (EPHY_SETTINGS_MAIN, EPHY_PREFS_DEFAULT_SEARCH_ENGINE, name);
+ if (position >= manager->engines->len)
+ return NULL;
+ else
+ return g_object_ref (manager->engines->pdata[position]);
}
-char **
-ephy_search_engine_manager_get_names (EphySearchEngineManager *manager)
+static void
+list_model_iface_init (GListModelInterface *iface,
+ gpointer iface_data)
{
- GHashTableIter iter;
- gpointer key;
- char **search_engine_names;
- guint size;
- guint i = 0;
-
- size = g_hash_table_size (manager->search_engines);
- search_engine_names = g_new0 (char *, size + 1);
-
- g_hash_table_iter_init (&iter, manager->search_engines);
-
- while (g_hash_table_iter_next (&iter, &key, NULL))
- search_engine_names[i++] = g_strdup ((char *)key);
+ iface->get_item_type = list_model_get_item_type;
+ iface->get_n_items = list_model_get_n_items;
+ iface->get_item = list_model_get_item;
+}
- return search_engine_names;
+EphySearchEngineManager *
+ephy_search_engine_manager_new (void)
+{
+ return EPHY_SEARCH_ENGINE_MANAGER (g_object_new (EPHY_TYPE_SEARCH_ENGINE_MANAGER, NULL));
}
/**
- * ephy_search_engine_manager_engine_exists:
+ * ephy_search_engine_manager_get_default_engine:
*
- * Checks if search engine @name exists in @manager.
- *
- * @manager: the #EphySearchEngineManager
- * @name: the name of the search engine
- *
- * Returns: %TRUE if the search engine was found, %FALSE otherwise.
+ * Returns: (transfer none): the default search engine for @manager.
*/
-gboolean
-ephy_search_engine_manager_engine_exists (EphySearchEngineManager *manager,
- const char *name)
-{
- return !!g_hash_table_lookup (manager->search_engines, name);
-}
-
-char **
-ephy_search_engine_manager_get_bangs (EphySearchEngineManager *manager)
+EphySearchEngine *
+ephy_search_engine_manager_get_default_engine (EphySearchEngineManager *manager)
{
- GHashTableIter iter;
- gpointer value;
- char **search_engine_bangs;
- guint size;
- guint i = 0;
-
- size = g_hash_table_size (manager->search_engines);
- search_engine_bangs = g_new0 (char *, size + 1);
-
- g_hash_table_iter_init (&iter, manager->search_engines);
+ g_assert (EPHY_IS_SEARCH_ENGINE (manager->default_engine));
- while (g_hash_table_iter_next (&iter, NULL, &value))
- search_engine_bangs[i++] = ((EphySearchEngineInfo *)value)->bang;
-
- return search_engine_bangs;
+ return manager->default_engine;
}
-static void
-ephy_search_engine_manager_apply_settings (EphySearchEngineManager *manager)
+/**
+ * ephy_search_engine_manager_set_default_engine:
+ * @engine: (transfer none): the search engine to set as default for @manager.
+ * This search engine must already be added to the search engine manager.
+ *
+ * Note that you must call ephy_search_engine_manager_save_to_settings() when
+ * appropriate to save it. It isn't done automatically because we don't save
+ * the search engines themselves on every change, as that would be pretty expensive
+ * when typing the information, so it's better if the default search engine and
+ * the search engines themselves are always kept in sync, in case there's an issue
+ * somewhere in the code where it doesn't save one part or another.
+ */
+void
+ephy_search_engine_manager_set_default_engine (EphySearchEngineManager *manager,
+ EphySearchEngine *engine)
{
- GHashTableIter iter;
- EphySearchEngineInfo *info;
- gpointer key;
- gpointer value;
- GVariantBuilder builder;
- GVariant *variant;
+ g_assert (EPHY_IS_SEARCH_ENGINE (engine));
+ /* Improper input validation if that happens in our code. */
+ g_assert (g_ptr_array_find (manager->engines, engine, NULL));
- g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
- g_hash_table_iter_init (&iter, manager->search_engines);
-
- while (g_hash_table_iter_next (&iter, &key, &value)) {
- GVariantDict dict;
-
- info = (EphySearchEngineInfo *)value;
- g_assert (key != NULL);
- g_assert (info != NULL);
- g_assert (info->address != NULL);
- g_assert (info->bang != NULL);
-
- g_variant_dict_init (&dict, NULL);
- g_variant_dict_insert (&dict, "url", "s", info->address);
- g_variant_dict_insert (&dict, "bang", "s", info->bang);
- g_variant_dict_insert (&dict, "name", "s", key);
-
- g_variant_builder_add_value (&builder, g_variant_dict_end (&dict));
- }
- variant = g_variant_builder_end (&builder);
- g_settings_set_value (EPHY_SETTINGS_MAIN, EPHY_PREFS_SEARCH_ENGINES, variant);
+ manager->default_engine = engine;
+ g_object_notify_by_pspec (G_OBJECT (manager), properties[PROP_DEFAULT_ENGINE]);
}
+/**
+ * ephy_search_engine_manager_add_engine:
+ * @engine: The search engine to add to @manager. @manager will take a reference
+ * on it.
+ *
+ * Adds search engine @engine to @manager.
+ */
void
ephy_search_engine_manager_add_engine (EphySearchEngineManager *manager,
- const char *name,
- const char *address,
- const char *bang)
+ EphySearchEngine *engine)
{
- EphySearchEngineInfo *info;
+ gboolean bang_existed = FALSE;
+ guint new_sorted_position;
- info = ephy_search_engine_info_new (address, bang);
- g_hash_table_insert (manager->search_engines, g_strdup (name), info);
- ephy_search_engine_manager_apply_settings (manager);
+ if (*ephy_search_engine_get_bang (engine) != '\0') {
+ bang_existed = !g_hash_table_insert (manager->bangs,
+ (gpointer)ephy_search_engine_get_bang (engine),
+ engine);
+ }
+ /* Programmer/validation error that doesn't properly use ephy_search_engine_manager_has_bang(). */
+ g_assert (!bang_existed);
+ g_signal_connect (engine, "notify::bang", G_CALLBACK (on_search_engine_bang_changed_cb), manager);
+
+ g_ptr_array_add (manager->engines, g_object_ref (engine));
+
+ /* It's a pity there isn't a more efficient g_ptr_array_add_sorted() function.
+ * Comparison should be fast anyway, but still.
+ */
+ g_ptr_array_sort (manager->engines, (GCompareFunc)search_engine_compare_func);
+
+ /* The engine likely will have moved in the array so we need to make sure
+ * to report the items-changed signal at the proper position.
+ */
+ g_assert (g_ptr_array_find (manager->engines, engine, &new_sorted_position));
+ g_list_model_items_changed (G_LIST_MODEL (manager),
+ new_sorted_position,
+ 0,
+ 1);
}
void
ephy_search_engine_manager_delete_engine (EphySearchEngineManager *manager,
- const char *name)
+ EphySearchEngine *engine)
{
- g_hash_table_remove (manager->search_engines, name);
- ephy_search_engine_manager_apply_settings (manager);
+ guint pos;
+ const char *bang;
+
+ /* Never allow not having a search engine, as too much relies on having one
+ * and it just doesn't make sense at all to not have one. We assert as the
+ * validation should prevent this from happening, so if it crashes then it's
+ * for a good reason and the code should be fixed.
+ */
+ g_assert (manager->engines->len > 1);
+
+ /* Removing an engine not in the manager is a programmer error. */
+ g_assert (g_ptr_array_find (manager->engines, engine, &pos));
+
+ bang = ephy_search_engine_get_bang (engine);
+ if (*bang != '\0')
+ g_hash_table_remove (manager->bangs, bang);
+
+ /* Temporary ref so that we can remove the engine, and be sure that
+ * the engine at index 0 isn't already the same as this one when
+ * setting back another engine as default one.
+ */
+ g_object_ref (engine);
+
+ g_ptr_array_remove_index (manager->engines, pos);
+
+ if (engine == manager->default_engine) {
+ g_assert (manager->engines->len != 0);
+
+ /* Just set the first search engine in the sorted array as new search engine
+ * so we're sure we'll still have a valid default search engine at any time.
+ */
+ ephy_search_engine_manager_set_default_engine (manager, manager->engines->pdata[0]);
+ }
+
+ /* Drop temporary ref. */
+ g_object_unref (engine);
+
+ g_list_model_items_changed (G_LIST_MODEL (manager), pos, 1, 0);
}
/**
- * ephy_search_engine_manager_rename:
- *
- * Renames search engine @old_name to @new_name, taking care of setting it back
- * as default search engine if needed.
+ * ephy_search_engine_manager_find_engine_by_name:
+ * @engine_name: The name of the search engine to look for.
*
- * @manager: a #EphySearchEngineManager
- * @old_name: the current name of the search engine
- * @new_name: the new name for search engine @old_name
+ * Iterates @manager and finds the first search engine with its name set to @engine_name.
+ * This is just a helper function, it isn't more efficient than iterating @manager
+ * yourself and making string comparison with the engine's name.
*
- * Returns: %FALSE if there wasn't any renaming to do (if both old and new names
- * were the same), %TRUE if the search engine was renamed.
+ * Returns: (transfer none): The #EphySearchEngine with name @engine_name if found in @manager, or %NULL if
not found.
*/
-gboolean
-ephy_search_engine_manager_rename (EphySearchEngineManager *manager,
- const char *old_name,
- const char *new_name)
+EphySearchEngine *
+ephy_search_engine_manager_find_engine_by_name (EphySearchEngineManager *manager,
+ const char *engine_name)
{
- EphySearchEngineInfo *info, *info_copy;
-
- if (g_strcmp0 (old_name, new_name) == 0)
- return FALSE;
-
- info = g_hash_table_lookup (manager->search_engines, old_name);
- g_assert_nonnull (info);
+ for (guint i = 0; i < manager->engines->len; i++) {
+ EphySearchEngine *engine = manager->engines->pdata[i];
- info_copy = ephy_search_engine_info_new (info->address, info->bang);
- g_hash_table_remove (manager->search_engines, old_name);
- g_hash_table_insert (manager->search_engines, g_strdup (new_name), info_copy);
- /* Set the search engine back as default engine if it was the default one. */
- if (g_strcmp0 (ephy_search_engine_manager_get_default_engine (manager), old_name) == 0)
- ephy_search_engine_manager_set_default_engine (manager, new_name);
- ephy_search_engine_manager_apply_settings (manager);
+ if (g_strcmp0 (ephy_search_engine_get_name (engine), engine_name) == 0)
+ return engine;
+ }
- return TRUE;
+ return NULL;
}
-void
-ephy_search_engine_manager_modify_engine (EphySearchEngineManager *manager,
- const char *name,
- const char *address,
- const char *bang)
+/**
+ * ephy_search_engine_manager_has_bang:
+ * @bang: the bang to look for
+ *
+ * Checks whether @manager has a search engine that uses @bang as shortcut bang.
+ * This is easier and more efficient than iterating manually on @manager yourself
+ * and check for the bang for each search engine, as @manager internally keeps
+ * a hash table with all used bangs.
+ *
+ * Returns: Whether @manager already has a search engine with its bang set to @bang.
+ */
+gboolean
+ephy_search_engine_manager_has_bang (EphySearchEngineManager *manager,
+ const char *bang)
{
- EphySearchEngineInfo *info;
-
- /* You can't modify a non-existant search engine. */
- g_assert (g_hash_table_contains (manager->search_engines, name));
-
- info = ephy_search_engine_info_new (address, bang);
- g_hash_table_replace (manager->search_engines,
- g_strdup (name),
- info);
- ephy_search_engine_manager_apply_settings (manager);
+ return g_hash_table_lookup (manager->bangs, bang) != NULL;
}
-const char *
-ephy_search_engine_manager_engine_from_bang (EphySearchEngineManager *manager,
- const char *bang)
+/**
+ * parse_bang_query:
+ * @search: the search with bangs to perform
+ * @choosen_bang_engine: (out): if this function returns a non %NULL value, this
+ * argument will be set to the search engine from @manager that should be used
+ * to perform the search using the search query this function returns.
+ *
+ * This is the implementation for ephy_search_engine_manager_parse_bang_search()
+ * and ephy_search_engine_manager_parse_bang_suggestions(). See the doc of the
+ * former for details on this function's behaviours.
+ *
+ * Returns: (transfer full): the search query without the bangs.
+ */
+static char *
+parse_bang_query (EphySearchEngineManager *manager,
+ const char *search,
+ EphySearchEngine **choosen_bang_engine)
{
- GHashTableIter iter;
- EphySearchEngineInfo *info;
- gpointer key;
- gpointer value;
+ g_autofree char *first_word = NULL;
+ g_autofree char *last_word = NULL;
+ g_autofree char *query_without_bangs = NULL;
- g_hash_table_iter_init (&iter, manager->search_engines);
+ /* i.e. the end of @last_word */
+ const char *last_non_space_p;
- while (g_hash_table_iter_next (&iter, &key, &value)) {
- info = (EphySearchEngineInfo *)value;
- if (g_strcmp0 (bang, info->bang) == 0)
- return (const char *)key;
- }
+ /* i.e. the start of @first_word */
+ const char *first_non_space_p;
- return NULL;
-}
+ /* Both of these are set appropriately when we discover each bang within @search. */
+ const char *query_start, *query_end;
-static char *
-ephy_search_engine_manager_replace_pattern (const char *string,
- const char *pattern,
- const char *replace)
-{
- gchar **strings;
- gchar *query_param;
- const gchar *escaped_replace;
- GString *buffer;
+ /* This one is separate from query_{start,end} because e.g. if the last word isn't
+ * a bang, then we'll want to include it so query_end will be last_non_space_p.
+ * Otherwise query_end will be space_p. */
+ const char *space_p;
+ EphySearchEngine *final_bang_engine = NULL, *bang_engine = NULL;
- strings = g_strsplit (string, pattern, -1);
- query_param = soup_form_encode ("q", replace, NULL);
- escaped_replace = query_param + 2;
+ g_assert (search != NULL);
+ if (*search == '\0')
+ return NULL;
- buffer = g_string_new (NULL);
+ last_non_space_p = search + strlen (search) - 1;
+ while (last_non_space_p != search && *last_non_space_p == ' ')
+ last_non_space_p = g_utf8_find_prev_char (search, last_non_space_p);
- for (guint i = 0; strings[i] != NULL; i++) {
- if (i > 0)
- g_string_append (buffer, escaped_replace);
+ first_non_space_p = search;
+ while (*first_non_space_p == ' ')
+ first_non_space_p = g_utf8_find_next_char (first_non_space_p, NULL);
- g_string_append (buffer, strings[i]);
- }
+ /* Means the search query is empty or is full of spaces. So not a bang search. */
+ if (last_non_space_p <= first_non_space_p)
+ return NULL;
- g_strfreev (strings);
- g_free (query_param);
+ /* There's no strrnchr() available, so must backwards iterate ourselves to
+ * find the space character between @last_non_space_p and @search's beginning
+ */
+ space_p = last_non_space_p;
+ while (space_p != search && *space_p != ' ')
+ space_p = g_utf8_find_prev_char (search, space_p);
+
+ /* This is necessary here because @last_non_space_p will point _at_ the
+ * last non space character, not _just after_ it, which is not how substring
+ * lengths are usually calculated like (since g_strndup (first_p, last_p - first_p)
+ * should work without having to use +1 all around).
+ */
+ last_non_space_p++;
+
+ /* There is a word, but only one, so it can't be a proper bang search */
+ if (space_p <= first_non_space_p)
+ return NULL;
- return g_string_free (buffer, FALSE);
-}
+ /* +1 to skip the space. */
+ last_word = g_strndup (space_p + 1, last_non_space_p - (space_p + 1));
+ bang_engine = g_hash_table_lookup (manager->bangs, last_word);
-char *
-ephy_search_engine_manager_build_search_address (EphySearchEngineManager *manager,
- const char *name,
- const char *search)
-{
- EphySearchEngineInfo *info;
+ /* Don't include the last word in the query as it's a proper bang. */
+ if (bang_engine) {
+ query_end = space_p;
+ final_bang_engine = bang_engine;
+ }
+ /* The last word isn't a bang, so include it in the query. */
+ else {
+ query_end = last_non_space_p;
+ }
- info = (EphySearchEngineInfo *)g_hash_table_lookup (manager->search_engines, name);
+ space_p = strchr (first_non_space_p, ' ');
+ first_word = g_strndup (first_non_space_p, space_p - first_non_space_p);
+ bang_engine = g_hash_table_lookup (manager->bangs, first_word);
+ if (bang_engine) {
+ /* +1 to skip the space. */
+ query_start = space_p + 1;
+
+ /* We prefer using the last typed bang (the one at the end), so that's
+ * what we'll prefer using here.
+ */
+ if (!final_bang_engine)
+ final_bang_engine = bang_engine;
+ } else {
+ /* It's not a proper bang, so we need to include it in the search query. */
+ query_start = first_non_space_p;
+ }
- if (info == NULL)
+ /* No valid bang was found for this search query, so it's not a bang search. */
+ if (!final_bang_engine)
return NULL;
- return ephy_search_engine_manager_replace_pattern (info->address, "%s", search);
+ /* Now that we've placed query_start and query_end properly depending on
+ * whether the first/last word is a valid bang, we can copy the part that
+ * doesn't include all the bangs to search this query using the search engine
+ * we found for the bang.
+ */
+ query_without_bangs = g_strndup (query_start, query_end - query_start);
+
+ *choosen_bang_engine = final_bang_engine;
+
+ return g_steal_pointer (&query_without_bangs);
}
+/**
+ * ephy_search_engine_manager_parse_bang_search:
+ *
+ * This function looks at the first and last word of @search, checks if
+ * one of them is the bang of one of the search engines in @manager, and
+ * returns the corresponding search URL as returned by ephy_search_engine_build_search_address().
+ * The last word will be looked at first, so that when someone changes their
+ * mind at the end of the line they can just type the new bang and it will
+ * be used instead of the first one.
+ *
+ * What is called a "bang search" is a search of the form "!bang this is the
+ * search query", or with the bang at the end or at both ends (in which case
+ * the end bang will be preferred).
+ *
+ * Returns: (transfer full) (nullable): The search URL corresponding to @search, with
+ * the search engine picked using the bang available in @search, or %NULL if
+ * there wasn't any recognized bang engine in @search. As such this function can
+ * also be used as a way of detecting whether @search is a "bang search", to
+ * process the search using the default search engine in that case.
+ */
char *
ephy_search_engine_manager_parse_bang_search (EphySearchEngineManager *manager,
const char *search)
{
- GHashTableIter iter;
- EphySearchEngineInfo *info;
- gpointer value;
- GString *buffer;
- char *search_address = NULL;
-
- g_hash_table_iter_init (&iter, manager->search_engines);
-
- while (g_hash_table_iter_next (&iter, NULL, &value)) {
- info = (EphySearchEngineInfo *)value;
- buffer = g_string_new (info->bang);
- g_string_append (buffer, " ");
- if (strstr (search, buffer->str) == search) {
- search_address = ephy_search_engine_manager_replace_pattern (info->address,
- "%s",
- (search + buffer->len));
- g_string_free (buffer, TRUE);
- return search_address;
- }
- g_string_free (buffer, TRUE);
+ EphySearchEngine *engine = NULL;
+ g_autofree char *no_bangs_query = parse_bang_query (manager, search, &engine);
+
+ if (no_bangs_query)
+ return ephy_search_engine_build_search_address (engine, no_bangs_query);
+ else
+ return NULL;
+}
+
+/**
+ * ephy_search_engine_manager_save_to_settings:
+ *
+ * Saves the search engines and the default search engine to the GSettings.
+ *
+ * You must call this function after having done the changes (e.g. when closing
+ * the preferences window).
+ */
+void
+ephy_search_engine_manager_save_to_settings (EphySearchEngineManager *manager)
+{
+ GVariantBuilder builder;
+ GVariant *variant;
+ gpointer item;
+ guint i = 0;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
+
+ while ((item = g_list_model_get_item (G_LIST_MODEL (manager), i++))) {
+ g_autoptr (EphySearchEngine) engine = EPHY_SEARCH_ENGINE (item);
+ GVariantDict dict;
+
+ g_assert (EPHY_IS_SEARCH_ENGINE (engine));
+
+ g_variant_dict_init (&dict, NULL);
+ g_variant_dict_insert (&dict, "name", "s", ephy_search_engine_get_name (engine));
+ g_variant_dict_insert (&dict, "url", "s", ephy_search_engine_get_url (engine));
+ g_variant_dict_insert (&dict, "bang", "s", ephy_search_engine_get_bang (engine));
+
+ g_variant_builder_add_value (&builder, g_variant_dict_end (&dict));
}
+ variant = g_variant_builder_end (&builder);
+ g_settings_set_value (EPHY_SETTINGS_MAIN, EPHY_PREFS_SEARCH_ENGINES, variant);
- return search_address;
+ g_settings_set_value (EPHY_SETTINGS_MAIN, EPHY_PREFS_DEFAULT_SEARCH_ENGINE,
+ g_variant_new_string (ephy_search_engine_get_name (manager->default_engine)));
}
diff --git a/lib/ephy-search-engine-manager.h b/lib/ephy-search-engine-manager.h
index 9222c7675..c1939d31f 100644
--- a/lib/ephy-search-engine-manager.h
+++ b/lib/ephy-search-engine-manager.h
@@ -24,6 +24,8 @@
#include <glib-object.h>
#include <glib/gi18n.h>
+#include "ephy-search-engine.h"
+
G_BEGIN_DECLS
/* TRANSLATORS: Please modify the main address of duckduckgo in order to match
@@ -36,39 +38,20 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (EphySearchEngineManager, ephy_search_engine_manager, EPHY, SEARCH_ENGINE_MANAGER,
GObject)
-EphySearchEngineManager *ephy_search_engine_manager_new (void);
-const char *ephy_search_engine_manager_get_address (EphySearchEngineManager
*manager,
- const char
*name);
-const char *ephy_search_engine_manager_get_default_search_address
- (EphySearchEngineManager
*manager);
-const char *ephy_search_engine_manager_get_bang (EphySearchEngineManager
*manager,
- const char
*name);
-char *ephy_search_engine_manager_get_default_engine (EphySearchEngineManager
*manager);
-gboolean ephy_search_engine_manager_set_default_engine (EphySearchEngineManager
*manager,
- const char
*name);
-char **ephy_search_engine_manager_get_names (EphySearchEngineManager
*manager);
-gboolean ephy_search_engine_manager_engine_exists (EphySearchEngineManager
*manager,
- const char
*name);
-char **ephy_search_engine_manager_get_bangs (EphySearchEngineManager
*manager);
-void ephy_search_engine_manager_add_engine (EphySearchEngineManager
*manager,
- const char
*name,
- const char
*address,
- const char
*bang);
-void ephy_search_engine_manager_delete_engine (EphySearchEngineManager
*manager,
- const char
*name);
-gboolean ephy_search_engine_manager_rename (EphySearchEngineManager
*manager,
- const char
*old_name,
- const char
*new_name);
-void ephy_search_engine_manager_modify_engine (EphySearchEngineManager
*manager,
- const char
*name,
- const char
*address,
- const char
*bang);
-const char *ephy_search_engine_manager_engine_from_bang (EphySearchEngineManager
*manager,
- const char
*bang);
-char *ephy_search_engine_manager_build_search_address (EphySearchEngineManager
*manager,
- const char
*name,
- const char
*search);
-char *ephy_search_engine_manager_parse_bang_search (EphySearchEngineManager
*manager,
- const char
*search);
+EphySearchEngineManager *ephy_search_engine_manager_new (void);
+EphySearchEngine *ephy_search_engine_manager_get_default_engine (EphySearchEngineManager *manager);
+void ephy_search_engine_manager_set_default_engine (EphySearchEngineManager *manager,
+ EphySearchEngine *engine);
+void ephy_search_engine_manager_add_engine (EphySearchEngineManager *manager,
+ EphySearchEngine *engine);
+void ephy_search_engine_manager_delete_engine (EphySearchEngineManager *manager,
+ EphySearchEngine *engine);
+EphySearchEngine *ephy_search_engine_manager_find_engine_by_name (EphySearchEngineManager *manager,
+ const char
*engine_name);
+gboolean ephy_search_engine_manager_has_bang (EphySearchEngineManager *manager,
+ const char *bang);
+char *ephy_search_engine_manager_parse_bang_search (EphySearchEngineManager *manager,
+ const char *search);
+void ephy_search_engine_manager_save_to_settings (EphySearchEngineManager *manager);
G_END_DECLS
diff --git a/lib/ephy-search-engine.c b/lib/ephy-search-engine.c
new file mode 100644
index 000000000..9e73764d9
--- /dev/null
+++ b/lib/ephy-search-engine.c
@@ -0,0 +1,244 @@
+/* ephy-search-engine.c
+ *
+ * Copyright 2021 vanadiae <vanadiae35 gmail 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+
+#include "ephy-search-engine.h"
+
+#include <libsoup/soup.h>
+
+struct _EphySearchEngine {
+ GObject parent_instance;
+
+ char *name;
+ char *url;
+ char *bang;
+};
+
+G_DEFINE_FINAL_TYPE (EphySearchEngine, ephy_search_engine, G_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_NAME,
+ PROP_URL,
+ PROP_BANG,
+ N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
+
+const char *
+ephy_search_engine_get_name (EphySearchEngine *self)
+{
+ return self->name;
+}
+
+void
+ephy_search_engine_set_name (EphySearchEngine *self,
+ const char *name)
+{
+ g_assert (name);
+
+ if (g_strcmp0 (name, self->name) == 0)
+ return;
+
+ g_free (self->name);
+ self->name = g_strdup (name);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_NAME]);
+}
+
+const char *
+ephy_search_engine_get_url (EphySearchEngine *self)
+{
+ return self->url;
+}
+
+void
+ephy_search_engine_set_url (EphySearchEngine *self,
+ const char *url)
+{
+ g_assert (url);
+
+ if (g_strcmp0 (url, self->url) == 0)
+ return;
+
+ g_free (self->url);
+ self->url = g_strdup (url);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_URL]);
+}
+
+const char *
+ephy_search_engine_get_bang (EphySearchEngine *self)
+{
+ return self->bang;
+}
+
+void
+ephy_search_engine_set_bang (EphySearchEngine *self,
+ const char *bang)
+{
+ g_assert (bang);
+
+ if (g_strcmp0 (bang, self->bang) == 0)
+ return;
+
+ g_free (self->bang);
+ self->bang = g_strdup (bang);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BANG]);
+}
+
+static void
+ephy_search_engine_finalize (GObject *object)
+{
+ EphySearchEngine *self = (EphySearchEngine *)object;
+
+ g_clear_pointer (&self->name, g_free);
+ g_clear_pointer (&self->url, g_free);
+ g_clear_pointer (&self->bang, g_free);
+
+ G_OBJECT_CLASS (ephy_search_engine_parent_class)->finalize (object);
+}
+
+static void
+ephy_search_engine_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EphySearchEngine *self = EPHY_SEARCH_ENGINE (object);
+
+ switch (prop_id) {
+ case PROP_NAME:
+ g_value_set_string (value, ephy_search_engine_get_name (self));
+ break;
+ case PROP_URL:
+ g_value_set_string (value, ephy_search_engine_get_url (self));
+ break;
+ case PROP_BANG:
+ g_value_set_string (value, ephy_search_engine_get_bang (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ephy_search_engine_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EphySearchEngine *self = EPHY_SEARCH_ENGINE (object);
+
+ switch (prop_id) {
+ case PROP_NAME:
+ ephy_search_engine_set_name (self, g_value_get_string (value));
+ break;
+ case PROP_URL:
+ ephy_search_engine_set_url (self, g_value_get_string (value));
+ break;
+ case PROP_BANG:
+ ephy_search_engine_set_bang (self, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ephy_search_engine_class_init (EphySearchEngineClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = ephy_search_engine_finalize;
+ object_class->get_property = ephy_search_engine_get_property;
+ object_class->set_property = ephy_search_engine_set_property;
+
+ properties [PROP_NAME] =
+ g_param_spec_string ("name",
+ "Name",
+ "Name",
+ "",
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_EXPLICIT_NOTIFY));
+ properties [PROP_URL] =
+ g_param_spec_string ("url",
+ "Url",
+ "The search URL with %s placeholder for this search engine.",
+ "",
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_EXPLICIT_NOTIFY));
+ properties [PROP_BANG] =
+ g_param_spec_string ("bang",
+ "Bang",
+ "The search shortcut (bang) for this search engine.",
+ "",
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_EXPLICIT_NOTIFY));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ephy_search_engine_init (EphySearchEngine *self)
+{
+ /* Default values set with the GParamSpec aren't actually set at the end
+ * of the GObject construction process, so we must ensure all properties
+ * we expect to be non NULL to be kept that way, as we want to allow
+ * safely omitting properties when using g_object_new().
+ */
+ self->name = g_strdup ("");
+ self->url = g_strdup ("");
+ self->bang = g_strdup ("");
+}
+
+static char *
+replace_placeholder (const char *url,
+ const char *search_query)
+{
+ GString *s = g_string_new (url);
+ g_autofree char *encoded_query = soup_form_encode ("q", search_query, NULL);
+
+ /* libsoup requires us to pass a field name to get the HTML-form encoded
+ * search query. But since we don't require that the search URL has the
+ * q= before the placeholder, just skip q= and use the encoded query
+ * directly.
+ */
+ g_string_replace (s, "%s", encoded_query + strlen ("q="), 0);
+
+ return g_string_free (s, FALSE);
+}
+
+/**
+ * ephy_search_engine_build_search_address:
+ * @self: an #EphySearchEngine
+ * @search_query: The search query to be used in the search URL.
+ *
+ * Returns: (transfer full): @self's search URL with all the %s placeholders
+ * replaced with @search_query.
+ */
+char *
+ephy_search_engine_build_search_address (EphySearchEngine *self,
+ const char *search_query)
+{
+ return replace_placeholder (self->url, search_query);
+}
diff --git a/lib/ephy-search-engine.h b/lib/ephy-search-engine.h
new file mode 100644
index 000000000..877def8a2
--- /dev/null
+++ b/lib/ephy-search-engine.h
@@ -0,0 +1,51 @@
+/* ephy-search-engine.h
+ *
+ * Copyright 2021 vanadiae <vanadiae35 gmail 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+
+#pragma once
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_SEARCH_ENGINE (ephy_search_engine_get_type())
+
+G_DECLARE_FINAL_TYPE (EphySearchEngine, ephy_search_engine, EPHY, SEARCH_ENGINE, GObject)
+
+/* It's intended that there's no ephy_search_engine_new() as that just can't be
+ * general enough to cover all the cases where you'd create an engine. So instead,
+ * just use g_object_new() with the properties you already have available for the
+ * new search engine, and all other properties will
+ * have a reasonable default value (i.e. empty or NULL).
+ */
+
+const char *ephy_search_engine_get_name (EphySearchEngine *self);
+void ephy_search_engine_set_name (EphySearchEngine *self,
+ const char *name);
+const char *ephy_search_engine_get_url (EphySearchEngine *self);
+void ephy_search_engine_set_url (EphySearchEngine *self,
+ const char *url);
+const char *ephy_search_engine_get_bang (EphySearchEngine *self);
+void ephy_search_engine_set_bang (EphySearchEngine *self,
+ const char *bang);
+char *ephy_search_engine_build_search_address (EphySearchEngine *self,
+ const char *search_query);
+
+G_END_DECLS
diff --git a/lib/meson.build b/lib/meson.build
index 264f9c5fb..a8d774757 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -23,6 +23,7 @@ libephymisc_sources = [
'ephy-output-encoding.c',
'ephy-permissions-manager.c',
'ephy-profile-utils.c',
+ 'ephy-search-engine.c',
'ephy-search-engine-manager.c',
'ephy-security-levels.c',
'ephy-settings.c',
diff --git a/src/ephy-suggestion-model.c b/src/ephy-suggestion-model.c
index 101dcb7d4..3299e2db6 100644
--- a/src/ephy-suggestion-model.c
+++ b/src/ephy-suggestion-model.c
@@ -320,24 +320,25 @@ add_search_engines (EphySuggestionModel *self,
{
EphyEmbedShell *shell;
EphySearchEngineManager *manager;
- char **engines;
- guint added = 0;
+ guint added;
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++) {
+ for (added = 0; added < g_list_model_get_n_items (G_LIST_MODEL (manager)); added++) {
+ g_autoptr (EphySearchEngine) engine = g_list_model_get_item (G_LIST_MODEL (manager), added);
EphySuggestion *suggestion;
+ const char *engine_name;
g_autofree char *address = NULL;
g_autofree char *escaped_title = NULL;
g_autofree char *markup = NULL;
g_autoptr (GUri) uri = NULL;
- address = ephy_search_engine_manager_build_search_address (manager, engines[i], query);
- escaped_title = g_markup_escape_text (engines[i], -1);
+ engine_name = ephy_search_engine_get_name (engine);
+ address = ephy_search_engine_build_search_address (engine, query);
+ escaped_title = g_markup_escape_text (engine_name, -1);
markup = dzl_fuzzy_highlight (escaped_title, query, FALSE);
- suggestion = ephy_suggestion_new (markup, engines[i], address);
+ suggestion = ephy_suggestion_new (markup, engine_name, address);
uri = g_uri_parse (address, G_URI_FLAGS_NONE, NULL);
if (uri) {
@@ -348,13 +349,11 @@ add_search_engines (EphySuggestionModel *self,
load_favicon (self, suggestion, address);
g_sequence_append (self->items, suggestion);
- added++;
}
- g_strfreev (engines);
-
return added;
}
+
typedef struct {
char *query;
char scope;
@@ -656,7 +655,7 @@ google_search_suggestions_cb (SoupSession *session,
JsonNode *node;
JsonArray *array;
JsonArray *suggestions;
- char *engine;
+ EphySearchEngine *engine;
int added = 0;
g_autoptr (GBytes) body = NULL;
#if SOUP_CHECK_VERSION (2, 99, 4)
@@ -698,11 +697,13 @@ google_search_suggestions_cb (SoupSession *session,
g_autofree char *address = NULL;
g_autofree char *escaped_title = NULL;
g_autofree char *markup = NULL;
+ const char *engine_name;
- address = ephy_search_engine_manager_build_search_address (manager, engine, str);
+ address = ephy_search_engine_build_search_address (engine, str);
escaped_title = g_markup_escape_text (str, -1);
markup = dzl_fuzzy_highlight (escaped_title, str, FALSE);
- suggestion = ephy_suggestion_new (markup, engine, address);
+ engine_name = ephy_search_engine_get_name (engine);
+ suggestion = ephy_suggestion_new (markup, engine_name, address);
g_sequence_append (data->google_suggestions, g_steal_pointer (&suggestion));
added++;
diff --git a/src/ephy-window.c b/src/ephy-window.c
index ab8caedf5..0bc8be18d 100644
--- a/src/ephy-window.c
+++ b/src/ephy-window.c
@@ -4031,16 +4031,15 @@ ephy_window_location_search (EphyWindow *window)
GtkApplication *gtk_application = gtk_window_get_application (GTK_WINDOW (window));
EphyEmbedShell *embed_shell = EPHY_EMBED_SHELL (gtk_application);
EphySearchEngineManager *search_engine_manager = ephy_embed_shell_get_search_engine_manager (embed_shell);
- char *search_engine_name = ephy_search_engine_manager_get_default_engine (search_engine_manager);
- const char *search_engine_bang = ephy_search_engine_manager_get_bang (search_engine_manager,
search_engine_name);
- char *entry_text = g_strconcat (search_engine_bang, " ", NULL);
+ EphySearchEngine *default_engine = ephy_search_engine_manager_get_default_engine (search_engine_manager);
+ const char *bang = ephy_search_engine_get_bang (default_engine);
+ char *entry_text = g_strconcat (bang, " ", NULL);
gtk_window_set_focus (GTK_WINDOW (window), GTK_WIDGET (location_gtk_entry));
gtk_entry_set_text (location_gtk_entry, entry_text);
gtk_editable_set_position (GTK_EDITABLE (location_gtk_entry), strlen (entry_text));
g_free (entry_text);
- g_free (search_engine_name);
}
/**
diff --git a/src/preferences/ephy-prefs-dialog.c b/src/preferences/ephy-prefs-dialog.c
index 6089b0860..29addb886 100644
--- a/src/preferences/ephy-prefs-dialog.c
+++ b/src/preferences/ephy-prefs-dialog.c
@@ -24,10 +24,11 @@
#include "clear-data-view.h"
#include "ephy-data-view.h"
+#include "ephy-embed-shell.h"
#include "ephy-embed-utils.h"
#include "ephy-gui.h"
#include "ephy-prefs-dialog.h"
-#include "ephy-settings.h"
+#include "ephy-search-engine-manager.h"
#include "passwords-view.h"
#include "prefs-general-page.h"
@@ -61,7 +62,11 @@ on_delete_event (EphyPrefsDialog *prefs_dialog)
{
prefs_general_page_on_pd_delete_event (prefs_dialog->general_page);
gtk_widget_destroy (GTK_WIDGET (prefs_dialog));
- g_settings_apply (EPHY_SETTINGS_MAIN);
+
+ /* To avoid any unnecessary IO when typing changes in the search engine
+ * list row's entries, only save when closing the prefs dialog.
+ */
+ ephy_search_engine_manager_save_to_settings (ephy_embed_shell_get_search_engine_manager
(ephy_embed_shell_get_default ()));
}
static void
@@ -133,6 +138,4 @@ ephy_prefs_dialog_init (EphyPrefsDialog *dialog)
gtk_window_set_icon_name (GTK_WINDOW (dialog), APPLICATION_ID);
ephy_gui_ensure_window_group (GTK_WINDOW (dialog));
-
- g_settings_delay (EPHY_SETTINGS_MAIN);
}
diff --git a/src/preferences/ephy-search-engine-listbox.c b/src/preferences/ephy-search-engine-listbox.c
index bdf407b3f..0f0788b5f 100644
--- a/src/preferences/ephy-search-engine-listbox.c
+++ b/src/preferences/ephy-search-engine-listbox.c
@@ -26,14 +26,165 @@
#include "embed/ephy-embed-shell.h"
#include "ephy-search-engine-manager.h"
+#define EMPTY_NEW_SEARCH_ENGINE_NAME (_("New search engine"))
+
+
+/* Goes with _EphyAddEngineButtonMergedModel. Used as a way of detecting if this
+ * is the list model item where we should be creating the "Add search engine" row
+ * instead of the normal EphySearchEngineRow.
+ */
+#define EPHY_TYPE_ADD_SEARCH_ENGINE_ROW_ITEM (ephy_add_search_engine_row_item_get_type ())
+
+G_DECLARE_FINAL_TYPE (EphyAddSearchEngineRowItem, ephy_add_search_engine_row_item, EPHY,
ADD_SEARCH_ENGINE_ROW_ITEM, GObject)
+
+struct _EphyAddSearchEngineRowItem {
+ GObject parent_instance;
+};
+
+G_DEFINE_TYPE (EphyAddSearchEngineRowItem, ephy_add_search_engine_row_item, G_TYPE_OBJECT)
+
+static void ephy_add_search_engine_row_item_class_init (EphyAddSearchEngineRowItemClass *klass) {}
+
+static void ephy_add_search_engine_row_item_init (EphyAddSearchEngineRowItem *self) {}
+
+/* This model is only needed because we want to use gtk_list_box_bind_model()
+ * while having a "Add search engine" row. In GTK4 we could get our way out
+ * using GtkFlattenListModel and 1-item GListStore for the outer and inner (i.e.
+ * the list model that only contains one item to indicate "this is an "Add search engine" row")
+ * required list models. But we're not GTK4 yet, so we need to proxy all list model
+ * calls appropriately to the EphySearchEngineManager one ourselves.
+ */
+#define EPHY_TYPE_ADD_ENGINE_BUTTON_MERGED_MODEL (ephy_add_engine_button_merged_model_get_type ())
+
+G_DECLARE_FINAL_TYPE (EphyAddEngineButtonMergedModel, ephy_add_engine_button_merged_model, EPHY,
ADD_ENGINE_BUTTON_MERGED_MODEL, GObject)
+
+struct _EphyAddEngineButtonMergedModel {
+ GObject parent_instance;
+
+ GListModel *model;
+ EphyAddSearchEngineRowItem *add_engine_row_item;
+};
+
+static void list_model_iface_init (GListModelInterface *iface,
+ gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (EphyAddEngineButtonMergedModel, ephy_add_engine_button_merged_model, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
+
+static void
+inner_model_items_changed_cb (GListModel *list,
+ guint position,
+ guint removed,
+ guint added,
+ gpointer user_data)
+{
+ EphyAddEngineButtonMergedModel *self = user_data;
+
+ /* Since we place our custom item at the end of the list model, we can pass
+ * items-changed informations unchanged.
+ */
+ g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
+}
+
+static GType
+list_model_get_item_type_func (GListModel *list)
+{
+ /* Yes, we're doing this so that EPHY_IS_SEARCH_ENGINE() and
+ * EPHY_IS_ADD_SEARCH_ENGINE_ROW_ITEM() will work later.
+ */
+ return G_TYPE_OBJECT;
+}
+
+static guint
+list_model_get_n_items_func (GListModel *list)
+{
+ EphyAddEngineButtonMergedModel *self = (gpointer)list;
+
+ /* +1 for the "add search engine" row placeholder object. */
+ return g_list_model_get_n_items (self->model) + 1;
+}
+
+static gpointer
+list_model_get_item_func (GListModel *list,
+ guint position)
+{
+ EphyAddEngineButtonMergedModel *self = (gpointer)list;
+
+ gpointer item = g_list_model_get_item (self->model, position);
+
+ if (item)
+ return item;
+ else {
+ guint n_items = g_list_model_get_n_items (self->model);
+
+ /* Normally, n_items is the length of the list model, so it can't have
+ * an associated item in the list model. So when we reach that case,
+ * return our "add search engine" row item. Else, we're out of the
+ * n_items + 1 range and so we must return NULL to indicate the end
+ * of the list model.
+ */
+ if (position == n_items)
+ return g_object_ref (self->add_engine_row_item);
+ else
+ return NULL;
+ }
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface,
+ gpointer iface_data)
+{
+ iface->get_item_type = list_model_get_item_type_func;
+ iface->get_n_items = list_model_get_n_items_func;
+ iface->get_item = list_model_get_item_func;
+}
+
+static void
+ephy_add_engine_button_merged_model_finalize (GObject *object)
+{
+ EphyAddEngineButtonMergedModel *self = (gpointer)object;
+
+ g_clear_object (&self->add_engine_row_item);
+ self->model = NULL;
+
+ G_OBJECT_CLASS (ephy_add_engine_button_merged_model_parent_class)->finalize (object);
+}
+
+static void
+ephy_add_engine_button_merged_model_class_init (EphyAddEngineButtonMergedModelClass *klass)
+{
+ GObjectClass *o_class = G_OBJECT_CLASS (klass);
+
+ o_class->finalize = ephy_add_engine_button_merged_model_finalize;
+}
+
+static void
+ephy_add_engine_button_merged_model_init (EphyAddEngineButtonMergedModel *self)
+{
+ self->model = G_LIST_MODEL (ephy_embed_shell_get_search_engine_manager (ephy_embed_shell_get_default ()));
+ self->add_engine_row_item = g_object_new (EPHY_TYPE_ADD_SEARCH_ENGINE_ROW_ITEM, NULL);
+
+ g_signal_connect_object (self->model, "items-changed", G_CALLBACK (inner_model_items_changed_cb), self, 0);
+}
+
struct _EphySearchEngineListBox {
GtkListBox parent_instance;
/* This widget isn't actually showed anywhere. It is just a stable place where we can add more radio
buttons without having to bother if the primary radio button gets removed. */
GtkWidget *radio_buttons_group;
-
GtkWidget *add_search_engine_row;
+
+ EphySearchEngine *empty_new_search_engine;
EphySearchEngineManager *manager;
+
+ EphyAddEngineButtonMergedModel *wrapper_model;
+
+ /* Used as a flag to avoid expanding the newly created row (which is our
+ * default behaviour). It'll only be set to TRUE after the model has been
+ * bound to the list box. This avoids having to iterate the list box to
+ * unexpand all rows after having expanded them all.
+ */
+ gboolean is_model_initially_loaded;
};
G_DEFINE_TYPE (EphySearchEngineListBox, ephy_search_engine_list_box, GTK_TYPE_LIST_BOX)
@@ -44,23 +195,61 @@ ephy_search_engine_list_box_new (void)
return g_object_new (EPHY_TYPE_SEARCH_ENGINE_LIST_BOX, NULL);
}
-/**
- * ephy_search_engine_listbox_set_can_add_engine:
- *
- * Sets whether the "Add search engine" row of @self is sensitive.
- *
- * @self: a #EphySearchEngineListBox
- * @can_add_engine: whether the user can add new search engines to @self
- */
-void
-ephy_search_engine_list_box_set_can_add_engine (EphySearchEngineListBox *self,
- gboolean can_add_engine)
+static void
+on_search_engine_name_changed_cb (EphySearchEngine *engine,
+ GParamSpec *pspec,
+ EphySearchEngineListBox *self)
{
- gtk_widget_set_sensitive (self->add_search_engine_row, can_add_engine);
+ const char *name = ephy_search_engine_get_name (engine);
+
+ /* If that's the empty search engine, then we keep it internally marked
+ * as "this was the empty search engine", since we don't have a way
+ * of knowing the previous name in notify:: callbacks. That won't be an
+ * issue even if someone tries naming another search engine with the same
+ * EMPTY_NEW_SEARCH_ENGINE_NAME, as the row entry's validation prevents
+ * this from happening (it checks if there's already a search engine with
+ * that name before setting this particular engine's name in the manager).
+ */
+ if (g_strcmp0 (name, EMPTY_NEW_SEARCH_ENGINE_NAME) == 0) {
+ self->empty_new_search_engine = engine;
+ gtk_widget_set_sensitive (self->add_search_engine_row, FALSE);
+ }
+ /* This search engine was the only "new empty" one, and it is no longer
+ * the "new empty" search engine, so allow adding a new search engine again.
+ */
+ else if (engine == self->empty_new_search_engine &&
+ g_strcmp0 (name, EMPTY_NEW_SEARCH_ENGINE_NAME) != 0) {
+ self->empty_new_search_engine = NULL;
+ gtk_widget_set_sensitive (self->add_search_engine_row, TRUE);
+ }
}
-/***** Private *****/
+static void
+on_list_box_manager_items_changed_cb (GListModel *list,
+ guint position,
+ guint removed,
+ guint added,
+ gpointer user_data)
+{
+ EphySearchEngineListBox *self = EPHY_SEARCH_ENGINE_LIST_BOX (user_data);
+ EphySearchEngineManager *manager = EPHY_SEARCH_ENGINE_MANAGER (list);
+
+ /* This callback is mostly only called when a search engine has been removed
+ * (potentially the new empty one), when clicking the Add Search Engine button
+ * (in which case we'll want to make the button insensitive), or when initially
+ * loading all the search engines. In all those cases, we check if we have the
+ * "empty new search engine" and update the Add Search Engine's button sensitivity
+ * based on it.
+ */
+ self->empty_new_search_engine = ephy_search_engine_manager_find_engine_by_name (manager,
EMPTY_NEW_SEARCH_ENGINE_NAME);
+ gtk_widget_set_sensitive (self->add_search_engine_row,
+ self->empty_new_search_engine == NULL);
+}
+
+/* This signal unexpands all other rows of the list box except the row
+ * that just got expanded.
+ */
static void
on_row_expand_state_changed_cb (EphySearchEngineRow *expanded_row,
GParamSpec *pspec,
@@ -87,82 +276,99 @@ on_row_expand_state_changed_cb (EphySearchEngineRow *expanded_row,
}
}
-/**
- * append_search_engine_row:
- *
- * Creates a new row showing search engine @engine_name, and adds
- * it to @search_engine_list_box.
- *
- * @search_engine_list_box: an #EphySearchEngineListBox
- * @engine_name: the name of an already existing engine in @search_engine_list_box->manager which will be
presented as a new row
- *
- * Returns: the newly added row.
- */
-static EphySearchEngineRow *
-append_search_engine_row (EphySearchEngineListBox *list_box,
- const char *engine_name)
+static void
+on_add_search_engine_row_clicked_cb (EphySearchEngineListBox *self,
+ GtkListBoxRow *clicked_row,
+ gpointer user_data)
{
- EphySearchEngineRow *new_row = ephy_search_engine_row_new (engine_name);
+ g_autoptr (EphySearchEngine) empty_engine = NULL;
- gtk_list_box_prepend (GTK_LIST_BOX (list_box),
- GTK_WIDGET (new_row));
- ephy_search_engine_row_set_radio_button_group (new_row,
- GTK_RADIO_BUTTON (list_box->radio_buttons_group));
- g_signal_connect (new_row,
- "notify::expanded",
- G_CALLBACK (on_row_expand_state_changed_cb),
- list_box);
+ /* Sanity check. Expander rows aren't supposed to be activable=True. */
+ g_assert ((gpointer)clicked_row == (gpointer)self->add_search_engine_row);
- return new_row;
+ empty_engine = g_object_new (EPHY_TYPE_SEARCH_ENGINE,
+ "name", EMPTY_NEW_SEARCH_ENGINE_NAME,
+ "url", "https://www.example.com/search?q=%s",
+ NULL);
+ ephy_search_engine_manager_add_engine (self->manager, empty_engine);
+
+ /* In on_search_engine_name_changed_cb above, we set the Add search engine
+ * row's sensitivity based on whether there is an engine named EMPTY_NEW_SEARCH_ENGINE_NAME.
+ */
}
-static void
-on_add_search_engine_row_clicked_cb (EphySearchEngineListBox *search_engine_list_box,
- GtkListBoxRow *add_search_engine_row,
- gpointer user_data)
+static GtkWidget *
+create_add_search_engine_row ()
{
- GtkWidget *search_engine_row;
+ GtkWidget *row = gtk_list_box_row_new ();
+ GtkWidget *label = gtk_label_new_with_mnemonic (_("A_dd Search Engine…"));
- g_assert (add_search_engine_row == GTK_LIST_BOX_ROW (search_engine_list_box->add_search_engine_row));
+ gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), true);
+ gtk_widget_set_size_request (row, -1, 50);
+ gtk_widget_show (row);
- /* Allow to remove the row if it was alone */
- if (gtk_list_box_get_row_at_index (GTK_LIST_BOX (search_engine_list_box), 2) == NULL)
- ephy_search_engine_row_set_can_remove (EPHY_SEARCH_ENGINE_ROW (gtk_list_box_get_row_at_index
(GTK_LIST_BOX (search_engine_list_box), 0)),
- TRUE);
- ephy_search_engine_manager_add_engine (search_engine_list_box->manager,
- EMPTY_NEW_SEARCH_ENGINE_NAME,
- "",
- "");
- search_engine_row = GTK_WIDGET (append_search_engine_row (search_engine_list_box,
EMPTY_NEW_SEARCH_ENGINE_NAME));
- hdy_expander_row_set_expanded (HDY_EXPANDER_ROW (search_engine_row), TRUE);
- /* Only allow one empty search engine to be created. This row will be sensitive again when the empty row
is renamed. */
- gtk_widget_set_sensitive (GTK_WIDGET (add_search_engine_row), FALSE);
+ gtk_widget_show (label);
+ gtk_container_add (GTK_CONTAINER (row), label);
+
+ return row;
}
-static void
-ephy_search_engine_list_box_finalize (GObject *object)
+static GtkWidget *
+create_search_engine_row (EphySearchEngine *engine,
+ EphySearchEngineListBox *self)
{
- EphySearchEngineListBox *self = (EphySearchEngineListBox *)object;
+ EphySearchEngineRow *row = ephy_search_engine_row_new (engine, self->manager);
- g_clear_pointer (&self->radio_buttons_group, g_object_unref);
+ g_signal_connect_object (engine, "notify::name", G_CALLBACK (on_search_engine_name_changed_cb), self, 0);
- G_OBJECT_CLASS (ephy_search_engine_list_box_parent_class)->finalize (object);
+ ephy_search_engine_row_set_radio_button_group (row,
+ GTK_RADIO_BUTTON (self->radio_buttons_group));
+ g_signal_connect (row,
+ "notify::expanded",
+ G_CALLBACK (on_row_expand_state_changed_cb),
+ self);
+
+ /* This check ensures we don't try expanding all rows when we initially bind
+ * the model to the list box.
+ */
+ if (self->is_model_initially_loaded) {
+ /* This will also unexpand all other rows, to make the new one stand out,
+ * in on_row_expand_state_changed_cb().
+ */
+ hdy_expander_row_set_expanded (HDY_EXPANDER_ROW (row), TRUE);
+ }
+
+ return GTK_WIDGET (row);
+}
+
+static GtkWidget *
+list_box_create_row_func (gpointer item,
+ gpointer user_data)
+{
+ EphySearchEngineListBox *self = EPHY_SEARCH_ENGINE_LIST_BOX (user_data);
+
+ g_assert (item != NULL);
+
+ if (EPHY_IS_SEARCH_ENGINE (item)) {
+ EphySearchEngine *engine = item;
+ return create_search_engine_row (engine, self);
+ } else if (EPHY_IS_ADD_SEARCH_ENGINE_ROW_ITEM (item)) {
+ self->add_search_engine_row = create_add_search_engine_row ();
+ return self->add_search_engine_row;
+ } else {
+ g_assert_not_reached ();
+ }
}
static void
-populate_search_engine_list_box (EphySearchEngineListBox *self)
+ephy_search_engine_list_box_finalize (GObject *object)
{
- g_auto (GStrv) engine_names = ephy_search_engine_manager_get_names (self->manager);
- g_autofree char *default_engine = ephy_search_engine_manager_get_default_engine (self->manager);
+ EphySearchEngineListBox *self = (EphySearchEngineListBox *)object;
- for (guint i = 0; engine_names[i] != NULL; ++i) {
- EphySearchEngineRow *row = append_search_engine_row (self, engine_names[i]);
- if (g_strcmp0 (engine_names[i], default_engine) == 0)
- ephy_search_engine_row_set_as_default (row);
- }
+ g_clear_object (&self->radio_buttons_group);
+ g_clear_object (&self->wrapper_model);
- if (ephy_search_engine_manager_engine_exists (self->manager, EMPTY_NEW_SEARCH_ENGINE_NAME))
- gtk_widget_set_sensitive (self->add_search_engine_row, FALSE);
+ G_OBJECT_CLASS (ephy_search_engine_list_box_parent_class)->finalize (object);
}
static void
@@ -175,7 +381,6 @@ ephy_search_engine_list_box_class_init (EphySearchEngineListBoxClass *klass)
gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/epiphany/gtk/search-engine-listbox.ui");
- gtk_widget_class_bind_template_child (widget_class, EphySearchEngineListBox, add_search_engine_row);
gtk_widget_class_bind_template_callback (widget_class, on_add_search_engine_row_clicked_cb);
}
@@ -193,12 +398,30 @@ ephy_search_engine_list_box_init (EphySearchEngineListBox *self)
*/
g_object_ref_sink (self->radio_buttons_group);
- gtk_list_box_set_sort_func (GTK_LIST_BOX (self), ephy_search_engine_row_get_sort_func (), NULL, NULL);
- gtk_list_box_invalidate_sort (GTK_LIST_BOX (self));
-
- populate_search_engine_list_box (self);
-
- /* The list box should have at least one "Add search engine" row and one search engine (the default one).
*/
- if (gtk_list_box_get_row_at_index (GTK_LIST_BOX (self), 2) == NULL)
- ephy_search_engine_row_set_can_remove (EPHY_SEARCH_ENGINE_ROW (gtk_list_box_get_row_at_index
(GTK_LIST_BOX (self), 0)), FALSE);
+ self->wrapper_model = g_object_new (EPHY_TYPE_ADD_ENGINE_BUTTON_MERGED_MODEL, NULL);
+ self->is_model_initially_loaded = FALSE;
+ gtk_list_box_bind_model (GTK_LIST_BOX (self),
+ G_LIST_MODEL (self->wrapper_model),
+ (GtkListBoxCreateWidgetFunc)list_box_create_row_func,
+ self, NULL);
+ self->is_model_initially_loaded = TRUE;
+
+ /* When the row's radio button gets parented all the way up to the window,
+ * it seems like GTK sets one of the radio button in the group as clicked,
+ * but messes things up somewhere. Whatever we do to click or not click this
+ * particular radio button when creating our row widget depending on whether
+ * it is the default engine, all the rows end up not "ticked". To circumvent
+ * this, just trick the manager into sending a dummy notify:: signal so that
+ * the row which matches the default engine updates its own radio button state.
+ * This is the cleanest way I found to workaround the issue.
+ */
+ ephy_search_engine_manager_set_default_engine (self->manager,
+ ephy_search_engine_manager_get_default_engine
(self->manager));
+
+ on_list_box_manager_items_changed_cb (G_LIST_MODEL (self->manager),
+ 0,
+ 0,
+ g_list_model_get_n_items (G_LIST_MODEL (self->manager)),
+ self);
+ g_signal_connect_object (self->manager, "items-changed", G_CALLBACK
(on_list_box_manager_items_changed_cb), self, 0);
}
diff --git a/src/preferences/ephy-search-engine-listbox.h b/src/preferences/ephy-search-engine-listbox.h
index 125d3bb8b..624901f31 100644
--- a/src/preferences/ephy-search-engine-listbox.h
+++ b/src/preferences/ephy-search-engine-listbox.h
@@ -23,8 +23,6 @@
#include <glib-object.h>
#include <gtk/gtk.h>
-#define EMPTY_NEW_SEARCH_ENGINE_NAME (_("New search engine"))
-
G_BEGIN_DECLS
#define EPHY_TYPE_SEARCH_ENGINE_LIST_BOX (ephy_search_engine_list_box_get_type())
@@ -33,7 +31,4 @@ G_DECLARE_FINAL_TYPE (EphySearchEngineListBox, ephy_search_engine_list_box, EPHY
GtkWidget *ephy_search_engine_list_box_new (void);
-void ephy_search_engine_list_box_set_can_add_engine (EphySearchEngineListBox *self,
- gboolean can_add_engine);
-
G_END_DECLS
diff --git a/src/preferences/ephy-search-engine-row.c b/src/preferences/ephy-search-engine-row.c
index 4b76469d7..257c8486c 100644
--- a/src/preferences/ephy-search-engine-row.c
+++ b/src/preferences/ephy-search-engine-row.c
@@ -39,13 +39,7 @@ struct _EphySearchEngineRow {
GtkWidget *remove_button;
GtkWidget *radio_button;
- /* This is only used to be able to rename the old search engine with a new name,
- * and to access the search engine's informations stored in the @manager.
- * It is always a valid name.
- */
- char *saved_name;
- /* This is the name that was previously in the entry. Use this only from on_name_entry_text_changed_cb() */
- char *previous_name;
+ EphySearchEngine *engine;
EphySearchEngineManager *manager;
};
@@ -53,7 +47,8 @@ G_DEFINE_TYPE (EphySearchEngineRow, ephy_search_engine_row, HDY_TYPE_EXPANDER_RO
enum {
PROP_0,
- PROP_SEARCH_ENGINE_NAME,
+ PROP_SEARCH_ENGINE,
+ PROP_MANAGER,
N_PROPS
};
@@ -63,65 +58,25 @@ static GParamSpec *properties[N_PROPS];
/**
* ephy_search_engine_row_new:
+ * @search_engine: the search engine to show. This search engine must already
+ * exist in @manager.
+ * @manager: The search engine manager to which @search_engine belongs.
*
- * Creates a new #EphySearchEngineRow showing @search_engine_name engine informations.
- *
- * @search_engine_name: the name of the search engine to show.
- * This search engine must already exist in the default search engine manager.
+ * Creates a new #EphySearchEngineRow showing @search_engine informations and
+ * allowing to edit them.
*
* Returns: a newly created #EphySearchEngineRow
*/
EphySearchEngineRow *
-ephy_search_engine_row_new (const char *search_engine_name)
+ephy_search_engine_row_new (EphySearchEngine *engine,
+ EphySearchEngineManager *manager)
{
return g_object_new (EPHY_TYPE_SEARCH_ENGINE_ROW,
- "search-engine-name", search_engine_name,
+ "search-engine", engine,
+ "manager", manager,
NULL);
}
-static int
-sort_search_engine_list_box_cb (EphySearchEngineRow *first_row,
- EphySearchEngineRow *second_row,
- gpointer user_data)
-{
- g_autofree char *first_row_name = NULL;
- g_autofree char *second_row_name = NULL;
-
- /* Place the "add search engine" row at the end.
- * This row isn't an expander row, only a regular row.
- */
- if (!EPHY_IS_SEARCH_ENGINE_ROW (first_row))
- return 1;
- if (!EPHY_IS_SEARCH_ENGINE_ROW (second_row))
- return -1;
-
- first_row_name = g_utf8_casefold (first_row->saved_name, -1);
- second_row_name = g_utf8_casefold (second_row->saved_name, -1);
-
- return g_strcmp0 (first_row_name, second_row_name);
-}
-
-GtkListBoxSortFunc
-ephy_search_engine_row_get_sort_func (void)
-{
- return (GtkListBoxSortFunc)sort_search_engine_list_box_cb;
-}
-
-/**
- * ephy_search_engine_row_set_can_remove:
- *
- * Sets whether the Remove button of @self is sensitive.
- *
- * @self: an #EphySearchEngineRow
- * @can_remove: whether the user can click the @self's Remove button
- */
-void
-ephy_search_engine_row_set_can_remove (EphySearchEngineRow *self,
- gboolean can_remove)
-{
- gtk_widget_set_sensitive (self->remove_button, can_remove);
-}
-
/**
* ephy_search_engine_row_set_radio_button_group:
*
@@ -138,47 +93,8 @@ ephy_search_engine_row_set_radio_button_group (EphySearchEngineRow *self,
gtk_radio_button_get_group (radio_button_group));
}
-/**
- * ephy_search_engine_row_set_as_default:
- *
- * Sets this search engine represented by @self as the default engine for
- * the default search engine manager. In practice, it toggles the default engine radio button.
- *
- * @self: an #EphySearchEngineRow
- */
-void
-ephy_search_engine_row_set_as_default (EphySearchEngineRow *self)
-{
- gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->radio_button), TRUE);
-}
-
/***** Private implementation *****/
-static gboolean
-search_engine_already_exists (EphySearchEngineRow *searched_row,
- const char *engine_name)
-{
- GList *children = gtk_container_get_children (GTK_CONTAINER (gtk_widget_get_parent (GTK_WIDGET
(searched_row))));
-
- for (; children->next != NULL; children = children->next) {
- EphySearchEngineRow *iterated_row;
-
- /* As it iterates on the whole list box, this function will run on the "add search engine" row, which
isn't an EphySearchEngineRow. */
- if (!EPHY_IS_SEARCH_ENGINE_ROW (children->data))
- continue;
-
- iterated_row = EPHY_SEARCH_ENGINE_ROW (children->data);
-
- if (iterated_row == searched_row)
- continue;
-
- if (g_strcmp0 (iterated_row->saved_name, engine_name) == 0)
- return TRUE;
- }
-
- return FALSE;
-}
-
/**
* validate_search_engine_address:
*
@@ -268,10 +184,10 @@ on_bang_entry_text_changed_cb (EphySearchEngineRow *row,
GtkEntry *bang_entry)
{
const char *bang = gtk_entry_get_text (bang_entry);
- const char *engine_from_bang = ephy_search_engine_manager_engine_from_bang (row->manager, bang);
/* Checks if the bang already exists */
- if (engine_from_bang && g_strcmp0 (engine_from_bang, row->saved_name) != 0) {
+ if (g_strcmp0 (bang, ephy_search_engine_get_bang (row->engine)) != 0 &&
+ ephy_search_engine_manager_has_bang (row->manager, bang)) {
set_entry_as_invalid (bang_entry, _("This shortcut is already used."));
} else if (strchr (bang, ' ') != NULL) {
set_entry_as_invalid (bang_entry, _("Search shortcuts must not contain any space."));
@@ -285,10 +201,7 @@ on_bang_entry_text_changed_cb (EphySearchEngineRow *row,
set_entry_as_invalid (bang_entry, _("Search shortcuts should start with a symbol such as !, # or @."));
} else {
set_entry_as_valid (bang_entry);
- ephy_search_engine_manager_modify_engine (row->manager,
- row->saved_name,
- ephy_search_engine_manager_get_address (row->manager,
row->saved_name),
- gtk_entry_get_text (bang_entry));
+ ephy_search_engine_set_bang (row->engine, bang);
}
}
@@ -298,17 +211,14 @@ on_address_entry_text_changed_cb (EphySearchEngineRow *row,
GtkEntry *address_entry)
{
const char *validation_message = NULL;
+ const char *url = gtk_entry_get_text (address_entry);
/* Address in invalid. */
- if (!validate_search_engine_address (gtk_entry_get_text (address_entry), &validation_message)) {
+ if (!validate_search_engine_address (url, &validation_message)) {
set_entry_as_invalid (address_entry, validation_message);
} else { /* Address in valid. */
set_entry_as_valid (address_entry);
- ephy_search_engine_manager_modify_engine (row->manager,
- row->saved_name,
- gtk_entry_get_text (address_entry),
- ephy_search_engine_manager_get_bang (row->manager,
- row->saved_name));
+ ephy_search_engine_set_url (row->engine, url);
}
}
@@ -401,10 +311,7 @@ update_bang_for_name (EphySearchEngineRow *row,
lowercase_acronym = g_utf8_strdown (acronym, -1); /* Bangs are usually lowercase */
final_bang = g_strconcat ("!", lowercase_acronym, NULL); /* "!" is the prefix for the bang */
gtk_entry_set_text (GTK_ENTRY (row->bang_entry), final_bang);
- ephy_search_engine_manager_modify_engine (row->manager,
- row->saved_name,
- ephy_search_engine_manager_get_address (row->manager,
row->saved_name),
- gtk_entry_get_text (GTK_ENTRY (row->bang_entry)));
+ ephy_search_engine_set_bang (row->engine, final_bang);
}
static void
@@ -412,7 +319,6 @@ on_name_entry_text_changed_cb (EphySearchEngineRow *row,
GParamSpec *pspec,
GtkEntry *name_entry)
{
- EphySearchEngineListBox *search_engine_list_box = EPHY_SEARCH_ENGINE_LIST_BOX (gtk_widget_get_parent
(GTK_WIDGET (row)));
const char *new_name = gtk_entry_get_text (name_entry);
/* This is an edge case when you copy the whole name then paste it again in
@@ -420,30 +326,17 @@ on_name_entry_text_changed_cb (EphySearchEngineRow *row,
* if the name didn't actually change. This could toggle the entry as invalid
* because the engine would already exist, so don't go any further in this case.
*/
- if (g_strcmp0 (row->previous_name, new_name) == 0)
+ if (g_strcmp0 (ephy_search_engine_get_name (row->engine), new_name) == 0)
return;
- g_free (row->previous_name);
- row->previous_name = g_strdup (new_name);
-
- hdy_preferences_row_set_title (HDY_PREFERENCES_ROW (row), new_name);
-
- if (g_strcmp0 (new_name, EMPTY_NEW_SEARCH_ENGINE_NAME) == 0)
- ephy_search_engine_list_box_set_can_add_engine (search_engine_list_box, FALSE);
-
/* Name validation. */
if (g_strcmp0 (new_name, "") == 0) {
set_entry_as_invalid (name_entry, _("A name is required"));
- } else if (search_engine_already_exists (row, new_name)) {
+ } else if (ephy_search_engine_manager_find_engine_by_name (row->manager, new_name)) {
set_entry_as_invalid (name_entry, _("This search engine already exists"));
} else {
set_entry_as_valid (name_entry);
- /* This allows the user to add new search engine again once it is renamed. */
- if (g_strcmp0 (row->saved_name, EMPTY_NEW_SEARCH_ENGINE_NAME) == 0 &&
- g_strcmp0 (new_name, EMPTY_NEW_SEARCH_ENGINE_NAME) != 0)
- ephy_search_engine_list_box_set_can_add_engine (search_engine_list_box, TRUE);
-
/* Let's not overwrite any existing bang, as that's likely not what is wanted.
* For example when I wanted to rename my "wiktionary en" search engine that
* had the !wte bang, it replaced the bang with !we, which is the one for
@@ -453,73 +346,47 @@ on_name_entry_text_changed_cb (EphySearchEngineRow *row,
if (g_strcmp0 (gtk_entry_get_text (GTK_ENTRY (row->bang_entry)), "") == 0)
update_bang_for_name (row, new_name);
- ephy_search_engine_manager_rename (row->manager,
- row->saved_name,
- new_name);
- g_free (row->saved_name);
- row->saved_name = g_strdup (new_name);
+ ephy_search_engine_set_name (row->engine, new_name);
}
}
static void
-on_radio_button_clicked_cb (EphySearchEngineRow *row,
- GtkButton *button)
+on_radio_button_active_changed_cb (EphySearchEngineRow *self,
+ GParamSpec *pspec,
+ GtkButton *button)
{
- /* This avoids having some random engines being set as default when adding a new row,
- * since when it default initialize the "active" property to %FALSE on object construction,
- * it records a "clicked" signal
- */
- if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
- ephy_search_engine_manager_set_default_engine (row->manager, row->saved_name);
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) &&
+ /* Avoid infinite loop between this callback and on_default_engine_changed_cb() */
+ ephy_search_engine_manager_get_default_engine (self->manager) != self->engine)
+ ephy_search_engine_manager_set_default_engine (self->manager, self->engine);
+}
+
+static void
+on_default_engine_changed_cb (EphySearchEngineManager *manager,
+ GParamSpec *pspec,
+ EphySearchEngineRow *self)
+{
+ if (ephy_search_engine_manager_get_default_engine (manager) == self->engine)
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->radio_button), TRUE);
}
static void
on_remove_button_clicked_cb (EphySearchEngineRow *row,
GtkButton *button)
{
- EphySearchEngineRow *top_row;
- g_autofree char *default_engine = ephy_search_engine_manager_get_default_engine (row->manager);
- GtkListBox *parent_list_box = GTK_LIST_BOX (gtk_widget_get_parent (GTK_WIDGET (row)));
-
- /* Temporarly ref the row, as we'll remove it from its parent container
- * but will still use some struct members of it.
- */
- g_object_ref (row);
-
- ephy_search_engine_manager_delete_engine (row->manager,
- row->saved_name);
-
/* FIXME: this should be fixed in libhandy
* Unexpand the row before removing it so the styling isn't broken.
* See the checked-expander-row-previous-sibling style class in HdyExpanderRow documentation.
*/
hdy_expander_row_set_expanded (HDY_EXPANDER_ROW (row), FALSE);
- if (!search_engine_already_exists (row, row->saved_name))
- ephy_search_engine_list_box_set_can_add_engine (EPHY_SEARCH_ENGINE_LIST_BOX (parent_list_box),
- TRUE);
-
- gtk_container_remove (GTK_CONTAINER (parent_list_box), GTK_WIDGET (row));
-
- top_row = EPHY_SEARCH_ENGINE_ROW (gtk_list_box_get_row_at_index (parent_list_box, 0));
- /* Set an other row (the first one) as default search engine to replace this one (if it was the default
one). */
- if (g_strcmp0 (default_engine,
- row->saved_name) == 0)
- ephy_search_engine_row_set_as_default (top_row);
- if (gtk_list_box_get_row_at_index (parent_list_box, 2) == NULL)
- gtk_widget_set_sensitive (top_row->remove_button, FALSE);
-
- /* Drop the temporary reference */
- g_object_unref (row);
+ ephy_search_engine_manager_delete_engine (row->manager, row->engine);
}
static void
ephy_search_engine_row_finalize (GObject *object)
{
- EphySearchEngineRow *self = (EphySearchEngineRow *)object;
-
- g_free (self->saved_name);
- g_free (self->previous_name);
+ /* EphySearchEngineRow *self = (EphySearchEngineRow *)object; */
G_OBJECT_CLASS (ephy_search_engine_row_parent_class)->finalize (object);
}
@@ -533,42 +400,65 @@ ephy_search_engine_row_set_property (GObject *object,
EphySearchEngineRow *self = EPHY_SEARCH_ENGINE_ROW (object);
switch (prop_id) {
- case PROP_SEARCH_ENGINE_NAME:
- g_free (self->saved_name);
- self->saved_name = g_value_dup_string (value);
- g_free (self->previous_name);
- self->previous_name = g_value_dup_string (value);
+ case PROP_SEARCH_ENGINE:
+ self->engine = g_value_get_object (value);
+ break;
+ case PROP_MANAGER:
+ self->manager = g_value_get_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
+static void
+on_manager_items_changed_cb (EphySearchEngineManager *manager,
+ guint position,
+ guint removed,
+ guint added,
+ EphySearchEngineRow *self)
+{
+ guint n_items = g_list_model_get_n_items (G_LIST_MODEL (manager));
+
+ /* We don't allow removing the engine if it's the last one, as it
+ * doesn't make sense at all and just too much relies on having a
+ * search engine available.
+ */
+ gtk_widget_set_sensitive (self->remove_button, n_items > 1);
+}
+
static void
on_ephy_search_engine_row_constructed (GObject *object)
{
EphySearchEngineRow *self = EPHY_SEARCH_ENGINE_ROW (object);
- g_autofree char *default_search_engine_name = ephy_search_engine_manager_get_default_engine
(self->manager);
- g_assert (self->saved_name != NULL);
- g_assert (g_strcmp0 (self->previous_name, self->saved_name) == 0);
+ g_assert (self->engine != NULL);
+ g_assert (self->manager != NULL);
- gtk_entry_set_text (GTK_ENTRY (self->name_entry), self->saved_name);
- hdy_preferences_row_set_title (HDY_PREFERENCES_ROW (self), self->saved_name);
+ gtk_entry_set_text (GTK_ENTRY (self->name_entry), ephy_search_engine_get_name (self->engine));
+
+ /* We can't directly bind that in the UI file because there's issues with
+ * properties bindings that involve the root widget (the <template> root one).
+ */
+ g_object_bind_property (self->name_entry, "text",
+ HDY_PREFERENCES_ROW (self), "title",
+ G_BINDING_SYNC_CREATE | G_BINDING_DEFAULT);
gtk_entry_set_text (GTK_ENTRY (self->address_entry),
- ephy_search_engine_manager_get_address (self->manager, self->saved_name));
+ ephy_search_engine_get_url (self->engine));
gtk_entry_set_text (GTK_ENTRY (self->bang_entry),
- ephy_search_engine_manager_get_bang (self->manager, self->saved_name));
-
- /* Tick the radio button if it's the default search engine. */
- if (g_strcmp0 (self->saved_name, default_search_engine_name) == 0)
- ephy_search_engine_row_set_as_default (self);
+ ephy_search_engine_get_bang (self->engine));
g_signal_connect_object (self->name_entry, "notify::text", G_CALLBACK (on_name_entry_text_changed_cb),
self, G_CONNECT_SWAPPED);
g_signal_connect_object (self->address_entry, "notify::text", G_CALLBACK
(on_address_entry_text_changed_cb), self, G_CONNECT_SWAPPED);
g_signal_connect_object (self->bang_entry, "notify::text", G_CALLBACK (on_bang_entry_text_changed_cb),
self, G_CONNECT_SWAPPED);
+ on_manager_items_changed_cb (self->manager, 0, 0, g_list_model_get_n_items (G_LIST_MODEL (self->manager)),
self);
+ g_signal_connect_object (self->manager, "items-changed", G_CALLBACK (on_manager_items_changed_cb), self,
0);
+
+ on_default_engine_changed_cb (self->manager, NULL, self);
+ g_signal_connect_object (self->manager, "notify::default-engine", G_CALLBACK
(on_default_engine_changed_cb), self, 0);
+
G_OBJECT_CLASS (ephy_search_engine_row_parent_class)->constructed (object);
}
@@ -582,11 +472,16 @@ ephy_search_engine_row_class_init (EphySearchEngineRowClass *klass)
object_class->set_property = ephy_search_engine_row_set_property;
object_class->constructed = on_ephy_search_engine_row_constructed;
- properties[PROP_SEARCH_ENGINE_NAME] = g_param_spec_string ("search-engine-name",
- "search-engine-name",
- "The name of the search engine",
- NULL,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
G_PARAM_STATIC_STRINGS);
+ properties[PROP_SEARCH_ENGINE] = g_param_spec_object ("search-engine",
+ "search-engine",
+ "The search engine that this row should show and
allow to edit.",
+ EPHY_TYPE_SEARCH_ENGINE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
G_PARAM_STATIC_STRINGS);
+ properties[PROP_MANAGER] = g_param_spec_object ("manager",
+ "manager",
+ "The search engine manager that manages @search-engine.",
+ EPHY_TYPE_SEARCH_ENGINE_MANAGER,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, N_PROPS, properties);
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/epiphany/gtk/search-engine-row.ui");
@@ -597,14 +492,12 @@ ephy_search_engine_row_class_init (EphySearchEngineRowClass *klass)
gtk_widget_class_bind_template_child (widget_class, EphySearchEngineRow, bang_entry);
gtk_widget_class_bind_template_child (widget_class, EphySearchEngineRow, remove_button);
- gtk_widget_class_bind_template_callback (widget_class, on_radio_button_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, on_radio_button_active_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, on_remove_button_clicked_cb);
}
static void
ephy_search_engine_row_init (EphySearchEngineRow *self)
{
- self->manager = ephy_embed_shell_get_search_engine_manager (ephy_embed_shell_get_default ());
-
gtk_widget_init_template (GTK_WIDGET (self));
}
diff --git a/src/preferences/ephy-search-engine-row.h b/src/preferences/ephy-search-engine-row.h
index 4e5da5555..2257a9c30 100644
--- a/src/preferences/ephy-search-engine-row.h
+++ b/src/preferences/ephy-search-engine-row.h
@@ -22,6 +22,7 @@
#pragma once
#include <handy.h>
+#include "ephy-search-engine-manager.h"
G_BEGIN_DECLS
@@ -29,12 +30,11 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (EphySearchEngineRow, ephy_search_engine_row, EPHY, SEARCH_ENGINE_ROW, HdyExpanderRow)
-EphySearchEngineRow *ephy_search_engine_row_new (const char *search_engine_name);
-GtkListBoxSortFunc ephy_search_engine_row_get_sort_func (void);
+EphySearchEngineRow *ephy_search_engine_row_new (EphySearchEngine *engine,
+ EphySearchEngineManager *manager);
void ephy_search_engine_row_set_can_remove (EphySearchEngineRow *self,
gboolean can_remove);
void ephy_search_engine_row_set_radio_button_group (EphySearchEngineRow *self,
GtkRadioButton *radio_button_group);
-void ephy_search_engine_row_set_as_default (EphySearchEngineRow *self);
G_END_DECLS
diff --git a/src/resources/gtk/search-engine-listbox.ui b/src/resources/gtk/search-engine-listbox.ui
index 094819bb5..89091aea3 100644
--- a/src/resources/gtk/search-engine-listbox.ui
+++ b/src/resources/gtk/search-engine-listbox.ui
@@ -5,20 +5,6 @@
<property name="visible">True</property>
<property name="selection-mode">none</property>
<signal name="row-activated" handler="on_add_search_engine_row_clicked_cb"/>
- <child>
- <object class="GtkListBoxRow" id="add_search_engine_row">
- <property name="visible">True</property>
- <property name="activatable">True</property>
- <property name="height-request">50</property>
- <child>
- <object class="GtkLabel">
- <property name="visible">True</property>
- <property name="label" translatable="yes">A_dd Search Engine…</property>
- <property name="use-underline">True</property>
- </object>
- </child>
- </object>
- </child>
<style>
<class name="content"/>
</style>
diff --git a/src/resources/gtk/search-engine-row.ui b/src/resources/gtk/search-engine-row.ui
index 2656603bf..fa41f5f7b 100644
--- a/src/resources/gtk/search-engine-row.ui
+++ b/src/resources/gtk/search-engine-row.ui
@@ -8,7 +8,7 @@
<property name="visible">True</property>
<property name="valign">center</property>
<property name="tooltip-text" translatable="yes">Selects the default search engine</property>
- <signal name="clicked" handler="on_radio_button_clicked_cb" object="EphySearchEngineRow"
swapped="yes"/>
+ <signal name="notify::active" handler="on_radio_button_active_changed_cb"
object="EphySearchEngineRow" swapped="yes"/>
</object>
</child>
<child>
diff --git a/src/search-provider/ephy-search-provider.c b/src/search-provider/ephy-search-provider.c
index fdbb37ef5..305d31d48 100644
--- a/src/search-provider/ephy-search-provider.c
+++ b/src/search-provider/ephy-search-provider.c
@@ -277,25 +277,10 @@ launch_search (EphySearchProvider *self,
guint timestamp)
{
g_autofree char *search_string = NULL;
- g_autofree char *query_param = NULL;
g_autofree char *effective_url = NULL;
- const char *address_search;
- EphyEmbedShell *shell;
- EphySearchEngineManager *search_engine_manager;
-
- shell = ephy_embed_shell_get_default ();
- search_engine_manager = ephy_embed_shell_get_search_engine_manager (shell);
- address_search = ephy_search_engine_manager_get_default_search_address (search_engine_manager);
search_string = g_strjoinv (" ", terms);
- query_param = soup_form_encode ("q", search_string, NULL);
-
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wformat-nonliteral"
- /* Format string under control of user input... but gsettings is trusted input. */
- /* + 2 here is getting rid of 'q=' */
- effective_url = g_strdup_printf (address_search, query_param + 2);
-#pragma GCC diagnostic pop
+ effective_url = ephy_embed_utils_autosearch_address (search_string);
launch_uri (effective_url, timestamp);
}
diff --git a/tests/ephy-web-view-test.c b/tests/ephy-web-view-test.c
index 3c359237e..1cc18770b 100644
--- a/tests/ephy-web-view-test.c
+++ b/tests/ephy-web-view-test.c
@@ -320,37 +320,35 @@ verify_normalize_or_autosearch_urls (EphyWebView *view,
static void
test_ephy_web_view_normalize_or_autosearch (void)
{
- char *default_engine;
EphyWebView *view;
EphySearchEngineManager *manager;
EphyEmbedShell *shell;
-
+ EphySearchEngine *default_engine;
+ g_autoptr (EphySearchEngine) test_engine = NULL;
view = EPHY_WEB_VIEW (ephy_web_view_new ());
shell = ephy_embed_shell_get_default ();
manager = ephy_embed_shell_get_search_engine_manager (shell);
-
default_engine = ephy_search_engine_manager_get_default_engine (manager);
- ephy_search_engine_manager_add_engine (manager,
- "org.gnome.Epiphany.EphyWebViewTest",
- "http://duckduckgo.com/?q=%s&t=epiphany",
- "");
- g_assert_true (ephy_search_engine_manager_set_default_engine (manager,
"org.gnome.Epiphany.EphyWebViewTest"));
+ test_engine = g_object_new (EPHY_TYPE_SEARCH_ENGINE,
+ "name", "org.gnome.Epiphany.EphyWebViewTest",
+ "url", "http://duckduckgo.com/?q=%s&t=epiphany",
+ NULL);
+ ephy_search_engine_manager_add_engine (manager, test_engine);
+ ephy_search_engine_manager_set_default_engine (manager, test_engine);
+ g_assert_true (ephy_search_engine_manager_get_default_engine (manager) == test_engine);
verify_normalize_or_autosearch_urls (view, normalize_or_autosearch_test_ddg, G_N_ELEMENTS
(normalize_or_autosearch_test_ddg));
- ephy_search_engine_manager_modify_engine (manager,
- "org.gnome.Epiphany.EphyWebViewTest",
- "http://www.google.com/?q=%s",
- "");
+ ephy_search_engine_set_url (test_engine, "http://www.google.com/?q=%s");
verify_normalize_or_autosearch_urls (view, normalize_or_autosearch_test_google, G_N_ELEMENTS
(normalize_or_autosearch_test_google));
- ephy_search_engine_manager_delete_engine (manager, "org.gnome.Epiphany.EphyWebViewTest");
+ ephy_search_engine_manager_delete_engine (manager, test_engine);
- g_assert_true (ephy_search_engine_manager_set_default_engine (manager, default_engine));
- g_free (default_engine);
+ ephy_search_engine_manager_set_default_engine (manager, default_engine);
+ g_assert_true (ephy_search_engine_manager_get_default_engine (manager) == default_engine);
g_object_unref (g_object_ref_sink (view));
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]