[gnome-builder/wip/gtk4-port: 720/736] plugins/symbol-tree: make the symbol tree work




commit 1b00e3c0e3d808d49b82c8311bd9201b1282904b
Author: Christian Hergert <chergert redhat com>
Date:   Thu Apr 21 18:00:32 2022 -0700

    plugins/symbol-tree: make the symbol tree work
    
    There is certainly more we can tweak on this as we go, but this gets the
    basics in place.

 src/plugins/symbol-tree/gbp-symbol-list-model.c   | 184 ++++++++++++++++++++
 src/plugins/symbol-tree/gbp-symbol-list-model.h   |  35 ++++
 src/plugins/symbol-tree/gbp-symbol-popover-row.ui |   4 +
 src/plugins/symbol-tree/gbp-symbol-popover.c      | 194 ++++++++++++++++++++--
 src/plugins/symbol-tree/gbp-symbol-popover.ui     |   5 +-
 src/plugins/symbol-tree/meson.build               |   1 +
 6 files changed, 408 insertions(+), 15 deletions(-)
---
diff --git a/src/plugins/symbol-tree/gbp-symbol-list-model.c b/src/plugins/symbol-tree/gbp-symbol-list-model.c
new file mode 100644
index 000000000..bde035a76
--- /dev/null
+++ b/src/plugins/symbol-tree/gbp-symbol-list-model.c
@@ -0,0 +1,184 @@
+/* gbp-symbol-list-model.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-symbol-list-model"
+
+#include "config.h"
+
+#include "gbp-symbol-list-model.h"
+
+struct _GbpSymbolListModel
+{
+  GObject        parent_instance;
+  IdeSymbolTree *tree;
+  IdeSymbolNode *parent;
+};
+
+enum {
+  PROP_0,
+  PROP_TREE,
+  PROP_PARENT,
+  N_PROPS
+};
+
+static GType
+gbp_symbol_list_model_get_item_type (GListModel *model)
+{
+  return IDE_TYPE_SYMBOL_NODE;
+}
+
+static guint
+gbp_symbol_list_model_get_n_items (GListModel *model)
+{
+  GbpSymbolListModel *self = GBP_SYMBOL_LIST_MODEL (model);
+
+  return ide_symbol_tree_get_n_children (self->tree, self->parent);
+}
+
+static gpointer
+gbp_symbol_list_model_get_item (GListModel *model,
+                                guint       position)
+{
+  GbpSymbolListModel *self = GBP_SYMBOL_LIST_MODEL (model);
+
+  if (position >= ide_symbol_tree_get_n_children (self->tree, self->parent))
+    return NULL;
+
+  return ide_symbol_tree_get_nth_child (self->tree, self->parent, position);
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+  iface->get_item = gbp_symbol_list_model_get_item;
+  iface->get_n_items = gbp_symbol_list_model_get_n_items;
+  iface->get_item_type = gbp_symbol_list_model_get_item_type;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GbpSymbolListModel, gbp_symbol_list_model, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+gbp_symbol_list_model_dispose (GObject *object)
+{
+  GbpSymbolListModel *self = (GbpSymbolListModel *)object;
+
+  g_clear_object (&self->tree);
+  g_clear_object (&self->parent);
+
+  G_OBJECT_CLASS (gbp_symbol_list_model_parent_class)->dispose (object);
+}
+
+static void
+gbp_symbol_list_model_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+  GbpSymbolListModel *self = GBP_SYMBOL_LIST_MODEL (object);
+
+  switch (prop_id)
+    {
+    case PROP_TREE:
+      g_value_set_object (value, self->tree);
+      break;
+
+    case PROP_PARENT:
+      g_value_set_object (value, self->parent);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_symbol_list_model_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  GbpSymbolListModel *self = GBP_SYMBOL_LIST_MODEL (object);
+
+  switch (prop_id)
+    {
+    case PROP_TREE:
+      self->tree = g_value_dup_object (value);
+      break;
+
+    case PROP_PARENT:
+      self->parent = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_symbol_list_model_class_init (GbpSymbolListModelClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = gbp_symbol_list_model_dispose;
+  object_class->get_property = gbp_symbol_list_model_get_property;
+  object_class->set_property = gbp_symbol_list_model_set_property;
+
+  properties [PROP_TREE] =
+    g_param_spec_object ("tree",
+                         "Tree",
+                         "The tree of nodes",
+                         IDE_TYPE_SYMBOL_TREE,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_PARENT] =
+    g_param_spec_object ("parent",
+                         "Parent",
+                         "The parent node",
+                         IDE_TYPE_SYMBOL_NODE,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gbp_symbol_list_model_init (GbpSymbolListModel *self)
+{
+}
+
+IdeSymbolTree *
+gbp_symbol_list_model_get_tree (GbpSymbolListModel *self)
+{
+  g_return_val_if_fail (GBP_IS_SYMBOL_LIST_MODEL (self), NULL);
+
+  return self->tree;
+}
+
+GbpSymbolListModel *
+gbp_symbol_list_model_new (IdeSymbolTree *tree,
+                           IdeSymbolNode *parent)
+{
+  return g_object_new (GBP_TYPE_SYMBOL_LIST_MODEL,
+                       "tree", tree,
+                       "parent", parent,
+                       NULL);
+}
diff --git a/src/plugins/symbol-tree/gbp-symbol-list-model.h b/src/plugins/symbol-tree/gbp-symbol-list-model.h
new file mode 100644
index 000000000..55eb8b986
--- /dev/null
+++ b/src/plugins/symbol-tree/gbp-symbol-list-model.h
@@ -0,0 +1,35 @@
+/* gbp-symbol-list-model.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-code.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_SYMBOL_LIST_MODEL (gbp_symbol_list_model_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpSymbolListModel, gbp_symbol_list_model, GBP, SYMBOL_LIST_MODEL, GObject)
+
+GbpSymbolListModel *gbp_symbol_list_model_new      (IdeSymbolTree      *tree,
+                                                    IdeSymbolNode      *parent);
+IdeSymbolTree      *gbp_symbol_list_model_get_tree (GbpSymbolListModel *self);
+
+G_END_DECLS
diff --git a/src/plugins/symbol-tree/gbp-symbol-popover-row.ui 
b/src/plugins/symbol-tree/gbp-symbol-popover-row.ui
index 71628f6c7..a4ec1685a 100644
--- a/src/plugins/symbol-tree/gbp-symbol-popover-row.ui
+++ b/src/plugins/symbol-tree/gbp-symbol-popover-row.ui
@@ -9,6 +9,10 @@
         <property name="child">
           <object class="GtkBox">
             <property name="spacing">6</property>
+            <property name="margin-top">3</property>
+            <property name="margin-bottom">3</property>
+            <property name="margin-start">3</property>
+            <property name="margin-end">3</property>
             <child>
               <object class="GtkImage">
                 <binding name="icon-name">
diff --git a/src/plugins/symbol-tree/gbp-symbol-popover.c b/src/plugins/symbol-tree/gbp-symbol-popover.c
index 0c48716db..37d4ae753 100644
--- a/src/plugins/symbol-tree/gbp-symbol-popover.c
+++ b/src/plugins/symbol-tree/gbp-symbol-popover.c
@@ -22,16 +22,23 @@
 
 #include "config.h"
 
+#include <libide-editor.h>
+#include <libide-gui.h>
+
+#include "gbp-symbol-list-model.h"
 #include "gbp-symbol-popover.h"
 
 struct _GbpSymbolPopover
 {
-  GtkPopover      parent_instance;
+  GtkPopover       parent_instance;
+
+  IdeSymbolTree   *symbol_tree;
+  GtkFilter       *filter;
 
-  IdeSymbolTree  *symbol_tree;
+  GtkSearchEntry  *search_entry;
+  GtkListView     *list_view;
 
-  GtkSearchEntry *search_entry;
-  GtkListView    *list_view;
+  char           **search_needle;
 };
 
 enum {
@@ -44,11 +51,44 @@ G_DEFINE_FINAL_TYPE (GbpSymbolPopover, gbp_symbol_popover, GTK_TYPE_POPOVER)
 
 static GParamSpec *properties [N_PROPS];
 
+static void
+gbp_symbol_popover_get_location_cb (GObject      *object,
+                                    GAsyncResult *result,
+                                    gpointer      user_data)
+{
+  IdeSymbolNode *node = (IdeSymbolNode *)object;
+  g_autoptr(GbpSymbolPopover) self = user_data;
+  g_autoptr(IdeLocation) location = NULL;
+  g_autoptr(GError) error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_SYMBOL_NODE (node));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (GBP_IS_SYMBOL_POPOVER (self));
+
+  if ((location = ide_symbol_node_get_location_finish (node, result, &error)))
+    {
+      IdeWorkspace *workspace = ide_widget_get_workspace (GTK_WIDGET (self));
+      g_autoptr(IdePanelPosition) position = ide_panel_position_new ();
+
+      ide_editor_focus_location (workspace, position, location);
+
+      gtk_popover_popdown (GTK_POPOVER (self));
+    }
+
+  IDE_EXIT;
+}
+
 static void
 gbp_symbol_popover_activate_cb (GbpSymbolPopover *self,
                                 guint             position,
                                 GtkListView      *list_view)
 {
+  GtkTreeListRow *row;
+  IdeSymbolNode *node;
+  GListModel *model;
+
   IDE_ENTRY;
 
   g_assert (GBP_IS_SYMBOL_POPOVER (self));
@@ -56,23 +96,37 @@ gbp_symbol_popover_activate_cb (GbpSymbolPopover *self,
 
   g_debug ("Activating symbol row at position %u", position);
 
+  model = G_LIST_MODEL (gtk_list_view_get_model (list_view));
+  row = g_list_model_get_item (model, position);
+  node = gtk_tree_list_row_get_item (row);
+
+  g_assert (IDE_IS_SYMBOL_NODE (node));
+
+  ide_symbol_node_get_location_async (node,
+                                      NULL,
+                                      gbp_symbol_popover_get_location_cb,
+                                      g_object_ref (self));
+
   IDE_EXIT;
 }
 
 static void
-gbp_symbol_popover_reload (GbpSymbolPopover *self)
+gbp_symbol_popover_search_changed_cb (GbpSymbolPopover *self,
+                                      GtkSearchEntry   *search_entry)
 {
-  IDE_ENTRY;
+  const char *text;
 
   g_assert (GBP_IS_SYMBOL_POPOVER (self));
+  g_assert (GTK_IS_SEARCH_ENTRY (search_entry));
 
-  if (self->symbol_tree == NULL)
-    {
-      gtk_list_view_set_model (self->list_view, NULL);
-      IDE_EXIT;
-    }
+  text = gtk_editable_get_text (GTK_EDITABLE (search_entry));
+  g_clear_pointer (&self->search_needle, g_strfreev);
 
-  IDE_EXIT;
+  if (text && *text)
+    self->search_needle = g_str_tokenize_and_fold (text, NULL, NULL);
+
+  if (self->filter != NULL)
+    gtk_filter_changed (self->filter, GTK_FILTER_CHANGE_DIFFERENT);
 }
 
 static void
@@ -80,6 +134,7 @@ gbp_symbol_popover_dispose (GObject *object)
 {
   GbpSymbolPopover *self = (GbpSymbolPopover *)object;
 
+  g_clear_object (&self->filter);
   g_clear_object (&self->symbol_tree);
 
   G_OBJECT_CLASS (gbp_symbol_popover_parent_class)->dispose (object);
@@ -143,8 +198,10 @@ gbp_symbol_popover_class_init (GbpSymbolPopoverClass *klass)
   g_object_class_install_properties (object_class, N_PROPS, properties);
 
   gtk_widget_class_set_template_from_resource (widget_class, "/plugins/symbol-tree/gbp-symbol-popover.ui");
+  gtk_widget_class_bind_template_child (widget_class, GbpSymbolPopover, list_view);
   gtk_widget_class_bind_template_child (widget_class, GbpSymbolPopover, search_entry);
   gtk_widget_class_bind_template_callback (widget_class, gbp_symbol_popover_activate_cb);
+  gtk_widget_class_bind_template_callback (widget_class, gbp_symbol_popover_search_changed_cb);
 }
 
 static void
@@ -169,6 +226,89 @@ gbp_symbol_popover_get_symbol_tree (GbpSymbolPopover *self)
   return self->symbol_tree;
 }
 
+static GListModel *
+get_child_model (gpointer item,
+                 gpointer user_data)
+{
+  IdeSymbolNode *node = item;
+  IdeSymbolTree *tree = user_data;
+
+  g_assert (IDE_IS_SYMBOL_NODE (node));
+  g_assert (IDE_IS_SYMBOL_TREE (tree));
+
+  return G_LIST_MODEL (gbp_symbol_list_model_new (tree, node));
+}
+
+static gboolean
+filter_node (IdeSymbolNode      *node,
+             const char * const *search_needle)
+{
+  /* Show only if the name matches every needle */
+  for (guint i = 0; search_needle[i]; i++)
+    {
+      const char *name = ide_symbol_node_get_name (node);
+
+      if (!name)
+        return FALSE;
+
+      if (g_str_match_string (search_needle[i], name, TRUE))
+        continue;
+
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+filter_by_name (gpointer item,
+                gpointer user_data)
+{
+  GbpSymbolPopover *self = user_data;
+  GtkTreeListRow *row = item;
+  GtkTreeListRow *parent;
+  IdeSymbolNode *node;
+  GListModel *children;
+  guint i, n;
+
+  g_assert (GTK_IS_TREE_LIST_ROW (row));
+  g_assert (GBP_IS_SYMBOL_POPOVER (self));
+
+  /* Show all items if search is empty */
+  if (!self->search_needle || !self->search_needle[0] || !*self->search_needle[0])
+    return TRUE;
+
+  /* Show a row if itself of any parent matches */
+  for (parent = row; parent; parent = gtk_tree_list_row_get_parent (parent))
+    {
+      node = gtk_tree_list_row_get_item (parent);
+      g_assert (IDE_IS_SYMBOL_NODE (node));
+
+      if (filter_node (node, (const char * const *)self->search_needle))
+        return TRUE;
+    }
+
+  /* Show a row if any child matches */
+  if ((children = gtk_tree_list_row_get_children (row)))
+    {
+      n = g_list_model_get_n_items (children);
+      for (i = 0; i < n; i++)
+        {
+          gboolean ret;
+
+          node = g_list_model_get_item (children, i);
+          g_assert (IDE_IS_SYMBOL_NODE (node));
+
+          ret = filter_node (node, (const char * const *)self->search_needle);
+          g_object_unref (node);
+          if (ret)
+            return TRUE;
+        }
+    }
+
+  return FALSE;
+}
+
 void
 gbp_symbol_popover_set_symbol_tree (GbpSymbolPopover *self,
                                     IdeSymbolTree    *symbol_tree)
@@ -178,7 +318,35 @@ gbp_symbol_popover_set_symbol_tree (GbpSymbolPopover *self,
 
   if (g_set_object (&self->symbol_tree, symbol_tree))
     {
-      gbp_symbol_popover_reload (self);
+      gtk_list_view_set_model (self->list_view, NULL);
+
+      if (symbol_tree != NULL)
+        {
+          GbpSymbolListModel *model;
+          GtkTreeListModel *tree_model;
+          GtkFilterListModel *filter_model;
+          GtkSingleSelection *selection;
+          GtkFilter *filter;
+
+          model = gbp_symbol_list_model_new (symbol_tree, NULL);
+          tree_model = gtk_tree_list_model_new (G_LIST_MODEL (model),
+                                                FALSE,
+                                                TRUE,
+                                                get_child_model,
+                                                g_object_ref (symbol_tree),
+                                                g_object_unref);
+
+          filter_model = gtk_filter_list_model_new (G_LIST_MODEL (tree_model), NULL);
+          filter = GTK_FILTER (gtk_custom_filter_new (filter_by_name, self, NULL));
+          gtk_filter_list_model_set_filter (filter_model, filter);
+          g_set_object (&self->filter, filter);
+          g_object_unref (filter);
+
+          selection = gtk_single_selection_new (G_LIST_MODEL (filter_model));
+          gtk_list_view_set_model (self->list_view, GTK_SELECTION_MODEL (selection));
+          g_object_unref (selection);
+        }
+
       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SYMBOL_TREE]);
     }
 }
diff --git a/src/plugins/symbol-tree/gbp-symbol-popover.ui b/src/plugins/symbol-tree/gbp-symbol-popover.ui
index 5a0c8d532..be0bfb44b 100644
--- a/src/plugins/symbol-tree/gbp-symbol-popover.ui
+++ b/src/plugins/symbol-tree/gbp-symbol-popover.ui
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <template class="GbpSymbolPopover" parent="GtkPopover">
-    <property name="width-request">300</property>
+    <property name="width-request">400</property>
     <child>
       <object class="GtkBox">
         <property name="orientation">vertical</property>
@@ -14,7 +14,7 @@
                 <property name="propagate-natural-width">true</property>
                 <property name="min-content-height">100</property>
                 <property name="max-content-height">600</property>
-                <property name="min-content-width">300</property>
+                <property name="min-content-width">400</property>
                 <property name="max-content-width">400</property>
                 <child>
                   <object class="GtkListView" id="list_view">
@@ -35,6 +35,7 @@
         <child>
           <object class="GtkSearchEntry" id="search_entry">
             <property name="placeholder-text" translatable="yes">Search Symbols…</property>
+            <signal name="search-changed" handler="gbp_symbol_popover_search_changed_cb" swapped="true" 
object="GbpSymbolPopover"/>
           </object>
         </child>
       </object>
diff --git a/src/plugins/symbol-tree/meson.build b/src/plugins/symbol-tree/meson.build
index 54fed0533..94f64dc50 100644
--- a/src/plugins/symbol-tree/meson.build
+++ b/src/plugins/symbol-tree/meson.build
@@ -1,5 +1,6 @@
 plugins_sources += files([
   'gbp-symbol-hover-provider.c',
+  'gbp-symbol-list-model.c',
   'gbp-symbol-popover.c',
   'gbp-symbol-util.c',
   'gbp-symbol-workspace-addin.c',


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