From e581631a9ce0803ba629ac67cd8934a6e23753ae Mon Sep 17 00:00:00 2001 From: Chris Toshok Date: Thu, 8 Jun 2000 23:16:22 +0000 Subject: New files. A tree model using a GNode structure to store it's info. 2000-06-08 Chris Toshok * 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 --- widgets/table/.cvsignore | 1 + widgets/table/e-cell-tree.c | 385 ++++++++++++++++++++++++++++++++++++++ widgets/table/e-cell-tree.h | 42 +++++ widgets/table/e-tree-example-1.c | 243 ++++++++++++++++++++++++ widgets/table/e-tree-gnode.c | 210 +++++++++++++++++++++ widgets/table/e-tree-gnode.h | 37 ++++ widgets/table/e-tree-model.c | 316 +++++++++++++++++++++++++++++++ widgets/table/e-tree-model.h | 72 +++++++ widgets/table/tree-expanded.xpm | 22 +++ widgets/table/tree-unexpanded.xpm | 22 +++ 10 files changed, 1350 insertions(+) create mode 100644 widgets/table/e-cell-tree.c create mode 100644 widgets/table/e-cell-tree.h create mode 100644 widgets/table/e-tree-example-1.c create mode 100644 widgets/table/e-tree-gnode.c create mode 100644 widgets/table/e-tree-gnode.h create mode 100644 widgets/table/e-tree-model.c create mode 100644 widgets/table/e-tree-model.h create mode 100644 widgets/table/tree-expanded.xpm create mode 100644 widgets/table/tree-unexpanded.xpm (limited to 'widgets/table') 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 + * + * A majority of code taken from: + * + * the ECellText renderer. + * + * Copyright (C) 1998 The Free Software Foundation + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 /* for BlackPixel */ +#include +#include + +#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 = ▭ + + 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 + * + */ +#ifndef _E_CELL_TREE_H_ +#define _E_CELL_TREE_H_ + +#include +#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 +#include +#include +#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 + +#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 " \ + \ + 0 \ + 4 \ + 1 \ + 2 \ + 3 \ + \ + \ +" + +/* + * 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 +#include +#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 +#include +#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", +" ", +" ", +" ", +" ", +" ......... ", +" .+++++++. ", +" .+++.+++. ", +" .+++.+++. ", +" .+.....+. ", +" .+++.+++. ", +" .+++.+++. ", +" .+++++++. ", +" ......... ", +" ", +" ", +" "}; -- cgit v1.2.3