[gnome-builder] libide-gui: minimal search popover display



commit aa417be2d1d5beebe0b6ad1d195bc08d35c8158f
Author: Christian Hergert <chergert redhat com>
Date:   Tue Jul 12 21:43:34 2022 -0700

    libide-gui: minimal search popover display
    
    Still lots to do here, but we just need something minimal now to be able
    to ship something semi-functional. This displays the results in a list
    view.
    
    Some day I'd like to have sections and grouping, but that will have to
    wait until we have new features from listview for it.

 src/libide/gui/ide-search-popover.c  | 218 ++++++++++++++++++++++++++++++++---
 src/libide/gui/ide-search-popover.ui | 139 +++++++++++-----------
 2 files changed, 268 insertions(+), 89 deletions(-)
---
diff --git a/src/libide/gui/ide-search-popover.c b/src/libide/gui/ide-search-popover.c
index 86c8f8c2a..ba71fb2ed 100644
--- a/src/libide/gui/ide-search-popover.c
+++ b/src/libide/gui/ide-search-popover.c
@@ -24,15 +24,25 @@
 
 #include "ide-search-popover-private.h"
 #include "ide-search-resources.h"
+#include "ide-gui-global.h"
+
+#define SEARCH_DELAY_MSEC 30
+#define MAX_RESULTS 1000 /* 0 for unlimited */
 
 struct _IdeSearchPopover
 {
   GtkPopover       parent_instance;
 
+  GCancellable    *cancellable;
   IdeSearchEngine *search_engine;
 
-  GtkBox          *nav_box;
   GtkSearchEntry  *entry;
+  GtkNoSelection  *selection;
+  GtkListView     *list_view;
+
+  guint            queued_search;
+
+  guint            activate_after_search : 1;
 };
 
 enum {
@@ -45,9 +55,46 @@ G_DEFINE_FINAL_TYPE (IdeSearchPopover, ide_search_popover, GTK_TYPE_POPOVER)
 
 static GParamSpec *properties [N_PROPS];
 
+static void
+ide_search_popover_cancel (IdeSearchPopover *self)
+{
+  g_assert (IDE_IS_SEARCH_POPOVER (self));
+
+  g_cancellable_cancel (self->cancellable);
+  g_clear_object (&self->cancellable);
+  self->cancellable = g_cancellable_new ();
+}
+
+static void
+ide_search_popover_activate (IdeSearchPopover *self,
+                             IdeSearchResult  *result)
+{
+  IdeWorkspace *workspace;
+  GtkWidget *last_focus = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_SEARCH_POPOVER (self));
+  g_assert (IDE_IS_SEARCH_RESULT (result));
+
+  /* We want GtkWindow:focus-widget because we don't care that the
+   * popover has a GtkText focused for text entry. We want what was
+   * last focused in the parent workspace window.
+   */
+  if ((workspace = ide_widget_get_workspace (GTK_WIDGET (self))))
+    last_focus = gtk_window_get_focus (GTK_WINDOW (workspace));
+
+  gtk_popover_popdown (GTK_POPOVER (self));
+
+  ide_search_result_activate (result, last_focus);
+
+  IDE_EXIT;
+}
+
 static void
 ide_search_popover_hide_action (GtkWidget  *widget,
-                                const char *action_Name,
+                                const char *action_name,
                                 GVariant   *param)
 {
   gtk_popover_popdown (GTK_POPOVER (widget));
@@ -60,10 +107,151 @@ ide_search_popover_set_search_engine (IdeSearchPopover *self,
   g_assert (IDE_IS_SEARCH_POPOVER (self));
   g_assert (IDE_IS_SEARCH_ENGINE (search_engine));
 
-  if (g_set_object (&self->search_engine, search_engine))
+  g_set_object (&self->search_engine, search_engine);
+}
+
+static void
+ide_search_popover_search_cb (GObject      *object,
+                              GAsyncResult *result,
+                              gpointer      user_data)
+{
+  IdeSearchEngine *search_engine = (IdeSearchEngine *)object;
+  g_autoptr(IdeSearchPopover) self = user_data;
+  g_autoptr(GListModel) results = NULL;
+  g_autoptr(GError) error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_SEARCH_ENGINE (search_engine));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_SEARCH_POPOVER (self));
+
+  results = ide_search_engine_search_finish (search_engine, result, &error);
+
+  if (error != NULL)
+    g_debug ("Search failed: %s", error->message);
+
+  gtk_no_selection_set_model (self->selection, results);
+
+  if (self->activate_after_search)
     {
-      /* TODO: Setup addins */
+      self->activate_after_search = FALSE;
+
+      if (results != NULL && g_list_model_get_n_items (results) > 0)
+        {
+          g_autoptr(IdeSearchResult) first = g_list_model_get_item (results, 0);
+          g_assert (IDE_IS_SEARCH_RESULT (first));
+          ide_search_popover_activate (self, first);
+        }
     }
+
+  IDE_EXIT;
+}
+
+static gboolean
+ide_search_popover_search_source_func (gpointer data)
+{
+  IdeSearchPopover *self = data;
+  const char *query;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_SEARCH_POPOVER (self));
+
+  self->queued_search = 0;
+
+  if (self->search_engine == NULL)
+    IDE_GOTO (failure);
+
+  ide_search_popover_cancel (self);
+
+  query = gtk_editable_get_text (GTK_EDITABLE (self->entry));
+
+  if (ide_str_empty0 (query))
+    IDE_GOTO (failure);
+  else
+    ide_search_engine_search_async (self->search_engine,
+                                    query,
+                                    MAX_RESULTS,
+                                    self->cancellable,
+                                    ide_search_popover_search_cb,
+                                    g_object_ref (self));
+
+  IDE_RETURN (G_SOURCE_REMOVE);
+
+failure:
+  self->activate_after_search = FALSE;
+  gtk_no_selection_set_model (self->selection, NULL);
+
+  IDE_RETURN (G_SOURCE_REMOVE);
+}
+
+static void
+ide_search_popover_queue_search (IdeSearchPopover *self)
+{
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_SEARCH_POPOVER (self));
+
+  if (self->queued_search == 0)
+    self->queued_search = g_timeout_add (SEARCH_DELAY_MSEC,
+                                         ide_search_popover_search_source_func,
+                                         self);
+}
+
+static void
+ide_search_popover_search_changed_cb (IdeSearchPopover *self,
+                                      GtkEditable      *editable)
+{
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_SEARCH_POPOVER (self));
+  g_assert (GTK_IS_EDITABLE (editable));
+
+  ide_search_popover_queue_search (self);
+
+  IDE_EXIT;
+}
+
+static void
+ide_search_popover_activate_cb (IdeSearchPopover *self,
+                                guint             position,
+                                GtkListView      *list_view)
+{
+  g_autoptr(IdeSearchResult) result = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_SEARCH_POPOVER (self));
+  g_assert (GTK_IS_LIST_VIEW (list_view));
+
+  result = g_list_model_get_item (G_LIST_MODEL (self->selection), position);
+
+  g_assert (result != NULL);
+  g_assert (IDE_IS_SEARCH_RESULT (result));
+
+  ide_search_popover_activate (self, result);
+
+  IDE_EXIT;
+}
+
+static void
+ide_search_popover_entry_activate_cb (IdeSearchPopover *self,
+                                      GtkEditable      *editable)
+{
+  g_assert (IDE_IS_SEARCH_POPOVER (self));
+  g_assert (GTK_IS_EDITABLE (editable));
+
+  /* Delay activation until the result comes back. Just send off
+   * another search out of simplifity here. When it comes back we
+   * activate. That way we always get the same result no matter if
+   * a search was in-progress while activate happened.
+   */
+  self->activate_after_search = TRUE;
+
+  ide_search_popover_queue_search (self);
 }
 
 static void
@@ -93,6 +281,9 @@ ide_search_popover_dispose (GObject *object)
 {
   IdeSearchPopover *self = (IdeSearchPopover *)object;
 
+  g_clear_handle_id (&self->queued_search, g_source_remove);
+
+  g_clear_object (&self->cancellable);
   g_clear_object (&self->search_engine);
 
   G_OBJECT_CLASS (ide_search_popover_parent_class)->dispose (object);
@@ -162,7 +353,11 @@ ide_search_popover_class_init (IdeSearchPopoverClass *klass)
 
   gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/libide-gui/ui/ide-search-popover.ui");
   gtk_widget_class_bind_template_child (widget_class, IdeSearchPopover, entry);
-  gtk_widget_class_bind_template_child (widget_class, IdeSearchPopover, nav_box);
+  gtk_widget_class_bind_template_child (widget_class, IdeSearchPopover, list_view);
+  gtk_widget_class_bind_template_child (widget_class, IdeSearchPopover, selection);
+  gtk_widget_class_bind_template_callback (widget_class, ide_search_popover_activate_cb);
+  gtk_widget_class_bind_template_callback (widget_class, ide_search_popover_entry_activate_cb);
+  gtk_widget_class_bind_template_callback (widget_class, ide_search_popover_search_changed_cb);
 
   gtk_widget_class_install_action (widget_class, "search.hide", NULL, ide_search_popover_hide_action);
 }
@@ -188,18 +383,9 @@ ide_search_popover_present (IdeSearchPopover *self,
                             int               parent_width,
                             int               parent_height)
 {
-  GdkRectangle point;
-  int min_width;
-  int nat_width;
-
   g_return_if_fail (IDE_IS_SEARCH_POPOVER (self));
 
-  gtk_widget_measure (GTK_WIDGET (self->nav_box),
-                      GTK_ORIENTATION_HORIZONTAL,
-                      -1,
-                      &min_width, &nat_width, NULL, NULL);
-
-  point = (GdkRectangle) { (parent_width - min_width) / 2, 100, 1, 1 };
-  gtk_popover_set_pointing_to (GTK_POPOVER (self), &point);
+  gtk_popover_set_pointing_to (GTK_POPOVER (self),
+                               &(GdkRectangle) { parent_width / 2, 100, 1, 1 });
   gtk_popover_present (GTK_POPOVER (self));
 }
diff --git a/src/libide/gui/ide-search-popover.ui b/src/libide/gui/ide-search-popover.ui
index f7839f7f1..668aa6cab 100644
--- a/src/libide/gui/ide-search-popover.ui
+++ b/src/libide/gui/ide-search-popover.ui
@@ -9,87 +9,80 @@
       <class name="global-search"/>
     </style>
     <child>
-      <object class="GtkBox">
-        <property name="orientation">horizontal</property>
+      <object class="GtkBox" id="main_box">
+        <property name="orientation">vertical</property>
+        <property name="hexpand">true</property>
         <child>
-          <object class="GtkBox" id="nav_box">
-            <property name="orientation">vertical</property>
-            <child>
-              <object class="AdwHeaderBar">
-                <property name="show-start-title-buttons">false</property>
-                <property name="show-end-title-buttons">false</property>
-                <child type="title">
-                  <object class="GtkLabel">
-                  </object>
-                </child>
-                <child type="end">
-                  <object class="GtkMenuButton">
-                    <property name="icon-name">open-menu-symbolic</property>
-                  </object>
-                </child>
-              </object>
-            </child>
-            <child>
-              <object class="GtkScrolledWindow">
-                <property name="vexpand">true</property>
-                <property name="hscrollbar-policy">never</property>
-                <property name="propagate-natural-width">true</property>
-                <child>
-                  <object class="GtkListBox">
-                    <property name="width-request">200</property>
-                    <style>
-                      <class name="navigation-sidebar"/>
-                    </style>
-                    <child>
-                      <object class="GtkListBoxRow">
-                        <child>
-                          <object class="GtkBox">
-                            <property name="orientation">horizontal</property>
-                            <property name="spacing">12</property>
-                            <child>
-                              <object class="GtkImage">
-                                <property name="icon-name">edit-find-symbolic</property>
-                              </object>
-                            </child>
-                            <child>
-                              <object class="GtkLabel">
-                                <property name="xalign">0</property>
-                                <property name="hexpand">true</property>
-                                <property name="label" translatable="yes">_Top Results</property>
-                                <property name="use-underline">true</property>
-                              </object>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                    </child>
-                  </object>
-                </child>
+          <object class="AdwHeaderBar">
+            <property name="show-start-title-buttons">false</property>
+            <property name="show-end-title-buttons">false</property>
+            <child type="title">
+              <object class="GtkSearchEntry" id="entry">
+                <property name="width-chars">40</property>
+                <signal name="activate" handler="ide_search_popover_entry_activate_cb" swapped="true" 
object="IdeSearchPopover"/>
+                <signal name="changed" handler="ide_search_popover_search_changed_cb" swapped="true" 
object="IdeSearchPopover"/>
               </object>
             </child>
           </object>
         </child>
         <child>
-          <object class="GtkBox" id="main_box">
-            <property name="orientation">vertical</property>
-            <property name="hexpand">true</property>
+          <object class="GtkScrolledWindow">
+            <property name="vexpand">true</property>
+            <property name="hscrollbar-policy">never</property>
+            <property name="min-content-height">400</property>
             <child>
-              <object class="AdwHeaderBar">
-                <property name="show-start-title-buttons">false</property>
-                <property name="show-end-title-buttons">false</property>
-                <child type="title">
-                  <object class="GtkSearchEntry" id="entry">
-                    <property name="width-chars">40</property>
+              <object class="GtkListView" id="list_view">
+                <signal name="activate" handler="ide_search_popover_activate_cb" swapped="true" 
object="IdeSearchPopover"/>
+                <property name="single-click-activate">True</property>
+                <property name="orientation">vertical</property>
+                <property name="model">
+                  <object class="GtkNoSelection" id="selection">
                   </object>
-                </child>
-              </object>
-            </child>
-            <child>
-              <object class="GtkScrolledWindow">
-                <property name="vexpand">true</property>
-                <property name="hscrollbar-policy">never</property>
-                <property name="propagate-natural-height">true</property>
-                <property name="min-content-height">400</property>
+                </property>
+                <property name="factory">
+                  <object class="GtkBuilderListItemFactory">
+                    <property name="bytes"><![CDATA[
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+<template class="GtkListItem">
+<property name="child">
+  <object class="GtkBox">
+    <property name="margin-top">6</property>
+    <property name="margin-bottom">6</property>
+    <property name="margin-start">6</property>
+    <property name="margin-end">6</property>
+    <property name="spacing">6</property>
+    <child>
+      <object class="GtkImage">
+        <property name="pixel-size">16</property>
+        <binding name="gicon">
+          <lookup name="gicon" type="IdeSearchResult">
+            <lookup name="item">GtkListItem</lookup>
+          </lookup>
+        </binding>
+      </object>
+    </child>
+    <child>
+      <object class="GtkLabel">
+        <property name="halign">start</property>
+        <property name="hexpand">true</property>
+        <property name="ellipsize">end</property>
+        <property name="use-markup">true</property>
+        <binding name="label">
+          <lookup name="title" type="IdeSearchResult">
+            <lookup name="item">GtkListItem</lookup>
+          </lookup>
+        </binding>
+      </object>
+    </child>
+  </object>
+</property>
+</template>
+</interface>
+]]>
+                    </property>
+                  </object>
+                </property>
               </object>
             </child>
           </object>


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