[gnome-builder/wip/chergert/perspective] search: simplify search implementation



commit ff526230edee956e2de106d0d45513dff595e78d
Author: Christian Hergert <christian hergert me>
Date:   Tue Nov 24 17:04:36 2015 -0800

    search: simplify search implementation
    
    This removes the Box and simply subclasses GtkEntry to do the things we
    want. We still use a popover to control the popdown list, but manually
    control that now.

 data/ui/ide-omni-search-entry.ui      |   53 -----
 libide/search/ide-omni-search-entry.c |  391 +++++++++++++--------------------
 2 files changed, 149 insertions(+), 295 deletions(-)
---
diff --git a/libide/search/ide-omni-search-entry.c b/libide/search/ide-omni-search-entry.c
index 1248843..6d1651a 100644
--- a/libide/search/ide-omni-search-entry.c
+++ b/libide/search/ide-omni-search-entry.c
@@ -24,27 +24,31 @@
 #include "ide-omni-search-entry.h"
 #include "ide-omni-search-display.h"
 
-#define SHORT_DELAY_TIMEOUT_MSEC 30
-#define LONG_DELAY_TIMEOUT_MSEC  30
+#define SHORT_DELAY_TIMEOUT_MSEC 20
+#define LONG_DELAY_TIMEOUT_MSEC  50
+#define LONG_DELAY_MAX_CHARS     3
+#define RESULTS_PER_PROVIDER     7
 
 struct _IdeOmniSearchEntry
 {
-  GtkBox           parent_instance;
-
-  /* Weak references */
-  IdeWorkbench     *workbench;
-  gulong           set_focus_handler;
+  GtkBox                parent_instance;
 
   /* Template references */
-  GtkMenuButton   *button;
   IdeOmniSearchDisplay *display;
-  GtkSearchEntry  *entry;
-  GtkPopover      *popover;
+  GtkEntry             *entry;
+  GtkPopover           *popover;
+
+  guint                 delay_timeout;
+};
+
+G_DEFINE_TYPE (IdeOmniSearchEntry, ide_omni_search_entry, GTK_TYPE_ENTRY)
 
-  guint            delay_timeout;
+enum {
+  CLEAR_SEARCH,
+  LAST_SIGNAL
 };
 
-G_DEFINE_TYPE (IdeOmniSearchEntry, ide_omni_search_entry, GTK_TYPE_BOX)
+static guint signals [LAST_SIGNAL];
 
 GtkWidget *
 ide_omni_search_entry_new (void)
@@ -52,24 +56,53 @@ ide_omni_search_entry_new (void)
   return g_object_new (IDE_TYPE_OMNI_SEARCH_ENTRY, NULL);
 }
 
+/**
+ * ide_omni_search_entry_get_search_engine:
+ * @self: An #IdeOmniSearchEntry.
+ *
+ * Gets the search engine to use with the current workbench.
+ *
+ * Returns: (transfer none): An #IdeSearchEngine.
+ */
 IdeSearchEngine *
 ide_omni_search_entry_get_search_engine (IdeOmniSearchEntry *self)
 {
+  IdeWorkbench *workbench;
   IdeContext *context;
-  IdeSearchEngine *search_engine;
 
   g_return_val_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self), NULL);
 
-  if (self->workbench == NULL)
+  workbench = ide_widget_get_workbench (GTK_WIDGET (self));
+  if (workbench == NULL)
     return NULL;
 
-  context = ide_workbench_get_context (self->workbench);
+  context = ide_workbench_get_context (workbench);
   if (context == NULL)
       return NULL;
 
-  search_engine = ide_context_get_search_engine (context);
+  return ide_context_get_search_engine (context);
+}
 
-  return search_engine;
+static void
+ide_omni_search_entry_completed (IdeOmniSearchEntry *self,
+                                 IdeSearchContext   *context)
+{
+  g_assert (IDE_IS_OMNI_SEARCH_ENTRY (self));
+  g_assert (IDE_IS_SEARCH_CONTEXT (context));
+
+  if (ide_omni_search_display_get_count (self->display) == 0)
+    {
+      gint position;
+
+      /*
+       * Hiding the popover will cause the entry to get focus,
+       * thereby selecting all available text. We don't want
+       * that to happen.
+       */
+      position = gtk_editable_get_position (GTK_EDITABLE (self));
+      gtk_widget_hide (GTK_WIDGET (self->popover));
+      gtk_editable_set_position (GTK_EDITABLE (self), position);
+    }
 }
 
 static gboolean
@@ -80,28 +113,29 @@ ide_omni_search_entry_delay_cb (gpointer user_data)
   IdeSearchContext *context;
   const gchar *search_text;
 
-  g_return_val_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self), G_SOURCE_REMOVE);
+  g_assert (IDE_IS_OMNI_SEARCH_ENTRY (self));
 
   self->delay_timeout = 0;
 
   if (self->display)
     {
       context = ide_omni_search_display_get_context (self->display);
-      if (context)
+      if (context != NULL)
         ide_search_context_cancel (context);
 
       search_engine = ide_omni_search_entry_get_search_engine (self);
-      if (!search_engine)
+      search_text = gtk_entry_get_text (GTK_ENTRY (self));
+      if (search_engine == NULL || search_text == NULL)
         return G_SOURCE_REMOVE;
 
-      search_text = gtk_entry_get_text (GTK_ENTRY (self->entry));
-      if (!search_text)
-        return G_SOURCE_REMOVE;
-
-      /* TODO: Remove search text */
       context = ide_search_engine_search (search_engine, search_text);
+      g_signal_connect_object (context,
+                               "completed",
+                               G_CALLBACK (ide_omni_search_entry_completed),
+                               self,
+                               G_CONNECT_SWAPPED);
       ide_omni_search_display_set_context (self->display, context);
-      ide_search_context_execute (context, search_text, 7);
+      ide_search_context_execute (context, search_text, RESULTS_PER_PROVIDER);
       g_object_unref (context);
     }
 
@@ -109,73 +143,58 @@ ide_omni_search_entry_delay_cb (gpointer user_data)
 }
 
 static void
-ide_omni_search_entry_popover_set_visible (IdeOmniSearchEntry *self,
-                                           gboolean            visible)
+ide_omni_search_entry_clear_search (IdeOmniSearchEntry *self)
 {
-  gboolean entry_has_text;
-
-  g_return_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self));
+  g_assert (IDE_IS_OMNI_SEARCH_ENTRY (self));
 
-  entry_has_text = !!(gtk_entry_get_text_length (GTK_ENTRY (self->entry)));
-
-  if (visible == gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->button)))
-    return;
-
-  if (visible && entry_has_text)
-    {
-      if (!gtk_widget_has_focus (GTK_WIDGET (self->entry)))
-        gtk_widget_grab_focus (GTK_WIDGET (self->entry));
-
-      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->button), TRUE);
-    }
-  else if (!visible)
-    {
-      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->button), FALSE);
-    }
+  gtk_widget_hide (GTK_WIDGET (self->popover));
+  gtk_entry_set_text (GTK_ENTRY (self), "");
 }
 
 static void
-ide_omni_search_entry_entry_activate (IdeOmniSearchEntry *self,
-                                      GtkSearchEntry     *entry)
+ide_omni_search_entry_activate (IdeOmniSearchEntry *self)
 {
-  g_return_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self));
-  g_return_if_fail (GTK_IS_SEARCH_ENTRY (entry));
+  g_assert (IDE_IS_OMNI_SEARCH_ENTRY (self));
 
-  ide_omni_search_display_activate (self->display);
-  gtk_entry_set_text (GTK_ENTRY (self->entry), "");
+  gtk_widget_activate (GTK_WIDGET (self->display));
+  ide_omni_search_entry_clear_search (self);
 }
 
 static void
-ide_omni_search_entry_entry_changed (IdeOmniSearchEntry *self,
-                                     GtkSearchEntry     *entry)
+ide_omni_search_entry_changed (IdeOmniSearchEntry *self)
 {
-  GtkWidget *button;
-  gboolean active;
-  gboolean sensitive;
-  guint delay_msec = SHORT_DELAY_TIMEOUT_MSEC;
+  const gchar *text;
+  gboolean had_focus;
+  guint position;
 
   g_return_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self));
-  g_return_if_fail (GTK_IS_SEARCH_ENTRY (entry));
 
-  button = GTK_WIDGET (self->button);
-  active = gtk_widget_has_focus (GTK_WIDGET (entry)) || (self->delay_timeout != 0);
-  sensitive = !!(gtk_entry_get_text_length (GTK_ENTRY (self->entry)));
+  text = gtk_entry_get_text (GTK_ENTRY (self));
+  had_focus = gtk_widget_has_focus (GTK_WIDGET (self));
+  position = gtk_editable_get_position (GTK_EDITABLE (self));
 
-  if (gtk_widget_get_sensitive (button) != sensitive)
-    gtk_widget_set_sensitive (button, sensitive);
+  gtk_widget_set_visible (GTK_WIDGET (self->popover), (text != NULL));
 
-  if (active)
-    ide_omni_search_entry_popover_set_visible (self, TRUE);
+  /*
+   * Showing the popover could steal focus, so reset the focus to the
+   * entry and reset the position which might get mucked up by focus
+   * changes.
+   */
+  if (had_focus)
+    {
+      gtk_widget_grab_focus (GTK_WIDGET (self));
+      gtk_editable_set_position (GTK_EDITABLE (self), position);
+    }
 
-  if (!self->delay_timeout)
+  if (self->delay_timeout == 0)
     {
-      const gchar *search_text;
+      guint delay_msec = SHORT_DELAY_TIMEOUT_MSEC;
 
-      search_text = gtk_entry_get_text (GTK_ENTRY (entry));
-      if (search_text)
+      if (text != NULL)
         {
-          if (strlen (search_text) < 3)
+          if (strlen (text) <= LONG_DELAY_MAX_CHARS)
             delay_msec = LONG_DELAY_TIMEOUT_MSEC;
+
           self->delay_timeout = g_timeout_add (delay_msec,
                                                ide_omni_search_entry_delay_cb,
                                                self);
@@ -183,47 +202,6 @@ ide_omni_search_entry_entry_changed (IdeOmniSearchEntry *self,
     }
 }
 
-static gboolean
-ide_omni_search_entry_entry_key_press_event (IdeOmniSearchEntry *self,
-                                             GdkEventKey        *key,
-                                             GtkSearchEntry     *entry)
-{
-  g_return_val_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self), GDK_EVENT_PROPAGATE);
-  g_return_val_if_fail (key, GDK_EVENT_PROPAGATE);
-  g_return_val_if_fail (GTK_IS_SEARCH_ENTRY (entry), GDK_EVENT_PROPAGATE);
-
-  switch (key->keyval)
-    {
-    case GDK_KEY_Escape:
-      {
-        ide_omni_search_entry_popover_set_visible (self, FALSE);
-        gtk_widget_grab_focus (gtk_widget_get_toplevel (GTK_WIDGET (entry)));
-
-        return GDK_EVENT_STOP;
-      }
-      break;
-
-    case GDK_KEY_Tab:
-    case GDK_KEY_KP_Tab:
-      if ((key->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) != 0)
-        break;
-      /* Fall through */
-    case GDK_KEY_Down:
-    case GDK_KEY_KP_Down:
-      if (gtk_widget_get_visible (GTK_WIDGET (self->popover)))
-        {
-          gtk_widget_grab_focus (GTK_WIDGET (self->display));
-          return GDK_EVENT_STOP;
-        }
-      break;
-
-    default:
-      break;
-    }
-
-  return GDK_EVENT_PROPAGATE;
-}
-
 static void
 ide_omni_search_entry_display_result_activated (IdeOmniSearchEntry   *self,
                                                 IdeSearchResult      *result,
@@ -233,164 +211,93 @@ ide_omni_search_entry_display_result_activated (IdeOmniSearchEntry   *self,
   g_return_if_fail (IDE_IS_SEARCH_RESULT (result));
   g_return_if_fail (IDE_IS_OMNI_SEARCH_DISPLAY (display));
 
-  gtk_entry_set_text (GTK_ENTRY (self->entry), "");
+  ide_omni_search_entry_clear_search (self);
 }
 
-static void
-ide_omni_search_entry_grab_focus (GtkWidget *widget)
+static gboolean
+ide_omni_search_entry_popover_key_press_event (IdeOmniSearchEntry *self,
+                                               GdkEventKey        *event,
+                                               GtkPopover         *popover)
 {
-  IdeOmniSearchEntry *self = (IdeOmniSearchEntry *)widget;
+  g_assert (IDE_IS_OMNI_SEARCH_ENTRY (self));
+  g_assert (event != NULL);
+  g_assert (GTK_IS_POPOVER (popover));
 
-  g_return_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self));
-
-  gtk_widget_grab_focus (GTK_WIDGET (self->entry));
+  return GTK_WIDGET_GET_CLASS (self)->key_press_event (GTK_WIDGET (self), event);
 }
 
 static void
-ide_omni_search_entry_workbench_set_focus (IdeOmniSearchEntry *self,
-                                           GtkWidget          *focus,
-                                           IdeWorkbench       *workbench)
-{
-  g_return_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self));
-  g_return_if_fail (!focus || GTK_IS_WIDGET (focus));
-  g_return_if_fail (IDE_IS_WORKBENCH (workbench));
-
-  if (!focus ||
-      (!gtk_widget_is_ancestor (focus, GTK_WIDGET (self)) &&
-       !gtk_widget_is_ancestor (focus, GTK_WIDGET (self->popover))))
-    {
-      gtk_entry_set_text (GTK_ENTRY (self->entry), "");
-    }
-  else
-    {
-      ide_omni_search_entry_popover_set_visible (self, TRUE);
-    }
-}
-
-static void
-ide_omni_search_entry_map (GtkWidget *widget)
+ide_omni_search_entry_destroy (GtkWidget *widget)
 {
   IdeOmniSearchEntry *self = (IdeOmniSearchEntry *)widget;
-  GtkWidget *toplevel;
 
-  g_return_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self));
+  ide_clear_source (&self->delay_timeout);
+  g_clear_pointer ((GtkWidget **)&self->popover, gtk_widget_destroy);
 
-  GTK_WIDGET_CLASS (ide_omni_search_entry_parent_class)->map (widget);
-
-  gtk_widget_set_sensitive (GTK_WIDGET (self->button), FALSE);
-  toplevel = gtk_widget_get_toplevel (widget);
-
-  if (IDE_IS_WORKBENCH (toplevel))
-    {
-      ide_set_weak_pointer (&self->workbench, IDE_WORKBENCH (toplevel));
-      self->set_focus_handler =
-        g_signal_connect_object (toplevel,
-                                 "set-focus",
-                                 G_CALLBACK (ide_omni_search_entry_workbench_set_focus),
-                                 self,
-                                 G_CONNECT_SWAPPED | G_CONNECT_AFTER);
-    }
+  GTK_WIDGET_CLASS (ide_omni_search_entry_parent_class)->destroy (widget);
 }
 
 static void
-ide_omni_search_entry_unmap (GtkWidget *widget)
+ide_omni_search_entry_class_init (IdeOmniSearchEntryClass *klass)
 {
-  IdeOmniSearchEntry *self = (IdeOmniSearchEntry *)widget;
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkBindingSet *binding_set;
 
-  g_return_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self));
+  widget_class->destroy = ide_omni_search_entry_destroy;
 
-  if (self->workbench)
-    {
-      ide_clear_signal_handler (self->workbench, &self->set_focus_handler);
-      ide_clear_weak_pointer (&self->workbench);
-    }
+  g_signal_override_class_handler ("activate",
+                                   G_TYPE_FROM_CLASS (klass),
+                                   G_CALLBACK (ide_omni_search_entry_activate));
 
-  GTK_WIDGET_CLASS (ide_omni_search_entry_parent_class)->unmap (widget);
+  signals [CLEAR_SEARCH] =
+    g_signal_new_class_handler ("clear-search",
+                                G_TYPE_FROM_CLASS (klass),
+                                G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+                                G_CALLBACK (ide_omni_search_entry_clear_search),
+                                NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+  binding_set = gtk_binding_set_by_class (klass);
+  gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0, "clear-search", 0);
+  gtk_binding_entry_add_signal (binding_set, GDK_KEY_Return, 0, "activate", 0);
+  gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Enter, 0, "activate", 0);
 }
 
 static void
-ide_omni_search_entry_constructed (GObject *object)
+ide_omni_search_entry_init (IdeOmniSearchEntry *self)
 {
-  IdeOmniSearchEntry *self = (IdeOmniSearchEntry *)object;
+  g_object_set (self,
+                "max-width-chars", 50,
+                "primary-icon-name", "edit-find-symbolic",
+                "primary-icon-activatable", FALSE,
+                "primary-icon-sensitive", FALSE,
+                NULL);
+
+  self->popover = g_object_new (GTK_TYPE_POPOVER,
+                                "width-request", 500,
+                                "relative-to", self,
+                                "position", GTK_POS_BOTTOM,
+                                NULL);
+
+  g_signal_connect_object (self->popover,
+                           "key-press-event",
+                           G_CALLBACK (ide_omni_search_entry_popover_key_press_event),
+                           self,
+                           G_CONNECT_SWAPPED);
 
-  g_return_if_fail (IDE_IS_OMNI_SEARCH_ENTRY (self));
+  self->display = g_object_new (IDE_TYPE_OMNI_SEARCH_DISPLAY,
+                                "visible", TRUE,
+                                NULL);
 
-  G_OBJECT_CLASS (ide_omni_search_entry_parent_class)->constructed (object);
+  gtk_container_add (GTK_CONTAINER (self->popover), GTK_WIDGET (self->display));
 
-  gtk_popover_set_relative_to (self->popover, GTK_WIDGET (self->entry));
+  g_signal_connect (self,
+                    "changed",
+                    G_CALLBACK (ide_omni_search_entry_changed),
+                    NULL);
 
-  g_signal_connect_object (self->entry,
-                           "activate",
-                           G_CALLBACK (ide_omni_search_entry_entry_activate),
-                           self,
-                           G_CONNECT_SWAPPED);
-  g_signal_connect_object (self->entry,
-                           "changed",
-                           G_CALLBACK (ide_omni_search_entry_entry_changed),
-                           self,
-                           G_CONNECT_SWAPPED);
-  g_signal_connect_object (self->entry,
-                           "key-press-event",
-                           G_CALLBACK (ide_omni_search_entry_entry_key_press_event),
-                           self,
-                           G_CONNECT_SWAPPED);
   g_signal_connect_object (self->display,
                            "result-activated",
                            G_CALLBACK (ide_omni_search_entry_display_result_activated),
                            self,
                            G_CONNECT_SWAPPED);
 }
-
-static void
-ide_omni_search_entry_finalize (GObject *object)
-{
-  IdeOmniSearchEntry *self = (IdeOmniSearchEntry *)object;
-
-  if (self->delay_timeout)
-    {
-      g_source_remove (self->delay_timeout);
-      self->delay_timeout = 0;
-    }
-
-  G_OBJECT_CLASS (ide_omni_search_entry_parent_class)->finalize (object);
-}
-
-static void
-ide_omni_search_entry_class_init (IdeOmniSearchEntryClass *klass)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
-
-  object_class->constructed = ide_omni_search_entry_constructed;
-  object_class->finalize = ide_omni_search_entry_finalize;
-
-  widget_class->grab_focus = ide_omni_search_entry_grab_focus;
-  widget_class->map = ide_omni_search_entry_map;
-  widget_class->unmap = ide_omni_search_entry_unmap;
-
-  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/ui/ide-omni-search-entry.ui");
-  gtk_widget_class_bind_template_child (widget_class, IdeOmniSearchEntry, button);
-  gtk_widget_class_bind_template_child (widget_class, IdeOmniSearchEntry, display);
-  gtk_widget_class_bind_template_child (widget_class, IdeOmniSearchEntry, entry);
-  gtk_widget_class_bind_template_child (widget_class, IdeOmniSearchEntry, popover);
-
-  g_type_ensure (IDE_TYPE_OMNI_SEARCH_DISPLAY);
-}
-
-static void
-ide_omni_search_entry_init (IdeOmniSearchEntry *self)
-{
-  gtk_widget_init_template (GTK_WIDGET (self));
-
-  /*
-   * WORKAROUND:
-   *
-   * The GtkWidget template things that popover is a child of ours. When in
-   * reality it is a child of the GtkMenuButton (since it owns the "popover"
-   * property. Both our widget and the menu button try to call
-   * gtk_widget_destroy() on it.
-   *
-   * https://bugzilla.gnome.org/show_bug.cgi?id=741529
-   */
-  g_object_ref (self->popover);
-}


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