summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJens Georg <mail@jensge.org>2020-07-18 18:51:03 +0200
committerJens Georg <mail@jensge.org>2020-07-18 18:55:10 +0200
commit18c4a5fdfc341cd4d88116105a4836d46d4f12c8 (patch)
treebff2e9d000aec2a970979c38adbc100b29f50aff
parent59a5b38947a61ad0d1f50ef4face08849132383f (diff)
downloadgupnp-tools-18c4a5fdfc341cd4d88116105a4836d46d4f12c8.tar.gz
av-cp: Provide suggestions for search entry
-rw-r--r--src/av-cp/entry-completion.c184
-rw-r--r--src/av-cp/entry-completion.h15
-rw-r--r--src/av-cp/meson.build5
-rw-r--r--src/av-cp/search-dialog.c6
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