[gnome-builder] tree: add simplified treeview implementation



commit 731426456a18d2f104bd413d835ab38e2f6cbcb7
Author: Christian Hergert <christian hergert me>
Date:   Sat Nov 22 04:21:30 2014 -0800

    tree: add simplified treeview implementation
    
    This gives us the concept of Nodes and Builders. Builders can build nodes
    and children recursively. This is convenient to build things like
    project structures where some content is dynamic.

 src/gnome-builder.mk       |    7 +
 src/tree/gb-tree-builder.c |  310 ++++++++++++++
 src/tree/gb-tree-builder.h |   83 ++++
 src/tree/gb-tree-node.c    |  569 ++++++++++++++++++++++++++
 src/tree/gb-tree-node.h    |   73 ++++
 src/tree/gb-tree.c         |  954 ++++++++++++++++++++++++++++++++++++++++++++
 src/tree/gb-tree.h         |   74 ++++
 7 files changed, 2070 insertions(+), 0 deletions(-)
---
diff --git a/src/gnome-builder.mk b/src/gnome-builder.mk
index 0358f32..18e42f7 100644
--- a/src/gnome-builder.mk
+++ b/src/gnome-builder.mk
@@ -147,6 +147,12 @@ libgnome_builder_la_SOURCES = \
        src/tabs/gb-tab.h \
        src/theatrics/gb-box-theatric.c \
        src/theatrics/gb-box-theatric.h \
+       src/tree/gb-tree.c \
+       src/tree/gb-tree.h \
+       src/tree/gb-tree-builder.c \
+       src/tree/gb-tree-builder.h \
+       src/tree/gb-tree-node.c \
+       src/tree/gb-tree-node.h \
        src/trie/trie.c \
        src/trie/trie.h \
        src/util/gb-cairo.c \
@@ -206,6 +212,7 @@ libgnome_builder_la_CFLAGS = \
        -I$(top_srcdir)/src/sidebar \
        -I$(top_srcdir)/src/snippets \
        -I$(top_srcdir)/src/tabs \
+       -I$(top_srcdir)/src/tree \
        -I$(top_srcdir)/src/trie \
        -I$(top_srcdir)/src/theatrics \
        -I$(top_srcdir)/src/util \
diff --git a/src/tree/gb-tree-builder.c b/src/tree/gb-tree-builder.c
new file mode 100644
index 0000000..de9fb65
--- /dev/null
+++ b/src/tree/gb-tree-builder.c
@@ -0,0 +1,310 @@
+/* gb-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/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "gb-tree.h"
+#include "gb-tree-builder.h"
+
+G_DEFINE_TYPE(GbTreeBuilder, gb_tree_builder, G_TYPE_INITIALLY_UNOWNED)
+
+struct _GbTreeBuilderPrivate
+{
+       GbTree *tree;
+};
+
+enum
+{
+       PROP_0,
+       PROP_TREE,
+       LAST_PROP
+};
+
+static GParamSpec *gParamSpecs[LAST_PROP];
+
+/**
+ * gb_tree_builder_node_activated:
+ * @builder: (in): A #GbTreeBuilder.
+ * @node: (in): A #GbTreeNode.
+ *
+ * Handle @node being activated. Builders may want to open a view
+ * or perform an action on such an event.
+ *
+ * Returns: %TRUE if the node activation was handled.
+ * Side effects: None.
+ */
+gboolean
+gb_tree_builder_node_activated (GbTreeBuilder *builder,
+                                GbTreeNode    *node)
+{
+       g_return_val_if_fail(GB_IS_TREE_BUILDER(builder), FALSE);
+       g_return_val_if_fail(GB_IS_TREE_NODE(node), FALSE);
+
+       if (GB_TREE_BUILDER_GET_CLASS(builder)->node_activated) {
+               return GB_TREE_BUILDER_GET_CLASS(builder)->
+                       node_activated(builder, node);
+       }
+
+       return FALSE;
+}
+
+void
+gb_tree_builder_node_popup (GbTreeBuilder *builder,
+                            GbTreeNode    *node)
+{
+   g_return_if_fail(GB_IS_TREE_BUILDER(builder));
+   g_return_if_fail(GB_IS_TREE_NODE(node));
+
+   if (GB_TREE_BUILDER_GET_CLASS(builder)->node_popup) {
+      GB_TREE_BUILDER_GET_CLASS(builder)->node_popup(builder, node);
+   }
+}
+
+/**
+ * gb_tree_builder_node_selected:
+ * @builder: (in): A #GbTreeBuilder.
+ * @node: (in): A #GbTreeNode.
+ *
+ * Update @node for being selected and update any actions or ui based
+ * on @node being selected.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+gb_tree_builder_node_selected (GbTreeBuilder *builder,
+                               GbTreeNode    *node)
+{
+       g_return_if_fail(GB_IS_TREE_BUILDER(builder));
+       g_return_if_fail(GB_IS_TREE_NODE(node));
+
+       if (GB_TREE_BUILDER_GET_CLASS(builder)->node_selected) {
+               GB_TREE_BUILDER_GET_CLASS(builder)->node_selected(builder, node);
+       }
+}
+
+/**
+ * gb_tree_builder_node_unselected:
+ * @builder: (in): A #GbTreeBuilder.
+ * @node: (in): A #GbTreeNode.
+ *
+ * Update @node and any actions that may be related to @node to account
+ * for it being unselected within the #GbTree.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+gb_tree_builder_node_unselected (GbTreeBuilder *builder,
+                                 GbTreeNode    *node)
+{
+       g_return_if_fail(GB_IS_TREE_BUILDER(builder));
+       g_return_if_fail(GB_IS_TREE_NODE(node));
+
+       if (GB_TREE_BUILDER_GET_CLASS(builder)->node_selected) {
+               GB_TREE_BUILDER_GET_CLASS(builder)->node_unselected(builder, node);
+       }
+}
+
+/**
+ * gb_tree_builder_build_node:
+ * @builder: (in): A #GbTreeBuilder.
+ * @node: (in): A #GbTreeNode.
+ *
+ * Build @node by setting any needed properties for the item or
+ * updating it's appearance. Additional actions may be registered
+ * based on @node<!-- -->'s type if needed.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+void
+gb_tree_builder_build_node (GbTreeBuilder *builder,
+                            GbTreeNode    *node)
+{
+       g_return_if_fail(GB_IS_TREE_BUILDER(builder));
+       g_return_if_fail(GB_IS_TREE_NODE(node));
+
+       if (GB_TREE_BUILDER_GET_CLASS(builder)->build_node) {
+               GB_TREE_BUILDER_GET_CLASS(builder)->build_node(builder, node);
+       }
+}
+
+/**
+ * gb_tree_builder_set_tree:
+ * @builder: (in): A #GbTreeBuilder.
+ * @tree: (in): A #GbTree.
+ *
+ * Sets the tree the builder is associated with.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+gb_tree_builder_set_tree (GbTreeBuilder *builder,
+                          GbTree        *tree)
+{
+       GbTreeBuilderPrivate *priv;
+
+       g_return_if_fail(GB_IS_TREE_BUILDER(builder));
+       g_return_if_fail(builder->priv->tree == NULL);
+       g_return_if_fail(GB_IS_TREE(tree));
+
+       priv = builder->priv;
+
+       if (tree) {
+               priv->tree = tree;
+               g_object_add_weak_pointer(G_OBJECT(priv->tree),
+                                         (gpointer *)&priv->tree);
+       }
+}
+
+/**
+ * gb_tree_builder_get_tree:
+ * @builder: (in): A #GbTreeBuilder.
+ *
+ * Gets the tree that owns the builder.
+ *
+ * Returns: (transfer none) (type GbTree*): A #GbTree.
+ */
+GtkWidget *
+gb_tree_builder_get_tree (GbTreeBuilder *builder)
+{
+   g_return_val_if_fail(GB_IS_TREE_BUILDER(builder), NULL);
+   return GTK_WIDGET(builder->priv->tree);
+}
+
+/**
+ * gb_tree_builder_finalize:
+ * @object: (in): A #GbTreeBuilder.
+ *
+ * Finalizer for a #GbTreeBuilder instance.
+ */
+static void
+gb_tree_builder_finalize (GObject *object)
+{
+       GbTreeBuilderPrivate *priv = GB_TREE_BUILDER(object)->priv;
+
+       if (priv->tree) {
+               g_object_remove_weak_pointer(G_OBJECT(priv->tree),
+                                            (gpointer *)&priv->tree);
+               priv->tree = NULL;
+       }
+
+       G_OBJECT_CLASS(gb_tree_builder_parent_class)->finalize(object);
+}
+
+/**
+ * gb_tree_builder_get_property:
+ * @object: (in): A #GObject.
+ * @prop_id: (in): The property identifier.
+ * @value: (out): The given property.
+ * @pspec: (in): A #ParamSpec.
+ *
+ * Get a given #GObject property.
+ */
+static void
+gb_tree_builder_get_property (GObject    *object,
+                              guint       prop_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
+{
+       GbTreeBuilder *builder = GB_TREE_BUILDER(object);
+
+       switch (prop_id) {
+       case PROP_TREE:
+               g_value_set_object(value, builder->priv->tree);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+       }
+}
+
+/**
+ * gb_tree_builder_set_property:
+ * @object: (in): A #GObject.
+ * @prop_id: (in): The property identifier.
+ * @value: (in): The given property.
+ * @pspec: (in): A #ParamSpec.
+ *
+ * Set a given #GObject property.
+ */
+static void
+gb_tree_builder_set_property (GObject      *object,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
+{
+       GbTreeBuilder *builder = GB_TREE_BUILDER(object);
+
+       switch (prop_id) {
+       case PROP_TREE:
+               gb_tree_builder_set_tree(builder, g_value_get_object(value));
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+       }
+}
+
+/**
+ * gb_tree_builder_class_init:
+ * @klass: (in): A #GbTreeBuilderClass.
+ *
+ * Initializes the #GbTreeBuilderClass and prepares the vtable.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+gb_tree_builder_class_init (GbTreeBuilderClass *klass)
+{
+       GObjectClass *object_class;
+
+       object_class = G_OBJECT_CLASS(klass);
+       object_class->finalize = gb_tree_builder_finalize;
+       object_class->get_property = gb_tree_builder_get_property;
+       object_class->set_property = gb_tree_builder_set_property;
+       g_type_class_add_private(object_class, sizeof(GbTreeBuilderPrivate));
+
+       gParamSpecs[PROP_TREE] =
+               g_param_spec_object("tree",
+                                   _("Tree"),
+                                   _("The GbTree the builder belongs to."),
+                                   GB_TYPE_TREE,
+                                   G_PARAM_READWRITE);
+       g_object_class_install_property(object_class, PROP_TREE,
+                                       gParamSpecs[PROP_TREE]);
+}
+
+/**
+ * gb_tree_builder_init:
+ * @: (in): A #GbTreeBuilder.
+ *
+ * Initializes the newly created #GbTreeBuilder instance.
+ *
+ * Returns: None.
+ * Side effects: None.
+ */
+static void
+gb_tree_builder_init (GbTreeBuilder *builder)
+{
+       builder->priv =
+               G_TYPE_INSTANCE_GET_PRIVATE(builder,
+                                           GB_TYPE_TREE_BUILDER,
+                                           GbTreeBuilderPrivate);
+}
diff --git a/src/tree/gb-tree-builder.h b/src/tree/gb-tree-builder.h
new file mode 100644
index 0000000..efeb3ec
--- /dev/null
+++ b/src/tree/gb-tree-builder.h
@@ -0,0 +1,83 @@
+/* gb-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 GB_TREE_BUILDER_H
+#define GB_TREE_BUILDER_H
+
+#include <glib-object.h>
+
+#include "gb-tree-node.h"
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_TREE_BUILDER            (gb_tree_builder_get_type())
+#define GB_TREE_BUILDER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GB_TYPE_TREE_BUILDER, 
GbTreeBuilder))
+#define GB_TREE_BUILDER_CONST(obj)      (G_TYPE_CHECK_INSTANCE_CAST ((obj), GB_TYPE_TREE_BUILDER, 
GbTreeBuilder const))
+#define GB_TREE_BUILDER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  GB_TYPE_TREE_BUILDER, 
GbTreeBuilderClass))
+#define GB_IS_TREE_BUILDER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GB_TYPE_TREE_BUILDER))
+#define GB_IS_TREE_BUILDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  GB_TYPE_TREE_BUILDER))
+#define GB_TREE_BUILDER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  GB_TYPE_TREE_BUILDER, 
GbTreeBuilderClass))
+
+typedef struct _GbTreeBuilder        GbTreeBuilder;
+typedef struct _GbTreeBuilderClass   GbTreeBuilderClass;
+typedef struct _GbTreeBuilderPrivate GbTreeBuilderPrivate;
+
+struct _GbTreeBuilder
+{
+       GInitiallyUnowned parent;
+
+       /*< private >*/
+       GbTreeBuilderPrivate *priv;
+};
+
+struct _GbTreeBuilderClass
+{
+       GInitiallyUnownedClass parent_class;
+
+   void     (*added)           (GbTreeBuilder *builder,
+                                GtkWidget     *tree);
+   void     (*removed)         (GbTreeBuilder *builder,
+                                GtkWidget     *tree);
+       void     (*build_node)      (GbTreeBuilder *builder,
+                                    GbTreeNode    *node);
+       gboolean (*node_activated)  (GbTreeBuilder *builder,
+                                    GbTreeNode    *node);
+       void     (*node_selected)   (GbTreeBuilder *builder,
+                                    GbTreeNode    *node);
+       void     (*node_unselected) (GbTreeBuilder *builder,
+                                    GbTreeNode    *node);
+   void     (*node_popup)      (GbTreeBuilder *builder,
+                                GbTreeNode    *node);
+};
+
+GtkWidget *gb_tree_builder_get_tree        (GbTreeBuilder *builder);
+GType      gb_tree_builder_get_type        (void) G_GNUC_CONST;
+void       gb_tree_builder_build_node      (GbTreeBuilder *builder,
+                                            GbTreeNode    *node);
+gboolean   gb_tree_builder_node_activated  (GbTreeBuilder *builder,
+                                            GbTreeNode    *node);
+void       gb_tree_builder_node_popup      (GbTreeBuilder *builder,
+                                            GbTreeNode    *node);
+void       gb_tree_builder_node_selected   (GbTreeBuilder *builder,
+                                            GbTreeNode    *node);
+void       gb_tree_builder_node_unselected (GbTreeBuilder *builder,
+                                            GbTreeNode    *node);
+
+G_END_DECLS
+
+#endif /* GB_TREE_BUILDER_H */
diff --git a/src/tree/gb-tree-node.c b/src/tree/gb-tree-node.c
new file mode 100644
index 0000000..b1a265a
--- /dev/null
+++ b/src/tree/gb-tree-node.c
@@ -0,0 +1,569 @@
+/* gb-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/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "gb-tree.h"
+#include "gb-tree-node.h"
+
+struct _GbTreeNodePrivate
+{
+  GObject       *item;
+  GQuark         icon_name;
+  GbTreeNode    *parent;
+  gchar         *text;
+  GbTree        *tree;
+  gboolean       use_markup;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GbTreeNode, gb_tree_node, G_TYPE_INITIALLY_UNOWNED)
+
+enum {
+  PROP_0,
+  PROP_ICON_NAME,
+  PROP_ITEM,
+  PROP_PARENT,
+  PROP_TEXT,
+  PROP_TREE,
+  PROP_USE_MARKUP,
+  LAST_PROP
+};
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+
+/**
+ * gb_tree_node_get_tree:
+ * @node: (in): A #GbTreeNode.
+ *
+ * Fetches the #GbTree instance that owns the node.
+ *
+ * Returns: (transfer none): A #GbTree.
+ */
+GbTree *
+gb_tree_node_get_tree (GbTreeNode *node)
+{
+  g_return_val_if_fail (GB_IS_TREE_NODE (node), NULL);
+
+  for (; node->priv->parent; node = node->priv->parent) { }
+
+  return node->priv->tree;
+}
+
+/**
+ * gb_tree_node_set_tree:
+ * @node: (in): A #GbTreeNode.
+ * @tree: (in): A #GbTree.
+ *
+ * Internal method to set the nodes tree.
+ */
+void
+_gb_tree_node_set_tree (GbTreeNode *node,
+                        GbTree     *tree)
+{
+  g_return_if_fail (GB_IS_TREE_NODE (node));
+  g_return_if_fail (GB_IS_TREE (tree));
+  g_return_if_fail (node->priv->tree == NULL);
+
+  node->priv->tree = tree;
+}
+
+/**
+ * gb_tree_node_append:
+ * @node: (in): A #GbTreeNode.
+ *
+ * Appends @child to the list of children owned by @node.
+ */
+void
+gb_tree_node_append (GbTreeNode *node,
+                     GbTreeNode *child)
+{
+  GbTree *tree = NULL;
+
+  g_return_if_fail (GB_IS_TREE_NODE (node));
+
+  g_object_get (node, "tree", &tree, NULL);
+  g_assert (tree);
+  gb_tree_append (tree, node, child);
+  g_clear_object (&tree);
+}
+
+/**
+ * gb_tree_node_prepend:
+ * @node: (in): A #GbTreeNode.
+ *
+ * Prepends @child to the list of children owned by @node.
+ */
+void
+gb_tree_node_prepend (GbTreeNode *node,
+                      GbTreeNode *child)
+{
+  GbTree *tree = NULL;
+
+  g_return_if_fail (GB_IS_TREE_NODE (node));
+
+  g_object_get (node, "tree", &tree, NULL);
+  gb_tree_prepend (tree, node, child);
+  g_clear_object (&tree);
+}
+
+/**
+ * gb_tree_node_remove:
+ * @node: (in): A #GbTreeNode.
+ *
+ * Removes @child from the list of children owned by @node.
+ */
+void
+gb_tree_node_remove (GbTreeNode *node,
+                     GbTreeNode *child)
+{
+  GtkTreeModel *model = NULL;
+  GtkTreePath *path;
+  GtkTreeIter iter;
+  GbTree *tree = NULL;
+
+  g_return_if_fail (GB_IS_TREE_NODE (node));
+
+  tree = gb_tree_node_get_tree (node);
+  model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree));
+  path = gb_tree_node_get_path (node);
+
+  g_object_ref (tree);
+  g_object_ref (model);
+
+  if (gtk_tree_model_get_iter (model, &iter, path))
+    gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
+
+  g_clear_object (&model);
+  g_clear_object (&tree);
+
+  gtk_tree_path_free (path);
+}
+
+/**
+ * gb_tree_node_get_path:
+ * @node: (in): A #GbTreeNode.
+ *
+ * Gets a #GtkTreePath for @node.
+ *
+ * Returns: (transfer full): A #GtkTreePath if successful; otherwise %NULL.
+ */
+GtkTreePath *
+gb_tree_node_get_path (GbTreeNode *node)
+{
+  GbTreeNode *toplevel;
+  GtkTreePath *path;
+  GList *list = NULL;
+
+  g_return_val_if_fail (GB_IS_TREE_NODE (node), NULL);
+
+  do
+    list = g_list_prepend (list, node);
+  while ((node = node->priv->parent));
+
+  toplevel = list->data;
+
+  g_assert (toplevel);
+  g_assert (toplevel->priv->tree);
+
+  list = g_list_remove_link (list, list);
+  path = gb_tree_get_path (toplevel->priv->tree, list);
+
+  g_list_free (list);
+
+  return path;
+}
+
+/**
+ * gb_tree_node_get_parent:
+ * @node: (in): A #GbTreeNode.
+ *
+ * Retrieves the parent #GbTreeNode for @node.
+ *
+ * Returns: (transfer none): A #GbTreeNode.
+ */
+GbTreeNode *
+gb_tree_node_get_parent (GbTreeNode *node)
+{
+  g_return_val_if_fail (GB_IS_TREE_NODE (node), NULL);
+
+  return node->priv->parent;
+}
+
+/**
+ * gb_tree_node_get_icon_name:
+ *
+ * Fetches the icon-name of the icon to display, or NULL for no icon.
+ *
+ * Returns: 
+ */
+const gchar *
+gb_tree_node_get_icon_name (GbTreeNode *node)
+{
+  g_return_val_if_fail (GB_IS_TREE_NODE (node), NULL);
+
+  return g_quark_to_string (node->priv->icon_name);
+}
+
+/**
+ * gb_tree_node_set_icon_name:
+ * @node: (in): A #GbTreeNode.
+ * @icon_name: (in): The icon name.
+ *
+ * Sets the icon name of the node. This is displayed in the pixbuf
+ * cell of the GbTree.
+ */
+void
+gb_tree_node_set_icon_name (GbTreeNode  *node,
+                            const gchar *icon_name)
+{
+  g_return_if_fail (GB_IS_TREE_NODE (node));
+
+  node->priv->icon_name = g_quark_from_string (icon_name);
+  g_object_notify_by_pspec (G_OBJECT (node), gParamSpecs [PROP_ICON_NAME]);
+}
+
+/**
+ * gb_tree_node_set_item:
+ * @node: (in): A #GbTreeNode.
+ * @item: (in): A #GObject.
+ *
+ * An optional object to associate with the node. This is handy to save needing
+ * to subclass the #GbTreeNode class.
+ */
+void
+gb_tree_node_set_item (GbTreeNode *node,
+                       GObject    *item)
+{
+  g_return_if_fail (GB_IS_TREE_NODE (node));
+  g_return_if_fail (!item || G_IS_OBJECT (item));
+
+  if (item != node->priv->item)
+    {
+      g_clear_object (&node->priv->item);
+      node->priv->item = item ? g_object_ref (item) : NULL;
+      g_object_notify_by_pspec (G_OBJECT (node), gParamSpecs [PROP_ITEM]);
+    }
+}
+
+/**
+ * gb_tree_node_set_parent:
+ * @node: (in): A #GbTreeNode.
+ * @parent: (in): A #GbTreeNode.
+ *
+ * Sets the parent for this node. This is a weak pointer to prevent
+ * cyclic references.
+ */
+static void
+gb_tree_node_set_parent (GbTreeNode *node,
+                         GbTreeNode *parent)
+{
+  g_return_if_fail (GB_IS_TREE_NODE (node));
+  g_return_if_fail (node->priv->parent == NULL);
+  g_return_if_fail (!parent || GB_IS_TREE_NODE (parent));
+
+  if (parent)
+    {
+      node->priv->parent = parent;
+      g_object_add_weak_pointer (G_OBJECT (node->priv->parent),
+                                 (gpointer *)&node->priv->parent);
+    }
+}
+
+/**
+ * gb_tree_node_set_text:
+ * @node: (in): A #GbTreeNode.
+ * @text: (in): The node text.
+ *
+ * Sets the text of the node. This is displayed in the text
+ * cell of the GbTree.
+ */
+static void
+gb_tree_node_set_text (GbTreeNode  *node,
+                       const gchar *text)
+{
+  g_return_if_fail (GB_IS_TREE_NODE (node));
+
+  if (text != node->priv->text)
+    {
+      g_free (node->priv->text);
+      node->priv->text = g_strdup (text);
+      g_object_notify_by_pspec (G_OBJECT (node), gParamSpecs [PROP_TEXT]);
+    }
+}
+
+/**
+ * gb_tree_node_set_use_markup:
+ * @node: (in): A #GbTreeNode.
+ * @use_markup: (in): If we should use markup.
+ *
+ * Sets if the text property should be interprited as GLib markup.
+ */
+static void
+gb_tree_node_set_use_markup (GbTreeNode *node,
+                             gboolean    use_markup)
+{
+  g_return_if_fail (GB_IS_TREE_NODE (node));
+
+  node->priv->use_markup = use_markup;
+  g_object_notify_by_pspec (G_OBJECT (node), gParamSpecs [PROP_USE_MARKUP]);
+}
+
+/**
+ * gb_tree_node_get_item:
+ * @node: (in): A #GbTreeNode.
+ *
+ * Gets a #GObject for the node, if one was set.
+ *
+ * Returns: (transfer none): A #GObject or %NULL.
+ */
+GObject *
+gb_tree_node_get_item (GbTreeNode *node)
+{
+  g_return_val_if_fail (GB_IS_TREE_NODE (node), NULL);
+
+  return node->priv->item;
+}
+
+/**
+ * gb_tree_node_finalize:
+ * @object: (in): A #GbTreeNode.
+ *
+ * Finalizer for a #GbTreeNode instance.  Frees any resources held by
+ * the instance.
+ */
+static void
+gb_tree_node_finalize (GObject *object)
+{
+  GbTreeNodePrivate *priv = GB_TREE_NODE (object)->priv;
+
+  g_clear_object (&priv->item);
+  g_clear_pointer (&priv->text, g_free);
+
+  if (priv->parent)
+    {
+      g_object_remove_weak_pointer (G_OBJECT (priv->parent),
+                                    (gpointer *)&priv->parent);
+      priv->parent = NULL;
+    }
+
+  G_OBJECT_CLASS (gb_tree_node_parent_class)->finalize (object);
+}
+
+/**
+ * gb_tree_node_get_property:
+ * @object: (in): A #GObject.
+ * @prop_id: (in): The property identifier.
+ * @value: (out): The given property.
+ * @pspec: (in): A #ParamSpec.
+ *
+ * Get a given #GObject property.
+ */
+static void
+gb_tree_node_get_property (GObject    *object,
+                           guint       prop_id,
+                           GValue     *value,
+                           GParamSpec *pspec)
+{
+  GbTreeNode *node = GB_TREE_NODE (object);
+
+  switch (prop_id)
+    {
+    case PROP_ICON_NAME:
+      g_value_set_string (value, g_quark_to_string (node->priv->icon_name));
+      break;
+
+    case PROP_ITEM:
+      g_value_set_object (value, node->priv->item);
+      break;
+
+    case PROP_PARENT:
+      g_value_set_object (value, node->priv->parent);
+      break;
+
+    case PROP_TEXT:
+      g_value_set_string (value, node->priv->text);
+      break;
+
+    case PROP_TREE:
+      g_value_set_object (value, gb_tree_node_get_tree (node));
+      break;
+
+    case PROP_USE_MARKUP:
+      g_value_set_boolean (value, node->priv->use_markup);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+/**
+ * gb_tree_node_set_property:
+ * @object: (in): A #GObject.
+ * @prop_id: (in): The property identifier.
+ * @value: (in): The given property.
+ * @pspec: (in): A #ParamSpec.
+ *
+ * Set a given #GObject property.
+ */
+static void
+gb_tree_node_set_property (GObject      *object,
+                           guint         prop_id,
+                           const GValue *value,
+                           GParamSpec   *pspec)
+{
+  GbTreeNode *node = GB_TREE_NODE (object);
+
+  switch (prop_id)
+    {
+    case PROP_ICON_NAME:
+      gb_tree_node_set_icon_name (node, g_value_get_string (value));
+      break;
+
+    case PROP_ITEM:
+      gb_tree_node_set_item (node, g_value_get_object (value));
+      break;
+
+    case PROP_PARENT:
+      gb_tree_node_set_parent (node, g_value_get_object (value));
+      break;
+
+    case PROP_TEXT:
+      gb_tree_node_set_text (node, g_value_get_string (value));
+      break;
+
+    case PROP_USE_MARKUP:
+      gb_tree_node_set_use_markup (node, g_value_get_boolean (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+/**
+ * gb_tree_node_class_init:
+ * @klass: (in): A #GbTreeNodeClass.
+ *
+ * Initializes the #GbTreeNodeClass and prepares the vtable.
+ */
+static void
+gb_tree_node_class_init (GbTreeNodeClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gb_tree_node_finalize;
+  object_class->get_property = gb_tree_node_get_property;
+  object_class->set_property = gb_tree_node_set_property;
+
+  /**
+   * GbTreeNode:icon-name:
+   *
+   * An icon-name to display on the row.
+   */
+  gParamSpecs[PROP_ICON_NAME] =
+    g_param_spec_string ("icon-name",
+                         _("Icon Name"),
+                         _("The icon name to display."),
+                         NULL,
+                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_ICON_NAME,
+                                   gParamSpecs[PROP_ICON_NAME]);
+
+  /**
+   * GbTreeNode:item:
+   *
+   * An optional #GObject to associate with the node.
+   */
+  gParamSpecs[PROP_ITEM] =
+    g_param_spec_object ("item",
+                         _("Item"),
+                         _("Optional object to associate with node."),
+                         G_TYPE_OBJECT,
+                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_ITEM,
+                                   gParamSpecs[PROP_ITEM]);
+
+  /**
+   * GbTreeNode:parent:
+   *
+   * The parent of the node.
+   */
+  gParamSpecs [PROP_PARENT] =
+    g_param_spec_object ("parent",
+                         _("Parent"),
+                         _("The parent node."),
+                         GB_TYPE_TREE_NODE,
+                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_PARENT,
+                                   gParamSpecs[PROP_PARENT]);
+
+  /**
+   * GbTreeNode:tree:
+   *
+   * The tree the node belongs to.
+   */
+  gParamSpecs [PROP_TREE] =
+    g_param_spec_object ("tree",
+                         _("Tree"),
+                         _("The GbTree the node belongs to."),
+                         GB_TYPE_TREE,
+                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_TREE,
+                                   gParamSpecs[PROP_TREE]);
+
+  /**
+   * GbTreeNode:text:
+   *
+   * Text to display on the tree node.
+   */
+  gParamSpecs [PROP_TEXT] =
+    g_param_spec_string ("text",
+                         _("Text"),
+                         _("The text of the node."),
+                         NULL,
+                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_TEXT,
+                                   gParamSpecs[PROP_TEXT]);
+
+  /**
+   * GbTreeNode:use-markup:
+   *
+   * If the "text" property includes #GMarkup.
+   */
+  gParamSpecs [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);
+  g_object_class_install_property (object_class, PROP_USE_MARKUP,
+                                   gParamSpecs[PROP_USE_MARKUP]);
+}
+
+/**
+ * gb_tree_node_init:
+ * @node: (in): A #GbTreeNode.
+ *
+ * Initializes the newly created #GbTreeNode instance.
+ */
+static void
+gb_tree_node_init (GbTreeNode *node)
+{
+  node->priv = gb_tree_node_get_instance_private (node);
+}
diff --git a/src/tree/gb-tree-node.h b/src/tree/gb-tree-node.h
new file mode 100644
index 0000000..24263a9
--- /dev/null
+++ b/src/tree/gb-tree-node.h
@@ -0,0 +1,73 @@
+/* gb-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 GB_TREE_NODE_H
+#define GB_TREE_NODE_H
+
+#include <gtk/gtk.h>
+
+#if 0
+#include "gb-project-item.h"
+#endif
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_TREE_NODE            (gb_tree_node_get_type())
+#define GB_TREE_NODE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GB_TYPE_TREE_NODE, GbTreeNode))
+#define GB_TREE_NODE_CONST(obj)      (G_TYPE_CHECK_INSTANCE_CAST ((obj), GB_TYPE_TREE_NODE, GbTreeNode 
const))
+#define GB_TREE_NODE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  GB_TYPE_TREE_NODE, GbTreeNodeClass))
+#define GB_IS_TREE_NODE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GB_TYPE_TREE_NODE))
+#define GB_IS_TREE_NODE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  GB_TYPE_TREE_NODE))
+#define GB_TREE_NODE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  GB_TYPE_TREE_NODE, GbTreeNodeClass))
+
+typedef struct _GbTreeNode        GbTreeNode;
+typedef struct _GbTreeNodeClass   GbTreeNodeClass;
+typedef struct _GbTreeNodePrivate GbTreeNodePrivate;
+
+struct _GbTreeNode
+{
+       GInitiallyUnowned parent;
+
+       /*< private >*/
+       GbTreeNodePrivate *priv;
+};
+
+struct _GbTreeNodeClass
+{
+       GInitiallyUnownedClass parent_class;
+};
+
+void           gb_tree_node_append        (GbTreeNode  *node,
+                                           GbTreeNode  *child);
+const gchar   *gb_tree_node_get_icon_name (GbTreeNode  *node);
+GObject       *gb_tree_node_get_item      (GbTreeNode  *node);
+GbTreeNode    *gb_tree_node_get_parent    (GbTreeNode  *node);
+GtkTreePath   *gb_tree_node_get_path      (GbTreeNode  *node);
+GType          gb_tree_node_get_type      (void) G_GNUC_CONST;
+void           gb_tree_node_prepend       (GbTreeNode  *node,
+                                           GbTreeNode  *child);
+void           gb_tree_node_remove        (GbTreeNode  *node,
+                                           GbTreeNode  *child);
+void           gb_tree_node_set_icon_name (GbTreeNode  *node,
+                                           const gchar *icon_name);
+void           gb_tree_node_set_item      (GbTreeNode  *node,
+                                           GObject     *item);
+
+G_END_DECLS
+
+#endif /* GB_TREE_NODE_H */
diff --git a/src/tree/gb-tree.c b/src/tree/gb-tree.c
new file mode 100644
index 0000000..692c03a
--- /dev/null
+++ b/src/tree/gb-tree.c
@@ -0,0 +1,954 @@
+/* gb-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/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "gb-log.h"
+#include "gb-tree.h"
+#include "gb-tree-node.h"
+
+struct _GbTreePrivate
+{
+  GPtrArray    *builders;
+  GMenu        *menu;
+  GbTreeNode   *root;
+  GbTreeNode   *selection;
+  GtkTreeStore *store;
+  guint         building : 1;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GbTree, gb_tree, GTK_TYPE_TREE_VIEW)
+
+enum {
+  PROP_0,
+  PROP_MENU,
+  PROP_ROOT,
+  PROP_SELECTION,
+  LAST_PROP
+};
+
+extern void _gb_tree_node_set_tree (GbTreeNode *node,
+                                    GbTree     *tree);
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+
+/**
+ * gb_tree_get_menu:
+ * @tree: (in): A #GbTree.
+ *
+ * Gets the #GMenu to be displayed when right clicking on an item in the tree.
+ * Node's should update actions sensitivity in their selection callbacks.
+ *
+ * Returns: (transfer none): A #GMenu or %NULL.
+ */
+GMenu *
+gb_tree_get_menu (GbTree *tree)
+{
+  g_return_val_if_fail (GB_IS_TREE (tree), NULL);
+
+  return tree->priv->menu;
+}
+
+/**
+ * gb_tree_set_menu:
+ * @menu: (in) (transfer none): A #GMenu or %NULL.
+ *
+ * Set the menu to be used when a popup is to be shown.
+ */
+void
+gb_tree_set_menu (GbTree *tree,
+                  GMenu  *menu)
+{
+  g_return_if_fail (GB_IS_TREE (tree));
+  g_return_if_fail (!menu || G_IS_MENU (menu));
+
+  if (menu != tree->priv->menu)
+    {
+      g_clear_object (&tree->priv->menu);
+      tree->priv->menu = menu ? g_object_ref (menu) : NULL;
+      g_object_notify_by_pspec (G_OBJECT (tree), gParamSpecs [PROP_MENU]);
+    }
+}
+
+/**
+ * gb_tree_unselect:
+ * @tree: (in): A #GbTree.
+ *
+ * Unselects the current item in the tree.
+ */
+static void
+gb_tree_unselect (GbTree *tree)
+{
+  GtkTreeSelection *selection;
+
+  ENTRY;
+
+  g_return_if_fail (GB_IS_TREE (tree));
+
+  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
+  gtk_tree_selection_unselect_all (selection);
+
+  EXIT;
+}
+
+/**
+ * gb_tree_select:
+ * @tree: (in): A #GbTree.
+ * @node: (in): A #GbTreeNode.
+ *
+ * Selects @node within the tree.
+ */
+static void
+gb_tree_select (GbTree     *tree,
+                GbTreeNode *node)
+{
+  GtkTreeSelection *selection;
+  GbTreePrivate *priv;
+  GtkTreePath *path;
+
+  ENTRY;
+
+  g_return_if_fail (GB_IS_TREE (tree));
+  g_return_if_fail (GB_IS_TREE_NODE (node));
+
+  priv = tree->priv;
+
+  if (priv->selection)
+    {
+      gb_tree_unselect (tree);
+      g_assert (!priv->selection);
+    }
+
+  priv->selection = node;
+
+  path = gb_tree_node_get_path (node);
+  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
+  gtk_tree_selection_select_path (selection, path);
+  gtk_tree_path_free (path);
+
+  EXIT;
+}
+
+static void
+gb_tree_menu_position_func (GtkMenu  *menu,
+                            gint     *x,
+                            gint     *y,
+                            gboolean *push_in,
+                            gpointer  user_data)
+{
+  GdkPoint *loc = user_data;
+
+  g_return_if_fail (loc != NULL);
+
+  if ((loc->x != -1) && (loc->y != -1))
+    {
+      *x = loc->x;
+      *y = loc->y;
+    }
+}
+
+static void
+check_visible_foreach (GtkWidget *widget,
+                       gpointer   user_data)
+{
+  gboolean *at_least_one_visible = user_data;
+
+  if (gtk_widget_get_visible (widget))
+    *at_least_one_visible = TRUE;
+}
+
+static void
+gb_tree_popup (GbTree         *tree,
+               GbTreeNode     *node,
+               GdkEventButton *button,
+               gint            target_x,
+               gint            target_y)
+{
+  GbTreePrivate *priv;
+  GbTreeBuilder *builder;
+  GtkWidget *menu;
+  GdkPoint loc = { -1, -1 };
+  gboolean at_least_one_visible = FALSE;
+  guint i;
+
+  ENTRY;
+
+  g_return_if_fail (GB_IS_TREE (tree));
+  g_return_if_fail (GB_IS_TREE_NODE (node));
+  g_return_if_fail (button != NULL);
+
+  priv = tree->priv;
+
+  menu = gtk_menu_new_from_model (G_MENU_MODEL (priv->menu));
+
+  for (i = 0; i < priv->builders->len; i++)
+    {
+      builder = g_ptr_array_index (priv->builders, i);
+      gb_tree_builder_node_popup (builder, node);
+    }
+
+  if ((target_x >= 0) &&  (target_y >= 0))
+    {
+      gdk_window_get_root_coords (gtk_widget_get_window (GTK_WIDGET (tree)),
+                                  target_x, target_y, &loc.x, &loc.y);
+      loc.x -= 12;
+    }
+
+  gtk_container_foreach (GTK_CONTAINER (menu),
+                         check_visible_foreach,
+                         &at_least_one_visible);
+
+  if (at_least_one_visible)
+    gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
+                    gb_tree_menu_position_func, &loc,
+                    button->button,
+                    button->time);
+
+  EXIT;
+}
+
+/**
+ * gb_tree_selection_changed:
+ * @tree: (in): A #GbTree.
+ *
+ * Handle the selection changing.
+ */
+static void
+gb_tree_selection_changed (GbTree           *tree,
+                           GtkTreeSelection *selection)
+{
+  GbTreePrivate *priv;
+  GbTreeBuilder *builder;
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+  GbTreeNode *node;
+  GbTreeNode *unselection;
+  gint i;
+
+  ENTRY;
+
+  g_return_if_fail (GB_IS_TREE (tree));
+  g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
+
+  priv = tree->priv;
+
+  if ((unselection = priv->selection))
+    {
+      priv->selection = NULL;
+      for (i = 0; i < priv->builders->len; i++)
+        {
+          builder = g_ptr_array_index (priv->builders, i);
+          gb_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);
+              gb_tree_builder_node_selected (builder, node);
+            }
+          g_object_unref (node);
+        }
+    }
+
+  EXIT;
+}
+
+/**
+ * gb_tree_get_selected:
+ * @tree: (in): A #GbTree.
+ *
+ * Gets the currently selected node in the tree.
+ *
+ * Returns: (transfer none): A #GbTreeNode.
+ */
+GbTreeNode *
+gb_tree_get_selected (GbTree *tree)
+{
+  GtkTreeSelection *selection;
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+  GbTreeNode *ret = NULL;
+
+  g_return_val_if_fail (GB_IS_TREE (tree), NULL);
+
+  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
+  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;
+}
+
+/**
+ * gb_tree_get_iter_for_node:
+ * @tree: (in): A #GbTree.
+ * @parent: (in) (allow-none): A #GtkTreeIter of parent or %NULL.
+ * @iter: (out): A location for a #GtkTreeIter.
+ * @node: (in): A #GbTreeNode expected to be within @parent children.
+ *
+ * Looks for the #GtkTreeIter that contains @node within the children
+ * of @parent. If that item is found, @iter is set and %TRUE is returned.
+ * Otherwise, %FALSE is returned.
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE.
+ */
+static gboolean
+gb_tree_get_iter_for_node (GbTree      *tree,
+                           GtkTreeIter *parent,
+                           GtkTreeIter *iter,
+                           GbTreeNode  *node)
+{
+  GbTreePrivate *priv;
+  GtkTreeModel *model;
+  GbTreeNode *that = NULL;
+  gboolean ret;
+
+  ENTRY;
+
+  g_return_val_if_fail (GB_IS_TREE (tree), FALSE);
+  g_return_val_if_fail (iter != NULL, FALSE);
+  g_return_val_if_fail (GB_IS_TREE_NODE (node), FALSE);
+
+  priv = tree->priv;
+  model = GTK_TREE_MODEL (priv->store);
+
+  if (parent)
+    ret = gtk_tree_model_iter_children (model, iter, parent);
+  else
+    ret = gtk_tree_model_get_iter_first (model, iter);
+
+  if (ret)
+    {
+      do
+        {
+          gtk_tree_model_get (model, iter, 0, &that, -1);
+          if (that == node)
+            {
+              g_clear_object (&that);
+              RETURN (TRUE);
+            }
+          g_clear_object (&that);
+        }
+      while (gtk_tree_model_iter_next (model, iter));
+    }
+
+  RETURN (FALSE);
+}
+
+/**
+ * gb_tree_get_path:
+ * @tree: (in): A #GbTree.
+ * @list: (in) (element-type GbTreeNode): A list of #GbTreeNode.
+ *
+ * Retrieves the GtkTreePath for a list of GbTreeNode.
+ *
+ * Returns: (transfer full): A #GtkTreePath.
+ */
+GtkTreePath *
+gb_tree_get_path (GbTree *tree,
+                  GList  *list)
+{
+  GbTreePrivate *priv;
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+  GtkTreeIter old_iter;
+  GtkTreeIter *parent = NULL;
+
+  g_return_val_if_fail (GB_IS_TREE (tree), NULL);
+
+  priv = tree->priv;
+  model = GTK_TREE_MODEL (priv->store);
+
+  if (!list || !gtk_tree_model_get_iter_first (model, &iter))
+    return NULL;
+
+  if (list->data == priv->root)
+    list = list->next;
+
+  while (gb_tree_get_iter_for_node (tree, parent, &iter, list->data))
+    {
+      old_iter = iter;
+      parent = &old_iter;
+      if (list->next)
+        list = list->next;
+      else
+        return gtk_tree_model_get_path (model, &iter);
+    }
+
+  return NULL;
+}
+
+static gboolean
+gb_tree_add_builder_foreach_cb (GtkTreeModel *model,
+                                GtkTreePath  *path,
+                                GtkTreeIter  *iter,
+                                gpointer      user_data)
+{
+  GbTreeNode *node = NULL;
+  GbTreeBuilder *builder = (GbTreeBuilder *) user_data;
+
+  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);
+  gb_tree_builder_build_node (builder, node);
+  g_clear_object (&node);
+
+  RETURN (FALSE);
+}
+
+/**
+ * gb_tree_add_builder:
+ * @tree: (in): A #GbTree.
+ * @builder: (in) (transfer full): A #GbTreeBuilder to add.
+ *
+ * Removes a builder from the tree.
+ */
+void
+gb_tree_add_builder (GbTree        *tree,
+                     GbTreeBuilder *builder)
+{
+  GbTreePrivate *priv;
+
+  ENTRY;
+
+  g_return_if_fail (GB_IS_TREE (tree));
+  g_return_if_fail (GB_IS_TREE_BUILDER (builder));
+
+  priv = tree->priv;
+
+  g_object_set (builder, "tree", tree, NULL);
+  g_ptr_array_add (priv->builders, g_object_ref_sink (builder));
+  priv->building = TRUE;
+  gtk_tree_model_foreach (GTK_TREE_MODEL (priv->store),
+                          gb_tree_add_builder_foreach_cb,
+                          builder);
+  priv->building = FALSE;
+
+  if (GB_TREE_BUILDER_GET_CLASS (builder)->added)
+    GB_TREE_BUILDER_GET_CLASS (builder)->added (builder, GTK_WIDGET (tree));
+
+  EXIT;
+}
+
+/**
+ * gb_tree_remove_builder:
+ * @tree: (in): A #GbTree.
+ * @builder: (in): A #GbTreeBuilder to remove.
+ *
+ * Removes a builder from the tree.
+ */
+void
+gb_tree_remove_builder (GbTree        *tree,
+                        GbTreeBuilder *builder)
+{
+  ENTRY;
+
+  g_return_if_fail (GB_IS_TREE (tree));
+  g_return_if_fail (GB_IS_TREE_BUILDER (builder));
+
+  if (GB_TREE_BUILDER_GET_CLASS (builder)->removed)
+    GB_TREE_BUILDER_GET_CLASS (builder)->removed (builder, GTK_WIDGET (tree));
+
+  g_ptr_array_remove (tree->priv->builders, builder);
+
+  EXIT;
+}
+
+/**
+ * gb_tree_set_root:
+ * @tree: (in): A #GbTree.
+ * @node: (in): A #GbTreeNode.
+ *
+ * Sets the root node of the #GbTree widget. This is used to build
+ * the items within the treeview. The item itself will not be added
+ * to the tree, but the direct children will be.
+ */
+static void
+gb_tree_set_root (GbTree     *tree,
+                  GbTreeNode *root)
+{
+  GbTreePrivate *priv;
+  GbTreeBuilder *builder;
+  gint i;
+
+  ENTRY;
+
+  g_return_if_fail (GB_IS_TREE (tree));
+
+  priv = tree->priv;
+
+  gtk_tree_store_clear (priv->store);
+  g_clear_object (&priv->root);
+
+  if (root)
+    {
+      priv->root = g_object_ref_sink (root);
+      _gb_tree_node_set_tree (root, tree);
+      for (i = 0; i < priv->builders->len; i++)
+        {
+          builder = g_ptr_array_index (priv->builders, i);
+          gb_tree_builder_build_node (builder, root);
+        }
+    }
+
+  EXIT;
+}
+
+void
+gb_tree_rebuild (GbTree *tree)
+{
+  GbTreePrivate *priv;
+  GbTreeNode *root;
+
+  g_return_if_fail (GB_IS_TREE (tree));
+
+  priv = tree->priv;
+
+  if ((root = priv->root ? g_object_ref (priv->root) : NULL))
+    {
+      gb_tree_set_root (tree, root);
+      g_object_unref (root);
+    }
+}
+
+/**
+ * pixbuf_func:
+ * @cell_layout: (in): A #GtkCellRendererPixbuf.
+ *
+ * Handle preparing a pixbuf cell renderer for drawing.
+ */
+static void
+pixbuf_func (GtkCellLayout   *cell_layout,
+             GtkCellRenderer *cell,
+             GtkTreeModel    *tree_model,
+             GtkTreeIter     *iter,
+             gpointer         data)
+{
+  const gchar *icon_name;
+  GbTreeNode *node;
+
+  gtk_tree_model_get (tree_model, iter, 0, &node, -1);
+  icon_name = node ? gb_tree_node_get_icon_name (node) : NULL;
+  g_object_set (cell, "icon-name", icon_name, NULL);
+  g_clear_object (&node);
+}
+
+/**
+ * text_func:
+ * @cell_layout: (in): A #GtkCellRendererText.
+ *
+ * Handle preparing a text cell renderer for drawing.
+ */
+static void
+text_func (GtkCellLayout   *cell_layout,
+           GtkCellRenderer *cell,
+           GtkTreeModel    *tree_model,
+           GtkTreeIter     *iter,
+           gpointer         data)
+{
+  gboolean use_markup = FALSE;
+  GbTreeNode *node = NULL;
+  gchar *text = NULL;
+
+  gtk_tree_model_get (tree_model, iter, 0, &node, -1);
+
+  if (node)
+    {
+      g_object_get (node,
+                    "text", &text,
+                    "use-markup", &use_markup,
+                    NULL);
+      g_object_set (cell,
+                    use_markup ? "markup" : "text", text,
+                    NULL);
+      g_free (text);
+    }
+}
+
+/**
+ * gb_tree_add:
+ * @tree: (in): A #GbTree.
+ * @node: (in): A #GbTreeNode.
+ * @child: (in): A #GbTreeNode.
+ * @prepend: (in): Should we prepend instead of append?
+ *
+ * Prepends or appends @child to @node within the #GbTree.
+ */
+static void
+gb_tree_add (GbTree     *tree,
+             GbTreeNode *node,
+             GbTreeNode *child,
+             gboolean    prepend)
+{
+  GbTreePrivate *priv;
+  GbTreeBuilder *builder;
+  GtkTreePath *path;
+  GtkTreeIter iter;
+  GtkTreeIter that;
+  gint i;
+
+  g_return_if_fail (GB_IS_TREE (tree));
+  g_return_if_fail (GB_IS_TREE_NODE (node));
+  g_return_if_fail (GB_IS_TREE_NODE (child));
+
+  priv = tree->priv;
+
+  g_object_set (child, "parent", node, NULL);
+
+  if ((path = gb_tree_node_get_path (node)))
+    {
+      gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->store), &iter, path);
+      if (prepend)
+        gtk_tree_store_prepend (priv->store, &that, &iter);
+      else
+        gtk_tree_store_append (priv->store, &that, &iter);
+      gtk_tree_store_set (priv->store, &that, 0, child, -1);
+      gtk_tree_path_free (path);
+    }
+  else
+    {
+      if (prepend)
+        gtk_tree_store_prepend (priv->store, &iter, NULL);
+      else
+        gtk_tree_store_append (priv->store, &iter, NULL);
+      gtk_tree_store_set (priv->store, &iter, 0, child, -1);
+    }
+
+  if (!priv->building)
+    {
+      for (i = 0; i < priv->builders->len; i++)
+        {
+          builder = g_ptr_array_index (priv->builders, i);
+          gb_tree_builder_build_node (builder, child);
+        }
+    }
+}
+
+/**
+ * gb_tree_append:
+ * @tree: (in): A #GbTree.
+ * @node: (in): A #GbTreeNode.
+ * @child: (in): A #GbTreeNode.
+ *
+ * Appends @child to @node within the #GbTree.
+ */
+void
+gb_tree_append (GbTree     *tree,
+                GbTreeNode *node,
+                GbTreeNode *child)
+{
+  gb_tree_add (tree, node, child, FALSE);
+}
+
+/**
+ * gb_tree_prepend:
+ * @tree: (in): A #GbTree.
+ * @node: (in): A #GbTreeNode.
+ * @child: (in): A #GbTreeNode.
+ *
+ * Appends @child to @node within the #GbTree.
+ */
+void
+gb_tree_prepend (GbTree     *tree,
+                 GbTreeNode *node,
+                 GbTreeNode *child)
+{
+  gb_tree_add (tree, node, child, TRUE);
+}
+
+/**
+ * gb_tree_row_activated:
+ * @tree_view: (in): A #GbTree.
+ * @path: (in): A #GtkTreePath.
+ *
+ * Handle the row being activated. Expand the row or collapse it.
+ */
+static void
+gb_tree_row_activated (GtkTreeView *tree_view,
+                       GtkTreePath *path)
+{
+  GbTreeBuilder *builder;
+  GbTreePrivate *priv;
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+  GbTreeNode *node = NULL;
+  gboolean handled = FALSE;
+  GbTree *tree = (GbTree *) tree_view;
+  gint i;
+
+  g_return_if_fail (GB_IS_TREE (tree));
+  g_return_if_fail (path != NULL);
+
+  priv = tree->priv;
+  model = GTK_TREE_MODEL (priv->store);
+
+  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 = gb_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 gboolean
+gb_tree_button_press_event (GbTree         *tree,
+                            GdkEventButton *button,
+                            gpointer        user_data)
+{
+  GtkAllocation alloc;
+  GbTreePrivate *priv;
+  GtkTreePath *tree_path = NULL;
+  GtkTreeIter iter;
+  GbTreeNode *node = NULL;
+  gint cell_y;
+
+  g_return_val_if_fail (GB_IS_TREE (tree), FALSE);
+
+  priv = tree->priv;
+
+  if ((button->type == GDK_BUTTON_PRESS) && (button->button == 3))
+    {
+      gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (tree),
+                                     button->x,
+                                     button->y,
+                                     &tree_path,
+                                     NULL,
+                                     NULL,
+                                     &cell_y);
+      if (!tree_path)
+        gb_tree_unselect (tree);
+      else
+        {
+          gtk_widget_get_allocation (GTK_WIDGET (tree), &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);
+          gb_tree_select (tree, node);
+          gb_tree_popup (tree, node, button,
+                         alloc.x + alloc.width,
+                         button->y - cell_y);
+          g_object_unref (node);
+          gtk_tree_path_free (tree_path);
+        }
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+/**
+ * gb_tree_finalize:
+ * @object: (in): A #GbTree.
+ *
+ * Finalizer for a #GbTree instance.  Frees any resources held by
+ * the instance.
+ */
+static void
+gb_tree_finalize (GObject *object)
+{
+  GbTreePrivate *priv = GB_TREE (object)->priv;
+
+  g_ptr_array_unref (priv->builders);
+  g_clear_object (&priv->menu);
+  g_clear_object (&priv->store);
+  g_clear_object (&priv->root);
+
+  G_OBJECT_CLASS (gb_tree_parent_class)->finalize (object);
+}
+
+/**
+ * gb_tree_get_property:
+ * @object: (in): A #GObject.
+ * @prop_id: (in): The property identifier.
+ * @value: (out): The given property.
+ * @pspec: (in): A #ParamSpec.
+ *
+ * Get a given #GObject property.
+ */
+static void
+gb_tree_get_property (GObject    *object,
+                      guint       prop_id,
+                      GValue     *value,
+                      GParamSpec *pspec)
+{
+  GbTree *tree = GB_TREE (object);
+
+  switch (prop_id)
+    {
+    case PROP_ROOT:
+      g_value_set_object (value, tree->priv->root);
+      break;
+
+    case PROP_SELECTION:
+      g_value_set_object (value, tree->priv->selection);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+/**
+ * gb_tree_set_property:
+ * @object: (in): A #GObject.
+ * @prop_id: (in): The property identifier.
+ * @value: (in): The given property.
+ * @pspec: (in): A #ParamSpec.
+ *
+ * Set a given #GObject property.
+ */
+static void
+gb_tree_set_property (GObject      *object,
+                      guint         prop_id,
+                      const GValue *value,
+                      GParamSpec   *pspec)
+{
+  GbTree *tree = GB_TREE (object);
+
+  switch (prop_id)
+    {
+    case PROP_ROOT:
+      gb_tree_set_root (tree, g_value_get_object (value));
+      break;
+
+    case PROP_SELECTION:
+      gb_tree_select (tree, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+/**
+ * gb_tree_class_init:
+ * @klass: (in): A #GbTreeClass.
+ *
+ * Initializes the #GbTreeClass and prepares the vtable.
+ */
+static void
+gb_tree_class_init (GbTreeClass *klass)
+{
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS (klass);
+  object_class->finalize = gb_tree_finalize;
+  object_class->get_property = gb_tree_get_property;
+  object_class->set_property = gb_tree_set_property;
+  g_type_class_add_private (object_class, sizeof (GbTreePrivate));
+
+  gParamSpecs[PROP_ROOT] =
+    g_param_spec_object ("root",
+                         _ ("Root"),
+                         _ ("The root object of the tree."),
+                         GB_TYPE_TREE_NODE,
+                         G_PARAM_READWRITE);
+  g_object_class_install_property (object_class, PROP_ROOT,
+                                   gParamSpecs[PROP_ROOT]);
+
+  gParamSpecs[PROP_SELECTION] =
+    g_param_spec_object ("selection",
+                         _ ("Selection"),
+                         _ ("The node selection."),
+                         GB_TYPE_TREE_NODE,
+                         G_PARAM_READWRITE);
+  g_object_class_install_property (object_class, PROP_SELECTION,
+                                   gParamSpecs[PROP_SELECTION]);
+}
+
+/**
+ * gb_tree_init:
+ * @tree: (in): A #GbTree.
+ *
+ * Initializes the newly created #GbTree instance.
+ */
+static void
+gb_tree_init (GbTree *tree)
+{
+  GtkTreeSelection *selection;
+  GtkCellRenderer *cell;
+  GtkCellLayout *column;
+
+  tree->priv = gb_tree_get_instance_private (tree);
+
+  tree->priv->builders = g_ptr_array_new ();
+  g_ptr_array_set_free_func (tree->priv->builders, g_object_unref);
+  tree->priv->store = gtk_tree_store_new (1, GB_TYPE_TREE_NODE);
+
+  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
+  g_signal_connect_swapped (selection, "changed",
+                            G_CALLBACK (gb_tree_selection_changed),
+                            tree);
+
+  column = g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
+                         "title", "Node",
+                         NULL);
+
+  cell = g_object_new (GTK_TYPE_CELL_RENDERER_PIXBUF, NULL);
+  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_END,
+                       NULL);
+  gtk_cell_layout_pack_start (column, cell, TRUE);
+  gtk_cell_layout_set_cell_data_func (column, cell, text_func, NULL, NULL);
+
+  gtk_tree_view_append_column (GTK_TREE_VIEW (tree),
+                               GTK_TREE_VIEW_COLUMN (column));
+
+  gtk_tree_view_set_model (GTK_TREE_VIEW (tree),
+                           GTK_TREE_MODEL (tree->priv->store));
+
+  g_signal_connect (tree, "row-activated",
+                    G_CALLBACK (gb_tree_row_activated),
+                    NULL);
+  g_signal_connect (tree, "button-press-event",
+                    G_CALLBACK (gb_tree_button_press_event),
+                    NULL);
+}
diff --git a/src/tree/gb-tree.h b/src/tree/gb-tree.h
new file mode 100644
index 0000000..1f75ab4
--- /dev/null
+++ b/src/tree/gb-tree.h
@@ -0,0 +1,74 @@
+/* gb-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 GB_TREE_H
+#define GB_TREE_H
+
+#include <gtk/gtk.h>
+
+#include "gb-tree-builder.h"
+#include "gb-tree-node.h"
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_TREE            (gb_tree_get_type())
+#define GB_TREE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GB_TYPE_TREE, GbTree))
+#define GB_TREE_CONST(obj)      (G_TYPE_CHECK_INSTANCE_CAST ((obj), GB_TYPE_TREE, GbTree const))
+#define GB_TREE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  GB_TYPE_TREE, GbTreeClass))
+#define GB_IS_TREE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GB_TYPE_TREE))
+#define GB_IS_TREE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  GB_TYPE_TREE))
+#define GB_TREE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  GB_TYPE_TREE, GbTreeClass))
+
+typedef struct _GbTree        GbTree;
+typedef struct _GbTreeClass   GbTreeClass;
+typedef struct _GbTreePrivate GbTreePrivate;
+
+struct _GbTree
+{
+       GtkTreeView parent;
+
+       /*< private >*/
+       GbTreePrivate *priv;
+};
+
+struct _GbTreeClass
+{
+       GtkTreeViewClass parent_class;
+};
+
+void          gb_tree_add_builder    (GbTree        *tree,
+                                      GbTreeBuilder *builder);
+GtkUIManager *gb_tree_get_menu_ui    (GbTree        *tree);
+GtkTreePath  *gb_tree_get_path       (GbTree        *tree,
+                                      GList         *list);
+GbTreeNode   *gb_tree_get_selected   (GbTree        *tree);
+GType         gb_tree_get_type       (void) G_GNUC_CONST;
+void          gb_tree_rebuild        (GbTree        *tree);
+void          gb_tree_remove_builder (GbTree        *tree,
+                                      GbTreeBuilder *builder);
+void          gb_tree_append         (GbTree        *tree,
+                                      GbTreeNode    *node,
+                                      GbTreeNode    *child);
+void          gb_tree_prepend        (GbTree        *tree,
+                                      GbTreeNode    *node,
+                                      GbTreeNode    *child);
+GbTree       *gb_tree_node_get_tree  (GbTreeNode    *node);
+
+G_END_DECLS
+
+#endif /* GB_TREE_H */


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