[gnome-builder/wip/chergert/perspective] libide: move GbTree to IdeTree



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]