aboutsummaryrefslogblamecommitdiffstats
path: root/widgets/e-table/e-tree-model.c
blob: c57bf2d6832c0bb9dc7ef7864c74aad34dfc5049 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13












                                                                           
                   








                                                                 

















                                                                                                   

                     

                                 
 

                                                  
                                            


                                                                       

 

                                
 
                         

 

                                                   
 


                                          

 

                                                 
 


                                          

 

                                                 
 


                                          




                                                                         














                                                                       

 

                                                    
 















                                                              




                                                                        












                                              




                                                               







































                                                                                                                 

 
                                 
           
                                                                                
 













                                                                                         

 

                                              
 
                                                                 
 
                                                             
 
 








                                                          
 







                                                





                                                                               
 





                                                             

 

                                     


                                   

                                              










































                                                                         
 









                                                                   

























                                                                                      




                                                                
                                           



                                                                
                                                               



                                                       



                                                         
                                                       
                                                      



                                                           
                                                          
                                                         
                                                           

                                                                      



                                                                                               
 
             









































                                                                                                    










                                                     

                                         
 

                                                 
 




                                                          
 





                                                              



                                                             
 


                                                                        
 

                                                                          

                  
 







                                                                                                   
                         

                                                                                   
                                                                                      
                                                                                          


                                                                    
                 
         

 

                                                   
 
                                 

 















                                                               
                                       




                                                                 
                                                         



















                                                                        





                                                                 





                                                                                      





                                                                                              





                                                                                       



















                                                                            
    
                                                                                 
 






                                              

 























                                                                                     
                                 


                                                                                                     

                                                                      




                                                                     


                                                                                                                   



                                                                                                         

                                                                                                          



                                                     
                                               
                                                                                                                

                                                                                   




                                                                   
                                            
         




                          


                                                    
                                                    





                                                                                 





                                               






                                                             

                                                                            




                                                                         
                                                                               
                                                                                        



















































                                                                                                     





























                                                                   
/* -*- 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 <stdlib.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;

typedef struct {
    gboolean expanded;
    guint visible_descendents;
    gpointer node_data;
} ENode;

enum {
    NODE_CHANGED,
    NODE_INSERTED,
    NODE_REMOVED,
    LAST_SIGNAL
};

static guint e_tree_model_signals [LAST_SIGNAL] = {0, };

static void add_visible_descendents_to_array (ETreeModel *etm, GNode *gnode, int *row, int *count);


/* virtual methods */

static void
etree_destroy (GtkObject *object)
{
    ETreeModel *etree = E_TREE_MODEL (object);

    /* XXX lots of stuff to free here */
    g_array_free (etree->row_array, TRUE);
    
    GTK_OBJECT_CLASS (e_tree_model_parent_class)->destroy (object);
}

static ETreePath*
etree_get_root (ETreeModel *etm)
{
    return etm->root;
}

static ETreePath*
etree_get_parent (ETreeModel *etm, ETreePath *path)
{
    g_return_val_if_fail (path, NULL);
    
    return path->parent;
}

static ETreePath*
etree_get_next (ETreeModel *etm, ETreePath *node)
{
    g_return_val_if_fail (node, NULL);

    return g_node_next_sibling(node);
}

static ETreePath*
etree_get_prev (ETreeModel *etm, ETreePath *node)
{
    g_return_val_if_fail (node, NULL);

    return g_node_prev_sibling (node);
}

static guint
etree_get_children (ETreeModel *etm, ETreePath* node, ETreePath ***paths)
{
    guint n_children;

    g_return_val_if_fail (node, 0);

    n_children = g_node_n_children (node);

    if (paths) {
        int i;
        (*paths) = g_malloc (sizeof (ETreePath*) * n_children);
        for (i = 0; i < n_children; i ++) {
            (*paths)[i] = g_node_nth_child (node, i);
        }
    }

    return n_children;
}

static gboolean
etree_is_expanded (ETreeModel *etm, ETreePath* node)
{
    g_return_val_if_fail (node && node->data, FALSE);
    
    return ((ENode*)node->data)->expanded;
}

static gboolean
etree_is_visible (ETreeModel *etm, ETreePath* node)
{
    g_return_val_if_fail (node, FALSE);

    for (node = node->parent; node; node = node->parent) {
        if (!((ENode*)node->data)->expanded)
            return FALSE;
    }

    return TRUE;
}

static void
etree_set_expanded (ETreeModel *etm, ETreePath* node, gboolean expanded)
{
    GNode *child;
    ENode *enode;
    int row;
    
    g_return_if_fail (node && node->data);

    enode = ((ENode*)node->data);

    if (enode->expanded == expanded)
        return;

    enode->expanded = expanded;

    /* if the node wasn't visible at present */
    if ((row = e_tree_model_row_of_node (etm, node)) == -1)
        return;

    row++;

    if (expanded) {
        GNode *parent;

        if (e_tree_model_node_is_visible (etm, node)) {
            enode->visible_descendents = 0;
            for (child = g_node_first_child (node); child;
                 child = g_node_next_sibling (child)) {
                add_visible_descendents_to_array (etm, child, &row, &enode->visible_descendents);
            }
        }
        /* now iterate back up the tree, adding to our
           ancestors' visible descendents */

        for (parent = node->parent; parent; parent = parent->parent) {
            ENode *parent_enode = (ENode*)parent->data;
            parent_enode->visible_descendents += enode->visible_descendents;
        }
    }
    else {
        int i;
        GNode *parent;

        if (e_tree_model_node_is_visible (etm, node)) {
            for (i = 0; i < enode->visible_descendents; i ++) {
                etm->row_array = g_array_remove_index (etm->row_array, row);
                e_table_model_row_deleted (E_TABLE_MODEL (etm), row);
            }
        }
        /* now iterate back up the tree, subtracting from our
           ancestors' visible descendents */

        for (parent = node->parent; parent; parent = parent->parent) {
            ENode *parent_enode = (ENode*)parent->data;

            parent_enode->visible_descendents -= enode->visible_descendents;
        }

        enode->visible_descendents = 0;
    }
}

/* fairly naive implementation */
static void
etree_set_expanded_recurse (ETreeModel *etm, ETreePath* node, gboolean expanded)
{
    ETreePath **paths;
    guint num_children;
    int i;

    e_tree_model_node_set_expanded (etm, node, expanded);

    num_children = e_tree_model_node_get_children (etm, node, &paths);
    if (num_children) {
        for (i = 0; i < num_children; i ++) {
            e_tree_model_node_set_expanded_recurse (etm, paths[i], expanded);
        }

        g_free (paths);
    }
}

static ETreePath *
etree_node_at_row (ETreeModel *etree, int row)
{
    g_return_val_if_fail (row < etree->row_array->len, NULL);

    return g_array_index (etree->row_array, GNode*, row);
}


/* ETable analogs */
static void*
etree_value_at (ETreeModel *etm, ETreePath* node, int col)
{
    /* shouldn't be called */
    g_assert (0);
    return NULL;
}

static GdkPixbuf*
etree_icon_at (ETreeModel *etm, ETreePath* node)
{
    /* 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;
}


/* ETable virtual functions we map */
static int
etable_row_count (ETableModel *etm)
{
    ETreeModel *tree = E_TREE_MODEL (etm);
    return tree->row_array->len;
}

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;

    e_tree_model_signals [NODE_CHANGED] =
        gtk_signal_new ("node_changed",
                GTK_RUN_LAST,
                klass->type,
                GTK_SIGNAL_OFFSET (ETreeModelClass, node_changed),
                gtk_marshal_NONE__POINTER,
                GTK_TYPE_NONE, 1, GTK_TYPE_POINTER);

    e_tree_model_signals [NODE_INSERTED] =
        gtk_signal_new ("node_inserted",
                GTK_RUN_LAST,
                klass->type,
                GTK_SIGNAL_OFFSET (ETreeModelClass, node_inserted),
                gtk_marshal_NONE__POINTER_POINTER,
                GTK_TYPE_NONE, 2, GTK_TYPE_POINTER, GTK_TYPE_POINTER);

    e_tree_model_signals [NODE_REMOVED] =
        gtk_signal_new ("node_removed",
                GTK_RUN_LAST,
                klass->type,
                GTK_SIGNAL_OFFSET (ETreeModelClass, node_removed),
                gtk_marshal_NONE__POINTER_POINTER,
                GTK_TYPE_NONE, 2, GTK_TYPE_POINTER, GTK_TYPE_POINTER);

    gtk_object_class_add_signals (klass, e_tree_model_signals, LAST_SIGNAL);

    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
    /* XX need to pass these through */
    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->value_to_string  = etable_value_to_string;
    table_class->thaw             = etable_thaw;
#endif

    tree_class->get_root          = etree_get_root;
    tree_class->get_prev          = etree_get_prev;
    tree_class->get_next          = etree_get_next;
    tree_class->get_parent        = etree_get_parent;

    tree_class->value_at          = etree_value_at;
    tree_class->icon_at           = etree_icon_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->is_expanded       = etree_is_expanded;
    tree_class->is_visible        = etree_is_visible;
    tree_class->set_expanded      = etree_set_expanded;
    tree_class->set_expanded_recurse = etree_set_expanded_recurse;
    tree_class->node_at_row       = etree_node_at_row;
}

E_MAKE_TYPE(e_tree_model, "ETreeModel", ETreeModel, e_tree_model_class_init, NULL, PARENT_TYPE)


/* signals */
void
e_tree_model_node_changed  (ETreeModel *tree_model, ETreePath *node)
{
    g_return_if_fail (tree_model != NULL);
    g_return_if_fail (E_IS_TREE_MODEL (tree_model));
    
    gtk_signal_emit (GTK_OBJECT (tree_model),
             e_tree_model_signals [NODE_CHANGED]);
}

void
e_tree_model_node_inserted (ETreeModel *tree_model,
                ETreePath *parent_node,
                ETreePath *inserted_node)
{
    g_return_if_fail (tree_model != NULL);
    g_return_if_fail (E_IS_TREE_MODEL (tree_model));
    
    gtk_signal_emit (GTK_OBJECT (tree_model),
             e_tree_model_signals [NODE_INSERTED],
             parent_node, inserted_node);
}

void
e_tree_model_node_removed  (ETreeModel *tree_model, ETreePath *parent_node, ETreePath *removed_node)
{
    g_return_if_fail (tree_model != NULL);
    g_return_if_fail (E_IS_TREE_MODEL (tree_model));
    
    gtk_signal_emit (GTK_OBJECT (tree_model),
             e_tree_model_signals [NODE_REMOVED],
             parent_node, removed_node);
}


void
e_tree_model_construct (ETreeModel *etree)
{
    etree->root = NULL;
    etree->root_visible = TRUE;
    etree->row_array = g_array_new (FALSE, FALSE, sizeof(GNode*));
}

ETreeModel *
e_tree_model_new ()
{
    ETreeModel *et;

    et = gtk_type_new (e_tree_model_get_type ());

    return et;
}

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)
{
    return ETM_CLASS(etree)->node_at_row (etree, row);
}

GdkPixbuf *
e_tree_model_icon_of_node (ETreeModel *etree, ETreePath *path)
{
    return ETM_CLASS(etree)->icon_at (etree, path);
}

int
e_tree_model_row_of_node (ETreeModel *etree, ETreePath *node)
{
    int i;

    for (i = 0; i < etree->row_array->len; i ++)
        if (g_array_index (etree->row_array, GNode*, i) == node)
            return i;

    g_warning ("e_tree_model_row_of_node failed for node %p\n", node);

    return -1;
}

void
e_tree_model_root_node_set_visible (ETreeModel *etm, gboolean visible)
{
    if (visible != etm->root_visible) {
        etm->root_visible = visible;
        if (etm->root) {
            if (visible) {
                etm->row_array = g_array_insert_val (etm->row_array, 0, etm->root);
            }
            else {
                ETreePath *root_path = e_tree_model_get_root (etm);
                e_tree_model_node_set_expanded (etm, root_path, TRUE);
                etm->row_array = g_array_remove_index (etm->row_array, 0);
            }
            
            e_table_model_changed (E_TABLE_MODEL (etm));
        }
    }
}

gboolean
e_tree_model_root_node_is_visible (ETreeModel *etm)
{
    return etm->root_visible;
}


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_node_depth (path) - 1;
}

ETreePath *
e_tree_model_node_get_parent (ETreeModel *etree, ETreePath *path)
{
    return ETM_CLASS(etree)->get_parent(etree, path);
}

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);
}

gboolean
e_tree_model_node_is_visible (ETreeModel *etree, ETreePath *path)
{
    return ETM_CLASS(etree)->is_visible (etree, path);
}

void
e_tree_model_node_set_expanded (ETreeModel *etree, ETreePath *path, gboolean expanded)
{
    ETM_CLASS(etree)->set_expanded (etree, path, expanded);
}

void
e_tree_model_node_set_expanded_recurse (ETreeModel *etree, ETreePath *path, gboolean expanded)
{
    ETM_CLASS(etree)->set_expanded_recurse (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);
}

guint
e_tree_model_node_num_visible_descendents (ETreeModel *etm, ETreePath *node)
{
    ENode *enode = (ENode*)node->data;

    return enode->visible_descendents;
}

gpointer
e_tree_model_node_get_data (ETreeModel *etm, ETreePath *node)
{
    ENode *enode;

    g_return_val_if_fail (node && node->data, NULL);

    enode = (ENode*)node->data;

    return enode->node_data;
}

void
e_tree_model_node_set_data (ETreeModel *etm, ETreePath *node, gpointer node_data)
{
    ENode *enode;

    g_return_if_fail (node && node->data);

    enode = (ENode*)node->data;

    enode->node_data = node_data;
}

ETreePath*
e_tree_model_node_insert (ETreeModel *tree_model,
              ETreePath *parent_path,
              int position,
              gpointer node_data)
{
    ENode *node;
    ETreePath *new_path;

    g_return_val_if_fail (parent_path != NULL || tree_model->root == NULL, NULL);

    node = g_new0 (ENode, 1);

    node->expanded = FALSE;
    node->node_data = node_data;

    if (parent_path != NULL) {

        new_path = g_node_new (node);

        g_node_insert (parent_path, position, new_path);

        if (e_tree_model_node_is_visible (tree_model, new_path)) {
            int parent_row;
            GNode *n;

            /* we need to iterate back up to the root, incrementing the number of visible
               descendents */
            for (n = parent_path; n; n = n->parent) {
                ENode *parent_enode = (ENode*)n->data;

                parent_enode->visible_descendents ++;
            }
                
            /* finally, insert a row into the table */
            if (position == -1)
                position = e_tree_model_node_num_visible_descendents (tree_model, parent_path) - 1;

            parent_row = e_tree_model_row_of_node (tree_model, parent_path);

            tree_model->row_array = g_array_insert_val (tree_model->row_array,
                                    parent_row + position + 1, new_path);

            e_table_model_row_inserted (E_TABLE_MODEL(tree_model), parent_row + position + 1);
        }
    }
    else {
        tree_model->root = g_node_new (node);
        if (tree_model->root_visible) {
            tree_model->row_array = g_array_insert_val (tree_model->row_array, 0, tree_model->root);
            e_table_model_row_inserted (E_TABLE_MODEL (tree_model), 0);
        }
        else {
            /* need to mark the new node as expanded or
                           we'll never see it's children */
            node->expanded = TRUE;
        }
        new_path = tree_model->root;
    }

    return new_path;
}
              
ETreePath *
e_tree_model_node_insert_before (ETreeModel *etree,
                 ETreePath *parent,
                 ETreePath *sibling,
                 gpointer node_data)
{
    return e_tree_model_node_insert (etree, parent,
                     g_node_child_position (parent, sibling),
                     node_data);
}

static void
child_remove (GNode *node, gpointer etree)
{
    e_tree_model_node_remove (etree, node);
}

gpointer
e_tree_model_node_remove (ETreeModel *etree, ETreePath *path)
{
    GNode *parent = path->parent;
    ENode *enode = (ENode*)path->data;
    gpointer ret = enode->node_data;

    /* remove children */
    g_node_children_foreach (path, G_TRAVERSE_ALL, child_remove, etree);

    /* clean up the display */
    if (parent) {
        if (e_tree_model_node_is_visible (etree, path)) {
            int row = e_tree_model_row_of_node (etree, path);
            e_table_model_row_deleted (E_TABLE_MODEL (etree), row);
            etree->row_array = g_array_remove_index (etree->row_array, row);

            /* we need to iterate back up to the root, incrementing the number of visible
               descendents */
            for (; parent; parent = parent->parent) {
                ENode *parent_enode = (ENode*)parent->data;

                parent_enode->visible_descendents --;
            }
        }
    }
    else if (path == etree->root) {
        etree->root = NULL;
        if (etree->root_visible) {
            etree->row_array = g_array_remove_index (etree->row_array, 0);
            e_table_model_row_deleted (E_TABLE_MODEL (etree), 0);
        }
    }
    else {
        /* XXX invalid path */
        return NULL;
    }


    /* now free up the storage from that path */
    g_node_destroy (path);
    g_free (enode);

    return ret;
}


static void
add_visible_descendents_to_array (ETreeModel *etm, GNode *gnode, int *row, int *count)
{
    GNode *child;
    ENode *enode;

    /* add a row for this node */
    etm->row_array = g_array_insert_val (etm->row_array, (*row), gnode);
    e_table_model_row_inserted (E_TABLE_MODEL (etm), (*row)++);
    (*count) ++;

    /* then loop over its children, calling this routine for each
           of them */
    enode = (ENode*)gnode->data;
    if (enode->expanded) {
        for (child = g_node_first_child (gnode); child;
             child = g_node_next_sibling (child)) {
            add_visible_descendents_to_array (etm, child, row, count);
        }
    }
}

void
e_tree_model_node_sort (ETreeModel *tree_model,
            ETreePath *node,
            GCompareFunc compare)
{
    int num_nodes = g_node_n_children (node);
    ETreePath **path_array;
    int i;

    if (num_nodes == 0)
        return;

    path_array = g_new (ETreePath*, num_nodes);

    for (i = 0; i < num_nodes; i ++) {
        path_array[i] = g_node_first_child(node);
        g_node_unlink (path_array[i]);
    }

    qsort (path_array, num_nodes, sizeof(ETreePath*), compare);

    for (i = 0; i < num_nodes; i ++) {
        g_node_append (node, path_array[i]);
    }

    g_free (path_array);

    e_table_model_changed (E_TABLE_MODEL (tree_model));
}