[gnome-todo] task-list-popover: Add filtering to task list names



commit 955546be6ae828ca129e8afc5aa8b96f1fb7399e
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Sun Sep 9 15:59:17 2018 -0300

    task-list-popover: Add filtering to task list names
    
    This is helpful when one has many tasklists, and wants to
    just type the name of it and Enter and be happy.
    
    There are a few quirks with forcus still, but it's certainly
    helpful already!

 data/ui/task-list-popover.ui               |  4 +-
 src/gtd-utils.c                            | 86 ++++++++++++++++++++++++++++++
 src/gtd-utils.h                            |  2 +
 src/task-list-view/gtd-task-list-popover.c | 61 ++++++++++++++++++++-
 4 files changed, 151 insertions(+), 2 deletions(-)
---
diff --git a/data/ui/task-list-popover.ui b/data/ui/task-list-popover.ui
index 2a2f3f6..1c21981 100644
--- a/data/ui/task-list-popover.ui
+++ b/data/ui/task-list-popover.ui
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <template class="GtdTaskListPopover" parent="GtkPopover">
+    <signal name="closed" handler="on_popover_closed_cb" object="GtdTaskListPopover" swapped="no" />
     <child>
       <object class="GtkBox">
         <property name="margin">18</property>
@@ -10,7 +11,8 @@
         <!-- Search entry -->
         <child>
           <object class="GtkSearchEntry" id="search_entry">
-            <property name="visible">false</property>
+            <signal name="activate" handler="on_search_entry_activated_cb" object="GtdTaskListPopover" 
swapped="no" />
+            <signal name="search-changed" handler="on_search_entry_search_changed_cb" 
object="GtdTaskListPopover" swapped="no" />
           </object>
         </child>
 
diff --git a/src/gtd-utils.c b/src/gtd-utils.c
index 47f3e3b..294355f 100644
--- a/src/gtd-utils.c
+++ b/src/gtd-utils.c
@@ -25,6 +25,92 @@
 
 #include <string.h>
 
+
+/* Combining diacritical mark?
+ *  Basic range: [0x0300,0x036F]
+ *  Supplement:  [0x1DC0,0x1DFF]
+ *  For Symbols: [0x20D0,0x20FF]
+ *  Half marks:  [0xFE20,0xFE2F]
+ */
+#define IS_CDM_UCS4(c) (((c) >= 0x0300 && (c) <= 0x036F)  || \
+                        ((c) >= 0x1DC0 && (c) <= 0x1DFF)  || \
+                        ((c) >= 0x20D0 && (c) <= 0x20FF)  || \
+                        ((c) >= 0xFE20 && (c) <= 0xFE2F))
+
+#define IS_SOFT_HYPHEN(c) ((c) == 0x00AD)
+
+
+/* Copied from tracker/src/libtracker-fts/tracker-parser-glib.c under the GPL
+ * And then from gnome-shell/src/shell-util.c
+ *
+ * Originally written by Aleksander Morgado <aleksander gnu org>
+ */
+gchar*
+gtd_normalize_casefold_and_unaccent (const gchar *str)
+{
+  g_autofree gchar *normalized = NULL;
+  gchar *tmp;
+  gint i = 0;
+  gint j = 0;
+  gint ilen;
+
+  if (str == NULL)
+    return NULL;
+
+  normalized = g_utf8_normalize (str, -1, G_NORMALIZE_NFKD);
+  tmp = g_utf8_casefold (normalized, -1);
+
+  ilen = strlen (tmp);
+
+  while (i < ilen)
+    {
+      gunichar unichar;
+      gchar *next_utf8;
+      gint utf8_len;
+
+      /* Get next character of the word as UCS4 */
+      unichar = g_utf8_get_char_validated (&tmp[i], -1);
+
+      /* Invalid UTF-8 character or end of original string. */
+      if (unichar == (gunichar) -1 ||
+          unichar == (gunichar) -2)
+        {
+          break;
+        }
+
+      /* Find next UTF-8 character */
+      next_utf8 = g_utf8_next_char (&tmp[i]);
+      utf8_len = next_utf8 - &tmp[i];
+
+      if (IS_CDM_UCS4 (unichar) || IS_SOFT_HYPHEN (unichar))
+        {
+          /* If the given unichar is a combining diacritical mark,
+           * just update the original index, not the output one */
+          i += utf8_len;
+          continue;
+        }
+
+      /* If already found a previous combining
+       * diacritical mark, indexes are different so
+       * need to copy characters. As output and input
+       * buffers may overlap, need to use memmove
+       * instead of memcpy */
+      if (i != j)
+        {
+          memmove (&tmp[j], &tmp[i], utf8_len);
+        }
+
+      /* Update both indexes */
+      i += utf8_len;
+      j += utf8_len;
+    }
+
+  /* Force proper string end */
+  tmp[j] = '\0';
+
+  return tmp;
+}
+
 GdkContentFormats*
 _gtd_get_content_formats (void)
 {
diff --git a/src/gtd-utils.h b/src/gtd-utils.h
index 6e82588..5d60f03 100644
--- a/src/gtd-utils.h
+++ b/src/gtd-utils.h
@@ -24,6 +24,8 @@
 
 G_BEGIN_DECLS
 
+gchar*               gtd_normalize_casefold_and_unaccent         (const gchar        *str);
+
 gchar*               gtd_str_replace                             (const gchar        *source,
                                                                   const gchar        *search,
                                                                   const gchar        *replacement);
diff --git a/src/task-list-view/gtd-task-list-popover.c b/src/task-list-view/gtd-task-list-popover.c
index 3ddbf6f..0bf4d82 100644
--- a/src/task-list-view/gtd-task-list-popover.c
+++ b/src/task-list-view/gtd-task-list-popover.c
@@ -20,6 +20,8 @@
 
 #define G_LOG_DOMAIN "GtdTaskListPopover"
 
+#include "contrib/gtd-list-model-filter.h"
+#include "gtd-debug.h"
 #include "gtd-manager.h"
 #include "gtd-provider.h"
 #include "gtd-task-list.h"
@@ -30,8 +32,11 @@ struct _GtdTaskListPopover
 {
   GtkPopover          parent;
 
+  GtdListModelFilter *filter_model;
+
   GtkSizeGroup       *sizegroup;
   GtkListBox         *listbox;
+  GtkEntry           *search_entry;
 
   GtdTaskList        *selected_list;
   GtdManager         *manager;
@@ -73,6 +78,24 @@ set_selected_tasklist (GtdTaskListPopover *self,
  * Callbacks
  */
 
+static gboolean
+filter_listbox_cb (GObject  *object,
+                   gpointer  user_data)
+{
+  GtdTaskListPopover *self;
+  g_autofree gchar *normalized_list_name = NULL;
+  g_autofree gchar *normalized_search_query = NULL;
+  GtdTaskList *list;
+
+  self = (GtdTaskListPopover*) user_data;
+  list = (GtdTaskList*) object;
+
+  normalized_search_query = gtd_normalize_casefold_and_unaccent (gtk_entry_get_text (self->search_entry));
+  normalized_list_name = gtd_normalize_casefold_and_unaccent (gtd_task_list_get_name (list));
+
+  return g_strrstr (normalized_list_name, normalized_search_query) != NULL;
+}
+
 static GtkWidget*
 create_list_row_cb (gpointer item,
                     gpointer data)
@@ -145,6 +168,32 @@ on_listbox_row_activated_cb (GtkListBox         *listbox,
 
   set_selected_tasklist (self, list);
   gtk_popover_popdown (GTK_POPOVER (self));
+  gtk_entry_set_text (self->search_entry, "");
+}
+
+static void
+on_popover_closed_cb (GtkPopover         *popover,
+                      GtdTaskListPopover *self)
+{
+  gtk_entry_set_text (self->search_entry, "");
+}
+
+static void
+on_search_entry_activated_cb (GtkEntry           *entry,
+                              GtdTaskListPopover *self)
+{
+  g_autoptr (GtdTaskList) list = g_list_model_get_item (G_LIST_MODEL (self->filter_model), 0);
+
+  set_selected_tasklist (self, list);
+  gtk_popover_popdown (GTK_POPOVER (self));
+  gtk_entry_set_text (self->search_entry, "");
+}
+
+static void
+on_search_entry_search_changed_cb (GtkEntry           *search_entry,
+                                   GtdTaskListPopover *self)
+{
+  gtd_list_model_filter_invalidate (self->filter_model);
 }
 
 
@@ -179,9 +228,13 @@ gtd_task_list_popover_class_init (GtdTaskListPopoverClass *klass)
   gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/todo/ui/task-list-popover.ui");
 
   gtk_widget_class_bind_template_child (widget_class, GtdTaskListPopover, listbox);
+  gtk_widget_class_bind_template_child (widget_class, GtdTaskListPopover, search_entry);
   gtk_widget_class_bind_template_child (widget_class, GtdTaskListPopover, sizegroup);
 
   gtk_widget_class_bind_template_callback (widget_class, on_listbox_row_activated_cb);
+  gtk_widget_class_bind_template_callback (widget_class, on_popover_closed_cb);
+  gtk_widget_class_bind_template_callback (widget_class, on_search_entry_activated_cb);
+  gtk_widget_class_bind_template_callback (widget_class, on_search_entry_search_changed_cb);
 }
 
 static void
@@ -189,10 +242,16 @@ gtd_task_list_popover_init (GtdTaskListPopover *self)
 {
   GtdManager *manager = gtd_manager_get_default ();
 
+  self->filter_model = gtd_list_model_filter_new (gtd_manager_get_task_lists_model (manager));
+  gtd_list_model_filter_set_filter_func (self->filter_model,
+                                         filter_listbox_cb,
+                                         self,
+                                         NULL);
+
   gtk_widget_init_template (GTK_WIDGET (self));
 
   gtk_list_box_bind_model (self->listbox,
-                           gtd_manager_get_task_lists_model (manager),
+                           G_LIST_MODEL (self->filter_model),
                            create_list_row_cb,
                            self,
                            NULL);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]