diff options
author | Jens Georg <mail@jensge.org> | 2020-07-18 18:51:03 +0200 |
---|---|---|
committer | Jens Georg <mail@jensge.org> | 2020-07-18 18:55:10 +0200 |
commit | 18c4a5fdfc341cd4d88116105a4836d46d4f12c8 (patch) | |
tree | bff2e9d000aec2a970979c38adbc100b29f50aff | |
parent | 59a5b38947a61ad0d1f50ef4face08849132383f (diff) | |
download | gupnp-tools-18c4a5fdfc341cd4d88116105a4836d46d4f12c8.tar.gz |
av-cp: Provide suggestions for search entry
-rw-r--r-- | src/av-cp/entry-completion.c | 184 | ||||
-rw-r--r-- | src/av-cp/entry-completion.h | 15 | ||||
-rw-r--r-- | src/av-cp/meson.build | 5 | ||||
-rw-r--r-- | src/av-cp/search-dialog.c | 6 |
4 files changed, 209 insertions, 1 deletions
diff --git a/src/av-cp/entry-completion.c b/src/av-cp/entry-completion.c new file mode 100644 index 0000000..7c9aaba --- /dev/null +++ b/src/av-cp/entry-completion.c @@ -0,0 +1,184 @@ +#include "entry-completion.h" + +static void +entry_completion_constructed (GObject *self); + +struct _EntryCompletion +{ + GtkEntryCompletion parent_instance; + + GtkListStore *store; +}; + +G_DEFINE_TYPE (EntryCompletion, entry_completion, GTK_TYPE_ENTRY_COMPLETION) + +enum { + PROP_0, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +EntryCompletion * +entry_completion_new (void) +{ + return g_object_new (ENTRY_TYPE_COMPLETION, NULL); +} + +static void +entry_completion_finalize (GObject *object) +{ + G_OBJECT_CLASS (entry_completion_parent_class)->finalize (object); +} + +static void +entry_completion_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +entry_completion_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) + { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static gboolean +entry_completion_on_match_selected (GtkEntryCompletion *self, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data) +{ + char *match = NULL; + GtkEntry *entry = gtk_entry_completion_get_entry (self); + gtk_tree_model_get (model, iter, 0, &match, -1); + + char *old_text = g_strdup (gtk_entry_get_text (entry)); + char *needle = strrchr (old_text, ' '); + if (needle != NULL) { + needle++; + *needle = '\0'; + } + + char *new_text = g_strconcat (needle == NULL ? "" : old_text, match, NULL); + gtk_entry_set_text (entry, new_text); + gtk_editable_set_position (GTK_EDITABLE (entry), strlen (new_text)); + g_free (new_text); + g_free (old_text); + + return TRUE; +} + +static void +entry_completion_class_init (EntryCompletionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = entry_completion_constructed; + object_class->finalize = entry_completion_finalize; + object_class->get_property = entry_completion_get_property; + object_class->set_property = entry_completion_set_property; + + GtkEntryCompletionClass *entry_class = GTK_ENTRY_COMPLETION_CLASS (klass); + entry_class->match_selected = entry_completion_on_match_selected; +} + +static gboolean +entry_completion_on_match (GtkEntryCompletion *self, const char *key, GtkTreeIter *iter, gpointer user_data) +{ + GtkTreeModel *model = gtk_entry_completion_get_model (self); + char *candidate; + GtkEntry *entry = gtk_entry_completion_get_entry (self); + gboolean retval = FALSE; + + gtk_tree_model_get (model, iter, 0, &candidate, -1); + + char *needle = strrchr (key, ' '); + if (needle != NULL) { + if ((needle - key)> gtk_editable_get_position (GTK_EDITABLE (entry))) { + g_print ("Position wrong\n"); + goto out; + } + needle++; + } else { + needle = key; + } + + if (strlen(needle) == 0) { + goto out; + } + + retval = g_str_has_prefix (candidate, needle); + +out: + g_free (candidate); + + return retval; +} + +static void +entry_completion_init (EntryCompletion *self) +{ +} + +static void +entry_completion_constructed (GObject *object) +{ + EntryCompletion *self = ENTRY_COMPLETION (object); + + if (G_OBJECT_CLASS (entry_completion_parent_class)->constructed != NULL) { + G_OBJECT_CLASS (entry_completion_parent_class)->constructed (G_OBJECT (self)); + } + + gtk_entry_completion_set_match_func (GTK_ENTRY_COMPLETION (self), + entry_completion_on_match, + NULL, + NULL); + + self->store = gtk_list_store_new (1, G_TYPE_STRING); + + gtk_entry_completion_set_model (GTK_ENTRY_COMPLETION (self), GTK_TREE_MODEL (self->store)); + gtk_entry_completion_set_text_column (GTK_ENTRY_COMPLETION (self), 0); +} + +void +entry_completion_set_search_criteria (EntryCompletion *self, char** criteria) +{ + GtkTreeIter iter; + gtk_list_store_clear (self->store); + // Prefill ListStore with the search expression keywords + gtk_list_store_insert_with_values (self->store, &iter, -1, + 0, "and", -1); + gtk_list_store_insert_with_values (self->store, &iter, -1, + 0, "or", -1); + gtk_list_store_insert_with_values (self->store, &iter, -1, + 0, "contains", -1); + gtk_list_store_insert_with_values (self->store, &iter, -1, + 0, "doesNotContain", -1); + gtk_list_store_insert_with_values (self->store, &iter, -1, + 0, "derivedFrom", -1); + gtk_list_store_insert_with_values (self->store, &iter, -1, + 0, "exists", -1); + gtk_list_store_insert_with_values (self->store, &iter, -1, + 0, "true", -1); + gtk_list_store_insert_with_values (self->store, &iter, -1, + 0, "false", -1); + + char **it = criteria; + while (*it != NULL) { + gtk_list_store_insert_with_values (self->store, &iter, -1, + 0, *it, -1); + it++; + } +}
\ No newline at end of file diff --git a/src/av-cp/entry-completion.h b/src/av-cp/entry-completion.h new file mode 100644 index 0000000..f999c41 --- /dev/null +++ b/src/av-cp/entry-completion.h @@ -0,0 +1,15 @@ +#pragma once + +#include <glib-object.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define ENTRY_TYPE_COMPLETION (entry_completion_get_type()) + +G_DECLARE_FINAL_TYPE (EntryCompletion, entry_completion, ENTRY, COMPLETION, GtkEntryCompletion) + +EntryCompletion *entry_completion_new (void); +void entry_completion_set_search_criteria (EntryCompletion *self, char** criteria); + +G_END_DECLS diff --git a/src/av-cp/meson.build b/src/av-cp/meson.build index 00bf411..f0a62e7 100644 --- a/src/av-cp/meson.build +++ b/src/av-cp/meson.build @@ -25,7 +25,10 @@ executable('gupnp-av-cp', 'search-dialog.c', 'search-dialog.h', 'server-device.c', - 'server-device.h'] + av_cp_resources + av_cp_win_resources, + 'server-device.h', + 'entry-completion.c', + 'entry-completion.h' + ] + av_cp_resources + av_cp_win_resources, dependencies : [gupnp, gssdp, gupnp_av, gtk, util, gtksourceview, config_header], export_dynamic : true, install : true, diff --git a/src/av-cp/search-dialog.c b/src/av-cp/search-dialog.c index d929cc5..c61bb78 100644 --- a/src/av-cp/search-dialog.c +++ b/src/av-cp/search-dialog.c @@ -23,6 +23,7 @@ #include <string.h> +#include "entry-completion.h" #include "search-dialog.h" #include "server-device.h" #include "didl-dialog.h" @@ -376,6 +377,7 @@ search_dialog_init (SearchDialog *self) priv = search_dialog_get_instance_private (self); priv->parser = gupnp_search_criteria_parser_new (); + gtk_entry_set_completion (priv->search_dialog_entry, entry_completion_new ()); GMenu *menu = g_menu_new (); g_menu_insert (menu, 0, _("Show _DIDL…"), "search.show-didl"); @@ -486,8 +488,12 @@ void search_dialog_set_server (SearchDialog *self, AVCPMediaServer *server) { SearchDialogPrivate *priv = search_dialog_get_instance_private (self); + GtkEntryCompletion *completion = gtk_entry_get_completion (priv->search_dialog_entry); priv->server = server; + + entry_completion_set_search_criteria (ENTRY_COMPLETION (completion), + av_cp_media_server_get_search_caps (server)); } void |