aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris Toshok <toshok@helixcode.com>2000-06-09 07:16:22 +0800
committerChris Toshok <toshok@src.gnome.org>2000-06-09 07:16:22 +0800
commite581631a9ce0803ba629ac67cd8934a6e23753ae (patch)
tree16fd9ebf869dd9fd82d265ce5a60a4ea7aeba652
parentf856d999c5cade657d045ea22b1fb64be481829d (diff)
downloadgsoc2013-evolution-e581631a9ce0803ba629ac67cd8934a6e23753ae.tar
gsoc2013-evolution-e581631a9ce0803ba629ac67cd8934a6e23753ae.tar.gz
gsoc2013-evolution-e581631a9ce0803ba629ac67cd8934a6e23753ae.tar.bz2
gsoc2013-evolution-e581631a9ce0803ba629ac67cd8934a6e23753ae.tar.lz
gsoc2013-evolution-e581631a9ce0803ba629ac67cd8934a6e23753ae.tar.xz
gsoc2013-evolution-e581631a9ce0803ba629ac67cd8934a6e23753ae.tar.zst
gsoc2013-evolution-e581631a9ce0803ba629ac67cd8934a6e23753ae.zip
New files. A tree model using a GNode structure to store it's info.
2000-06-08 Chris Toshok <toshok@helixcode.com> * e-tree-gnode.c, e-tree-gnode.h: New files. A tree model using a GNode structure to store it's info. * e-tree-model.c, e-tree-model.h: New files. A proxy model sitting between a table model and the real tree model (of which ETreeGNode is an example). * e-cell-tree.c, e-cell-tree.h: New files. A cell renderer capable of wrapping up a subcell and drawing the tree controls for expanding/collapsing trees. * tree-expanded.xpm, tree-unexpanded.xpm: New files. the standard + and - icons. * e-tree-example-1.c: New file, giving a (pretty poor :) example of using ETreeGNode. * Makefile.am: at the tree stuff to the build, and build tree-example-1. * .cvsignore: ignore tree-example-1. svn path=/trunk/; revision=3483
-rw-r--r--widgets/e-table/.cvsignore1
-rw-r--r--widgets/e-table/ChangeLog24
-rw-r--r--widgets/e-table/Makefile.am21
-rw-r--r--widgets/e-table/e-cell-tree.c385
-rw-r--r--widgets/e-table/e-cell-tree.h42
-rw-r--r--widgets/e-table/e-tree-example-1.c243
-rw-r--r--widgets/e-table/e-tree-gnode.c210
-rw-r--r--widgets/e-table/e-tree-gnode.h37
-rw-r--r--widgets/e-table/e-tree-model.c316
-rw-r--r--widgets/e-table/e-tree-model.h72
-rw-r--r--widgets/e-table/tree-expanded.xpm22
-rw-r--r--widgets/e-table/tree-unexpanded.xpm22
-rw-r--r--widgets/table/.cvsignore1
-rw-r--r--widgets/table/e-cell-tree.c385
-rw-r--r--widgets/table/e-cell-tree.h42
-rw-r--r--widgets/table/e-tree-example-1.c243
-rw-r--r--widgets/table/e-tree-gnode.c210
-rw-r--r--widgets/table/e-tree-gnode.h37
-rw-r--r--widgets/table/e-tree-model.c316
-rw-r--r--widgets/table/e-tree-model.h72
-rw-r--r--widgets/table/tree-expanded.xpm22
-rw-r--r--widgets/table/tree-unexpanded.xpm22
22 files changed, 2743 insertions, 2 deletions
diff --git a/widgets/e-table/.cvsignore b/widgets/e-table/.cvsignore
index 1c61242460..ecfa617948 100644
--- a/widgets/e-table/.cvsignore
+++ b/widgets/e-table/.cvsignore
@@ -9,3 +9,4 @@ table-test
table-example-1
table-example-2
table-size-test
+tree-example-1
diff --git a/widgets/e-table/ChangeLog b/widgets/e-table/ChangeLog
index ba7b064f65..5f6000a4eb 100644
--- a/widgets/e-table/ChangeLog
+++ b/widgets/e-table/ChangeLog
@@ -1,3 +1,27 @@
+2000-06-08 Chris Toshok <toshok@helixcode.com>
+
+ * e-tree-gnode.c, e-tree-gnode.h: New files. A tree model using a
+ GNode structure to store it's info.
+
+ * e-tree-model.c, e-tree-model.h: New files. A proxy model
+ sitting between a table model and the real tree model (of which
+ ETreeGNode is an example).
+
+ * e-cell-tree.c, e-cell-tree.h: New files. A cell renderer
+ capable of wrapping up a subcell and drawing the tree controls for
+ expanding/collapsing trees.
+
+ * tree-expanded.xpm, tree-unexpanded.xpm: New files. the standard
+ + and - icons.
+
+ * e-tree-example-1.c: New file, giving a (pretty poor :) example
+ of using ETreeGNode.
+
+ * Makefile.am: at the tree stuff to the build, and build
+ tree-example-1.
+
+ * .cvsignore: ignore tree-example-1.
+
2000-06-08 Christopher James Lahey <clahey@helixcode.com>
* The field chooser works now.
diff --git a/widgets/e-table/Makefile.am b/widgets/e-table/Makefile.am
index bb0cc97edb..8048435753 100644
--- a/widgets/e-table/Makefile.am
+++ b/widgets/e-table/Makefile.am
@@ -31,6 +31,8 @@ libetable_a_SOURCES = \
e-cell-text.h \
e-cell-toggle.c \
e-cell-toggle.h \
+ e-cell-tree.c \
+ e-cell-tree.h \
e-table.c \
e-table.h \
e-table-col-dnd.h \
@@ -72,10 +74,14 @@ libetable_a_SOURCES = \
e-table-subset-variable.c \
e-table-subset-variable.h \
e-table-text-model.c \
- e-table-text-model.h
+ e-table-text-model.h \
+ e-tree-gnode.c \
+ e-tree-gnode.h \
+ e-tree-model.c \
+ e-tree-model.h
noinst_PROGRAMS = \
- table-test table-example-1 table-example-2 table-size-test
+ table-test table-example-1 table-example-2 table-size-test tree-example-1
table_test_SOURCES = \
test-table.c \
@@ -125,6 +131,17 @@ table_example_2_LDADD = \
table_example_2_LDFLAGS = `gnome-config --libs gdk_pixbuf`
+tree_example_1_SOURCES = \
+ e-tree-example-1.c
+
+tree_example_1_LDFLAGS = `gnome-config --libs gdk_pixbuf`
+
+tree_example_1_LDADD = \
+ libetable.a \
+ $(EXTRA_GNOME_LIBS) \
+ $(top_builddir)/widgets/e-text/libetext.a \
+ $(top_builddir)/e-util/libeutil.la
+
icons = \
arrow-down.xpm \
arrow-up.xpm \
diff --git a/widgets/e-table/e-cell-tree.c b/widgets/e-table/e-cell-tree.c
new file mode 100644
index 0000000000..9f40bd0eae
--- /dev/null
+++ b/widgets/e-table/e-cell-tree.c
@@ -0,0 +1,385 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* e-cell-tree.c - Tree cell renderer
+ * Copyright (C) 2000 Helix Code, Inc.
+ *
+ * Author: Chris Toshok <toshok@helixcode.com>
+ *
+ * A majority of code taken from:
+ *
+ * the ECellText renderer.
+ *
+ * Copyright (C) 1998 The Free Software Foundation
+ *
+ */
+
+#include <config.h>
+#include <gtk/gtkenums.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtkinvisible.h>
+#include <gtk/gtksignal.h>
+#include <gdk/gdkkeysyms.h>
+#include <libgnomeui/gnome-canvas.h>
+#include <stdio.h>
+#include "e-table-sorted-variable.h"
+#include "e-tree-model.h"
+#include "e-cell-tree.h"
+#include "e-util/e-util.h"
+#include "e-table-item.h"
+
+#include <gdk/gdkx.h> /* for BlackPixel */
+#include <ctype.h>
+#include <math.h>
+
+#include "tree-expanded.xpm"
+#include "tree-unexpanded.xpm"
+
+#define PARENT_TYPE e_cell_get_type ()
+
+typedef struct {
+ ECellView cell_view;
+ ECellView *subcell_view;
+ GdkGC *gc;
+
+ GnomeCanvas *canvas;
+
+} ECellTreeView;
+
+static ECellClass *parent_class;
+
+static GdkPixbuf *tree_expanded_pixbuf, *tree_unexpanded_pixbuf;
+
+static ETreePath*
+e_cell_tree_get_node (ETreeModel *tree_model, int row)
+{
+ return (ETreePath*)e_table_model_value_at (E_TABLE_MODEL(tree_model), -1, row);
+}
+
+static ETreeModel*
+e_cell_tree_get_tree_model (ETableModel *table_model, int row)
+{
+ return (ETreeModel*)e_table_model_value_at (table_model, -2, row);
+}
+
+/*
+ * ECell::new_view method
+ */
+static ECellView *
+ect_new_view (ECell *ecell, ETableModel *table_model, void *e_table_item_view)
+{
+ ECellTree *ect = E_CELL_TREE (ecell);
+ ECellTreeView *tree_view = g_new0 (ECellTreeView, 1);
+ GnomeCanvas *canvas = GNOME_CANVAS_ITEM (e_table_item_view)->canvas;
+
+ tree_view->cell_view.ecell = ecell;
+ tree_view->cell_view.e_table_model = table_model;
+ tree_view->cell_view.e_table_item_view = e_table_item_view;
+
+ /* create our subcell view */
+ tree_view->subcell_view = e_cell_new_view (ect->subcell, table_model, e_table_item_view /* XXX */);
+
+ tree_view->canvas = canvas;
+
+ return (ECellView *)tree_view;
+}
+
+/*
+ * ECell::kill_view method
+ */
+static void
+ect_kill_view (ECellView *ecv)
+{
+ ECellTreeView *tree_view = (ECellTreeView *) ecv;
+
+ /* kill our subcell view */
+ e_cell_kill_view (tree_view->subcell_view);
+
+ g_free (tree_view);
+}
+
+/*
+ * ECell::realize method
+ */
+static void
+ect_realize (ECellView *ecell_view)
+{
+ ECellTreeView *tree_view = (ECellTreeView *) ecell_view;
+
+ /* realize our subcell view */
+ e_cell_realize (tree_view->subcell_view);
+
+ tree_view->gc = gdk_gc_new (GTK_WIDGET (tree_view->canvas)->window);
+
+ gdk_gc_set_line_attributes (tree_view->gc, 1,
+ GDK_LINE_ON_OFF_DASH, None, None);
+ gdk_gc_set_dashes (tree_view->gc, 0, "\1\1", 2);
+
+ if (parent_class->realize)
+ (* parent_class->realize) (ecell_view);
+}
+
+/*
+ * ECell::unrealize method
+ */
+static void
+ect_unrealize (ECellView *ecv)
+{
+ ECellTreeView *tree_view = (ECellTreeView *) ecv;
+
+ /* unrealize our subcell view. */
+ e_cell_unrealize (tree_view->subcell_view);
+
+ gdk_gc_unref (tree_view->gc);
+ tree_view->gc = NULL;
+
+ if (parent_class->unrealize)
+ (* parent_class->unrealize) (ecv);
+}
+
+#define INDENT_AMOUNT 16
+/*
+ * ECell::draw method
+ */
+static void
+ect_draw (ECellView *ecell_view, GdkDrawable *drawable,
+ int model_col, int view_col, int row, gboolean selected,
+ int x1, int y1, int x2, int y2)
+{
+ ECellTreeView *tree_view = (ECellTreeView *)ecell_view;
+ ETreeModel *tree_model = e_cell_tree_get_tree_model(ecell_view->e_table_model, row);
+ ETreePath *node;
+ GdkRectangle rect, *clip_rect;
+ GtkWidget *canvas = GTK_WIDGET (tree_view->canvas);
+ GdkGC *fg_gc = canvas->style->fg_gc[GTK_STATE_ACTIVE];
+ GdkColor *background, *foreground;
+
+ int offset, subcell_offset;
+ gboolean expanded, expandable;
+
+ /* only draw the tree effects if we're the active sort */
+ if (/* XXX */ TRUE) {
+ /*
+ * need to get the following things from the model
+ * 1. depth of item.
+ * 2. whether or not it has any children.
+ * 3. whether the item is a toplevel item.
+ * 3. ... anything else?
+ */
+ node = e_cell_tree_get_node (tree_model, row);
+
+ offset = (e_tree_model_node_depth (tree_model, node) + 1) * INDENT_AMOUNT;
+ expandable = e_tree_model_node_is_expandable (tree_model, node);
+ expanded = e_tree_model_node_is_expanded (tree_model, node);
+ subcell_offset = offset;
+
+ /*
+ * Be a nice citizen: clip to the region we are supposed to draw on
+ */
+ rect.x = x1;
+ rect.y = y1;
+ rect.width = offset;
+ rect.height = y2 - y1;
+
+ gdk_gc_set_clip_rectangle (tree_view->gc, &rect);
+ gdk_gc_set_clip_rectangle (fg_gc, &rect);
+ clip_rect = &rect;
+
+ if (selected){
+ background = &canvas->style->bg [GTK_STATE_SELECTED];
+ foreground = &canvas->style->text [GTK_STATE_SELECTED];
+ } else {
+ background = &canvas->style->base [GTK_STATE_NORMAL];
+ foreground = &canvas->style->text [GTK_STATE_NORMAL];
+ }
+ gdk_gc_set_foreground (tree_view->gc, background);
+ gdk_draw_rectangle (drawable, tree_view->gc, TRUE,
+ rect.x, rect.y, rect.width, rect.height);
+ gdk_gc_set_foreground (tree_view->gc, foreground);
+
+ if (E_CELL_TREE(tree_view->cell_view.ecell)->draw_lines) {
+ /* draw our lines */
+ gdk_draw_line (drawable, tree_view->gc,
+ rect.x + offset - INDENT_AMOUNT / 2,
+ rect.y,
+ rect.x + offset - INDENT_AMOUNT / 2,
+ (e_tree_model_node_get_next (tree_model, node)
+ ? rect.y + rect.height
+ : rect.y + rect.height / 2));
+
+ gdk_draw_line (drawable, tree_view->gc,
+ rect.x + offset - INDENT_AMOUNT / 2 + 1,
+ rect.y + rect.height / 2,
+ rect.x + offset,
+ rect.y + rect.height / 2);
+
+ /* now traverse back up to the root of the tree, checking at
+ each level if the node has siblings, and drawing the
+ correct vertical pipe for it's configuration. */
+ node = e_tree_model_node_get_parent (tree_model, node);
+ offset -= INDENT_AMOUNT;
+ while (node && ! e_tree_model_node_is_root (tree_model, node)) {
+ if (e_tree_model_node_get_next(tree_model, node)) {
+ gdk_draw_line (drawable, tree_view->gc,
+ rect.x + offset - INDENT_AMOUNT / 2,
+ rect.y,
+ rect.x + offset - INDENT_AMOUNT / 2,
+ rect.y + rect.height);
+ }
+ node = e_tree_model_node_get_parent (tree_model, node);
+ offset -= INDENT_AMOUNT;
+ }
+ }
+
+ /* now draw our icon if we're expandable */
+ if (expandable) {
+ GdkPixbuf *image = expanded ? tree_expanded_pixbuf : tree_unexpanded_pixbuf;
+ int width, height;
+
+ width = gdk_pixbuf_get_width(image);
+ height = gdk_pixbuf_get_height(image);
+
+ gdk_pixbuf_render_to_drawable_alpha (image,
+ drawable,
+ 0, 0,
+ x1 + subcell_offset - INDENT_AMOUNT / 2 - width / 2,
+ y1 + (y2 - y1) / 2 - height / 2,
+ width, height,
+ GDK_PIXBUF_ALPHA_BILEVEL,
+ 128,
+ GDK_RGB_DITHER_NORMAL,
+ width, 0);
+ }
+ }
+
+ /* Now cause our subcell to draw its contents, shifted by
+ subcell_offset pixels */
+ e_cell_draw (tree_view->subcell_view, drawable,
+ model_col, view_col, row, selected,
+ x1 + subcell_offset, y1, x2 + subcell_offset, y2);
+}
+
+/*
+ * ECell::event method
+ */
+static gint
+ect_event (ECellView *ecell_view, GdkEvent *event, int model_col, int view_col, int row)
+{
+ ECellTreeView *tree_view = (ECellTreeView *) ecell_view;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ case GDK_BUTTON_RELEASE: {
+ /* if the event happened in our area of control (and
+ we care about it), handle it. */
+ ETreeModel *tree_model = e_cell_tree_get_tree_model (ecell_view->e_table_model, row);
+ ETreePath *node = e_cell_tree_get_node (tree_model, row);
+ int offset = (e_tree_model_node_depth (tree_model, node) + 1) * INDENT_AMOUNT;
+
+ /* only activate the tree control if the click/release happens in the icon's area. */
+ if (event->button.x > (offset - INDENT_AMOUNT) && event->button.x < offset) {
+ if (e_tree_model_node_is_expandable (tree_model, node)) {
+ e_tree_model_node_set_expanded (tree_model,
+ node,
+ !e_tree_model_node_is_expanded(tree_model, node));
+ }
+ return TRUE;
+ }
+ }
+ default:
+ /* otherwise, pass it off to our subcell_view */
+ e_cell_event(tree_view->subcell_view, event, model_col, view_col, row);
+ return TRUE;
+ }
+}
+
+/*
+ * ECell::height method
+ */
+static int
+ect_height (ECellView *ecell_view, int model_col, int view_col, int row)
+{
+ ECellTreeView *tree_view = (ECellTreeView *) ecell_view;
+
+ return e_cell_height (tree_view->subcell_view, model_col, view_col, row);
+}
+
+/*
+ * ECellView::enter_edit method
+ */
+static void *
+ect_enter_edit (ECellView *ecell_view, int model_col, int view_col, int row)
+{
+ return NULL;
+}
+
+/*
+ * ECellView::leave_edit method
+ */
+static void
+ect_leave_edit (ECellView *ecell_view, int model_col, int view_col, int row, void *edit_context)
+{
+}
+
+/*
+ * GtkObject::destroy method
+ */
+static void
+ect_destroy (GtkObject *object)
+{
+ ECellTree *ect = E_CELL_TREE (object);
+
+ /* destroy our subcell */
+ gtk_object_destroy (GTK_OBJECT (ect->subcell));
+
+ GTK_OBJECT_CLASS (parent_class)->destroy (object);
+}
+
+static void
+e_cell_tree_class_init (GtkObjectClass *object_class)
+{
+ ECellClass *ecc = (ECellClass *) object_class;
+
+ object_class->destroy = ect_destroy;
+
+ ecc->new_view = ect_new_view;
+ ecc->kill_view = ect_kill_view;
+ ecc->realize = ect_realize;
+ ecc->unrealize = ect_unrealize;
+ ecc->draw = ect_draw;
+ ecc->event = ect_event;
+ ecc->height = ect_height;
+ ecc->enter_edit = ect_enter_edit;
+ ecc->leave_edit = ect_leave_edit;
+
+ parent_class = gtk_type_class (PARENT_TYPE);
+
+ /*
+ * Create our pixbuf for expanding/unexpanding
+ */
+ tree_expanded_pixbuf = gdk_pixbuf_new_from_xpm_data(tree_expanded_xpm);
+ tree_unexpanded_pixbuf = gdk_pixbuf_new_from_xpm_data(tree_unexpanded_xpm);
+}
+
+E_MAKE_TYPE(e_cell_tree, "ECellTree", ECellTree, e_cell_tree_class_init, NULL, PARENT_TYPE);
+
+void
+e_cell_tree_construct (ECellTree *ect,
+ gboolean draw_lines,
+ ECell *subcell)
+{
+ ect->subcell = subcell;
+ ect->draw_lines = draw_lines;
+}
+
+
+ECell *
+e_cell_tree_new (ETableModel *etm,
+ gboolean draw_lines,
+ ECell *subcell)
+{
+ ECellTree *ect = gtk_type_new (e_cell_tree_get_type ());
+
+ e_cell_tree_construct (ect, draw_lines, subcell);
+
+ return (ECell *) ect;
+}
diff --git a/widgets/e-table/e-cell-tree.h b/widgets/e-table/e-cell-tree.h
new file mode 100644
index 0000000000..6bd842b39e
--- /dev/null
+++ b/widgets/e-table/e-cell-tree.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* ECellTree - Tree item for e-table.
+ * Copyright (C) 2000 Helix Code, Inc.
+ * Author: Chris Toshok <toshok@helixcode.com>
+ *
+ */
+#ifndef _E_CELL_TREE_H_
+#define _E_CELL_TREE_H_
+
+#include <libgnomeui/gnome-canvas.h>
+#include "e-cell.h"
+
+#define E_CELL_TREE_TYPE (e_cell_tree_get_type ())
+#define E_CELL_TREE(o) (GTK_CHECK_CAST ((o), E_CELL_TREE_TYPE, ECellTree))
+#define E_CELL_TREE_CLASS(k) (GTK_CHECK_CLASS_CAST((k), E_CELL_TREE_TYPE, ECellTreeClass))
+#define E_IS_CELL_TREE(o) (GTK_CHECK_TYPE ((o), E_CELL_TREE_TYPE))
+#define E_IS_CELL_TREE_CLASS(k) (GTK_CHECK_CLASS_TYPE ((k), E_CELL_TREE_TYPE))
+
+typedef struct {
+ ECell parent;
+
+ gboolean draw_lines;
+
+ GdkPixbuf *expanded_image;
+ GdkPixbuf *unexpanded_image;
+
+ ECell *subcell;
+} ECellTree;
+
+typedef struct {
+ ECellClass parent_class;
+} ECellTreeClass;
+
+GtkType e_cell_tree_get_type (void);
+ECell *e_cell_tree_new (ETableModel *model, gboolean draw_lines,
+ ECell *subcell);
+void e_cell_tree_construct (ECellTree *ect, gboolean draw_lines,
+ ECell *subcell);
+
+#endif /* _E_CELL_TREE_H_ */
+
+
diff --git a/widgets/e-table/e-tree-example-1.c b/widgets/e-table/e-tree-example-1.c
new file mode 100644
index 0000000000..e5563a8071
--- /dev/null
+++ b/widgets/e-table/e-tree-example-1.c
@@ -0,0 +1,243 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* This code is GPL. */
+#include <stdio.h>
+#include <string.h>
+#include <gnome.h>
+#include "e-util/e-cursors.h"
+#include "e-tree-gnode.h"
+#include "e-table-header.h"
+#include "e-table-header-item.h"
+#include "e-table-item.h"
+#include "e-cell-text.h"
+#include "e-cell-tree.h"
+#include "e-cell-checkbox.h"
+#include "e-table.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#define ROWS 10
+#define COLS 4
+
+#define IMPORTANCE_COLUMN 4
+#define COLOR_COLUMN 5
+
+/*
+ * Here we define the initial layout of the table. This is an xml
+ * format that allows you to change the initial ordering of the
+ * columns or to do sorting or grouping initially. This specification
+ * shows all 5 columns, but moves the importance column nearer to the
+ * front. It also sorts by the "Full Name" column (ascending.)
+ * Sorting and grouping take the model column as their arguments
+ * (sorting is specified by the "column" argument to the leaf elemnt.
+ */
+
+#define INITIAL_SPEC "<ETableSpecification> \
+ <columns-shown> \
+ <column> 0 </column> \
+ <column> 4 </column> \
+ <column> 1 </column> \
+ <column> 2 </column> \
+ <column> 3 </column> \
+ </columns-shown> \
+ <grouping></grouping> \
+</ETableSpecification>"
+
+/*
+ * Virtual Column list:
+ * 0 Subject
+ * 1 Full Name
+ * 2 Email
+ * 3 Date
+ */
+char *headers [COLS] = {
+ "Subject",
+ "Full Name",
+ "Email",
+ "Date"
+};
+
+/*
+ * ETableSimple callbacks
+ * These are the callbacks that define the behavior of our custom model.
+ */
+
+/*
+ * Since our model is a constant size, we can just return its size in
+ * the column and row count fields.
+ */
+
+/* This function returns the number of columns in our ETableModel. */
+static int
+my_col_count (ETableModel *etc, void *data)
+{
+ return COLS;
+}
+
+/* This function returns the value at a particular point in our ETableModel. */
+static void *
+my_value_at (ETreeModel *etc, GNode *node, int col, void *data)
+{
+ switch (col) {
+ case 0: return "Re: Two things";
+ case 1: return "Chris Toshok";
+ case 2: return "toshok@helixcode.com";
+ case 3: return "Jun 07 2000";
+ default: return NULL;
+ }
+}
+
+/* This function sets the value at a particular point in our ETableModel. */
+static void
+my_set_value_at (ETableModel *etc, GNode *node, int col, const void *val, void *data)
+{
+}
+
+/* This function returns whether a particular cell is editable. */
+static gboolean
+my_is_editable (ETableModel *etc, GNode *node, int col, void *data)
+{
+ return FALSE;
+}
+
+/* This function duplicates the value passed to it. */
+static void *
+my_duplicate_value (ETableModel *etc, int col, const void *value, void *data)
+{
+ return g_strdup (value);
+}
+
+/* This function frees the value passed to it. */
+static void
+my_free_value (ETableModel *etc, int col, void *value, void *data)
+{
+ g_free (value);
+}
+
+/* This function is for when the model is unfrozen. This can mostly
+ be ignored for simple models. */
+static void
+my_thaw (ETableModel *etc, void *data)
+{
+}
+
+/* We create a window containing our new tree. */
+static void
+create_tree (void)
+{
+ GtkWidget *e_table, *window, *frame;
+ ECell *cell_left_just;
+ ECell *cell_tree;
+ ETableHeader *e_table_header;
+ int i, j;
+ ETreeModel *e_tree_model = NULL;
+ GNode *root_node;
+
+ /* create a root node with 5 children */
+ root_node = g_node_new (NULL);
+ for (i = 0; i < 5; i++){
+ GNode *n = g_node_insert (root_node, 0, g_node_new(NULL));
+ for (j = 0; j < 5; j ++) {
+ g_node_insert (n, 0, g_node_new(NULL));
+ }
+ }
+
+ /* Next we create our model. This uses the functions we defined
+ earlier. */
+ e_tree_model = e_tree_gnode_new (root_node,
+ my_value_at,
+ NULL);
+
+ /*
+ * Next we create a header. The ETableHeader is used in two
+ * different way. The first is the full_header. This is the
+ * list of possible columns in the view. The second use is
+ * completely internal. Many of the ETableHeader functions are
+ * for that purpose. The only functions we really need are
+ * e_table_header_new and e_table_header_add_col.
+ *
+ * First we create the header.
+ */
+ e_table_header = e_table_header_new ();
+
+ /*
+ * Next we have to build renderers for all of the columns.
+ * Since all our columns are text columns, we can simply use
+ * the same renderer over and over again. If we had different
+ * types of columns, we could use a different renderer for
+ * each column.
+ */
+ cell_left_just = e_cell_text_new (E_TABLE_MODEL(e_tree_model), NULL, GTK_JUSTIFY_LEFT);
+
+ /*
+ * This renderer is used for the tree column (the leftmost one), and
+ * has as its subcell renderer the text renderer. this means that
+ * text is displayed to the right of the tree pipes.
+ */
+ cell_tree = e_cell_tree_new (E_TABLE_MODEL(e_tree_model), TRUE, cell_left_just);
+
+ /*
+ * Next we create a column object for each view column and add
+ * them to the header. We don't create a column object for
+ * the importance column since it will not be shown.
+ */
+ for (i = 0; i < COLS; i++) {
+ /* Create the column. */
+ ETableCol *ecol = e_table_col_new (
+ i, headers [i],
+ 80, 20,
+ i == 0 ? cell_tree
+ : cell_left_just,
+ g_str_compare, TRUE);
+ /* Add it to the header. */
+ e_table_header_add_column (e_table_header, ecol, i);
+ }
+
+ /*
+ * Here we create a window for our new table. This window
+ * will get shown and the person will be able to test their
+ * item.
+ */
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+ /* This frame is simply to get a bevel around our table. */
+ frame = gtk_frame_new (NULL);
+
+ /*
+ * Here we create the table. We give it the three pieces of
+ * the table we've created, the header, the model, and the
+ * initial layout. It does the rest.
+ */
+ e_table = e_table_new (e_table_header, E_TABLE_MODEL(e_tree_model), INITIAL_SPEC);
+
+ if (!e_table) printf ("BAH!");
+
+ /* Build the gtk widget hierarchy. */
+ gtk_container_add (GTK_CONTAINER (frame), e_table);
+ gtk_container_add (GTK_CONTAINER (window), frame);
+
+ /* Size the initial window. */
+ gtk_widget_set_usize (window, 200, 200);
+
+ /* Show it all. */
+ gtk_widget_show_all (window);
+}
+
+/* This is the main function which just initializes gnome and call our create_tree function */
+
+int
+main (int argc, char *argv [])
+{
+ gnome_init ("TableExample", "TableExample", argc, argv);
+ e_cursors_init ();
+
+ gtk_widget_push_visual (gdk_rgb_get_visual ());
+ gtk_widget_push_colormap (gdk_rgb_get_cmap ());
+
+ create_tree ();
+
+ gtk_main ();
+
+ e_cursors_shutdown ();
+ return 0;
+}
+
diff --git a/widgets/e-table/e-tree-gnode.c b/widgets/e-table/e-tree-gnode.c
new file mode 100644
index 0000000000..ce53751b56
--- /dev/null
+++ b/widgets/e-table/e-tree-gnode.c
@@ -0,0 +1,210 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * e-tree-gnode.c: a Tree Model that reflects a GNode structure visually.
+ *
+ * Author:
+ * Chris Toshok (toshok@helixcode.com)
+ *
+ * (C) 2000 Helix Code, Inc.
+ */
+#include <config.h>
+#include <gtk/gtksignal.h>
+#include "e-util/e-util.h"
+#include "e-tree-gnode.h"
+
+#define PARENT_TYPE E_TREE_MODEL_TYPE
+
+static ETreePath *
+gnode_get_root (ETreeModel *etm)
+{
+ ETreeGNode *etg = E_TREE_GNODE (etm);
+ ETreePath *path = NULL;
+
+ path = g_list_append(path, etg->root);
+
+ return path;
+}
+
+static ETreePath *
+gnode_get_prev (ETreeModel *etm, ETreePath *node)
+{
+ ETreePath *prev_path;
+
+ GNode *gnode;
+ GNode *prev_sibling;
+
+ g_return_val_if_fail (node && node->data, NULL);
+
+ gnode = (GNode*)node->data;
+ prev_sibling = g_node_prev_sibling(gnode);
+
+ if (!prev_sibling)
+ return NULL;
+
+ prev_path = g_list_copy (node->next);
+ prev_path = g_list_prepend (prev_path, prev_sibling);
+ return prev_path;
+}
+
+static ETreePath *
+gnode_get_next (ETreeModel *etm, ETreePath *node)
+{
+ ETreePath *next_path;
+ GNode *gnode;
+ GNode *next_sibling;
+
+ g_return_val_if_fail (node && node->data, NULL);
+
+ gnode = (GNode*)node->data;
+ next_sibling = g_node_next_sibling(gnode);
+
+ if (!next_sibling)
+ return NULL;
+
+ next_path = g_list_copy (node->next);
+ next_path = g_list_prepend (next_path, next_sibling);
+ return next_path;
+}
+
+static void *
+gnode_value_at (ETreeModel *etm, ETreePath *node, int col)
+{
+ ETreeGNode *etg = E_TREE_GNODE (etm);
+ GNode *gnode;
+
+ g_return_val_if_fail (node && node->data, NULL);
+
+ gnode = (GNode*)node->data;
+
+ return etg->value_at (etm, gnode, col, etg->data);
+}
+
+static void
+gnode_set_value_at (ETreeModel *etm, ETreePath *node, int col, const void *val)
+{
+ ETreeGNode *etg = E_TREE_GNODE (etm);
+ GNode *gnode;
+
+ g_return_if_fail (node && node->data);
+
+ gnode = (GNode*)node->data;
+
+ /* XXX */
+}
+
+static gboolean
+gnode_is_editable (ETreeModel *etm, ETreePath *node, int col)
+{
+ ETreeGNode *etg = E_TREE_GNODE (etm);
+ GNode *gnode;
+
+ g_return_val_if_fail (node && node->data, FALSE);
+
+ gnode = (GNode*)node->data;
+
+ /* XXX */
+ return FALSE;
+}
+
+static guint
+gnode_get_children (ETreeModel *etm, ETreePath *node, ETreePath ***paths)
+{
+ ETreeGNode *etg = E_TREE_GNODE (etm);
+ GNode *gnode;
+ guint n_children;
+
+ g_return_val_if_fail (node && node->data, 0);
+
+ gnode = (GNode*)node->data;
+
+ n_children = g_node_n_children (gnode);
+
+ if (paths)
+ {
+ int i;
+ (*paths) = g_malloc (sizeof (ETreePath*) * n_children);
+ for (i = 0; i < n_children; i ++) {
+ (*paths)[i] = g_list_copy (node);
+ (*paths)[i] = g_list_prepend ((*paths)[i], g_node_nth_child (gnode, i));
+ }
+ }
+
+ return n_children;
+}
+
+static void
+gnode_release_paths (ETreeModel *etm, ETreePath **paths, guint num_paths)
+{
+ guint i;
+ g_return_if_fail (paths);
+
+ for (i = 0; i < num_paths; i ++)
+ g_list_free (paths[i]);
+ g_free (paths);
+}
+
+static gboolean
+gnode_is_expanded (ETreeModel *etm, ETreePath *node)
+{
+ ETreeGNode *etg = E_TREE_GNODE (etm);
+ GNode *gnode;
+
+ g_return_val_if_fail (node && node->data, FALSE);
+
+ gnode = (GNode*)node->data;
+
+ return (gboolean)gnode->data;
+}
+
+static void
+gnode_set_expanded (ETreeModel *etm, ETreePath *node, gboolean expanded)
+{
+ ETreeGNode *etg = E_TREE_GNODE (etm);
+ GNode *gnode;
+ int num_descendents;
+
+ g_return_if_fail (node && node->data);
+
+ gnode = (GNode*)node->data;
+
+ /* XXX */
+ gnode->data = (gpointer)expanded;
+
+ e_table_model_changed (E_TABLE_MODEL(etm));
+}
+
+static void
+e_tree_gnode_class_init (GtkObjectClass *object_class)
+{
+ ETreeModelClass *model_class = (ETreeModelClass *) object_class;
+
+ model_class->get_root = gnode_get_root;
+ model_class->get_next = gnode_get_next;
+ model_class->get_prev = gnode_get_prev;
+ model_class->value_at = gnode_value_at;
+ model_class->set_value_at = gnode_set_value_at;
+ model_class->is_editable = gnode_is_editable;
+ model_class->get_children = gnode_get_children;
+ model_class->release_paths = gnode_release_paths;
+ model_class->is_expanded = gnode_is_expanded;
+ model_class->set_expanded = gnode_set_expanded;
+}
+
+E_MAKE_TYPE(e_tree_gnode, "ETreeGNode", ETreeGNode, e_tree_gnode_class_init, NULL, PARENT_TYPE)
+
+ETreeModel *
+e_tree_gnode_new (GNode *root_node,
+ ETreeGNodeValueAtFn value_at,
+ void *data)
+{
+ ETreeGNode *etg;
+
+ etg = gtk_type_new (e_tree_gnode_get_type ());
+
+ etg->root = root_node;
+
+ etg->value_at = value_at;
+ etg->data = data;
+
+ return (ETreeModel*)etg;
+}
diff --git a/widgets/e-table/e-tree-gnode.h b/widgets/e-table/e-tree-gnode.h
new file mode 100644
index 0000000000..56e6b19b80
--- /dev/null
+++ b/widgets/e-table/e-tree-gnode.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+#ifndef _E_TREE_GNODE_H_
+#define _E_TREE_GNODE_H_
+
+#include "e-tree-model.h"
+
+#define E_TREE_GNODE_TYPE (e_tree_gnode_get_type ())
+#define E_TREE_GNODE(o) (GTK_CHECK_CAST ((o), E_TREE_GNODE_TYPE, ETreeGNode))
+#define E_TREE_GNODE_CLASS(k) (GTK_CHECK_CLASS_CAST((k), E_TREE_GNODE_TYPE, ETreeGNodeClass))
+#define E_IS_TREE_GNODE(o) (GTK_CHECK_TYPE ((o), E_TREE_GNODE_TYPE))
+#define E_IS_TREE_GNODE_CLASS(k) (GTK_CHECK_CLASS_TYPE ((k), E_TREE_GNODE_TYPE))
+
+typedef void *(*ETreeGNodeValueAtFn)(ETreeModel *model, GNode *node, int col, void *data);
+
+
+typedef struct {
+ ETreeModel parent;
+
+ GNode *root;
+
+ ETreeGNodeValueAtFn value_at;
+
+ void *data;
+} ETreeGNode;
+
+typedef struct {
+ ETreeModelClass parent_class;
+} ETreeGNodeClass;
+
+GtkType e_tree_gnode_get_type (void);
+
+ETreeModel *e_tree_gnode_new (GNode *tree,
+ ETreeGNodeValueAtFn value_at,
+ void *data);
+
+#endif /* _E_TREE_GNODE_H_ */
diff --git a/widgets/e-table/e-tree-model.c b/widgets/e-table/e-tree-model.c
new file mode 100644
index 0000000000..d67e59f3f5
--- /dev/null
+++ b/widgets/e-table/e-tree-model.c
@@ -0,0 +1,316 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * e-tree-model.c: a Tree Model
+ *
+ * Author:
+ * Chris Toshok (toshok@helixcode.com)
+ *
+ * Adapted from the gtree code and ETableModel.
+ *
+ * (C) 2000 Helix Code, Inc.
+ */
+#include <config.h>
+#include <gtk/gtksignal.h>
+#include "e-util/e-util.h"
+#include "e-tree-model.h"
+
+#define ETM_CLASS(e) ((ETreeModelClass *)((GtkObject *)e)->klass)
+
+#define PARENT_TYPE E_TABLE_MODEL_TYPE
+
+static ETableModel *e_tree_model_parent_class;
+
+/* virtual methods */
+
+static ETreePath*
+etree_get_root (ETreeModel *etm)
+{
+ /* shouldn't be called */
+ g_assert(0);
+ return NULL;
+}
+
+static void*
+etree_value_at (ETreeModel *etm, ETreePath* node, int col)
+{
+ /* shouldn't be called */
+ g_assert(0);
+ return NULL;
+}
+
+static void
+etree_set_value_at (ETreeModel *etm, ETreePath* node, int col, const void *val)
+{
+ /* shouldn't be called */
+ g_assert(0);
+}
+
+static gboolean
+etree_is_editable (ETreeModel *etm, ETreePath* node, int col)
+{
+ /* shouldn't be called */
+ g_assert(0);
+ return FALSE;
+}
+
+static gboolean
+etree_is_expanded (ETreeModel *etm, ETreePath* node)
+{
+ /* shouldn't be called */
+ g_assert(0);
+ return FALSE;
+}
+
+static guint
+etree_get_children (ETreeModel *etm, ETreePath* node, ETreePath ***paths)
+{
+ /* shouldn't be called */
+ g_assert(0);
+ return 0;
+}
+
+static void
+etree_release_paths (ETreeModel *etm, ETreePath **paths, guint num_paths)
+{
+ /* shouldn't be called */
+ g_assert(0);
+}
+
+static void
+etree_set_expanded (ETreeModel *etm, ETreePath* node, gboolean expanded)
+{
+ /* shouldn't be called */
+ g_assert(0);
+}
+
+static void
+etree_destroy (GtkObject *object)
+{
+}
+
+guint
+e_tree_model_node_num_visible_descendents (ETreeModel *etm, ETreePath *node)
+{
+ int count = 1;
+ if (e_tree_model_node_is_expanded (etm, node)) {
+ ETreePath **paths;
+ int i;
+ int num_paths;
+
+ num_paths = e_tree_model_node_get_children (etm, node, &paths);
+
+ for (i = 0; i < num_paths; i ++)
+ count += e_tree_model_node_num_visible_descendents(etm, paths[i]);
+
+ e_tree_model_release_paths (etm, paths, num_paths);
+ }
+
+ return count;
+}
+
+static int
+etable_row_count (ETableModel *etm)
+{
+ return e_tree_model_node_num_visible_descendents (E_TREE_MODEL (etm), e_tree_model_get_root (E_TREE_MODEL (etm)));
+}
+
+static void *
+etable_value_at (ETableModel *etm, int col, int row)
+{
+ ETreeModel *etree = E_TREE_MODEL(etm);
+ ETreeModelClass *et_class = ETM_CLASS(etm);
+ ETreePath* node = e_tree_model_node_at_row (etree, row);
+
+ g_return_val_if_fail (node, NULL);
+
+ if (col == -1)
+ return node;
+ else if (col == -2)
+ return etm;
+ else
+ return et_class->value_at (etree, node, col);
+}
+
+static void
+etable_set_value_at (ETableModel *etm, int col, int row, const void *val)
+{
+ ETreeModel *etree = E_TREE_MODEL(etm);
+ ETreeModelClass *et_class = ETM_CLASS(etm);
+ ETreePath* node = e_tree_model_node_at_row (etree, row);
+
+ g_return_if_fail (node);
+
+ et_class->set_value_at (etree, node, col, val);
+}
+
+static gboolean
+etable_is_cell_editable (ETableModel *etm, int col, int row)
+{
+ ETreeModel *etree = E_TREE_MODEL(etm);
+ ETreeModelClass *et_class = ETM_CLASS(etm);
+ ETreePath* node = e_tree_model_node_at_row (etree, row);
+
+ g_return_val_if_fail (node, FALSE);
+
+ return et_class->is_editable (etree, node, col);
+}
+
+static void
+e_tree_model_class_init (GtkObjectClass *klass)
+{
+ ETableModelClass *table_class = (ETableModelClass *) klass;
+ ETreeModelClass *tree_class = (ETreeModelClass *) klass;
+
+ e_tree_model_parent_class = gtk_type_class (PARENT_TYPE);
+
+ klass->destroy = etree_destroy;
+
+ table_class->row_count = etable_row_count;
+ table_class->value_at = etable_value_at;
+ table_class->set_value_at = etable_set_value_at;
+ table_class->is_cell_editable = etable_is_cell_editable;
+#if 0
+ table_class->duplicate_value = etable_duplicate_value;
+ table_class->free_value = etable_free_value;
+ table_class->initialize_value = etable_initialize_value;
+ table_class->value_is_empty = etable_value_is_empty;
+ table_class->thaw = etable_thaw;
+#endif
+
+ tree_class->get_root = etree_get_root;
+ tree_class->value_at = etree_value_at;
+ tree_class->set_value_at = etree_set_value_at;
+ tree_class->is_editable = etree_is_editable;
+
+ tree_class->get_children = etree_get_children;
+ tree_class->release_paths = etree_release_paths;
+ tree_class->is_expanded = etree_is_expanded;
+ tree_class->set_expanded = etree_set_expanded;
+}
+
+E_MAKE_TYPE(e_tree_model, "ETreeModel", ETreeModel, e_tree_model_class_init, NULL, PARENT_TYPE)
+
+/* signals */
+
+ETreeModel *
+e_tree_model_new ()
+{
+ ETreeModel *et;
+
+ et = gtk_type_new (e_tree_model_get_type ());
+
+ return et;
+}
+
+static ETreePath *
+e_tree_model_node_at_row_1 (ETreeModel *etree, int *row, ETreePath *node)
+{
+ ETreePath *ret = NULL;
+
+ if (*row == 0)
+ ret = node;
+ else if (e_tree_model_node_is_expanded (etree, node)) {
+ int num_children;
+ int i;
+ ETreePath **paths;
+
+ num_children = e_tree_model_node_get_children (etree, node, &paths);
+
+ for (i = 0; i < num_children; i ++) {
+ ETreePath *p;
+
+ (*row) --;
+
+ p = e_tree_model_node_at_row_1 (etree, row, paths[i]);
+
+ if (p) {
+ ret = p;
+ break;
+ }
+ }
+
+ /* XXX need to find why this release is causing problems */
+ /* e_tree_model_release_paths (etree, paths, num_children); */
+ }
+
+ return ret;
+}
+
+ETreePath *
+e_tree_model_get_root (ETreeModel *etree)
+{
+ return ETM_CLASS(etree)->get_root(etree);
+}
+
+ETreePath *
+e_tree_model_node_at_row (ETreeModel *etree, int row)
+{
+ /* XXX icky, perform a depth first search of the tree. we need this optimized sorely */
+ return e_tree_model_node_at_row_1 (etree, &row, ETM_CLASS(etree)->get_root(etree));
+}
+
+ETreePath *
+e_tree_model_node_get_next (ETreeModel *etree, ETreePath *node)
+{
+ return ETM_CLASS(etree)->get_next(etree, node);
+}
+
+ETreePath *
+e_tree_model_node_get_prev (ETreeModel *etree, ETreePath *node)
+{
+ return ETM_CLASS(etree)->get_prev(etree, node);
+}
+
+guint
+e_tree_model_node_depth (ETreeModel *etree, ETreePath *path)
+{
+ return g_list_length (path) - 1;
+}
+
+ETreePath *
+e_tree_model_node_get_parent (ETreeModel *etree, ETreePath *path)
+{
+ g_return_val_if_fail (path, NULL);
+
+ if (path->next == NULL)
+ return NULL;
+ else
+ return g_list_copy (path->next);
+}
+
+gboolean
+e_tree_model_node_is_root (ETreeModel *etree, ETreePath *path)
+{
+ return (e_tree_model_node_depth (etree, path) == 0);
+}
+
+gboolean
+e_tree_model_node_is_expandable (ETreeModel *etree, ETreePath *path)
+{
+ return (e_tree_model_node_get_children (etree, path, NULL) > 0);
+}
+
+gboolean
+e_tree_model_node_is_expanded (ETreeModel *etree, ETreePath *path)
+{
+ return ETM_CLASS(etree)->is_expanded (etree, path);
+}
+
+void
+e_tree_model_node_set_expanded (ETreeModel *etree, ETreePath *path, gboolean expanded)
+{
+ ETM_CLASS(etree)->set_expanded (etree, path, expanded);
+}
+
+guint
+e_tree_model_node_get_children (ETreeModel *etree, ETreePath *path, ETreePath ***paths)
+{
+ return ETM_CLASS(etree)->get_children (etree, path, paths);
+}
+
+void
+e_tree_model_release_paths (ETreeModel *etree, ETreePath **paths, guint num_paths)
+{
+ ETM_CLASS(etree)->release_paths (etree, paths, num_paths);
+}
+
diff --git a/widgets/e-table/e-tree-model.h b/widgets/e-table/e-tree-model.h
new file mode 100644
index 0000000000..6924f3ea0e
--- /dev/null
+++ b/widgets/e-table/e-tree-model.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+#ifndef _E_TREE_MODEL_H_
+#define _E_TREE_MODEL_H_
+
+#include "e-table-model.h"
+
+#define E_TREE_MODEL_TYPE (e_tree_model_get_type ())
+#define E_TREE_MODEL(o) (GTK_CHECK_CAST ((o), E_TREE_MODEL_TYPE, ETreeModel))
+#define E_TREE_MODEL_CLASS(k) (GTK_CHECK_CLASS_CAST((k), E_TREE_MODEL_TYPE, ETreeModelClass))
+#define E_IS_TREE_MODEL(o) (GTK_CHECK_TYPE ((o), E_TREE_MODEL_TYPE))
+#define E_IS_TREE_MODEL_CLASS(k) (GTK_CHECK_CLASS_TYPE ((k), E_TREE_MODEL_TYPE))
+
+typedef gpointer ETreePathItem;
+typedef GList ETreePath;
+
+typedef struct {
+ ETableModel base;
+
+ ETableModel *source;
+
+ ETreePath *root_node;
+
+ GArray *array;
+
+} ETreeModel;
+
+typedef struct {
+ ETableModelClass parent_class;
+
+ /*
+ * Virtual methods
+ */
+ ETreePath *(*get_root) (ETreeModel *etm);
+
+ ETreePath *(*get_next) (ETreeModel *etm, ETreePath* node);
+ ETreePath *(*get_prev) (ETreeModel *etm, ETreePath* node);
+
+ void *(*value_at) (ETreeModel *etm, ETreePath* node, int col);
+ void (*set_value_at) (ETreeModel *etm, ETreePath* node, int col, const void *val);
+ gboolean (*is_editable) (ETreeModel *etm, ETreePath* node, int col);
+
+ guint (*get_children) (ETreeModel *etm, ETreePath* node, ETreePath ***paths);
+ void (*release_paths) (ETreeModel *etm, ETreePath **paths, guint num_paths);
+ gboolean (*is_expanded) (ETreeModel *etm, ETreePath* node);
+ void (*set_expanded) (ETreeModel *etm, ETreePath* node, gboolean expanded);
+
+ /*
+ * Signals
+ */
+
+} ETreeModelClass;
+
+GtkType e_tree_model_get_type (void);
+
+ETreeModel *e_tree_model_new (void);
+
+/* operations on "nodes" in the tree */
+ETreePath * e_tree_model_get_root (ETreeModel *etree);
+ETreePath * e_tree_model_node_at_row (ETreeModel *etree, int row);
+guint e_tree_model_node_depth (ETreeModel *etree, ETreePath *path);
+ETreePath *e_tree_model_node_get_parent (ETreeModel *etree, ETreePath *path);
+ETreePath *e_tree_model_node_get_next (ETreeModel *etree, ETreePath *path);
+ETreePath *e_tree_model_node_get_prev (ETreeModel *etree, ETreePath *path);
+gboolean e_tree_model_node_is_root (ETreeModel *etree, ETreePath *path);
+
+gboolean e_tree_model_node_is_expandable (ETreeModel *etree, ETreePath *path);
+gboolean e_tree_model_node_is_expanded (ETreeModel *etree, ETreePath *path);
+guint e_tree_model_node_get_children (ETreeModel *etree, ETreePath *path, ETreePath ***paths);
+void e_tree_model_release_paths (ETreeModel *etree, ETreePath **paths, guint num_paths);
+guint e_tree_model_node_num_visible_descendents (ETreeModel *etm, ETreePath *node);
+
+#endif /* _E_TREE_MODEL_H */
diff --git a/widgets/e-table/tree-expanded.xpm b/widgets/e-table/tree-expanded.xpm
new file mode 100644
index 0000000000..fc748953eb
--- /dev/null
+++ b/widgets/e-table/tree-expanded.xpm
@@ -0,0 +1,22 @@
+/* XPM */
+static char * tree_expanded_xpm[] = {
+"16 16 3 1",
+" c None",
+". c #000000",
+"+ c #FFFFFF",
+" ",
+" ",
+" ",
+" ",
+" ......... ",
+" .+++++++. ",
+" .+++++++. ",
+" .+++++++. ",
+" .+.....+. ",
+" .+++++++. ",
+" .+++++++. ",
+" .+++++++. ",
+" ......... ",
+" ",
+" ",
+" "};
diff --git a/widgets/e-table/tree-unexpanded.xpm b/widgets/e-table/tree-unexpanded.xpm
new file mode 100644
index 0000000000..0dfb12a0a5
--- /dev/null
+++ b/widgets/e-table/tree-unexpanded.xpm
@@ -0,0 +1,22 @@
+/* XPM */
+static char * tree_unexpanded_xpm[] = {
+"16 16 3 1",
+" c None",
+". c #000000",
+"+ c #FFFFFF",
+" ",
+" ",
+" ",
+" ",
+" ......... ",
+" .+++++++. ",
+" .+++.+++. ",
+" .+++.+++. ",
+" .+.....+. ",
+" .+++.+++. ",
+" .+++.+++. ",
+" .+++++++. ",
+" ......... ",
+" ",
+" ",
+" "};
diff --git a/widgets/table/.cvsignore b/widgets/table/.cvsignore
index 1c61242460..ecfa617948 100644
--- a/widgets/table/.cvsignore
+++ b/widgets/table/.cvsignore
@@ -9,3 +9,4 @@ table-test
table-example-1
table-example-2
table-size-test
+tree-example-1
diff --git a/widgets/table/e-cell-tree.c b/widgets/table/e-cell-tree.c
new file mode 100644
index 0000000000..9f40bd0eae
--- /dev/null
+++ b/widgets/table/e-cell-tree.c
@@ -0,0 +1,385 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* e-cell-tree.c - Tree cell renderer
+ * Copyright (C) 2000 Helix Code, Inc.
+ *
+ * Author: Chris Toshok <toshok@helixcode.com>
+ *
+ * A majority of code taken from:
+ *
+ * the ECellText renderer.
+ *
+ * Copyright (C) 1998 The Free Software Foundation
+ *
+ */
+
+#include <config.h>
+#include <gtk/gtkenums.h>
+#include <gtk/gtkentry.h>
+#include <gtk/gtkwindow.h>
+#include <gtk/gtkinvisible.h>
+#include <gtk/gtksignal.h>
+#include <gdk/gdkkeysyms.h>
+#include <libgnomeui/gnome-canvas.h>
+#include <stdio.h>
+#include "e-table-sorted-variable.h"
+#include "e-tree-model.h"
+#include "e-cell-tree.h"
+#include "e-util/e-util.h"
+#include "e-table-item.h"
+
+#include <gdk/gdkx.h> /* for BlackPixel */
+#include <ctype.h>
+#include <math.h>
+
+#include "tree-expanded.xpm"
+#include "tree-unexpanded.xpm"
+
+#define PARENT_TYPE e_cell_get_type ()
+
+typedef struct {
+ ECellView cell_view;
+ ECellView *subcell_view;
+ GdkGC *gc;
+
+ GnomeCanvas *canvas;
+
+} ECellTreeView;
+
+static ECellClass *parent_class;
+
+static GdkPixbuf *tree_expanded_pixbuf, *tree_unexpanded_pixbuf;
+
+static ETreePath*
+e_cell_tree_get_node (ETreeModel *tree_model, int row)
+{
+ return (ETreePath*)e_table_model_value_at (E_TABLE_MODEL(tree_model), -1, row);
+}
+
+static ETreeModel*
+e_cell_tree_get_tree_model (ETableModel *table_model, int row)
+{
+ return (ETreeModel*)e_table_model_value_at (table_model, -2, row);
+}
+
+/*
+ * ECell::new_view method
+ */
+static ECellView *
+ect_new_view (ECell *ecell, ETableModel *table_model, void *e_table_item_view)
+{
+ ECellTree *ect = E_CELL_TREE (ecell);
+ ECellTreeView *tree_view = g_new0 (ECellTreeView, 1);
+ GnomeCanvas *canvas = GNOME_CANVAS_ITEM (e_table_item_view)->canvas;
+
+ tree_view->cell_view.ecell = ecell;
+ tree_view->cell_view.e_table_model = table_model;
+ tree_view->cell_view.e_table_item_view = e_table_item_view;
+
+ /* create our subcell view */
+ tree_view->subcell_view = e_cell_new_view (ect->subcell, table_model, e_table_item_view /* XXX */);
+
+ tree_view->canvas = canvas;
+
+ return (ECellView *)tree_view;
+}
+
+/*
+ * ECell::kill_view method
+ */
+static void
+ect_kill_view (ECellView *ecv)
+{
+ ECellTreeView *tree_view = (ECellTreeView *) ecv;
+
+ /* kill our subcell view */
+ e_cell_kill_view (tree_view->subcell_view);
+
+ g_free (tree_view);
+}
+
+/*
+ * ECell::realize method
+ */
+static void
+ect_realize (ECellView *ecell_view)
+{
+ ECellTreeView *tree_view = (ECellTreeView *) ecell_view;
+
+ /* realize our subcell view */
+ e_cell_realize (tree_view->subcell_view);
+
+ tree_view->gc = gdk_gc_new (GTK_WIDGET (tree_view->canvas)->window);
+
+ gdk_gc_set_line_attributes (tree_view->gc, 1,
+ GDK_LINE_ON_OFF_DASH, None, None);
+ gdk_gc_set_dashes (tree_view->gc, 0, "\1\1", 2);
+
+ if (parent_class->realize)
+ (* parent_class->realize) (ecell_view);
+}
+
+/*
+ * ECell::unrealize method
+ */
+static void
+ect_unrealize (ECellView *ecv)
+{
+ ECellTreeView *tree_view = (ECellTreeView *) ecv;
+
+ /* unrealize our subcell view. */
+ e_cell_unrealize (tree_view->subcell_view);
+
+ gdk_gc_unref (tree_view->gc);
+ tree_view->gc = NULL;
+
+ if (parent_class->unrealize)
+ (* parent_class->unrealize) (ecv);
+}
+
+#define INDENT_AMOUNT 16
+/*
+ * ECell::draw method
+ */
+static void
+ect_draw (ECellView *ecell_view, GdkDrawable *drawable,
+ int model_col, int view_col, int row, gboolean selected,
+ int x1, int y1, int x2, int y2)
+{
+ ECellTreeView *tree_view = (ECellTreeView *)ecell_view;
+ ETreeModel *tree_model = e_cell_tree_get_tree_model(ecell_view->e_table_model, row);
+ ETreePath *node;
+ GdkRectangle rect, *clip_rect;
+ GtkWidget *canvas = GTK_WIDGET (tree_view->canvas);
+ GdkGC *fg_gc = canvas->style->fg_gc[GTK_STATE_ACTIVE];
+ GdkColor *background, *foreground;
+
+ int offset, subcell_offset;
+ gboolean expanded, expandable;
+
+ /* only draw the tree effects if we're the active sort */
+ if (/* XXX */ TRUE) {
+ /*
+ * need to get the following things from the model
+ * 1. depth of item.
+ * 2. whether or not it has any children.
+ * 3. whether the item is a toplevel item.
+ * 3. ... anything else?
+ */
+ node = e_cell_tree_get_node (tree_model, row);
+
+ offset = (e_tree_model_node_depth (tree_model, node) + 1) * INDENT_AMOUNT;
+ expandable = e_tree_model_node_is_expandable (tree_model, node);
+ expanded = e_tree_model_node_is_expanded (tree_model, node);
+ subcell_offset = offset;
+
+ /*
+ * Be a nice citizen: clip to the region we are supposed to draw on
+ */
+ rect.x = x1;
+ rect.y = y1;
+ rect.width = offset;
+ rect.height = y2 - y1;
+
+ gdk_gc_set_clip_rectangle (tree_view->gc, &rect);
+ gdk_gc_set_clip_rectangle (fg_gc, &rect);
+ clip_rect = &rect;
+
+ if (selected){
+ background = &canvas->style->bg [GTK_STATE_SELECTED];
+ foreground = &canvas->style->text [GTK_STATE_SELECTED];
+ } else {
+ background = &canvas->style->base [GTK_STATE_NORMAL];
+ foreground = &canvas->style->text [GTK_STATE_NORMAL];
+ }
+ gdk_gc_set_foreground (tree_view->gc, background);
+ gdk_draw_rectangle (drawable, tree_view->gc, TRUE,
+ rect.x, rect.y, rect.width, rect.height);
+ gdk_gc_set_foreground (tree_view->gc, foreground);
+
+ if (E_CELL_TREE(tree_view->cell_view.ecell)->draw_lines) {
+ /* draw our lines */
+ gdk_draw_line (drawable, tree_view->gc,
+ rect.x + offset - INDENT_AMOUNT / 2,
+ rect.y,
+ rect.x + offset - INDENT_AMOUNT / 2,
+ (e_tree_model_node_get_next (tree_model, node)
+ ? rect.y + rect.height
+ : rect.y + rect.height / 2));
+
+ gdk_draw_line (drawable, tree_view->gc,
+ rect.x + offset - INDENT_AMOUNT / 2 + 1,
+ rect.y + rect.height / 2,
+ rect.x + offset,
+ rect.y + rect.height / 2);
+
+ /* now traverse back up to the root of the tree, checking at
+ each level if the node has siblings, and drawing the
+ correct vertical pipe for it's configuration. */
+ node = e_tree_model_node_get_parent (tree_model, node);
+ offset -= INDENT_AMOUNT;
+ while (node && ! e_tree_model_node_is_root (tree_model, node)) {
+ if (e_tree_model_node_get_next(tree_model, node)) {
+ gdk_draw_line (drawable, tree_view->gc,
+ rect.x + offset - INDENT_AMOUNT / 2,
+ rect.y,
+ rect.x + offset - INDENT_AMOUNT / 2,
+ rect.y + rect.height);
+ }
+ node = e_tree_model_node_get_parent (tree_model, node);
+ offset -= INDENT_AMOUNT;
+ }
+ }
+
+ /* now draw our icon if we're expandable */
+ if (expandable) {
+ GdkPixbuf *image = expanded ? tree_expanded_pixbuf : tree_unexpanded_pixbuf;
+ int width, height;
+
+ width = gdk_pixbuf_get_width(image);
+ height = gdk_pixbuf_get_height(image);
+
+ gdk_pixbuf_render_to_drawable_alpha (image,
+ drawable,
+ 0, 0,
+ x1 + subcell_offset - INDENT_AMOUNT / 2 - width / 2,
+ y1 + (y2 - y1) / 2 - height / 2,
+ width, height,
+ GDK_PIXBUF_ALPHA_BILEVEL,
+ 128,
+ GDK_RGB_DITHER_NORMAL,
+ width, 0);
+ }
+ }
+
+ /* Now cause our subcell to draw its contents, shifted by
+ subcell_offset pixels */
+ e_cell_draw (tree_view->subcell_view, drawable,
+ model_col, view_col, row, selected,
+ x1 + subcell_offset, y1, x2 + subcell_offset, y2);
+}
+
+/*
+ * ECell::event method
+ */
+static gint
+ect_event (ECellView *ecell_view, GdkEvent *event, int model_col, int view_col, int row)
+{
+ ECellTreeView *tree_view = (ECellTreeView *) ecell_view;
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ case GDK_BUTTON_RELEASE: {
+ /* if the event happened in our area of control (and
+ we care about it), handle it. */
+ ETreeModel *tree_model = e_cell_tree_get_tree_model (ecell_view->e_table_model, row);
+ ETreePath *node = e_cell_tree_get_node (tree_model, row);
+ int offset = (e_tree_model_node_depth (tree_model, node) + 1) * INDENT_AMOUNT;
+
+ /* only activate the tree control if the click/release happens in the icon's area. */
+ if (event->button.x > (offset - INDENT_AMOUNT) && event->button.x < offset) {
+ if (e_tree_model_node_is_expandable (tree_model, node)) {
+ e_tree_model_node_set_expanded (tree_model,
+ node,
+ !e_tree_model_node_is_expanded(tree_model, node));
+ }
+ return TRUE;
+ }
+ }
+ default:
+ /* otherwise, pass it off to our subcell_view */
+ e_cell_event(tree_view->subcell_view, event, model_col, view_col, row);
+ return TRUE;
+ }
+}
+
+/*
+ * ECell::height method
+ */
+static int
+ect_height (ECellView *ecell_view, int model_col, int view_col, int row)
+{
+ ECellTreeView *tree_view = (ECellTreeView *) ecell_view;
+
+ return e_cell_height (tree_view->subcell_view, model_col, view_col, row);
+}
+
+/*
+ * ECellView::enter_edit method
+ */
+static void *
+ect_enter_edit (ECellView *ecell_view, int model_col, int view_col, int row)
+{
+ return NULL;
+}
+
+/*
+ * ECellView::leave_edit method
+ */
+static void
+ect_leave_edit (ECellView *ecell_view, int model_col, int view_col, int row, void *edit_context)
+{
+}
+
+/*
+ * GtkObject::destroy method
+ */
+static void
+ect_destroy (GtkObject *object)
+{
+ ECellTree *ect = E_CELL_TREE (object);
+
+ /* destroy our subcell */
+ gtk_object_destroy (GTK_OBJECT (ect->subcell));
+
+ GTK_OBJECT_CLASS (parent_class)->destroy (object);
+}
+
+static void
+e_cell_tree_class_init (GtkObjectClass *object_class)
+{
+ ECellClass *ecc = (ECellClass *) object_class;
+
+ object_class->destroy = ect_destroy;
+
+ ecc->new_view = ect_new_view;
+ ecc->kill_view = ect_kill_view;
+ ecc->realize = ect_realize;
+ ecc->unrealize = ect_unrealize;
+ ecc->draw = ect_draw;
+ ecc->event = ect_event;
+ ecc->height = ect_height;
+ ecc->enter_edit = ect_enter_edit;
+ ecc->leave_edit = ect_leave_edit;
+
+ parent_class = gtk_type_class (PARENT_TYPE);
+
+ /*
+ * Create our pixbuf for expanding/unexpanding
+ */
+ tree_expanded_pixbuf = gdk_pixbuf_new_from_xpm_data(tree_expanded_xpm);
+ tree_unexpanded_pixbuf = gdk_pixbuf_new_from_xpm_data(tree_unexpanded_xpm);
+}
+
+E_MAKE_TYPE(e_cell_tree, "ECellTree", ECellTree, e_cell_tree_class_init, NULL, PARENT_TYPE);
+
+void
+e_cell_tree_construct (ECellTree *ect,
+ gboolean draw_lines,
+ ECell *subcell)
+{
+ ect->subcell = subcell;
+ ect->draw_lines = draw_lines;
+}
+
+
+ECell *
+e_cell_tree_new (ETableModel *etm,
+ gboolean draw_lines,
+ ECell *subcell)
+{
+ ECellTree *ect = gtk_type_new (e_cell_tree_get_type ());
+
+ e_cell_tree_construct (ect, draw_lines, subcell);
+
+ return (ECell *) ect;
+}
diff --git a/widgets/table/e-cell-tree.h b/widgets/table/e-cell-tree.h
new file mode 100644
index 0000000000..6bd842b39e
--- /dev/null
+++ b/widgets/table/e-cell-tree.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* ECellTree - Tree item for e-table.
+ * Copyright (C) 2000 Helix Code, Inc.
+ * Author: Chris Toshok <toshok@helixcode.com>
+ *
+ */
+#ifndef _E_CELL_TREE_H_
+#define _E_CELL_TREE_H_
+
+#include <libgnomeui/gnome-canvas.h>
+#include "e-cell.h"
+
+#define E_CELL_TREE_TYPE (e_cell_tree_get_type ())
+#define E_CELL_TREE(o) (GTK_CHECK_CAST ((o), E_CELL_TREE_TYPE, ECellTree))
+#define E_CELL_TREE_CLASS(k) (GTK_CHECK_CLASS_CAST((k), E_CELL_TREE_TYPE, ECellTreeClass))
+#define E_IS_CELL_TREE(o) (GTK_CHECK_TYPE ((o), E_CELL_TREE_TYPE))
+#define E_IS_CELL_TREE_CLASS(k) (GTK_CHECK_CLASS_TYPE ((k), E_CELL_TREE_TYPE))
+
+typedef struct {
+ ECell parent;
+
+ gboolean draw_lines;
+
+ GdkPixbuf *expanded_image;
+ GdkPixbuf *unexpanded_image;
+
+ ECell *subcell;
+} ECellTree;
+
+typedef struct {
+ ECellClass parent_class;
+} ECellTreeClass;
+
+GtkType e_cell_tree_get_type (void);
+ECell *e_cell_tree_new (ETableModel *model, gboolean draw_lines,
+ ECell *subcell);
+void e_cell_tree_construct (ECellTree *ect, gboolean draw_lines,
+ ECell *subcell);
+
+#endif /* _E_CELL_TREE_H_ */
+
+
diff --git a/widgets/table/e-tree-example-1.c b/widgets/table/e-tree-example-1.c
new file mode 100644
index 0000000000..e5563a8071
--- /dev/null
+++ b/widgets/table/e-tree-example-1.c
@@ -0,0 +1,243 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* This code is GPL. */
+#include <stdio.h>
+#include <string.h>
+#include <gnome.h>
+#include "e-util/e-cursors.h"
+#include "e-tree-gnode.h"
+#include "e-table-header.h"
+#include "e-table-header-item.h"
+#include "e-table-item.h"
+#include "e-cell-text.h"
+#include "e-cell-tree.h"
+#include "e-cell-checkbox.h"
+#include "e-table.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#define ROWS 10
+#define COLS 4
+
+#define IMPORTANCE_COLUMN 4
+#define COLOR_COLUMN 5
+
+/*
+ * Here we define the initial layout of the table. This is an xml
+ * format that allows you to change the initial ordering of the
+ * columns or to do sorting or grouping initially. This specification
+ * shows all 5 columns, but moves the importance column nearer to the
+ * front. It also sorts by the "Full Name" column (ascending.)
+ * Sorting and grouping take the model column as their arguments
+ * (sorting is specified by the "column" argument to the leaf elemnt.
+ */
+
+#define INITIAL_SPEC "<ETableSpecification> \
+ <columns-shown> \
+ <column> 0 </column> \
+ <column> 4 </column> \
+ <column> 1 </column> \
+ <column> 2 </column> \
+ <column> 3 </column> \
+ </columns-shown> \
+ <grouping></grouping> \
+</ETableSpecification>"
+
+/*
+ * Virtual Column list:
+ * 0 Subject
+ * 1 Full Name
+ * 2 Email
+ * 3 Date
+ */
+char *headers [COLS] = {
+ "Subject",
+ "Full Name",
+ "Email",
+ "Date"
+};
+
+/*
+ * ETableSimple callbacks
+ * These are the callbacks that define the behavior of our custom model.
+ */
+
+/*
+ * Since our model is a constant size, we can just return its size in
+ * the column and row count fields.
+ */
+
+/* This function returns the number of columns in our ETableModel. */
+static int
+my_col_count (ETableModel *etc, void *data)
+{
+ return COLS;
+}
+
+/* This function returns the value at a particular point in our ETableModel. */
+static void *
+my_value_at (ETreeModel *etc, GNode *node, int col, void *data)
+{
+ switch (col) {
+ case 0: return "Re: Two things";
+ case 1: return "Chris Toshok";
+ case 2: return "toshok@helixcode.com";
+ case 3: return "Jun 07 2000";
+ default: return NULL;
+ }
+}
+
+/* This function sets the value at a particular point in our ETableModel. */
+static void
+my_set_value_at (ETableModel *etc, GNode *node, int col, const void *val, void *data)
+{
+}
+
+/* This function returns whether a particular cell is editable. */
+static gboolean
+my_is_editable (ETableModel *etc, GNode *node, int col, void *data)
+{
+ return FALSE;
+}
+
+/* This function duplicates the value passed to it. */
+static void *
+my_duplicate_value (ETableModel *etc, int col, const void *value, void *data)
+{
+ return g_strdup (value);
+}
+
+/* This function frees the value passed to it. */
+static void
+my_free_value (ETableModel *etc, int col, void *value, void *data)
+{
+ g_free (value);
+}
+
+/* This function is for when the model is unfrozen. This can mostly
+ be ignored for simple models. */
+static void
+my_thaw (ETableModel *etc, void *data)
+{
+}
+
+/* We create a window containing our new tree. */
+static void
+create_tree (void)
+{
+ GtkWidget *e_table, *window, *frame;
+ ECell *cell_left_just;
+ ECell *cell_tree;
+ ETableHeader *e_table_header;
+ int i, j;
+ ETreeModel *e_tree_model = NULL;
+ GNode *root_node;
+
+ /* create a root node with 5 children */
+ root_node = g_node_new (NULL);
+ for (i = 0; i < 5; i++){
+ GNode *n = g_node_insert (root_node, 0, g_node_new(NULL));
+ for (j = 0; j < 5; j ++) {
+ g_node_insert (n, 0, g_node_new(NULL));
+ }
+ }
+
+ /* Next we create our model. This uses the functions we defined
+ earlier. */
+ e_tree_model = e_tree_gnode_new (root_node,
+ my_value_at,
+ NULL);
+
+ /*
+ * Next we create a header. The ETableHeader is used in two
+ * different way. The first is the full_header. This is the
+ * list of possible columns in the view. The second use is
+ * completely internal. Many of the ETableHeader functions are
+ * for that purpose. The only functions we really need are
+ * e_table_header_new and e_table_header_add_col.
+ *
+ * First we create the header.
+ */
+ e_table_header = e_table_header_new ();
+
+ /*
+ * Next we have to build renderers for all of the columns.
+ * Since all our columns are text columns, we can simply use
+ * the same renderer over and over again. If we had different
+ * types of columns, we could use a different renderer for
+ * each column.
+ */
+ cell_left_just = e_cell_text_new (E_TABLE_MODEL(e_tree_model), NULL, GTK_JUSTIFY_LEFT);
+
+ /*
+ * This renderer is used for the tree column (the leftmost one), and
+ * has as its subcell renderer the text renderer. this means that
+ * text is displayed to the right of the tree pipes.
+ */
+ cell_tree = e_cell_tree_new (E_TABLE_MODEL(e_tree_model), TRUE, cell_left_just);
+
+ /*
+ * Next we create a column object for each view column and add
+ * them to the header. We don't create a column object for
+ * the importance column since it will not be shown.
+ */
+ for (i = 0; i < COLS; i++) {
+ /* Create the column. */
+ ETableCol *ecol = e_table_col_new (
+ i, headers [i],
+ 80, 20,
+ i == 0 ? cell_tree
+ : cell_left_just,
+ g_str_compare, TRUE);
+ /* Add it to the header. */
+ e_table_header_add_column (e_table_header, ecol, i);
+ }
+
+ /*
+ * Here we create a window for our new table. This window
+ * will get shown and the person will be able to test their
+ * item.
+ */
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+ /* This frame is simply to get a bevel around our table. */
+ frame = gtk_frame_new (NULL);
+
+ /*
+ * Here we create the table. We give it the three pieces of
+ * the table we've created, the header, the model, and the
+ * initial layout. It does the rest.
+ */
+ e_table = e_table_new (e_table_header, E_TABLE_MODEL(e_tree_model), INITIAL_SPEC);
+
+ if (!e_table) printf ("BAH!");
+
+ /* Build the gtk widget hierarchy. */
+ gtk_container_add (GTK_CONTAINER (frame), e_table);
+ gtk_container_add (GTK_CONTAINER (window), frame);
+
+ /* Size the initial window. */
+ gtk_widget_set_usize (window, 200, 200);
+
+ /* Show it all. */
+ gtk_widget_show_all (window);
+}
+
+/* This is the main function which just initializes gnome and call our create_tree function */
+
+int
+main (int argc, char *argv [])
+{
+ gnome_init ("TableExample", "TableExample", argc, argv);
+ e_cursors_init ();
+
+ gtk_widget_push_visual (gdk_rgb_get_visual ());
+ gtk_widget_push_colormap (gdk_rgb_get_cmap ());
+
+ create_tree ();
+
+ gtk_main ();
+
+ e_cursors_shutdown ();
+ return 0;
+}
+
diff --git a/widgets/table/e-tree-gnode.c b/widgets/table/e-tree-gnode.c
new file mode 100644
index 0000000000..ce53751b56
--- /dev/null
+++ b/widgets/table/e-tree-gnode.c
@@ -0,0 +1,210 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * e-tree-gnode.c: a Tree Model that reflects a GNode structure visually.
+ *
+ * Author:
+ * Chris Toshok (toshok@helixcode.com)
+ *
+ * (C) 2000 Helix Code, Inc.
+ */
+#include <config.h>
+#include <gtk/gtksignal.h>
+#include "e-util/e-util.h"
+#include "e-tree-gnode.h"
+
+#define PARENT_TYPE E_TREE_MODEL_TYPE
+
+static ETreePath *
+gnode_get_root (ETreeModel *etm)
+{
+ ETreeGNode *etg = E_TREE_GNODE (etm);
+ ETreePath *path = NULL;
+
+ path = g_list_append(path, etg->root);
+
+ return path;
+}
+
+static ETreePath *
+gnode_get_prev (ETreeModel *etm, ETreePath *node)
+{
+ ETreePath *prev_path;
+
+ GNode *gnode;
+ GNode *prev_sibling;
+
+ g_return_val_if_fail (node && node->data, NULL);
+
+ gnode = (GNode*)node->data;
+ prev_sibling = g_node_prev_sibling(gnode);
+
+ if (!prev_sibling)
+ return NULL;
+
+ prev_path = g_list_copy (node->next);
+ prev_path = g_list_prepend (prev_path, prev_sibling);
+ return prev_path;
+}
+
+static ETreePath *
+gnode_get_next (ETreeModel *etm, ETreePath *node)
+{
+ ETreePath *next_path;
+ GNode *gnode;
+ GNode *next_sibling;
+
+ g_return_val_if_fail (node && node->data, NULL);
+
+ gnode = (GNode*)node->data;
+ next_sibling = g_node_next_sibling(gnode);
+
+ if (!next_sibling)
+ return NULL;
+
+ next_path = g_list_copy (node->next);
+ next_path = g_list_prepend (next_path, next_sibling);
+ return next_path;
+}
+
+static void *
+gnode_value_at (ETreeModel *etm, ETreePath *node, int col)
+{
+ ETreeGNode *etg = E_TREE_GNODE (etm);
+ GNode *gnode;
+
+ g_return_val_if_fail (node && node->data, NULL);
+
+ gnode = (GNode*)node->data;
+
+ return etg->value_at (etm, gnode, col, etg->data);
+}
+
+static void
+gnode_set_value_at (ETreeModel *etm, ETreePath *node, int col, const void *val)
+{
+ ETreeGNode *etg = E_TREE_GNODE (etm);
+ GNode *gnode;
+
+ g_return_if_fail (node && node->data);
+
+ gnode = (GNode*)node->data;
+
+ /* XXX */
+}
+
+static gboolean
+gnode_is_editable (ETreeModel *etm, ETreePath *node, int col)
+{
+ ETreeGNode *etg = E_TREE_GNODE (etm);
+ GNode *gnode;
+
+ g_return_val_if_fail (node && node->data, FALSE);
+
+ gnode = (GNode*)node->data;
+
+ /* XXX */
+ return FALSE;
+}
+
+static guint
+gnode_get_children (ETreeModel *etm, ETreePath *node, ETreePath ***paths)
+{
+ ETreeGNode *etg = E_TREE_GNODE (etm);
+ GNode *gnode;
+ guint n_children;
+
+ g_return_val_if_fail (node && node->data, 0);
+
+ gnode = (GNode*)node->data;
+
+ n_children = g_node_n_children (gnode);
+
+ if (paths)
+ {
+ int i;
+ (*paths) = g_malloc (sizeof (ETreePath*) * n_children);
+ for (i = 0; i < n_children; i ++) {
+ (*paths)[i] = g_list_copy (node);
+ (*paths)[i] = g_list_prepend ((*paths)[i], g_node_nth_child (gnode, i));
+ }
+ }
+
+ return n_children;
+}
+
+static void
+gnode_release_paths (ETreeModel *etm, ETreePath **paths, guint num_paths)
+{
+ guint i;
+ g_return_if_fail (paths);
+
+ for (i = 0; i < num_paths; i ++)
+ g_list_free (paths[i]);
+ g_free (paths);
+}
+
+static gboolean
+gnode_is_expanded (ETreeModel *etm, ETreePath *node)
+{
+ ETreeGNode *etg = E_TREE_GNODE (etm);
+ GNode *gnode;
+
+ g_return_val_if_fail (node && node->data, FALSE);
+
+ gnode = (GNode*)node->data;
+
+ return (gboolean)gnode->data;
+}
+
+static void
+gnode_set_expanded (ETreeModel *etm, ETreePath *node, gboolean expanded)
+{
+ ETreeGNode *etg = E_TREE_GNODE (etm);
+ GNode *gnode;
+ int num_descendents;
+
+ g_return_if_fail (node && node->data);
+
+ gnode = (GNode*)node->data;
+
+ /* XXX */
+ gnode->data = (gpointer)expanded;
+
+ e_table_model_changed (E_TABLE_MODEL(etm));
+}
+
+static void
+e_tree_gnode_class_init (GtkObjectClass *object_class)
+{
+ ETreeModelClass *model_class = (ETreeModelClass *) object_class;
+
+ model_class->get_root = gnode_get_root;
+ model_class->get_next = gnode_get_next;
+ model_class->get_prev = gnode_get_prev;
+ model_class->value_at = gnode_value_at;
+ model_class->set_value_at = gnode_set_value_at;
+ model_class->is_editable = gnode_is_editable;
+ model_class->get_children = gnode_get_children;
+ model_class->release_paths = gnode_release_paths;
+ model_class->is_expanded = gnode_is_expanded;
+ model_class->set_expanded = gnode_set_expanded;
+}
+
+E_MAKE_TYPE(e_tree_gnode, "ETreeGNode", ETreeGNode, e_tree_gnode_class_init, NULL, PARENT_TYPE)
+
+ETreeModel *
+e_tree_gnode_new (GNode *root_node,
+ ETreeGNodeValueAtFn value_at,
+ void *data)
+{
+ ETreeGNode *etg;
+
+ etg = gtk_type_new (e_tree_gnode_get_type ());
+
+ etg->root = root_node;
+
+ etg->value_at = value_at;
+ etg->data = data;
+
+ return (ETreeModel*)etg;
+}
diff --git a/widgets/table/e-tree-gnode.h b/widgets/table/e-tree-gnode.h
new file mode 100644
index 0000000000..56e6b19b80
--- /dev/null
+++ b/widgets/table/e-tree-gnode.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+#ifndef _E_TREE_GNODE_H_
+#define _E_TREE_GNODE_H_
+
+#include "e-tree-model.h"
+
+#define E_TREE_GNODE_TYPE (e_tree_gnode_get_type ())
+#define E_TREE_GNODE(o) (GTK_CHECK_CAST ((o), E_TREE_GNODE_TYPE, ETreeGNode))
+#define E_TREE_GNODE_CLASS(k) (GTK_CHECK_CLASS_CAST((k), E_TREE_GNODE_TYPE, ETreeGNodeClass))
+#define E_IS_TREE_GNODE(o) (GTK_CHECK_TYPE ((o), E_TREE_GNODE_TYPE))
+#define E_IS_TREE_GNODE_CLASS(k) (GTK_CHECK_CLASS_TYPE ((k), E_TREE_GNODE_TYPE))
+
+typedef void *(*ETreeGNodeValueAtFn)(ETreeModel *model, GNode *node, int col, void *data);
+
+
+typedef struct {
+ ETreeModel parent;
+
+ GNode *root;
+
+ ETreeGNodeValueAtFn value_at;
+
+ void *data;
+} ETreeGNode;
+
+typedef struct {
+ ETreeModelClass parent_class;
+} ETreeGNodeClass;
+
+GtkType e_tree_gnode_get_type (void);
+
+ETreeModel *e_tree_gnode_new (GNode *tree,
+ ETreeGNodeValueAtFn value_at,
+ void *data);
+
+#endif /* _E_TREE_GNODE_H_ */
diff --git a/widgets/table/e-tree-model.c b/widgets/table/e-tree-model.c
new file mode 100644
index 0000000000..d67e59f3f5
--- /dev/null
+++ b/widgets/table/e-tree-model.c
@@ -0,0 +1,316 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * e-tree-model.c: a Tree Model
+ *
+ * Author:
+ * Chris Toshok (toshok@helixcode.com)
+ *
+ * Adapted from the gtree code and ETableModel.
+ *
+ * (C) 2000 Helix Code, Inc.
+ */
+#include <config.h>
+#include <gtk/gtksignal.h>
+#include "e-util/e-util.h"
+#include "e-tree-model.h"
+
+#define ETM_CLASS(e) ((ETreeModelClass *)((GtkObject *)e)->klass)
+
+#define PARENT_TYPE E_TABLE_MODEL_TYPE
+
+static ETableModel *e_tree_model_parent_class;
+
+/* virtual methods */
+
+static ETreePath*
+etree_get_root (ETreeModel *etm)
+{
+ /* shouldn't be called */
+ g_assert(0);
+ return NULL;
+}
+
+static void*
+etree_value_at (ETreeModel *etm, ETreePath* node, int col)
+{
+ /* shouldn't be called */
+ g_assert(0);
+ return NULL;
+}
+
+static void
+etree_set_value_at (ETreeModel *etm, ETreePath* node, int col, const void *val)
+{
+ /* shouldn't be called */
+ g_assert(0);
+}
+
+static gboolean
+etree_is_editable (ETreeModel *etm, ETreePath* node, int col)
+{
+ /* shouldn't be called */
+ g_assert(0);
+ return FALSE;
+}
+
+static gboolean
+etree_is_expanded (ETreeModel *etm, ETreePath* node)
+{
+ /* shouldn't be called */
+ g_assert(0);
+ return FALSE;
+}
+
+static guint
+etree_get_children (ETreeModel *etm, ETreePath* node, ETreePath ***paths)
+{
+ /* shouldn't be called */
+ g_assert(0);
+ return 0;
+}
+
+static void
+etree_release_paths (ETreeModel *etm, ETreePath **paths, guint num_paths)
+{
+ /* shouldn't be called */
+ g_assert(0);
+}
+
+static void
+etree_set_expanded (ETreeModel *etm, ETreePath* node, gboolean expanded)
+{
+ /* shouldn't be called */
+ g_assert(0);
+}
+
+static void
+etree_destroy (GtkObject *object)
+{
+}
+
+guint
+e_tree_model_node_num_visible_descendents (ETreeModel *etm, ETreePath *node)
+{
+ int count = 1;
+ if (e_tree_model_node_is_expanded (etm, node)) {
+ ETreePath **paths;
+ int i;
+ int num_paths;
+
+ num_paths = e_tree_model_node_get_children (etm, node, &paths);
+
+ for (i = 0; i < num_paths; i ++)
+ count += e_tree_model_node_num_visible_descendents(etm, paths[i]);
+
+ e_tree_model_release_paths (etm, paths, num_paths);
+ }
+
+ return count;
+}
+
+static int
+etable_row_count (ETableModel *etm)
+{
+ return e_tree_model_node_num_visible_descendents (E_TREE_MODEL (etm), e_tree_model_get_root (E_TREE_MODEL (etm)));
+}
+
+static void *
+etable_value_at (ETableModel *etm, int col, int row)
+{
+ ETreeModel *etree = E_TREE_MODEL(etm);
+ ETreeModelClass *et_class = ETM_CLASS(etm);
+ ETreePath* node = e_tree_model_node_at_row (etree, row);
+
+ g_return_val_if_fail (node, NULL);
+
+ if (col == -1)
+ return node;
+ else if (col == -2)
+ return etm;
+ else
+ return et_class->value_at (etree, node, col);
+}
+
+static void
+etable_set_value_at (ETableModel *etm, int col, int row, const void *val)
+{
+ ETreeModel *etree = E_TREE_MODEL(etm);
+ ETreeModelClass *et_class = ETM_CLASS(etm);
+ ETreePath* node = e_tree_model_node_at_row (etree, row);
+
+ g_return_if_fail (node);
+
+ et_class->set_value_at (etree, node, col, val);
+}
+
+static gboolean
+etable_is_cell_editable (ETableModel *etm, int col, int row)
+{
+ ETreeModel *etree = E_TREE_MODEL(etm);
+ ETreeModelClass *et_class = ETM_CLASS(etm);
+ ETreePath* node = e_tree_model_node_at_row (etree, row);
+
+ g_return_val_if_fail (node, FALSE);
+
+ return et_class->is_editable (etree, node, col);
+}
+
+static void
+e_tree_model_class_init (GtkObjectClass *klass)
+{
+ ETableModelClass *table_class = (ETableModelClass *) klass;
+ ETreeModelClass *tree_class = (ETreeModelClass *) klass;
+
+ e_tree_model_parent_class = gtk_type_class (PARENT_TYPE);
+
+ klass->destroy = etree_destroy;
+
+ table_class->row_count = etable_row_count;
+ table_class->value_at = etable_value_at;
+ table_class->set_value_at = etable_set_value_at;
+ table_class->is_cell_editable = etable_is_cell_editable;
+#if 0
+ table_class->duplicate_value = etable_duplicate_value;
+ table_class->free_value = etable_free_value;
+ table_class->initialize_value = etable_initialize_value;
+ table_class->value_is_empty = etable_value_is_empty;
+ table_class->thaw = etable_thaw;
+#endif
+
+ tree_class->get_root = etree_get_root;
+ tree_class->value_at = etree_value_at;
+ tree_class->set_value_at = etree_set_value_at;
+ tree_class->is_editable = etree_is_editable;
+
+ tree_class->get_children = etree_get_children;
+ tree_class->release_paths = etree_release_paths;
+ tree_class->is_expanded = etree_is_expanded;
+ tree_class->set_expanded = etree_set_expanded;
+}
+
+E_MAKE_TYPE(e_tree_model, "ETreeModel", ETreeModel, e_tree_model_class_init, NULL, PARENT_TYPE)
+
+/* signals */
+
+ETreeModel *
+e_tree_model_new ()
+{
+ ETreeModel *et;
+
+ et = gtk_type_new (e_tree_model_get_type ());
+
+ return et;
+}
+
+static ETreePath *
+e_tree_model_node_at_row_1 (ETreeModel *etree, int *row, ETreePath *node)
+{
+ ETreePath *ret = NULL;
+
+ if (*row == 0)
+ ret = node;
+ else if (e_tree_model_node_is_expanded (etree, node)) {
+ int num_children;
+ int i;
+ ETreePath **paths;
+
+ num_children = e_tree_model_node_get_children (etree, node, &paths);
+
+ for (i = 0; i < num_children; i ++) {
+ ETreePath *p;
+
+ (*row) --;
+
+ p = e_tree_model_node_at_row_1 (etree, row, paths[i]);
+
+ if (p) {
+ ret = p;
+ break;
+ }
+ }
+
+ /* XXX need to find why this release is causing problems */
+ /* e_tree_model_release_paths (etree, paths, num_children); */
+ }
+
+ return ret;
+}
+
+ETreePath *
+e_tree_model_get_root (ETreeModel *etree)
+{
+ return ETM_CLASS(etree)->get_root(etree);
+}
+
+ETreePath *
+e_tree_model_node_at_row (ETreeModel *etree, int row)
+{
+ /* XXX icky, perform a depth first search of the tree. we need this optimized sorely */
+ return e_tree_model_node_at_row_1 (etree, &row, ETM_CLASS(etree)->get_root(etree));
+}
+
+ETreePath *
+e_tree_model_node_get_next (ETreeModel *etree, ETreePath *node)
+{
+ return ETM_CLASS(etree)->get_next(etree, node);
+}
+
+ETreePath *
+e_tree_model_node_get_prev (ETreeModel *etree, ETreePath *node)
+{
+ return ETM_CLASS(etree)->get_prev(etree, node);
+}
+
+guint
+e_tree_model_node_depth (ETreeModel *etree, ETreePath *path)
+{
+ return g_list_length (path) - 1;
+}
+
+ETreePath *
+e_tree_model_node_get_parent (ETreeModel *etree, ETreePath *path)
+{
+ g_return_val_if_fail (path, NULL);
+
+ if (path->next == NULL)
+ return NULL;
+ else
+ return g_list_copy (path->next);
+}
+
+gboolean
+e_tree_model_node_is_root (ETreeModel *etree, ETreePath *path)
+{
+ return (e_tree_model_node_depth (etree, path) == 0);
+}
+
+gboolean
+e_tree_model_node_is_expandable (ETreeModel *etree, ETreePath *path)
+{
+ return (e_tree_model_node_get_children (etree, path, NULL) > 0);
+}
+
+gboolean
+e_tree_model_node_is_expanded (ETreeModel *etree, ETreePath *path)
+{
+ return ETM_CLASS(etree)->is_expanded (etree, path);
+}
+
+void
+e_tree_model_node_set_expanded (ETreeModel *etree, ETreePath *path, gboolean expanded)
+{
+ ETM_CLASS(etree)->set_expanded (etree, path, expanded);
+}
+
+guint
+e_tree_model_node_get_children (ETreeModel *etree, ETreePath *path, ETreePath ***paths)
+{
+ return ETM_CLASS(etree)->get_children (etree, path, paths);
+}
+
+void
+e_tree_model_release_paths (ETreeModel *etree, ETreePath **paths, guint num_paths)
+{
+ ETM_CLASS(etree)->release_paths (etree, paths, num_paths);
+}
+
diff --git a/widgets/table/e-tree-model.h b/widgets/table/e-tree-model.h
new file mode 100644
index 0000000000..6924f3ea0e
--- /dev/null
+++ b/widgets/table/e-tree-model.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+#ifndef _E_TREE_MODEL_H_
+#define _E_TREE_MODEL_H_
+
+#include "e-table-model.h"
+
+#define E_TREE_MODEL_TYPE (e_tree_model_get_type ())
+#define E_TREE_MODEL(o) (GTK_CHECK_CAST ((o), E_TREE_MODEL_TYPE, ETreeModel))
+#define E_TREE_MODEL_CLASS(k) (GTK_CHECK_CLASS_CAST((k), E_TREE_MODEL_TYPE, ETreeModelClass))
+#define E_IS_TREE_MODEL(o) (GTK_CHECK_TYPE ((o), E_TREE_MODEL_TYPE))
+#define E_IS_TREE_MODEL_CLASS(k) (GTK_CHECK_CLASS_TYPE ((k), E_TREE_MODEL_TYPE))
+
+typedef gpointer ETreePathItem;
+typedef GList ETreePath;
+
+typedef struct {
+ ETableModel base;
+
+ ETableModel *source;
+
+ ETreePath *root_node;
+
+ GArray *array;
+
+} ETreeModel;
+
+typedef struct {
+ ETableModelClass parent_class;
+
+ /*
+ * Virtual methods
+ */
+ ETreePath *(*get_root) (ETreeModel *etm);
+
+ ETreePath *(*get_next) (ETreeModel *etm, ETreePath* node);
+ ETreePath *(*get_prev) (ETreeModel *etm, ETreePath* node);
+
+ void *(*value_at) (ETreeModel *etm, ETreePath* node, int col);
+ void (*set_value_at) (ETreeModel *etm, ETreePath* node, int col, const void *val);
+ gboolean (*is_editable) (ETreeModel *etm, ETreePath* node, int col);
+
+ guint (*get_children) (ETreeModel *etm, ETreePath* node, ETreePath ***paths);
+ void (*release_paths) (ETreeModel *etm, ETreePath **paths, guint num_paths);
+ gboolean (*is_expanded) (ETreeModel *etm, ETreePath* node);
+ void (*set_expanded) (ETreeModel *etm, ETreePath* node, gboolean expanded);
+
+ /*
+ * Signals
+ */
+
+} ETreeModelClass;
+
+GtkType e_tree_model_get_type (void);
+
+ETreeModel *e_tree_model_new (void);
+
+/* operations on "nodes" in the tree */
+ETreePath * e_tree_model_get_root (ETreeModel *etree);
+ETreePath * e_tree_model_node_at_row (ETreeModel *etree, int row);
+guint e_tree_model_node_depth (ETreeModel *etree, ETreePath *path);
+ETreePath *e_tree_model_node_get_parent (ETreeModel *etree, ETreePath *path);
+ETreePath *e_tree_model_node_get_next (ETreeModel *etree, ETreePath *path);
+ETreePath *e_tree_model_node_get_prev (ETreeModel *etree, ETreePath *path);
+gboolean e_tree_model_node_is_root (ETreeModel *etree, ETreePath *path);
+
+gboolean e_tree_model_node_is_expandable (ETreeModel *etree, ETreePath *path);
+gboolean e_tree_model_node_is_expanded (ETreeModel *etree, ETreePath *path);
+guint e_tree_model_node_get_children (ETreeModel *etree, ETreePath *path, ETreePath ***paths);
+void e_tree_model_release_paths (ETreeModel *etree, ETreePath **paths, guint num_paths);
+guint e_tree_model_node_num_visible_descendents (ETreeModel *etm, ETreePath *node);
+
+#endif /* _E_TREE_MODEL_H */
diff --git a/widgets/table/tree-expanded.xpm b/widgets/table/tree-expanded.xpm
new file mode 100644
index 0000000000..fc748953eb
--- /dev/null
+++ b/widgets/table/tree-expanded.xpm
@@ -0,0 +1,22 @@
+/* XPM */
+static char * tree_expanded_xpm[] = {
+"16 16 3 1",
+" c None",
+". c #000000",
+"+ c #FFFFFF",
+" ",
+" ",
+" ",
+" ",
+" ......... ",
+" .+++++++. ",
+" .+++++++. ",
+" .+++++++. ",
+" .+.....+. ",
+" .+++++++. ",
+" .+++++++. ",
+" .+++++++. ",
+" ......... ",
+" ",
+" ",
+" "};
diff --git a/widgets/table/tree-unexpanded.xpm b/widgets/table/tree-unexpanded.xpm
new file mode 100644
index 0000000000..0dfb12a0a5
--- /dev/null
+++ b/widgets/table/tree-unexpanded.xpm
@@ -0,0 +1,22 @@
+/* XPM */
+static char * tree_unexpanded_xpm[] = {
+"16 16 3 1",
+" c None",
+". c #000000",
+"+ c #FFFFFF",
+" ",
+" ",
+" ",
+" ",
+" ......... ",
+" .+++++++. ",
+" .+++.+++. ",
+" .+++.+++. ",
+" .+.....+. ",
+" .+++.+++. ",
+" .+++.+++. ",
+" .+++++++. ",
+" ......... ",
+" ",
+" ",
+" "};