[gnome-builder/wip/chergert/perspective] libide: move GbTree to IdeTree
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/chergert/perspective] libide: move GbTree to IdeTree
- Date: Wed, 18 Nov 2015 13:10:29 +0000 (UTC)
commit b2de0f9486027a26f71e48645af8b9e56ac2bf91
Author: Christian Hergert <chergert redhat com>
Date: Wed Nov 18 05:10:21 2015 -0800
libide: move GbTree to IdeTree
libide/Makefile.am | 8 +
libide/ide-tree-builder.c | 302 ++++++++
libide/ide-tree-builder.h | 54 ++
libide/ide-tree-node.c | 970 +++++++++++++++++++++++++
libide/ide-tree-node.h | 73 ++
libide/ide-tree-private.h | 79 ++
libide/ide-tree-types.h | 40 +
libide/ide-tree.c | 1749 +++++++++++++++++++++++++++++++++++++++++++++
libide/ide-tree.h | 98 +++
9 files changed, 3373 insertions(+), 0 deletions(-)
---
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 3d21325..a25f297 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -199,6 +199,13 @@ libide_1_0_la_public_sources = \
ide-test-suite.h \
ide-thread-pool.c \
ide-thread-pool.h \
+ ide-tree-builder.c \
+ ide-tree-builder.h \
+ ide-tree-node.c \
+ ide-tree-node.h \
+ ide-tree-types.h \
+ ide-tree.c \
+ ide-tree.h \
ide-types.h \
ide-unsaved-file.c \
ide-unsaved-file.h \
@@ -299,6 +306,7 @@ libide_1_0_la_SOURCES = \
ide-source-view-capture.h \
ide-source-view-movements.c \
ide-source-view-movements.h \
+ ide-tree-private.h \
ide-vim-iter.c \
ide-vim-iter.h \
ide-workbench-actions.c \
diff --git a/libide/ide-tree-builder.c b/libide/ide-tree-builder.c
new file mode 100644
index 0000000..2e680e1
--- /dev/null
+++ b/libide/ide-tree-builder.c
@@ -0,0 +1,302 @@
+/* ide-tree-builder.c
+ *
+ * Copyright (C) 2011 Christian Hergert <chris dronelabs 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/>.
+ */
+
+#define G_LOG_DOMAIN "tree-builder"
+
+#include <glib/gi18n.h>
+
+#include "ide-tree.h"
+#include "ide-tree-builder.h"
+
+typedef struct
+{
+ IdeTree *tree;
+} IdeTreeBuilderPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeTreeBuilder, ide_tree_builder, G_TYPE_INITIALLY_UNOWNED)
+
+enum {
+ PROP_0,
+ PROP_TREE,
+ LAST_PROP
+};
+
+enum {
+ ADDED,
+ REMOVED,
+ BUILD_NODE,
+ NODE_ACTIVATED,
+ NODE_POPUP,
+ NODE_SELECTED,
+ NODE_UNSELECTED,
+ LAST_SIGNAL
+};
+
+static GParamSpec *properties [LAST_PROP];
+static guint signals [LAST_SIGNAL];
+
+gboolean
+_ide_tree_builder_node_activated (IdeTreeBuilder *builder,
+ IdeTreeNode *node)
+{
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail (IDE_IS_TREE_BUILDER(builder), FALSE);
+ g_return_val_if_fail (IDE_IS_TREE_NODE(node), FALSE);
+
+ g_signal_emit (builder, signals [NODE_ACTIVATED], 0, node, &ret);
+
+ return ret;
+}
+
+void
+_ide_tree_builder_node_popup (IdeTreeBuilder *builder,
+ IdeTreeNode *node,
+ GMenu *menu)
+{
+ g_return_if_fail (IDE_IS_TREE_BUILDER (builder));
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+ g_return_if_fail (G_IS_MENU (menu));
+
+ g_signal_emit (builder, signals [NODE_POPUP], 0, node, menu);
+}
+
+void
+_ide_tree_builder_node_selected (IdeTreeBuilder *builder,
+ IdeTreeNode *node)
+{
+ g_return_if_fail (IDE_IS_TREE_BUILDER (builder));
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+
+ g_signal_emit (builder, signals [NODE_SELECTED], 0, node);
+}
+
+void
+_ide_tree_builder_node_unselected (IdeTreeBuilder *builder,
+ IdeTreeNode *node)
+{
+ g_return_if_fail (IDE_IS_TREE_BUILDER (builder));
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+
+ g_signal_emit (builder, signals [NODE_UNSELECTED], 0, node);
+}
+
+void
+_ide_tree_builder_build_node (IdeTreeBuilder *builder,
+ IdeTreeNode *node)
+{
+ g_return_if_fail (IDE_IS_TREE_BUILDER (builder));
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+
+ g_signal_emit (builder, signals [BUILD_NODE], 0, node);
+}
+
+void
+_ide_tree_builder_added (IdeTreeBuilder *builder,
+ IdeTree *tree)
+{
+ g_return_if_fail (IDE_IS_TREE_BUILDER (builder));
+ g_return_if_fail (IDE_IS_TREE (tree));
+
+ g_signal_emit (builder, signals [ADDED], 0, tree);
+}
+
+void
+_ide_tree_builder_removed (IdeTreeBuilder *builder,
+ IdeTree *tree)
+{
+ g_return_if_fail (IDE_IS_TREE_BUILDER (builder));
+ g_return_if_fail (IDE_IS_TREE (tree));
+
+ g_signal_emit (builder, signals [REMOVED], 0, tree);
+}
+
+void
+_ide_tree_builder_set_tree (IdeTreeBuilder *builder,
+ IdeTree *tree)
+{
+ IdeTreeBuilderPrivate *priv = ide_tree_builder_get_instance_private (builder);
+
+ g_return_if_fail (IDE_IS_TREE_BUILDER (builder));
+ g_return_if_fail (priv->tree == NULL);
+ g_return_if_fail (IDE_IS_TREE (tree));
+
+ if (priv->tree != tree)
+ {
+ if (priv->tree != NULL)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (priv->tree), (gpointer *)&priv->tree);
+ priv->tree = NULL;
+ }
+
+ if (tree != NULL)
+ {
+ priv->tree = tree;
+ g_object_add_weak_pointer (G_OBJECT (priv->tree), (gpointer *)&priv->tree);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (builder), properties [PROP_TREE]);
+ }
+}
+
+/**
+ * ide_tree_builder_get_tree:
+ * @builder: (in): A #IdeTreeBuilder.
+ *
+ * Gets the tree that owns the builder.
+ *
+ * Returns: (transfer none) (type IdeTree) (nullable): A #IdeTree or %NULL.
+ */
+IdeTree *
+ide_tree_builder_get_tree (IdeTreeBuilder *builder)
+{
+ IdeTreeBuilderPrivate *priv = ide_tree_builder_get_instance_private (builder);
+
+ g_return_val_if_fail (IDE_IS_TREE_BUILDER (builder), NULL);
+
+ return priv->tree;
+}
+
+static void
+ide_tree_builder_finalize (GObject *object)
+{
+ IdeTreeBuilder *builder = IDE_TREE_BUILDER (object);
+ IdeTreeBuilderPrivate *priv = ide_tree_builder_get_instance_private (builder);
+
+ if (priv->tree)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (priv->tree), (gpointer *)&priv->tree);
+ priv->tree = NULL;
+ }
+
+ G_OBJECT_CLASS (ide_tree_builder_parent_class)->finalize (object);
+}
+
+static void
+ide_tree_builder_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeTreeBuilder *builder = IDE_TREE_BUILDER (object);
+ IdeTreeBuilderPrivate *priv = ide_tree_builder_get_instance_private (builder);
+
+ switch (prop_id)
+ {
+ case PROP_TREE:
+ g_value_set_object (value, priv->tree);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_tree_builder_class_init (IdeTreeBuilderClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = ide_tree_builder_finalize;
+ object_class->get_property = ide_tree_builder_get_property;
+
+ properties[PROP_TREE] =
+ g_param_spec_object("tree",
+ "Tree",
+ "The IdeTree the builder belongs to.",
+ IDE_TYPE_TREE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ signals [ADDED] =
+ g_signal_new ("added",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IdeTreeBuilderClass, added),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ IDE_TYPE_TREE);
+
+ signals [BUILD_NODE] =
+ g_signal_new ("build-node",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IdeTreeBuilderClass, build_node),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ IDE_TYPE_TREE_NODE);
+
+ signals [NODE_ACTIVATED] =
+ g_signal_new ("node-activated",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IdeTreeBuilderClass, node_activated),
+ NULL, NULL, NULL,
+ G_TYPE_BOOLEAN,
+ 1,
+ IDE_TYPE_TREE_NODE);
+
+ signals [NODE_POPUP] =
+ g_signal_new ("node-popup",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IdeTreeBuilderClass, node_popup),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 2,
+ IDE_TYPE_TREE_NODE,
+ G_TYPE_MENU);
+
+ signals [NODE_SELECTED] =
+ g_signal_new ("node-selected",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IdeTreeBuilderClass, node_selected),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ IDE_TYPE_TREE_NODE);
+
+ signals [NODE_UNSELECTED] =
+ g_signal_new ("node-unselected",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IdeTreeBuilderClass, node_unselected),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ IDE_TYPE_TREE_NODE);
+
+ signals [REMOVED] =
+ g_signal_new ("removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IdeTreeBuilderClass, removed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ IDE_TYPE_TREE);
+}
+
+static void
+ide_tree_builder_init (IdeTreeBuilder *builder)
+{
+}
diff --git a/libide/ide-tree-builder.h b/libide/ide-tree-builder.h
new file mode 100644
index 0000000..c04e1d8
--- /dev/null
+++ b/libide/ide-tree-builder.h
@@ -0,0 +1,54 @@
+/* ide-tree-builder.h
+ *
+ * Copyright (C) 2011 Christian Hergert <chris dronelabs 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/>.
+ */
+
+#ifndef IDE_TREE_BUILDER_H
+#define IDE_TREE_BUILDER_H
+
+#include <glib-object.h>
+
+#include "ide-tree-node.h"
+#include "ide-tree-types.h"
+
+G_BEGIN_DECLS
+
+struct _IdeTreeBuilderClass
+{
+ GInitiallyUnownedClass parent_class;
+
+ void (*added) (IdeTreeBuilder *builder,
+ GtkWidget *tree);
+ void (*removed) (IdeTreeBuilder *builder,
+ GtkWidget *tree);
+ void (*build_node) (IdeTreeBuilder *builder,
+ IdeTreeNode *node);
+ gboolean (*node_activated) (IdeTreeBuilder *builder,
+ IdeTreeNode *node);
+ void (*node_selected) (IdeTreeBuilder *builder,
+ IdeTreeNode *node);
+ void (*node_unselected) (IdeTreeBuilder *builder,
+ IdeTreeNode *node);
+ void (*node_popup) (IdeTreeBuilder *builder,
+ IdeTreeNode *node,
+ GMenu *menu);
+};
+
+IdeTree *ide_tree_builder_get_tree (IdeTreeBuilder *builder);
+
+G_END_DECLS
+
+#endif /* IDE_TREE_BUILDER_H */
diff --git a/libide/ide-tree-node.c b/libide/ide-tree-node.c
new file mode 100644
index 0000000..71d5f5e
--- /dev/null
+++ b/libide/ide-tree-node.c
@@ -0,0 +1,970 @@
+/* ide-tree-node.c
+ *
+ * Copyright (C) 2011 Christian Hergert <chris dronelabs 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/>.
+ */
+
+#define G_LOG_DOMAIN "tree-node"
+
+#include <glib/gi18n.h>
+
+#include "egg-counter.h"
+
+#include "ide-tree.h"
+#include "ide-tree-node.h"
+#include "ide-tree-private.h"
+
+struct _IdeTreeNode
+{
+ GInitiallyUnowned parent_instance;
+
+ GObject *item;
+ IdeTreeNode *parent;
+ gchar *text;
+ IdeTree *tree;
+ GQuark icon_name;
+ guint use_markup : 1;
+ guint needs_build : 1;
+ guint is_dummy : 1;
+ guint children_possible : 1;
+ guint use_dim_label : 1;
+};
+
+typedef struct
+{
+ IdeTreeNode *self;
+ GtkPopover *popover;
+} PopupRequest;
+
+G_DEFINE_TYPE (IdeTreeNode, ide_tree_node, G_TYPE_INITIALLY_UNOWNED)
+EGG_DEFINE_COUNTER (instances, "IdeTreeNode", "Instances", "Number of IdeTreeNode instances")
+
+enum {
+ PROP_0,
+ PROP_CHILDREN_POSSIBLE,
+ PROP_ICON_NAME,
+ PROP_ITEM,
+ PROP_PARENT,
+ PROP_TEXT,
+ PROP_TREE,
+ PROP_USE_DIM_LABEL,
+ PROP_USE_MARKUP,
+ LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+/**
+ * ide_tree_node_new:
+ *
+ * Creates a new #IdeTreeNode instance. This is handy for situations where you
+ * do not want to subclass #IdeTreeNode.
+ *
+ * Returns: (transfer full): A #IdeTreeNode
+ */
+IdeTreeNode *
+ide_tree_node_new (void)
+{
+ return g_object_new (IDE_TYPE_TREE_NODE, NULL);
+}
+
+/**
+ * ide_tree_node_get_tree:
+ * @node: (in): A #IdeTreeNode.
+ *
+ * Fetches the #IdeTree instance that owns the node.
+ *
+ * Returns: (transfer none): A #IdeTree.
+ */
+IdeTree *
+ide_tree_node_get_tree (IdeTreeNode *node)
+{
+ g_return_val_if_fail (IDE_IS_TREE_NODE (node), NULL);
+
+ return node->tree;
+}
+
+/**
+ * ide_tree_node_set_tree:
+ * @node: (in): A #IdeTreeNode.
+ * @tree: (in): A #IdeTree.
+ *
+ * Internal method to set the nodes tree.
+ */
+void
+_ide_tree_node_set_tree (IdeTreeNode *node,
+ IdeTree *tree)
+{
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+ g_return_if_fail (!tree || IDE_IS_TREE (tree));
+
+ if (node->tree != tree)
+ {
+ if (node->tree != NULL)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (node->tree), (gpointer *)&node->tree);
+ node->tree = NULL;
+ }
+
+ if (tree != NULL)
+ {
+ node->tree = tree;
+ g_object_add_weak_pointer (G_OBJECT (node->tree), (gpointer *)&node->tree);
+ }
+ }
+}
+
+/**
+ * ide_tree_node_insert_sorted:
+ * @node: A #IdeTreeNode.
+ * @child: A #IdeTreeNode.
+ * @compare_func: (scope call): A compare func to compare nodes.
+ * @user_data: user data for @compare_func.
+ *
+ * Inserts a @child as a child of @node, sorting it among the other children.
+ */
+void
+ide_tree_node_insert_sorted (IdeTreeNode *node,
+ IdeTreeNode *child,
+ IdeTreeNodeCompareFunc compare_func,
+ gpointer user_data)
+{
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+ g_return_if_fail (IDE_IS_TREE_NODE (child));
+ g_return_if_fail (compare_func != NULL);
+
+ _ide_tree_insert_sorted (node->tree, node, child, compare_func, user_data);
+}
+
+/**
+ * ide_tree_node_append:
+ * @node: A #IdeTreeNode.
+ * @child: A #IdeTreeNode.
+ *
+ * Appends @child to the list of children owned by @node.
+ */
+void
+ide_tree_node_append (IdeTreeNode *node,
+ IdeTreeNode *child)
+{
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+
+ _ide_tree_append (node->tree, node, child);
+}
+
+/**
+ * ide_tree_node_prepend:
+ * @node: A #IdeTreeNode.
+ * @child: A #IdeTreeNode.
+ *
+ * Prepends @child to the list of children owned by @node.
+ */
+void
+ide_tree_node_prepend (IdeTreeNode *node,
+ IdeTreeNode *child)
+{
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+
+ _ide_tree_prepend (node->tree, node, child);
+}
+
+/**
+ * ide_tree_node_remove:
+ * @node: A #IdeTreeNode.
+ * @child: A #IdeTreeNode.
+ *
+ * Removes @child from the list of children owned by @node.
+ */
+void
+ide_tree_node_remove (IdeTreeNode *node,
+ IdeTreeNode *child)
+{
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+ g_return_if_fail (IDE_IS_TREE_NODE (child));
+
+ _ide_tree_remove (node->tree, child);
+}
+
+/**
+ * ide_tree_node_get_path:
+ * @node: (in): A #IdeTreeNode.
+ *
+ * Gets a #GtkTreePath for @node.
+ *
+ * Returns: (nullable) (transfer full): A #GtkTreePath if successful; otherwise %NULL.
+ */
+GtkTreePath *
+ide_tree_node_get_path (IdeTreeNode *node)
+{
+ IdeTreeNode *toplevel;
+ GtkTreePath *path;
+ GList *list = NULL;
+
+ g_return_val_if_fail (IDE_IS_TREE_NODE (node), NULL);
+
+ if ((node->parent == NULL) || (node->tree == NULL))
+ return NULL;
+
+ do
+ {
+ list = g_list_prepend (list, node);
+ }
+ while ((node = node->parent));
+
+ toplevel = list->data;
+
+ g_assert (toplevel);
+ g_assert (toplevel->tree);
+
+ path = _ide_tree_get_path (toplevel->tree, list);
+
+ g_list_free (list);
+
+ return path;
+}
+
+gboolean
+ide_tree_node_get_iter (IdeTreeNode *self,
+ GtkTreeIter *iter)
+{
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail (IDE_IS_TREE_NODE (self), FALSE);
+ g_return_val_if_fail (iter != NULL, FALSE);
+
+ if (self->tree != NULL)
+ ret = _ide_tree_get_iter (self->tree, self, iter);
+
+ return ret;
+}
+
+/**
+ * ide_tree_node_get_parent:
+ * @node: (in): A #IdeTreeNode.
+ *
+ * Retrieves the parent #IdeTreeNode for @node.
+ *
+ * Returns: (transfer none): A #IdeTreeNode.
+ */
+IdeTreeNode *
+ide_tree_node_get_parent (IdeTreeNode *node)
+{
+ g_return_val_if_fail (IDE_IS_TREE_NODE (node), NULL);
+
+ return node->parent;
+}
+
+/**
+ * ide_tree_node_get_icon_name:
+ *
+ * Fetches the icon-name of the icon to display, or NULL for no icon.
+ */
+const gchar *
+ide_tree_node_get_icon_name (IdeTreeNode *node)
+{
+ g_return_val_if_fail (IDE_IS_TREE_NODE (node), NULL);
+
+ return g_quark_to_string (node->icon_name);
+}
+
+/**
+ * ide_tree_node_set_icon_name:
+ * @node: (in): A #IdeTreeNode.
+ * @icon_name: (in): The icon name.
+ *
+ * Sets the icon name of the node. This is displayed in the pixbuf
+ * cell of the IdeTree.
+ */
+void
+ide_tree_node_set_icon_name (IdeTreeNode *node,
+ const gchar *icon_name)
+{
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+
+ node->icon_name = g_quark_from_string (icon_name);
+ g_object_notify_by_pspec (G_OBJECT (node), properties [PROP_ICON_NAME]);
+}
+
+/**
+ * ide_tree_node_set_item:
+ * @node: (in): A #IdeTreeNode.
+ * @item: (in): A #GObject.
+ *
+ * An optional object to associate with the node. This is handy to save needing
+ * to subclass the #IdeTreeNode class.
+ */
+void
+ide_tree_node_set_item (IdeTreeNode *node,
+ GObject *item)
+{
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+ g_return_if_fail (!item || G_IS_OBJECT (item));
+
+ if (g_set_object (&node->item, item))
+ g_object_notify_by_pspec (G_OBJECT (node), properties [PROP_ITEM]);
+}
+
+void
+_ide_tree_node_set_parent (IdeTreeNode *node,
+ IdeTreeNode *parent)
+{
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+ g_return_if_fail (node->parent == NULL);
+ g_return_if_fail (!parent || IDE_IS_TREE_NODE (parent));
+
+ if (parent != node->parent)
+ {
+ if (node->parent != NULL)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (node->parent), (gpointer *)&node->parent);
+ node->parent = NULL;
+ }
+
+ if (parent != NULL)
+ {
+ node->parent = parent;
+ g_object_add_weak_pointer (G_OBJECT (node->parent), (gpointer *)&node->parent);
+ }
+ }
+}
+
+const gchar *
+ide_tree_node_get_text (IdeTreeNode *node)
+{
+ g_return_val_if_fail (IDE_IS_TREE_NODE (node), NULL);
+
+ return node->text;
+}
+
+/**
+ * ide_tree_node_set_text:
+ * @node: (in): A #IdeTreeNode.
+ * @text: (in): The node text.
+ *
+ * Sets the text of the node. This is displayed in the text
+ * cell of the IdeTree.
+ */
+void
+ide_tree_node_set_text (IdeTreeNode *node,
+ const gchar *text)
+{
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+
+ if (g_strcmp0 (text, node->text) != 0)
+ {
+ g_free (node->text);
+ node->text = g_strdup (text);
+ g_object_notify_by_pspec (G_OBJECT (node), properties [PROP_TEXT]);
+ }
+}
+
+gboolean
+ide_tree_node_get_use_markup (IdeTreeNode *self)
+{
+ g_return_val_if_fail (IDE_IS_TREE_NODE (self), FALSE);
+
+ return self->use_markup;
+}
+
+void
+ide_tree_node_set_use_markup (IdeTreeNode *self,
+ gboolean use_markup)
+{
+ g_return_if_fail (IDE_IS_TREE_NODE (self));
+
+ use_markup = !!use_markup;
+
+ if (self->use_markup != use_markup)
+ {
+ self->use_markup = use_markup;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_USE_MARKUP]);
+ }
+}
+
+/**
+ * ide_tree_node_get_item:
+ * @node: (in): A #IdeTreeNode.
+ *
+ * Gets a #GObject for the node, if one was set.
+ *
+ * Returns: (transfer none): A #GObject or %NULL.
+ */
+GObject *
+ide_tree_node_get_item (IdeTreeNode *node)
+{
+ g_return_val_if_fail (IDE_IS_TREE_NODE (node), NULL);
+
+ return node->item;
+}
+
+gboolean
+ide_tree_node_expand (IdeTreeNode *node,
+ gboolean expand_ancestors)
+{
+ IdeTree *tree;
+ GtkTreePath *path;
+ gboolean ret;
+
+ g_return_val_if_fail (IDE_IS_TREE_NODE (node), FALSE);
+
+ tree = ide_tree_node_get_tree (node);
+ path = ide_tree_node_get_path (node);
+ ret = gtk_tree_view_expand_row (GTK_TREE_VIEW (tree), path, FALSE);
+ if (expand_ancestors)
+ gtk_tree_view_expand_to_path (GTK_TREE_VIEW (tree), path);
+ gtk_tree_path_free (path);
+
+ return ret;
+}
+
+void
+ide_tree_node_collapse (IdeTreeNode *node)
+{
+ IdeTree *tree;
+ GtkTreePath *path;
+
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+
+ tree = ide_tree_node_get_tree (node);
+ path = ide_tree_node_get_path (node);
+ gtk_tree_view_collapse_row (GTK_TREE_VIEW (tree), path);
+ gtk_tree_path_free (path);
+}
+
+void
+ide_tree_node_select (IdeTreeNode *node)
+{
+ IdeTree *tree;
+ GtkTreePath *path;
+ GtkTreeSelection *selection;
+
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+
+ tree = ide_tree_node_get_tree (node);
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
+ path = ide_tree_node_get_path (node);
+ gtk_tree_selection_select_path (selection, path);
+ gtk_tree_path_free (path);
+}
+
+void
+ide_tree_node_get_area (IdeTreeNode *node,
+ GdkRectangle *area)
+{
+ IdeTree *tree;
+ GtkTreeViewColumn *column;
+ GtkTreePath *path;
+
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+ g_return_if_fail (area != NULL);
+
+ tree = ide_tree_node_get_tree (node);
+ path = ide_tree_node_get_path (node);
+ column = gtk_tree_view_get_column (GTK_TREE_VIEW (tree), 0);
+ gtk_tree_view_get_cell_area (GTK_TREE_VIEW (tree), path, column, area);
+ gtk_tree_path_free (path);
+}
+
+void
+ide_tree_node_invalidate (IdeTreeNode *self)
+{
+ g_return_if_fail (IDE_IS_TREE_NODE (self));
+
+ if (self->tree != NULL)
+ _ide_tree_invalidate (self->tree, self);
+}
+
+gboolean
+ide_tree_node_get_expanded (IdeTreeNode *self)
+{
+ GtkTreePath *path;
+ gboolean ret = TRUE;
+
+ g_return_val_if_fail (IDE_IS_TREE_NODE (self), FALSE);
+
+ if ((self->tree != NULL) && (self->parent != NULL))
+ {
+ path = ide_tree_node_get_path (self);
+ ret = gtk_tree_view_row_expanded (GTK_TREE_VIEW (self->tree), path);
+ gtk_tree_path_free (path);
+ }
+
+ return ret;
+}
+
+static void
+ide_tree_node_finalize (GObject *object)
+{
+ IdeTreeNode *self = IDE_TREE_NODE (object);
+
+ g_clear_object (&self->item);
+ g_clear_pointer (&self->text, g_free);
+
+ if (self->tree)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (self->tree), (gpointer *)&self->tree);
+ self->tree = NULL;
+ }
+
+ if (self->parent)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (self->parent),
+ (gpointer *)&self->parent);
+ self->parent = NULL;
+ }
+
+ G_OBJECT_CLASS (ide_tree_node_parent_class)->finalize (object);
+
+ EGG_COUNTER_DEC (instances);
+}
+
+static void
+ide_tree_node_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeTreeNode *node = IDE_TREE_NODE (object);
+
+ switch (prop_id)
+ {
+ case PROP_CHILDREN_POSSIBLE:
+ g_value_set_boolean (value, ide_tree_node_get_children_possible (node));
+ break;
+
+ case PROP_ICON_NAME:
+ g_value_set_string (value, g_quark_to_string (node->icon_name));
+ break;
+
+ case PROP_ITEM:
+ g_value_set_object (value, node->item);
+ break;
+
+ case PROP_PARENT:
+ g_value_set_object (value, node->parent);
+ break;
+
+ case PROP_TEXT:
+ g_value_set_string (value, node->text);
+ break;
+
+ case PROP_TREE:
+ g_value_set_object (value, ide_tree_node_get_tree (node));
+ break;
+
+ case PROP_USE_DIM_LABEL:
+ g_value_set_boolean (value, node->use_dim_label);
+ break;
+
+ case PROP_USE_MARKUP:
+ g_value_set_boolean (value, node->use_markup);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_tree_node_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeTreeNode *node = IDE_TREE_NODE (object);
+
+ switch (prop_id)
+ {
+ case PROP_CHILDREN_POSSIBLE:
+ ide_tree_node_set_children_possible (node, g_value_get_boolean (value));
+ break;
+
+ case PROP_ICON_NAME:
+ ide_tree_node_set_icon_name (node, g_value_get_string (value));
+ break;
+
+ case PROP_ITEM:
+ ide_tree_node_set_item (node, g_value_get_object (value));
+ break;
+
+ case PROP_TEXT:
+ ide_tree_node_set_text (node, g_value_get_string (value));
+ break;
+
+ case PROP_USE_DIM_LABEL:
+ ide_tree_node_set_use_dim_label (node, g_value_get_boolean (value));
+ break;
+
+ case PROP_USE_MARKUP:
+ ide_tree_node_set_use_markup (node, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_tree_node_class_init (IdeTreeNodeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = ide_tree_node_finalize;
+ object_class->get_property = ide_tree_node_get_property;
+ object_class->set_property = ide_tree_node_set_property;
+
+ /**
+ * IdeTreeNode:children-possible:
+ *
+ * This property allows for more lazy loading of nodes.
+ *
+ * When a node becomes visible, we normally build it's children nodes
+ * so that we know if we need an expansion arrow. However, that can
+ * be expensive when rendering directories with lots of subdirectories.
+ *
+ * Using this, you can always show an arrow without building the children
+ * and simply hide the arrow if there were in fact no children (upon
+ * expansion).
+ */
+ properties [PROP_CHILDREN_POSSIBLE] =
+ g_param_spec_boolean ("children-possible",
+ "Children Possible",
+ "Allows for lazy creation of children nodes.",
+ FALSE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * IdeTreeNode:icon-name:
+ *
+ * An icon-name to display on the row.
+ */
+ properties[PROP_ICON_NAME] =
+ g_param_spec_string ("icon-name",
+ "Icon Name",
+ "The icon name to display.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * IdeTreeNode:item:
+ *
+ * An optional #GObject to associate with the node.
+ */
+ properties[PROP_ITEM] =
+ g_param_spec_object ("item",
+ "Item",
+ "Optional object to associate with node.",
+ G_TYPE_OBJECT,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * IdeTreeNode:parent:
+ *
+ * The parent of the node.
+ */
+ properties [PROP_PARENT] =
+ g_param_spec_object ("parent",
+ "Parent",
+ "The parent node.",
+ IDE_TYPE_TREE_NODE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * IdeTreeNode:tree:
+ *
+ * The tree the node belongs to.
+ */
+ properties [PROP_TREE] =
+ g_param_spec_object ("tree",
+ "Tree",
+ "The IdeTree the node belongs to.",
+ IDE_TYPE_TREE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * IdeTreeNode:text:
+ *
+ * Text to display on the tree node.
+ */
+ properties [PROP_TEXT] =
+ g_param_spec_string ("text",
+ "Text",
+ "The text of the node.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * IdeTreeNode:use-markup:
+ *
+ * If the "text" property includes #GMarkup.
+ */
+ properties [PROP_USE_MARKUP] =
+ g_param_spec_boolean ("use-markup",
+ "Use Markup",
+ "If text should be translated as markup.",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ properties [PROP_USE_DIM_LABEL] =
+ g_param_spec_boolean ("use-dim-label",
+ "Use Dim Label",
+ "If text should be rendered with a dim label.",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+ide_tree_node_init (IdeTreeNode *node)
+{
+ EGG_COUNTER_INC (instances);
+
+ node->needs_build = TRUE;
+}
+
+static gboolean
+ide_tree_node_show_popover_timeout_cb (gpointer data)
+{
+ PopupRequest *popreq = data;
+ GdkRectangle rect;
+ GtkAllocation alloc;
+ IdeTree *tree;
+
+ g_assert (popreq);
+ g_assert (IDE_IS_TREE_NODE (popreq->self));
+ g_assert (GTK_IS_POPOVER (popreq->popover));
+
+ if (!(tree = ide_tree_node_get_tree (popreq->self)))
+ goto cleanup;
+
+ ide_tree_node_get_area (popreq->self, &rect);
+ gtk_widget_get_allocation (GTK_WIDGET (tree), &alloc);
+
+ if ((rect.x + rect.width) > (alloc.x + alloc.width))
+ rect.width = (alloc.x + alloc.width) - rect.x;
+
+ /*
+ * FIXME: Wouldn't this be better placed in a theme?
+ */
+ switch (gtk_popover_get_position (popreq->popover))
+ {
+ case GTK_POS_BOTTOM:
+ case GTK_POS_TOP:
+ rect.y += 3;
+ rect.height -= 6;
+ break;
+ case GTK_POS_RIGHT:
+ case GTK_POS_LEFT:
+ rect.x += 3;
+ rect.width -= 6;
+ break;
+
+ default:
+ break;
+ }
+
+ gtk_popover_set_relative_to (popreq->popover, GTK_WIDGET (tree));
+ gtk_popover_set_pointing_to (popreq->popover, &rect);
+ gtk_widget_show (GTK_WIDGET (popreq->popover));
+
+cleanup:
+ g_object_unref (popreq->self);
+ g_object_unref (popreq->popover);
+ g_free (popreq);
+
+ return G_SOURCE_REMOVE;
+}
+
+void
+ide_tree_node_show_popover (IdeTreeNode *self,
+ GtkPopover *popover)
+{
+ GdkRectangle cell_area;
+ GdkRectangle visible_rect;
+ IdeTree *tree;
+ PopupRequest *popreq;
+
+ g_return_if_fail (IDE_IS_TREE_NODE (self));
+ g_return_if_fail (GTK_IS_POPOVER (popover));
+
+ tree = ide_tree_node_get_tree (self);
+ gtk_tree_view_get_visible_rect (GTK_TREE_VIEW (tree), &visible_rect);
+ ide_tree_node_get_area (self, &cell_area);
+ gtk_tree_view_convert_bin_window_to_tree_coords (GTK_TREE_VIEW (tree),
+ cell_area.x,
+ cell_area.y,
+ &cell_area.x,
+ &cell_area.y);
+
+ popreq = g_new0 (PopupRequest, 1);
+ popreq->self = g_object_ref (self);
+ popreq->popover = g_object_ref (popover);
+
+ /*
+ * If the node is not on screen, we need to animate until we get there.
+ */
+ if ((cell_area.y < visible_rect.y) ||
+ ((cell_area.y + cell_area.height) >
+ (visible_rect.y + visible_rect.height)))
+ {
+ GtkTreePath *path;
+
+ path = ide_tree_node_get_path (self);
+ gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (tree), path, NULL, FALSE, 0, 0);
+ gtk_tree_path_free (path);
+
+ /*
+ * FIXME: Time period comes from gtk animation duration.
+ * Not curently available in pubic API.
+ * We need to be greater than the max timeout it
+ * could take to move, since we must have it
+ * on screen by then.
+ *
+ * One alternative might be to check the result
+ * and if we are still not on screen, then just
+ * pin it to a row-height from the top or bottom.
+ */
+ g_timeout_add (300,
+ ide_tree_node_show_popover_timeout_cb,
+ popreq);
+ }
+ else
+ {
+ ide_tree_node_show_popover_timeout_cb (popreq);
+ }
+}
+
+gboolean
+_ide_tree_node_get_needs_build (IdeTreeNode *self)
+{
+ g_assert (IDE_IS_TREE_NODE (self));
+
+ return self->needs_build;
+}
+
+void
+_ide_tree_node_set_needs_build (IdeTreeNode *self,
+ gboolean needs_build)
+{
+ g_assert (IDE_IS_TREE_NODE (self));
+
+ self->needs_build = !!needs_build;
+
+ if (!needs_build)
+ self->is_dummy = FALSE;
+}
+
+void
+_ide_tree_node_add_dummy_child (IdeTreeNode *self)
+{
+ GtkTreeStore *model;
+ IdeTreeNode *dummy;
+ GtkTreeIter iter;
+ GtkTreeIter parent;
+
+ g_assert (IDE_IS_TREE_NODE (self));
+
+ model = _ide_tree_get_store (self->tree);
+ ide_tree_node_get_iter (self, &parent);
+ dummy = g_object_ref_sink (ide_tree_node_new ());
+ gtk_tree_store_insert_with_values (model, &iter, &parent, -1,
+ 0, dummy,
+ -1);
+ g_object_unref (dummy);
+}
+
+void
+_ide_tree_node_remove_dummy_child (IdeTreeNode *self)
+{
+ GtkTreeStore *model;
+ GtkTreeIter iter;
+ GtkTreeIter children;
+
+ g_assert (IDE_IS_TREE_NODE (self));
+
+ if (self->parent == NULL)
+ return;
+
+ model = _ide_tree_get_store (self->tree);
+
+ if (ide_tree_node_get_iter (self, &iter) &&
+ gtk_tree_model_iter_children (GTK_TREE_MODEL (model), &children, &iter))
+ {
+ while (gtk_tree_store_remove (model, &children)) { }
+ }
+}
+
+gboolean
+ide_tree_node_get_children_possible (IdeTreeNode *self)
+{
+ g_return_val_if_fail (IDE_IS_TREE_NODE (self), FALSE);
+
+ return self->children_possible;
+}
+
+/**
+ * ide_tree_node_set_children_possible:
+ * @self: A #IdeTreeNode.
+ * @children_possible: If the node has children.
+ *
+ * If the node has not yet been built, setting this to %TRUE will add a
+ * dummy child node. This dummy node will be removed when when the node
+ * is built by the registered #IdeTreeBuilder instances.
+ */
+void
+ide_tree_node_set_children_possible (IdeTreeNode *self,
+ gboolean children_possible)
+{
+ g_return_if_fail (IDE_IS_TREE_NODE (self));
+
+ children_possible = !!children_possible;
+
+ if (children_possible != self->children_possible)
+ {
+ self->children_possible = children_possible;
+
+ if (self->needs_build)
+ {
+ if (self->children_possible)
+ _ide_tree_node_add_dummy_child (self);
+ else
+ _ide_tree_node_remove_dummy_child (self);
+ }
+ }
+}
+
+gboolean
+ide_tree_node_get_use_dim_label (IdeTreeNode *self)
+{
+ g_return_val_if_fail (IDE_IS_TREE_NODE (self), FALSE);
+
+ return self->use_dim_label;
+}
+
+void
+ide_tree_node_set_use_dim_label (IdeTreeNode *self,
+ gboolean use_dim_label)
+{
+ g_return_if_fail (IDE_IS_TREE_NODE (self));
+
+ use_dim_label = !!use_dim_label;
+
+ if (use_dim_label != self->use_dim_label)
+ {
+ self->use_dim_label = use_dim_label;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_USE_DIM_LABEL]);
+ }
+}
diff --git a/libide/ide-tree-node.h b/libide/ide-tree-node.h
new file mode 100644
index 0000000..0ebd3dc
--- /dev/null
+++ b/libide/ide-tree-node.h
@@ -0,0 +1,73 @@
+/* ide-tree-node.h
+ *
+ * Copyright (C) 2011 Christian Hergert <chris dronelabs 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/>.
+ */
+
+#ifndef IDE_TREE_NODE_H
+#define IDE_TREE_NODE_H
+
+#include "ide-tree-types.h"
+
+G_BEGIN_DECLS
+
+IdeTreeNode *ide_tree_node_new (void);
+void ide_tree_node_append (IdeTreeNode *node,
+ IdeTreeNode *child);
+void ide_tree_node_insert_sorted (IdeTreeNode *node,
+ IdeTreeNode *child,
+ IdeTreeNodeCompareFunc compare_func,
+ gpointer user_data);
+const gchar *ide_tree_node_get_icon_name (IdeTreeNode *node);
+GObject *ide_tree_node_get_item (IdeTreeNode *node);
+IdeTreeNode *ide_tree_node_get_parent (IdeTreeNode *node);
+GtkTreePath *ide_tree_node_get_path (IdeTreeNode *node);
+gboolean ide_tree_node_get_iter (IdeTreeNode *node,
+ GtkTreeIter *iter);
+void ide_tree_node_prepend (IdeTreeNode *node,
+ IdeTreeNode *child);
+void ide_tree_node_remove (IdeTreeNode *node,
+ IdeTreeNode *child);
+void ide_tree_node_set_icon_name (IdeTreeNode *node,
+ const gchar *icon_name);
+void ide_tree_node_set_item (IdeTreeNode *node,
+ GObject *item);
+gboolean ide_tree_node_expand (IdeTreeNode *node,
+ gboolean expand_ancestors);
+void ide_tree_node_collapse (IdeTreeNode *node);
+void ide_tree_node_select (IdeTreeNode *node);
+void ide_tree_node_get_area (IdeTreeNode *node,
+ GdkRectangle *area);
+void ide_tree_node_invalidate (IdeTreeNode *node);
+gboolean ide_tree_node_get_expanded (IdeTreeNode *node);
+void ide_tree_node_show_popover (IdeTreeNode *node,
+ GtkPopover *popover);
+const gchar *ide_tree_node_get_text (IdeTreeNode *node);
+void ide_tree_node_set_text (IdeTreeNode *node,
+ const gchar *text);
+IdeTree *ide_tree_node_get_tree (IdeTreeNode *node);
+gboolean ide_tree_node_get_children_possible (IdeTreeNode *self);
+void ide_tree_node_set_children_possible (IdeTreeNode *self,
+ gboolean children_possible);
+gboolean ide_tree_node_get_use_markup (IdeTreeNode *self);
+void ide_tree_node_set_use_markup (IdeTreeNode *self,
+ gboolean use_markup);
+gboolean ide_tree_node_get_use_dim_label (IdeTreeNode *self);
+void ide_tree_node_set_use_dim_label (IdeTreeNode *self,
+ gboolean use_dim_label);
+
+G_END_DECLS
+
+#endif /* IDE_TREE_NODE_H */
diff --git a/libide/ide-tree-private.h b/libide/ide-tree-private.h
new file mode 100644
index 0000000..08ccd3c
--- /dev/null
+++ b/libide/ide-tree-private.h
@@ -0,0 +1,79 @@
+/* ide-tree-private.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * 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/>.
+ */
+
+#ifndef IDE_TREE_PRIVATE_H
+#define IDE_TREE_PRIVATE_H
+
+#include "ide-tree-types.h"
+
+G_BEGIN_DECLS
+
+void _ide_tree_invalidate (IdeTree *tree,
+ IdeTreeNode *node);
+GtkTreePath *_ide_tree_get_path (IdeTree *tree,
+ GList *list);
+void _ide_tree_build_node (IdeTree *self,
+ IdeTreeNode *node);
+void _ide_tree_append (IdeTree *self,
+ IdeTreeNode *node,
+ IdeTreeNode *child);
+void _ide_tree_prepend (IdeTree *self,
+ IdeTreeNode *node,
+ IdeTreeNode *child);
+void _ide_tree_insert_sorted (IdeTree *self,
+ IdeTreeNode *node,
+ IdeTreeNode *child,
+ IdeTreeNodeCompareFunc compare_func,
+ gpointer user_data);
+void _ide_tree_remove (IdeTree *self,
+ IdeTreeNode *node);
+gboolean _ide_tree_get_iter (IdeTree *self,
+ IdeTreeNode *node,
+ GtkTreeIter *iter);
+GtkTreeStore*_ide_tree_get_store (IdeTree *self);
+
+void _ide_tree_node_set_tree (IdeTreeNode *node,
+ IdeTree *tree);
+void _ide_tree_node_set_parent (IdeTreeNode *node,
+ IdeTreeNode *parent);
+gboolean _ide_tree_node_get_needs_build (IdeTreeNode *node);
+void _ide_tree_node_set_needs_build (IdeTreeNode *node,
+ gboolean needs_build);
+void _ide_tree_node_remove_dummy_child (IdeTreeNode *node);
+
+void _ide_tree_builder_set_tree (IdeTreeBuilder *builder,
+ IdeTree *tree);
+void _ide_tree_builder_added (IdeTreeBuilder *builder,
+ IdeTree *tree);
+void _ide_tree_builder_removed (IdeTreeBuilder *builder,
+ IdeTree *tree);
+void _ide_tree_builder_build_node (IdeTreeBuilder *builder,
+ IdeTreeNode *node);
+gboolean _ide_tree_builder_node_activated (IdeTreeBuilder *builder,
+ IdeTreeNode *node);
+void _ide_tree_builder_node_popup (IdeTreeBuilder *builder,
+ IdeTreeNode *node,
+ GMenu *menu);
+void _ide_tree_builder_node_selected (IdeTreeBuilder *builder,
+ IdeTreeNode *node);
+void _ide_tree_builder_node_unselected (IdeTreeBuilder *builder,
+ IdeTreeNode *node);
+
+G_END_DECLS
+
+#endif /* IDE_TREE_PRIVATE_H */
diff --git a/libide/ide-tree-types.h b/libide/ide-tree-types.h
new file mode 100644
index 0000000..33e8d63
--- /dev/null
+++ b/libide/ide-tree-types.h
@@ -0,0 +1,40 @@
+/* ide-tree-types.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * 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/>.
+ */
+
+#ifndef IDE_TREE_TYPES_H
+#define IDE_TREE_TYPES_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_TREE (ide_tree_get_type())
+#define IDE_TYPE_TREE_NODE (ide_tree_node_get_type())
+#define IDE_TYPE_TREE_BUILDER (ide_tree_builder_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (IdeTree, ide_tree, IDE, TREE, GtkTreeView)
+G_DECLARE_DERIVABLE_TYPE (IdeTreeBuilder, ide_tree_builder, IDE, TREE_BUILDER, GInitiallyUnowned)
+G_DECLARE_FINAL_TYPE (IdeTreeNode, ide_tree_node, IDE, TREE_NODE, GInitiallyUnowned)
+
+typedef gint (*IdeTreeNodeCompareFunc) (IdeTreeNode *a,
+ IdeTreeNode *b,
+ gpointer user_data);
+
+G_END_DECLS
+
+#endif /* IDE_TREE_TYPES_H */
diff --git a/libide/ide-tree.c b/libide/ide-tree.c
new file mode 100644
index 0000000..cbff593
--- /dev/null
+++ b/libide/ide-tree.c
@@ -0,0 +1,1749 @@
+/* ide-tree.c
+ *
+ * Copyright (C) 2011 Christian Hergert <chris dronelabs 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/>.
+ */
+
+#define G_LOG_DOMAIN "tree"
+
+#include <glib/gi18n.h>
+#include <ide.h>
+
+#include "ide-gtk.h"
+#include "ide-tree.h"
+#include "ide-tree-node.h"
+#include "ide-tree-private.h"
+
+typedef struct
+{
+ GPtrArray *builders;
+ IdeTreeNode *root;
+ IdeTreeNode *selection;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell_pixbuf;
+ GtkCellRenderer *cell_text;
+ GtkTreeStore *store;
+ GdkRGBA dim_foreground;
+ guint show_icons : 1;
+} IdeTreePrivate;
+
+typedef struct
+{
+ gpointer key;
+ GEqualFunc equal_func;
+ IdeTreeNode *result;
+} NodeLookup;
+
+typedef struct
+{
+ IdeTree *self;
+ IdeTreeFilterFunc filter_func;
+ gpointer filter_data;
+ GDestroyNotify filter_data_destroy;
+} FilterFunc;
+
+static void ide_tree_buildable_init (GtkBuildableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (IdeTree, ide_tree, GTK_TYPE_TREE_VIEW,
+ G_ADD_PRIVATE (IdeTree)
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, ide_tree_buildable_init))
+
+enum {
+ PROP_0,
+ PROP_ROOT,
+ PROP_SELECTION,
+ PROP_SHOW_ICONS,
+ LAST_PROP
+};
+
+enum {
+ ACTION,
+ POPULATE_POPUP,
+ LAST_SIGNAL
+};
+
+static GtkBuildableIface *ide_tree_parent_buildable_iface;
+static GParamSpec *properties [LAST_PROP];
+static guint signals [LAST_SIGNAL];
+
+void
+_ide_tree_build_node (IdeTree *self,
+ IdeTreeNode *node)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ gsize i;
+
+ g_assert (IDE_IS_TREE (self));
+ g_assert (IDE_IS_TREE_NODE (node));
+
+ _ide_tree_node_set_needs_build (node, FALSE);
+ _ide_tree_node_remove_dummy_child (node);
+
+ for (i = 0; i < priv->builders->len; i++)
+ {
+ IdeTreeBuilder *builder;
+
+ builder = g_ptr_array_index (priv->builders, i);
+ _ide_tree_builder_build_node (builder, node);
+ }
+}
+
+static void
+ide_tree_unselect (IdeTree *self)
+{
+ GtkTreeSelection *selection;
+
+ IDE_ENTRY;
+
+ g_return_if_fail (IDE_IS_TREE (self));
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
+ gtk_tree_selection_unselect_all (selection);
+
+ IDE_EXIT;
+}
+
+static void
+ide_tree_select (IdeTree *self,
+ IdeTreeNode *node)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ GtkTreeSelection *selection;
+ GtkTreePath *path;
+
+ IDE_ENTRY;
+
+ g_return_if_fail (IDE_IS_TREE (self));
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+
+ if (priv->selection)
+ {
+ ide_tree_unselect (self);
+ g_assert (!priv->selection);
+ }
+
+ priv->selection = node;
+
+ path = ide_tree_node_get_path (node);
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
+ gtk_tree_selection_select_path (selection, path);
+ gtk_tree_path_free (path);
+
+ IDE_EXIT;
+}
+
+static guint
+ide_tree_get_row_height (IdeTree *self)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ guint extra_padding;
+ gint pix_min_height;
+ gint pix_nat_height;
+ gint text_min_height;
+ gint text_nat_height;
+
+ g_assert (IDE_IS_TREE (self));
+
+ gtk_widget_style_get (GTK_WIDGET (self),
+ "vertical-separator", &extra_padding,
+ NULL);
+
+ gtk_cell_renderer_get_preferred_height (priv->cell_pixbuf,
+ GTK_WIDGET (self),
+ &pix_min_height,
+ &pix_nat_height);
+ gtk_cell_renderer_get_preferred_height (priv->cell_text,
+ GTK_WIDGET (self),
+ &text_min_height,
+ &text_nat_height);
+
+ return MAX (pix_nat_height, text_nat_height) + extra_padding;
+}
+
+static void
+ide_tree_menu_position_func (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ gpointer user_data)
+{
+ GdkPoint *loc = user_data;
+ GtkRequisition req;
+ GdkRectangle rect;
+ GdkScreen *screen;
+ gint monitor;
+
+ g_return_if_fail (loc != NULL);
+
+ gtk_widget_get_preferred_size (GTK_WIDGET (menu), NULL, &req);
+ screen = gtk_widget_get_screen (GTK_WIDGET (menu));
+ monitor = gdk_screen_get_monitor_at_point (screen, *x, *y);
+ gdk_screen_get_monitor_geometry (screen, monitor, &rect);
+
+ if ((loc->x != -1) && (loc->y != -1))
+ {
+ if ((loc->y + req.height) <= (rect.y + rect.height))
+ {
+ *x = loc->x;
+ *y = loc->y;
+ }
+ else
+ {
+ GtkWidget *attached;
+ guint row_height;
+
+ attached = gtk_menu_get_attach_widget (menu);
+ row_height = ide_tree_get_row_height (IDE_TREE (attached));
+
+ *x = loc->x;
+ *y = loc->y + row_height - req.height;
+ }
+ }
+}
+
+static void
+check_visible_foreach (GtkWidget *widget,
+ gpointer user_data)
+{
+ gboolean *at_least_one_visible = user_data;
+
+ if (*at_least_one_visible == FALSE)
+ *at_least_one_visible = gtk_widget_get_visible (widget);
+}
+
+static GMenu *
+ide_tree_create_menu (IdeTree *self,
+ IdeTreeNode *node)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ GMenu *menu;
+ guint i;
+
+ g_return_val_if_fail (IDE_IS_TREE (self), NULL);
+ g_return_val_if_fail (IDE_IS_TREE_NODE (node), NULL);
+
+ menu = g_menu_new ();
+
+ for (i = 0; i < priv->builders->len; i++)
+ {
+ IdeTreeBuilder *builder;
+
+ builder = g_ptr_array_index (priv->builders, i);
+ _ide_tree_builder_node_popup (builder, node, menu);
+ }
+
+ return menu;
+}
+
+static void
+ide_tree_popup (IdeTree *self,
+ IdeTreeNode *node,
+ GdkEventButton *event,
+ gint target_x,
+ gint target_y)
+{
+ GdkPoint loc = { -1, -1 };
+ gboolean at_least_one_visible = FALSE;
+ GtkWidget *menu_widget;
+ GMenu *menu;
+ gint button;
+ gint event_time;
+
+ IDE_ENTRY;
+
+ g_return_if_fail (IDE_IS_TREE (self));
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+
+ menu = ide_tree_create_menu (self, node);
+ menu_widget = gtk_menu_new_from_model (G_MENU_MODEL (menu));
+ g_clear_object (&menu);
+
+ g_signal_emit (self, signals [POPULATE_POPUP], 0, menu_widget);
+
+ if ((target_x >= 0) && (target_y >= 0))
+ {
+ gdk_window_get_root_coords (gtk_widget_get_window (GTK_WIDGET (self)),
+ target_x, target_y, &loc.x, &loc.y);
+ loc.x -= 12;
+ }
+
+ gtk_container_foreach (GTK_CONTAINER (menu_widget),
+ check_visible_foreach,
+ &at_least_one_visible);
+
+ if (event != NULL)
+ {
+ button = event->button;
+ event_time = event->time;
+ }
+ else
+ {
+ button = 0;
+ event_time = gtk_get_current_event_time ();
+ }
+
+ if (at_least_one_visible)
+ {
+ gtk_menu_attach_to_widget (GTK_MENU (menu_widget),
+ GTK_WIDGET (self),
+ NULL);
+ gtk_menu_popup (GTK_MENU (menu_widget), NULL, NULL,
+ ide_tree_menu_position_func, &loc,
+ button, event_time);
+ }
+
+ IDE_EXIT;
+}
+
+static gboolean
+ide_tree_popup_menu (GtkWidget *widget)
+{
+ IdeTree *self = (IdeTree *)widget;
+ IdeTreeNode *node;
+ GdkRectangle area;
+
+ g_assert (IDE_IS_TREE (self));
+
+ if (!(node = ide_tree_get_selected (self)))
+ return FALSE;
+
+ ide_tree_node_get_area (node, &area);
+ ide_tree_popup (self, node, NULL, area.x + area.width, area.y - 1);
+
+ return TRUE;
+}
+
+static void
+ide_tree_selection_changed (IdeTree *self,
+ GtkTreeSelection *selection)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ IdeTreeBuilder *builder;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ IdeTreeNode *node;
+ IdeTreeNode *unselection;
+ gint i;
+
+ IDE_ENTRY;
+
+ g_return_if_fail (IDE_IS_TREE (self));
+ g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
+
+ if ((unselection = priv->selection))
+ {
+ priv->selection = NULL;
+ for (i = 0; i < priv->builders->len; i++)
+ {
+ builder = g_ptr_array_index (priv->builders, i);
+ _ide_tree_builder_node_unselected (builder, unselection);
+ }
+ }
+
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ {
+ gtk_tree_model_get (model, &iter, 0, &node, -1);
+ if (node)
+ {
+ for (i = 0; i < priv->builders->len; i++)
+ {
+ builder = g_ptr_array_index (priv->builders, i);
+ _ide_tree_builder_node_selected (builder, node);
+ }
+ g_object_unref (node);
+ }
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SELECTION]);
+
+ IDE_EXIT;
+}
+
+static gboolean
+ide_tree_add_builder_foreach_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ IdeTreeBuilder *builder = user_data;
+ IdeTreeNode *node = NULL;
+
+ IDE_ENTRY;
+
+ g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE);
+ g_return_val_if_fail (path != NULL, FALSE);
+ g_return_val_if_fail (iter != NULL, FALSE);
+
+ gtk_tree_model_get (model, iter, 0, &node, -1);
+ if (!_ide_tree_node_get_needs_build (node))
+ _ide_tree_builder_build_node (builder, node);
+ g_clear_object (&node);
+
+ IDE_RETURN (FALSE);
+}
+
+static gboolean
+ide_tree_foreach (IdeTree *self,
+ GtkTreeIter *iter,
+ GtkTreeModelForeachFunc func,
+ gpointer user_data)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeIter child;
+ gboolean ret;
+
+ g_assert (IDE_IS_TREE (self));
+ g_assert (iter != NULL);
+ g_assert (gtk_tree_store_iter_is_valid (priv->store, iter));
+ g_assert (func != NULL);
+
+ model = GTK_TREE_MODEL (priv->store);
+ path = gtk_tree_model_get_path (model, iter);
+ ret = func (model, path, iter, user_data);
+ gtk_tree_path_free (path);
+
+ if (ret)
+ return TRUE;
+
+ if (gtk_tree_model_iter_children (model, &child, iter))
+ {
+ do
+ {
+ if (ide_tree_foreach (self, &child, func, user_data))
+ return TRUE;
+ }
+ while (gtk_tree_model_iter_next (model, &child));
+ }
+
+ return FALSE;
+}
+
+static void
+pixbuf_func (GtkCellLayout *cell_layout,
+ GtkCellRenderer *cell,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ const gchar *icon_name;
+ IdeTreeNode *node;
+
+ g_assert (GTK_IS_CELL_LAYOUT (cell_layout));
+ g_assert (GTK_IS_CELL_RENDERER_PIXBUF (cell));
+ g_assert (GTK_IS_TREE_MODEL (tree_model));
+ g_assert (iter != NULL);
+
+ gtk_tree_model_get (tree_model, iter, 0, &node, -1);
+ icon_name = node ? ide_tree_node_get_icon_name (node) : NULL;
+ g_object_set (cell, "icon-name", icon_name, NULL);
+ g_clear_object (&node);
+}
+
+static void
+text_func (GtkCellLayout *cell_layout,
+ GtkCellRenderer *cell,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ IdeTree *self = data;
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ IdeTreeNode *node = NULL;
+
+ g_assert (IDE_IS_TREE (self));
+ g_assert (GTK_IS_CELL_LAYOUT (cell_layout));
+ g_assert (GTK_IS_CELL_RENDERER_TEXT (cell));
+ g_assert (GTK_IS_TREE_MODEL (tree_model));
+ g_assert (iter != NULL);
+
+ gtk_tree_model_get (tree_model, iter, 0, &node, -1);
+
+ if (node)
+ {
+ GdkRGBA *rgba = NULL;
+ const gchar *text;
+ gboolean use_markup;
+
+ text = ide_tree_node_get_text (node);
+ use_markup = ide_tree_node_get_use_markup (node);
+
+ if (ide_tree_node_get_use_dim_label (node))
+ rgba = &priv->dim_foreground;
+
+ g_object_set (cell,
+ use_markup ? "markup" : "text", text,
+ "foreground-rgba", rgba,
+ NULL);
+ }
+}
+
+static void
+ide_tree_add (IdeTree *self,
+ IdeTreeNode *node,
+ IdeTreeNode *child,
+ gboolean prepend)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ GtkTreePath *path;
+ GtkTreeIter *parentptr = NULL;
+ GtkTreeIter iter;
+ GtkTreeIter parent;
+
+ g_return_if_fail (IDE_IS_TREE (self));
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+ g_return_if_fail (IDE_IS_TREE_NODE (child));
+
+ _ide_tree_node_set_tree (child, self);
+ _ide_tree_node_set_parent (child, node);
+
+ g_object_ref_sink (child);
+
+ if (node != priv->root)
+ {
+ path = ide_tree_node_get_path (node);
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->store), &parent, path);
+ parentptr = &parent;
+ g_clear_pointer (&path, gtk_tree_path_free);
+ }
+
+ gtk_tree_store_insert_with_values (priv->store, &iter, parentptr,
+ prepend ? 0 : -1,
+ 0, child,
+ -1);
+
+ if (node == priv->root)
+ _ide_tree_build_node (self, child);
+
+ g_object_unref (child);
+}
+
+void
+_ide_tree_insert_sorted (IdeTree *self,
+ IdeTreeNode *node,
+ IdeTreeNode *child,
+ IdeTreeNodeCompareFunc compare_func,
+ gpointer user_data)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ GtkTreeModel *model;
+ GtkTreeIter *parent = NULL;
+ GtkTreeIter node_iter;
+ GtkTreeIter children;
+
+ g_return_if_fail (IDE_IS_TREE (self));
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+ g_return_if_fail (IDE_IS_TREE_NODE (child));
+ g_return_if_fail (compare_func != NULL);
+
+ model = GTK_TREE_MODEL (priv->store);
+
+ _ide_tree_node_set_tree (child, self);
+ _ide_tree_node_set_parent (child, node);
+
+ g_object_ref_sink (child);
+
+ if (ide_tree_node_get_iter (node, &node_iter))
+ parent = &node_iter;
+
+ if (gtk_tree_model_iter_children (model, &children, parent))
+ {
+ do
+ {
+ g_autoptr(IdeTreeNode) sibling = NULL;
+ GtkTreeIter that;
+
+ gtk_tree_model_get (model, &children, 0, &sibling, -1);
+
+ if (compare_func (sibling, child, user_data) > 0)
+ {
+ gtk_tree_store_insert_before (priv->store, &that, parent, &children);
+ gtk_tree_store_set (priv->store, &that, 0, child, -1);
+ goto inserted;
+ }
+ }
+ while (gtk_tree_model_iter_next (model, &children));
+ }
+
+ gtk_tree_store_append (priv->store, &children, parent);
+ gtk_tree_store_set (priv->store, &children, 0, child, -1);
+
+inserted:
+ if (node == priv->root)
+ _ide_tree_build_node (self, child);
+
+ g_object_unref (child);
+}
+
+static void
+ide_tree_row_activated (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column)
+{
+ IdeTree *self = (IdeTree *)tree_view;
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ IdeTreeBuilder *builder;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ IdeTreeNode *node = NULL;
+ gboolean handled = FALSE;
+ gint i;
+
+ g_return_if_fail (IDE_IS_TREE (self));
+ g_return_if_fail (path != NULL);
+
+ model = gtk_tree_view_get_model (tree_view);
+
+ if (gtk_tree_model_get_iter (model, &iter, path))
+ {
+ gtk_tree_model_get (model, &iter, 0, &node, -1);
+ for (i = 0; i < priv->builders->len; i++)
+ {
+ builder = g_ptr_array_index (priv->builders, i);
+ if ((handled = _ide_tree_builder_node_activated (builder, node)))
+ break;
+ }
+ g_clear_object (&node);
+ }
+
+ if (!handled)
+ {
+ if (gtk_tree_view_row_expanded (tree_view, path))
+ gtk_tree_view_collapse_row (tree_view, path);
+ else
+ gtk_tree_view_expand_to_path (tree_view, path);
+ }
+}
+
+static void
+ide_tree_row_expanded (GtkTreeView *tree_view,
+ GtkTreeIter *iter,
+ GtkTreePath *path)
+{
+ IdeTree *self = (IdeTree *)tree_view;
+ GtkTreeModel *model;
+ IdeTreeNode *node;
+
+ g_assert (IDE_IS_TREE (self));
+ g_assert (iter != NULL);
+ g_assert (path != NULL);
+
+ model = gtk_tree_view_get_model (tree_view);
+
+ gtk_tree_model_get (model, iter, 0, &node, -1);
+
+ /*
+ * If we are expanding a row that has a dummy child, we might need to
+ * build the node immediately, and re-expand it.
+ */
+ if (_ide_tree_node_get_needs_build (node))
+ {
+ _ide_tree_build_node (self, node);
+ ide_tree_node_expand (node, FALSE);
+ ide_tree_node_select (node);
+ }
+
+ g_clear_object (&node);
+}
+
+
+static gboolean
+ide_tree_button_press_event (GtkWidget *widget,
+ GdkEventButton *button)
+{
+ IdeTree *self = (IdeTree *)widget;
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ GtkAllocation alloc;
+ GtkTreePath *tree_path = NULL;
+ GtkTreeIter iter;
+ IdeTreeNode *node = NULL;
+ gint cell_y;
+
+ g_assert (IDE_IS_TREE (self));
+ g_assert (button != NULL);
+
+ if ((button->type == GDK_BUTTON_PRESS) && (button->button == GDK_BUTTON_SECONDARY))
+ {
+ if (!gtk_widget_has_focus (GTK_WIDGET (self)))
+ gtk_widget_grab_focus (GTK_WIDGET (self));
+
+ gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (self),
+ button->x,
+ button->y,
+ &tree_path,
+ NULL,
+ NULL,
+ &cell_y);
+
+ if (!tree_path)
+ {
+ ide_tree_unselect (self);
+ }
+ else
+ {
+ gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->store), &iter, tree_path);
+ gtk_tree_model_get (GTK_TREE_MODEL (priv->store), &iter, 0, &node, -1);
+ ide_tree_select (self, node);
+ ide_tree_popup (self, node, button,
+ alloc.x + alloc.width,
+ button->y - cell_y);
+ g_object_unref (node);
+ gtk_tree_path_free (tree_path);
+ }
+
+ return GDK_EVENT_STOP;
+ }
+
+ return GTK_WIDGET_CLASS (ide_tree_parent_class)->button_press_event (widget, button);
+}
+
+static gboolean
+ide_tree_find_item_foreach_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ IdeTreeNode *node = NULL;
+ NodeLookup *lookup = user_data;
+ gboolean ret = FALSE;
+
+ g_assert (GTK_IS_TREE_MODEL (model));
+ g_assert (path != NULL);
+ g_assert (iter != NULL);
+ g_assert (lookup != NULL);
+
+ gtk_tree_model_get (model, iter, 0, &node, -1);
+
+ if (node != NULL)
+ {
+ GObject *item;
+
+ item = ide_tree_node_get_item (node);
+
+ if (lookup->equal_func (lookup->key, item))
+ {
+ lookup->result = node;
+ ret = TRUE;
+ }
+ }
+
+ g_clear_object (&node);
+
+ return ret;
+}
+
+static void
+ide_tree_real_action (IdeTree *self,
+ const gchar *prefix,
+ const gchar *action_name,
+ const gchar *param)
+{
+ GVariant *variant = NULL;
+
+ g_assert (IDE_IS_TREE (self));
+
+ if (*param != 0)
+ {
+ GError *error = NULL;
+
+ variant = g_variant_parse (NULL, param, NULL, NULL, &error);
+
+ if (variant == NULL)
+ {
+ g_warning ("can't parse keybinding parameters \"%s\": %s",
+ param, error->message);
+ g_clear_error (&error);
+ return;
+ }
+ }
+
+ ide_widget_action (GTK_WIDGET (self), prefix, action_name, variant);
+}
+
+static gboolean
+ide_tree_default_search_equal_func (GtkTreeModel *model,
+ gint column,
+ const gchar *key,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ IdeTreeNode *node = NULL;
+ gboolean ret = TRUE;
+
+ g_assert (GTK_IS_TREE_MODEL (model));
+ g_assert (column == 0);
+ g_assert (key != NULL);
+ g_assert (iter != NULL);
+
+ gtk_tree_model_get (model, iter, 0, &node, -1);
+
+ if (node != NULL)
+ {
+ const gchar *text;
+
+ text = ide_tree_node_get_text (node);
+ ret = !(strstr (key, text) != NULL);
+ g_object_unref (node);
+ }
+
+ return ret;
+}
+
+static void
+ide_tree_add_child (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const gchar *type)
+{
+ IdeTree *self = (IdeTree *)buildable;
+
+ g_assert (IDE_IS_TREE (self));
+ g_assert (GTK_IS_BUILDER (builder));
+ g_assert (G_IS_OBJECT (child));
+
+ if (g_strcmp0 (type, "builder") == 0)
+ {
+ if (!IDE_IS_TREE_BUILDER (child))
+ {
+ g_warning ("Attempt to add invalid builder of type %s to IdeTree.",
+ g_type_name (G_OBJECT_TYPE (child)));
+ return;
+ }
+
+ ide_tree_add_builder (self, IDE_TREE_BUILDER (child));
+ return;
+ }
+
+ ide_tree_parent_buildable_iface->add_child (buildable, builder, child, type);
+}
+
+static void
+ide_tree_style_updated (GtkWidget *widget)
+{
+ IdeTree *self = (IdeTree *)widget;
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ GtkStyleContext *style_context;
+ GtkStateFlags flags;
+
+ g_assert (IDE_IS_TREE (self));
+
+ GTK_WIDGET_CLASS (ide_tree_parent_class)->style_updated (widget);
+
+ flags = gtk_widget_get_state_flags (widget);
+
+ style_context = gtk_widget_get_style_context (widget);
+ gtk_style_context_save (style_context);
+ gtk_style_context_add_class (style_context, "dim-label");
+ gtk_style_context_get_color (style_context, flags, &priv->dim_foreground);
+ gtk_style_context_restore (style_context);
+}
+
+static void
+ide_tree_finalize (GObject *object)
+{
+ IdeTree *self = IDE_TREE (object);
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+
+ g_ptr_array_unref (priv->builders);
+ g_clear_object (&priv->store);
+ g_clear_object (&priv->root);
+
+ G_OBJECT_CLASS (ide_tree_parent_class)->finalize (object);
+}
+
+static void
+ide_tree_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeTree *self = IDE_TREE (object);
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_ROOT:
+ g_value_set_object (value, priv->root);
+ break;
+
+ case PROP_SELECTION:
+ g_value_set_object (value, priv->selection);
+ break;
+
+ case PROP_SHOW_ICONS:
+ g_value_set_boolean (value, priv->show_icons);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_tree_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeTree *self = IDE_TREE (object);
+
+ switch (prop_id)
+ {
+ case PROP_ROOT:
+ ide_tree_set_root (self, g_value_get_object (value));
+ break;
+
+ case PROP_SELECTION:
+ ide_tree_select (self, g_value_get_object (value));
+ break;
+
+ case PROP_SHOW_ICONS:
+ ide_tree_set_show_icons (self, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_tree_buildable_init (GtkBuildableIface *iface)
+{
+ ide_tree_parent_buildable_iface = g_type_interface_peek_parent (iface);
+
+ iface->add_child = ide_tree_add_child;
+}
+
+static void
+ide_tree_class_init (IdeTreeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
+
+ object_class->finalize = ide_tree_finalize;
+ object_class->get_property = ide_tree_get_property;
+ object_class->set_property = ide_tree_set_property;
+
+ widget_class->popup_menu = ide_tree_popup_menu;
+ widget_class->button_press_event = ide_tree_button_press_event;
+ widget_class->style_updated = ide_tree_style_updated;
+
+ tree_view_class->row_activated = ide_tree_row_activated;
+ tree_view_class->row_expanded = ide_tree_row_expanded;
+
+ klass->action = ide_tree_real_action;
+
+ properties[PROP_ROOT] =
+ g_param_spec_object ("root",
+ "Root",
+ "The root object of the tree.",
+ IDE_TYPE_TREE_NODE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_SELECTION] =
+ g_param_spec_object ("selection",
+ "Selection",
+ "The node selection.",
+ IDE_TYPE_TREE_NODE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ properties [PROP_SHOW_ICONS] =
+ g_param_spec_boolean ("show-icons",
+ "Show Icons",
+ "Show Icons",
+ FALSE,
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ signals [ACTION] =
+ g_signal_new ("action",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (IdeTreeClass, action),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 3,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ signals [POPULATE_POPUP] =
+ g_signal_new ("populate-popup",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IdeTreeClass, populate_popup),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ GTK_TYPE_WIDGET);
+}
+
+static void
+ide_tree_init (IdeTree *self)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ GtkTreeSelection *selection;
+ GtkCellRenderer *cell;
+ GtkCellLayout *column;
+
+ priv->builders = g_ptr_array_new ();
+ g_ptr_array_set_free_func (priv->builders, g_object_unref);
+ priv->store = gtk_tree_store_new (1, IDE_TYPE_TREE_NODE);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
+ g_signal_connect_object (selection, "changed",
+ G_CALLBACK (ide_tree_selection_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ column = g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
+ "title", "Node",
+ NULL);
+ priv->column = GTK_TREE_VIEW_COLUMN (column);
+
+ cell = g_object_new (GTK_TYPE_CELL_RENDERER_PIXBUF,
+ "xpad", 3,
+ "visible", priv->show_icons,
+ NULL);
+ priv->cell_pixbuf = cell;
+ g_object_bind_property (self, "show-icons", cell, "visible", 0);
+ gtk_cell_layout_pack_start (column, cell, FALSE);
+ gtk_cell_layout_set_cell_data_func (column, cell, pixbuf_func, NULL, NULL);
+
+ cell = g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
+ "ellipsize", PANGO_ELLIPSIZE_NONE,
+ NULL);
+ priv->cell_text = cell;
+ gtk_cell_layout_pack_start (column, cell, TRUE);
+ gtk_cell_layout_set_cell_data_func (column, cell, text_func, self, NULL);
+
+ gtk_tree_view_append_column (GTK_TREE_VIEW (self),
+ GTK_TREE_VIEW_COLUMN (column));
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (self),
+ GTK_TREE_MODEL (priv->store));
+
+ gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (self),
+ ide_tree_default_search_equal_func,
+ NULL, NULL);
+ gtk_tree_view_set_search_column (GTK_TREE_VIEW (self), 0);
+}
+
+void
+ide_tree_expand_to_node (IdeTree *self,
+ IdeTreeNode *node)
+{
+ g_assert (IDE_IS_TREE (self));
+ g_assert (IDE_IS_TREE_NODE (node));
+
+ if (ide_tree_node_get_expanded (node))
+ {
+ ide_tree_node_expand (node, TRUE);
+ }
+ else
+ {
+ ide_tree_node_expand (node, TRUE);
+ ide_tree_node_collapse (node);
+ }
+}
+
+gboolean
+ide_tree_get_show_icons (IdeTree *self)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_TREE (self), FALSE);
+
+ return priv->show_icons;
+}
+
+void
+ide_tree_set_show_icons (IdeTree *self,
+ gboolean show_icons)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_TREE (self));
+
+ show_icons = !!show_icons;
+
+ if (show_icons != priv->show_icons)
+ {
+ priv->show_icons = show_icons;
+ g_object_set (priv->cell_pixbuf, "visible", show_icons, NULL);
+ /*
+ * WORKAROUND:
+ *
+ * Changing the visibility of the cell does not force a redraw of the
+ * tree view. So to force it, we will hide/show our entire pixbuf/text
+ * column.
+ */
+ gtk_tree_view_column_set_visible (priv->column, FALSE);
+ gtk_tree_view_column_set_visible (priv->column, TRUE);
+ g_object_notify_by_pspec (G_OBJECT (self),
+ properties [PROP_SHOW_ICONS]);
+ }
+}
+
+/**
+ * ide_tree_get_selected:
+ * @self: (in): A #IdeTree.
+ *
+ * Gets the currently selected node in the tree.
+ *
+ * Returns: (transfer none): A #IdeTreeNode.
+ */
+IdeTreeNode *
+ide_tree_get_selected (IdeTree *self)
+{
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ IdeTreeNode *ret = NULL;
+
+ g_return_val_if_fail (IDE_IS_TREE (self), NULL);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ {
+ gtk_tree_model_get (model, &iter, 0, &ret, -1);
+
+ /*
+ * We incurred an extra reference when extracting the value from
+ * the treemodel. Since it owns the reference, we can drop it here
+ * so that we don't transfer the ownership to the caller.
+ */
+ g_object_unref (ret);
+ }
+
+ return ret;
+}
+
+void
+ide_tree_scroll_to_node (IdeTree *self,
+ IdeTreeNode *node)
+{
+ GtkTreePath *path;
+
+ g_return_if_fail (IDE_IS_TREE (self));
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+
+ path = ide_tree_node_get_path (node);
+ gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), path, NULL, FALSE, 0, 0);
+ gtk_tree_path_free (path);
+}
+
+GtkTreePath *
+_ide_tree_get_path (IdeTree *self,
+ GList *list)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GtkTreeIter *iter_ptr;
+ GList *list_iter;
+
+ g_assert (IDE_IS_TREE (self));
+
+ model = GTK_TREE_MODEL (priv->store);
+
+ if ((list == NULL) || (list->data != priv->root) || (list->next == NULL))
+ return NULL;
+
+ iter_ptr = NULL;
+
+ for (list_iter = list->next; list_iter; list_iter = list_iter->next)
+ {
+ GtkTreeIter children;
+
+ if (gtk_tree_model_iter_children (model, &children, iter_ptr))
+ {
+ gboolean found = FALSE;
+
+ do
+ {
+ g_autoptr(IdeTreeNode) item = NULL;
+
+ gtk_tree_model_get (model, &children, 0, &item, -1);
+ found = (item == (IdeTreeNode *)list_iter->data);
+ }
+ while (!found && gtk_tree_model_iter_next (model, &children));
+
+ if (found)
+ {
+ iter = children;
+ iter_ptr = &iter;
+ continue;
+ }
+ }
+
+ return NULL;
+ }
+
+ return gtk_tree_model_get_path (model, &iter);
+}
+
+/**
+ * ide_tree_add_builder:
+ * @self: (in): A #IdeTree.
+ * @builder: (in) (transfer full): A #IdeTreeBuilder to add.
+ *
+ * Removes a builder from the tree.
+ */
+void
+ide_tree_add_builder (IdeTree *self,
+ IdeTreeBuilder *builder)
+{
+ GtkTreeIter iter;
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+
+ IDE_ENTRY;
+
+ g_return_if_fail (IDE_IS_TREE (self));
+ g_return_if_fail (IDE_IS_TREE_BUILDER (builder));
+
+ g_ptr_array_add (priv->builders, g_object_ref_sink (builder));
+
+ _ide_tree_builder_set_tree (builder, self);
+ _ide_tree_builder_added (builder, self);
+
+ if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->store), &iter))
+ ide_tree_foreach (self, &iter, ide_tree_add_builder_foreach_cb, builder);
+
+ IDE_EXIT;
+}
+
+/**
+ * ide_tree_remove_builder:
+ * @self: (in): A #IdeTree.
+ * @builder: (in): A #IdeTreeBuilder to remove.
+ *
+ * Removes a builder from the tree.
+ */
+void
+ide_tree_remove_builder (IdeTree *self,
+ IdeTreeBuilder *builder)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ gsize i;
+
+ IDE_ENTRY;
+
+ g_return_if_fail (IDE_IS_TREE (self));
+ g_return_if_fail (IDE_IS_TREE_BUILDER (builder));
+
+ for (i = 0; i < priv->builders->len; i++)
+ {
+ if (builder == g_ptr_array_index (priv->builders, i))
+ {
+ g_object_ref (builder);
+ g_ptr_array_remove_index (priv->builders, i);
+ _ide_tree_builder_removed (builder, self);
+ g_object_unref (builder);
+ }
+ }
+
+ IDE_EXIT;
+}
+
+/**
+ * ide_tree_get_root:
+ *
+ * Retrieves the root node of the tree. The root node is not a visible node
+ * in the self, but a placeholder for all other builders to build upon.
+ *
+ * Returns: (transfer none) (nullable): A #IdeTreeNode or %NULL.
+ */
+IdeTreeNode *
+ide_tree_get_root (IdeTree *self)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_TREE (self), NULL);
+
+ return priv->root;
+}
+
+/**
+ * ide_tree_set_root:
+ * @self: (in): A #IdeTree.
+ * @node: (in): A #IdeTreeNode.
+ *
+ * Sets the root node of the #IdeTree widget. This is used to build
+ * the items within the treeview. The item itself will not be added
+ * to the self, but the direct children will be.
+ */
+void
+ide_tree_set_root (IdeTree *self,
+ IdeTreeNode *root)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+
+ IDE_ENTRY;
+
+ g_return_if_fail (IDE_IS_TREE (self));
+
+ if (priv->root != root)
+ {
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
+ GtkTreeModel *current;
+
+ gtk_tree_selection_unselect_all (selection);
+
+ if (priv->root != NULL)
+ {
+ _ide_tree_node_set_parent (priv->root, NULL);
+ _ide_tree_node_set_tree (priv->root, NULL);
+ gtk_tree_store_clear (priv->store);
+ g_clear_object (&priv->root);
+ }
+
+ current = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
+ if (GTK_IS_TREE_MODEL_FILTER (current))
+ gtk_tree_model_filter_clear_cache (GTK_TREE_MODEL_FILTER (current));
+
+ if (root != NULL)
+ {
+ priv->root = g_object_ref_sink (root);
+ _ide_tree_node_set_parent (priv->root, NULL);
+ _ide_tree_node_set_tree (priv->root, self);
+ _ide_tree_build_node (self, priv->root);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ROOT]);
+ }
+
+ IDE_EXIT;
+}
+
+void
+ide_tree_rebuild (IdeTree *self)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ GtkTreeSelection *selection;
+
+ g_return_if_fail (IDE_IS_TREE (self));
+
+ /*
+ * We don't want notification of selection changes while rebuilding.
+ */
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
+ gtk_tree_selection_unselect_all (selection);
+
+ if (priv->root != NULL)
+ {
+ gtk_tree_store_clear (priv->store);
+ _ide_tree_build_node (self, priv->root);
+ }
+}
+
+/**
+ * ide_tree_find_custom:
+ * @self: A #IdeTree
+ * @equal_func: (scope call): A #GEqualFunc
+ * @key: the key for @equal_func
+ *
+ * Walks the entire tree looking for the first item that matches given
+ * @equal_func and @key.
+ *
+ * The first parameter to @equal_func will always be @key.
+ * The second parameter will be the nodes #IdeTreeNode:item property.
+ *
+ * Returns: (nullable) (transfer none): A #IdeTreeNode or %NULL.
+ */
+IdeTreeNode *
+ide_tree_find_custom (IdeTree *self,
+ GEqualFunc equal_func,
+ gpointer key)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ NodeLookup lookup;
+
+ g_return_val_if_fail (IDE_IS_TREE (self), NULL);
+ g_return_val_if_fail (equal_func != NULL, NULL);
+
+ lookup.key = key;
+ lookup.equal_func = equal_func;
+ lookup.result = NULL;
+
+ gtk_tree_model_foreach (GTK_TREE_MODEL (priv->store),
+ ide_tree_find_item_foreach_cb,
+ &lookup);
+
+ return lookup.result;
+}
+
+/**
+ * ide_tree_find_item:
+ * @self: A #IdeTree.
+ * @item: (allow-none): A #GObject or %NULL.
+ *
+ * Finds a #IdeTreeNode with an item property matching @item.
+ *
+ * Returns: (transfer none) (nullable): A #IdeTreeNode or %NULL.
+ */
+IdeTreeNode *
+ide_tree_find_item (IdeTree *self,
+ GObject *item)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ NodeLookup lookup;
+
+ g_return_val_if_fail (IDE_IS_TREE (self), NULL);
+ g_return_val_if_fail (!item || G_IS_OBJECT (item), NULL);
+
+ lookup.key = item;
+ lookup.equal_func = g_direct_equal;
+ lookup.result = NULL;
+
+ gtk_tree_model_foreach (GTK_TREE_MODEL (priv->store),
+ ide_tree_find_item_foreach_cb,
+ &lookup);
+
+ return lookup.result;
+}
+
+void
+_ide_tree_append (IdeTree *self,
+ IdeTreeNode *node,
+ IdeTreeNode *child)
+{
+ g_return_if_fail (IDE_IS_TREE (self));
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+ g_return_if_fail (IDE_IS_TREE_NODE (child));
+
+ ide_tree_add (self, node, child, FALSE);
+}
+
+void
+_ide_tree_prepend (IdeTree *self,
+ IdeTreeNode *node,
+ IdeTreeNode *child)
+{
+ g_return_if_fail (IDE_IS_TREE (self));
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+ g_return_if_fail (IDE_IS_TREE_NODE (child));
+
+ ide_tree_add (self, node, child, TRUE);
+}
+
+void
+_ide_tree_invalidate (IdeTree *self,
+ IdeTreeNode *node)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ IdeTreeNode *parent;
+ GtkTreeIter iter;
+ GtkTreeIter child;
+
+ g_return_if_fail (IDE_IS_TREE (self));
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+
+ model = GTK_TREE_MODEL (priv->store);
+ path = ide_tree_node_get_path (node);
+ gtk_tree_model_get_iter (model, &iter, path);
+
+ if (gtk_tree_model_iter_children (model, &child, &iter))
+ {
+ while (gtk_tree_store_remove (priv->store, &child))
+ {
+ }
+ }
+
+ _ide_tree_node_set_needs_build (node, TRUE);
+
+ parent = ide_tree_node_get_parent (node);
+
+ if ((parent == NULL) || ide_tree_node_get_expanded (parent))
+ _ide_tree_build_node (self, node);
+
+ gtk_tree_path_free (path);
+}
+
+/**
+ * ide_tree_find_child_node:
+ * @self: A #IdeTree
+ * @node: A #IdeTreeNode
+ * @find_func: (scope call): A callback to locate the child
+ * @user_data: user data for @find_func
+ *
+ * Searches through the direct children of @node for a matching child.
+ * @find_func should return %TRUE if the child matches, otherwise %FALSE.
+ *
+ * Returns: (transfer none) (nullable): A #IdeTreeNode or %NULL.
+ */
+IdeTreeNode *
+ide_tree_find_child_node (IdeTree *self,
+ IdeTreeNode *node,
+ IdeTreeFindFunc find_func,
+ gpointer user_data)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ GtkTreeIter children;
+
+ g_return_val_if_fail (IDE_IS_TREE (self), NULL);
+ g_return_val_if_fail (!node || IDE_IS_TREE_NODE (node), NULL);
+ g_return_val_if_fail (find_func, NULL);
+
+ if (node == NULL)
+ node = priv->root;
+
+ if (node == NULL)
+ {
+ g_warning ("Cannot find node. No root node has been set on %s.",
+ g_type_name (G_OBJECT_TYPE (self)));
+ return NULL;
+ }
+
+ if (_ide_tree_node_get_needs_build (node))
+ _ide_tree_build_node (self, node);
+
+ model = GTK_TREE_MODEL (priv->store);
+ path = ide_tree_node_get_path (node);
+
+ if (path != NULL)
+ {
+ if (!gtk_tree_model_get_iter (model, &iter, path))
+ goto failure;
+
+ if (!gtk_tree_model_iter_children (model, &children, &iter))
+ goto failure;
+ }
+ else
+ {
+ if (!gtk_tree_model_iter_children (model, &children, NULL))
+ goto failure;
+ }
+
+ do
+ {
+ IdeTreeNode *child = NULL;
+
+ gtk_tree_model_get (model, &children, 0, &child, -1);
+
+ if (find_func (self, node, child, user_data))
+ {
+ /*
+ * We want to returned a borrowed reference to the child node.
+ * It is safe to unref the child here before we return.
+ */
+ g_object_unref (child);
+ return child;
+ }
+
+ g_clear_object (&child);
+ }
+ while (gtk_tree_model_iter_next (model, &children));
+
+failure:
+ g_clear_pointer (&path, gtk_tree_path_free);
+
+ return NULL;
+}
+
+void
+_ide_tree_remove (IdeTree *self,
+ IdeTreeNode *node)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ g_return_if_fail (IDE_IS_TREE (self));
+ g_return_if_fail (IDE_IS_TREE_NODE (node));
+
+ path = ide_tree_node_get_path (node);
+
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->store), &iter, path))
+ gtk_tree_store_remove (priv->store, &iter);
+
+ gtk_tree_path_free (path);
+}
+
+gboolean
+_ide_tree_get_iter (IdeTree *self,
+ IdeTreeNode *node,
+ GtkTreeIter *iter)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+ GtkTreePath *path;
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail (IDE_IS_TREE (self), FALSE);
+ g_return_val_if_fail (IDE_IS_TREE_NODE (node), FALSE);
+ g_return_val_if_fail (iter, FALSE);
+
+ if ((path = ide_tree_node_get_path (node)) != NULL)
+ {
+ ret = gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->store), iter, path);
+ gtk_tree_path_free (path);
+ }
+
+ return ret;
+}
+
+static void
+filter_func_free (gpointer user_data)
+{
+ FilterFunc *data = user_data;
+
+ if (data->filter_data_destroy)
+ data->filter_data_destroy (data->filter_data);
+
+ g_free (data);
+}
+
+static gboolean
+ide_tree_model_filter_recursive (GtkTreeModel *model,
+ GtkTreeIter *parent,
+ FilterFunc *filter)
+{
+ GtkTreeIter child;
+
+ if (gtk_tree_model_iter_children (model, &child, parent))
+ {
+ do
+ {
+ g_autoptr(IdeTreeNode) node = NULL;
+
+ gtk_tree_model_get (model, &child, 0, &node, -1);
+
+ if ((node != NULL) && !_ide_tree_node_get_needs_build (node))
+ {
+ if (filter->filter_func (filter->self, node, filter->filter_data))
+ return TRUE;
+
+ if (ide_tree_model_filter_recursive (model, &child, filter))
+ return TRUE;
+ }
+ }
+ while (gtk_tree_model_iter_next (model, &child));
+ }
+
+ return FALSE;
+}
+
+static gboolean
+ide_tree_model_filter_visible_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ IdeTreeNode *node = NULL;
+ FilterFunc *filter = data;
+ gboolean ret;
+
+ g_assert (filter != NULL);
+ g_assert (IDE_IS_TREE (filter->self));
+ g_assert (filter->filter_func != NULL);
+
+ /*
+ * This is a rather complex situation.
+ *
+ * We might not match, but one of our children nodes might match.
+ * Furthering the issue, the children might still need to be built.
+ * For some cases, this could be really expensive (think file tree)
+ * but for other things, it could be cheap. If you are going to use
+ * a filter func for your tree, you probably should avoid being
+ * too lazy and ensure the nodes are available.
+ *
+ * Therefore, we will only check available nodes, and ignore the
+ * case where the children nodes need to be built. *
+ *
+ * TODO: Another option would be to iteratively build the items after
+ * the initial filter.
+ */
+
+ gtk_tree_model_get (model, iter, 0, &node, -1);
+ ret = filter->filter_func (filter->self, node, filter->filter_data);
+ g_clear_object (&node);
+
+ /*
+ * Short circuit if we already matched.
+ */
+ if (ret)
+ return TRUE;
+
+ /*
+ * If any of our children match, we should match.
+ */
+ if (ide_tree_model_filter_recursive (model, iter, filter))
+ return TRUE;
+
+ return FALSE;
+}
+
+/**
+ * ide_tree_set_filter:
+ * @self: A #IdeTree
+ * @filter_func: (scope notified): A callback to determien visibility.
+ * @filter_data: User data for @filter_func.
+ * @filter_data_destroy: Destroy notify for @filter_data.
+ *
+ * Sets the filter function to be used to determine visability of a tree node.
+ */
+void
+ide_tree_set_filter (IdeTree *self,
+ IdeTreeFilterFunc filter_func,
+ gpointer filter_data,
+ GDestroyNotify filter_data_destroy)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_TREE (self));
+
+ if (filter_func == NULL)
+ {
+ gtk_tree_view_set_model (GTK_TREE_VIEW (self), GTK_TREE_MODEL (priv->store));
+ }
+ else
+ {
+ FilterFunc *data;
+ GtkTreeModel *filter;
+
+ data = g_new0 (FilterFunc, 1);
+ data->self = self;
+ data->filter_func = filter_func;
+ data->filter_data = filter_data;
+ data->filter_data_destroy = filter_data_destroy;
+
+ filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (priv->store), NULL);
+ gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter),
+ ide_tree_model_filter_visible_func,
+ data,
+ filter_func_free);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (self), GTK_TREE_MODEL (filter));
+ g_clear_object (&filter);
+ }
+}
+
+GtkTreeStore *
+_ide_tree_get_store (IdeTree *self)
+{
+ IdeTreePrivate *priv = ide_tree_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_TREE (self), NULL);
+
+ return priv->store;
+}
diff --git a/libide/ide-tree.h b/libide/ide-tree.h
new file mode 100644
index 0000000..33ebd10
--- /dev/null
+++ b/libide/ide-tree.h
@@ -0,0 +1,98 @@
+/* ide-tree.h
+ *
+ * Copyright (C) 2011 Christian Hergert <chris dronelabs 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/>.
+ */
+
+#ifndef IDE_TREE_H
+#define IDE_TREE_H
+
+#include <gtk/gtk.h>
+
+#include "ide-tree-builder.h"
+#include "ide-tree-node.h"
+#include "ide-tree-types.h"
+
+G_BEGIN_DECLS
+
+/**
+ * IdeTreeFindFunc:
+ *
+ * Callback to check @child, a child of @node, matches a lookup
+ * request. Returns %TRUE if @child matches, %FALSE if not.
+ *
+ * Returns: %TRUE if @child matched
+ */
+typedef gboolean (*IdeTreeFindFunc) (IdeTree *tree,
+ IdeTreeNode *node,
+ IdeTreeNode *child,
+ gpointer user_data);
+
+/**
+ * IdeTreeFilterFunc:
+ *
+ * Callback to check if @node should be visible.
+ *
+ * Returns: %TRUE if @node should be visible.
+ */
+typedef gboolean (*IdeTreeFilterFunc) (IdeTree *tree,
+ IdeTreeNode *node,
+ gpointer user_data);
+
+struct _IdeTreeClass
+{
+ GtkTreeViewClass parent_class;
+
+ void (*action) (IdeTree *self,
+ const gchar *action_group,
+ const gchar *action_name,
+ const gchar *param);
+ void (*populate_popup) (IdeTree *self,
+ GtkWidget *widget);
+};
+
+void ide_tree_add_builder (IdeTree *self,
+ IdeTreeBuilder *builder);
+void ide_tree_remove_builder (IdeTree *self,
+ IdeTreeBuilder *builder);
+IdeTreeNode *ide_tree_find_item (IdeTree *self,
+ GObject *item);
+IdeTreeNode *ide_tree_find_custom (IdeTree *self,
+ GEqualFunc equal_func,
+ gpointer key);
+IdeTreeNode *ide_tree_get_selected (IdeTree *self);
+void ide_tree_rebuild (IdeTree *self);
+void ide_tree_set_root (IdeTree *self,
+ IdeTreeNode *node);
+IdeTreeNode *ide_tree_get_root (IdeTree *self);
+void ide_tree_set_show_icons (IdeTree *self,
+ gboolean show_icons);
+gboolean ide_tree_get_show_icons (IdeTree *self);
+void ide_tree_scroll_to_node (IdeTree *self,
+ IdeTreeNode *node);
+void ide_tree_expand_to_node (IdeTree *self,
+ IdeTreeNode *node);
+IdeTreeNode *ide_tree_find_child_node (IdeTree *self,
+ IdeTreeNode *node,
+ IdeTreeFindFunc find_func,
+ gpointer user_data);
+void ide_tree_set_filter (IdeTree *self,
+ IdeTreeFilterFunc filter_func,
+ gpointer filter_data,
+ GDestroyNotify filter_data_destroy);
+
+G_END_DECLS
+
+#endif /* IDE_TREE_H */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]