2006-08-22 [colin] 2.4.0cvs73
[claws.git] / src / gtk / gtksctree.c
index 06bb7390f877fdf1f891125389f598b6c14411d7..851c0eadae1fc8705b1e479573a098035409c405 100644 (file)
@@ -6,7 +6,16 @@
 
 #include "gtksctree.h"
 #include "sylpheed-marshal.h"
+#include "stock_pixmap.h"
 
+#define CLIST_UNFROZEN(clist)     (((GtkCList*) (clist))->freeze_count == 0)
+#define CLIST_REFRESH(clist)    G_STMT_START { \
+  if (CLIST_UNFROZEN (clist)) \
+    GTK_CLIST_GET_CLASS (clist)->refresh ((GtkCList*) (clist)); \
+} G_STMT_END
+#define CELL_SPACING               1
+#define CLIST_OPTIMUM_SIZE         64
+#define COLUMN_INSET               3
 
 enum {
        ROW_POPUP_MENU,
@@ -16,6 +25,8 @@ enum {
        LAST_SIGNAL
 };
 
+static GdkPixmap *emptyxpm = NULL;
+static GdkBitmap *emptyxpmmask = NULL;
 
 static void gtk_sctree_class_init (GtkSCTreeClass *class);
 static void gtk_sctree_init (GtkSCTree *sctree);
@@ -39,26 +50,33 @@ static void gtk_sctree_drag_data_received (GtkWidget *widget, GdkDragContext *co
 static void gtk_sctree_clear (GtkCList *clist);
 static void gtk_sctree_collapse (GtkCTree *ctree, GtkCTreeNode *node);
        
-static void tree_sort (GtkCTree *ctree, GtkCTreeNode  *node, gpointer data);
+static void stree_sort (GtkCTree *ctree, GtkCTreeNode  *node, gpointer data);
 void gtk_sctree_sort_node (GtkCTree *ctree, GtkCTreeNode *node);
 void gtk_sctree_sort_recursive (GtkCTree *ctree, GtkCTreeNode *node);
 
-static void gtk_ctree_link (GtkCTree *ctree,
+static void gtk_sctree_link (GtkCTree *ctree,
                        GtkCTreeNode  *node,
                        GtkCTreeNode  *parent,
                        GtkCTreeNode  *sibling,
                        gboolean       update_focus_row);
 
-static void gtk_ctree_unlink (GtkCTree      *ctree, 
+static void gtk_sctree_unlink (GtkCTree      *ctree, 
                        GtkCTreeNode  *node,
                        gboolean       update_focus_row);
 
-static void tree_update_level (GtkCTree      *ctree, 
+static void stree_update_level (GtkCTree      *ctree, 
                        GtkCTreeNode  *node, 
                        gpointer       data);
 
-static GtkCTreeNode * gtk_ctree_last_visible (GtkCTree     *ctree,
+static GtkCTreeNode * gtk_sctree_last_visible (GtkCTree     *ctree,
                                              GtkCTreeNode *node);
+static void gtk_sctree_real_tree_expand            (GtkCTree      *ctree,
+                                                GtkCTreeNode  *node);
+static void
+sreal_tree_move (GtkCTree     *ctree,
+               GtkCTreeNode *node,
+               GtkCTreeNode *new_parent, 
+               GtkCTreeNode *new_sibling);
 
 static GtkCTreeClass *parent_class;
 
@@ -157,6 +175,8 @@ gtk_sctree_class_init (GtkSCTreeClass *klass)
 
        clist_class->clear = gtk_sctree_clear;
         ctree_class->tree_collapse = gtk_sctree_collapse;
+       ctree_class->tree_expand = gtk_sctree_real_tree_expand;
+       ctree_class->tree_move = sreal_tree_move;
        
        widget_class->button_press_event = gtk_sctree_button_press;
        widget_class->button_release_event = gtk_sctree_button_release;
@@ -198,7 +218,7 @@ select_range (GtkSCTree *sctree, gint row)
        gint prev_row;
        gint min, max;
        gint i;
-
+       GList *node;
        if (sctree->anchor_row == NULL) {
                prev_row = row;
                sctree->anchor_row = gtk_ctree_node_nth(GTK_CTREE(sctree), row);
@@ -214,13 +234,32 @@ select_range (GtkSCTree *sctree, gint row)
                min = prev_row;
                max = row;
        }
-       for (i = min; i <= max; i++)
-               gtk_clist_select_row (GTK_CLIST (sctree), i, -1);
+       sctree->selecting_range = TRUE;
+       
+       if (max < min) {
+               int t = min;
+               min = max;
+               max = t;
+       }
+       node = g_list_nth((GTK_CLIST(sctree))->row_list, min);
+       for (i = min; i < max; i++) {
+               if (node && GTK_CTREE_ROW (node)->row.selectable) {
+                       g_signal_emit_by_name(G_OBJECT(sctree), "tree_select_row",
+                               node, -1);
+               }
+               node = node->next;
+       }
+
+       sctree->selecting_range = FALSE;
+       gtk_clist_select_row (GTK_CLIST (sctree), max, -1);
 }
 
 /* Handles row selection according to the specified modifier state */
+/* in certain cases, we arrive here from a function knowing the GtkCTreeNode, and having
+ * already slowly found row using g_list_position. In which case, _node will be non-NULL
+ * to avoid this function having to slowly find it with g_list_nth. */
 static void
-select_row (GtkSCTree *sctree, gint row, gint col, guint state)
+select_row (GtkSCTree *sctree, gint row, gint col, guint state, GtkCTreeNode *_node)
 {
        gboolean range, additive;
        g_return_if_fail (sctree != NULL);
@@ -233,16 +272,41 @@ select_row (GtkSCTree *sctree, gint row, gint col, guint state)
                   (GTK_CLIST(sctree)->selection_mode != GTK_SELECTION_SINGLE) &&
                   (GTK_CLIST(sctree)->selection_mode != GTK_SELECTION_BROWSE);
 
-       gtk_clist_freeze (GTK_CLIST (sctree));
+       /* heavy GUI updates will be done only if we're selecting a range.
+        * additive selection is ctrl-click, where the user is the slow factor.
+        * So we only freeze the list if selecting a range (potentially thousands 
+        * of lines. */
+       if (range)
+               gtk_clist_freeze (GTK_CLIST (sctree));
+
        GTK_CLIST(sctree)->focus_row = row;
-       GTK_CLIST_GET_CLASS(sctree)->refresh(GTK_CLIST(sctree));
-       if (!additive)
+
+       if (!additive) {
+               /* if this selection isn't additive, we have to unselect what
+                * is selected. Here, heavy GUI updates can occur if we have 
+                * a big selection. See if more than one line is selected, in
+                * which case, freeze, else don't. */
+
+               gboolean should_freeze = FALSE;
+               if (GTK_CLIST(sctree)->selection
+                && GTK_CLIST(sctree)->selection->next) {
+                       should_freeze = TRUE;
+                       sctree->selecting_range = TRUE;
+                       gtk_clist_freeze (GTK_CLIST (sctree));
+               }
+
                gtk_clist_unselect_all (GTK_CLIST (sctree));
 
+               if (should_freeze) {
+                       gtk_clist_thaw (GTK_CLIST (sctree));
+                       sctree->selecting_range = FALSE;
+               }
+       }
+
        if (!range) {
                GtkCTreeNode *node;
 
-               node = gtk_ctree_node_nth (GTK_CTREE(sctree), row);
+               node = _node ? _node : gtk_ctree_node_nth (GTK_CTREE(sctree), row);
 
                /*No need to manage overlapped list*/
                if (additive) {
@@ -260,7 +324,9 @@ select_row (GtkSCTree *sctree, gint row, gint col, guint state)
                sctree->anchor_row = node;
        } else
                select_range (sctree, row);
-       gtk_clist_thaw (GTK_CLIST (sctree));
+       
+       if (range)
+               gtk_clist_thaw (GTK_CLIST (sctree));
 }
 
 /* Our handler for button_press events.  We override all of GtkCList's broken
@@ -318,22 +384,28 @@ gtk_sctree_button_press (GtkWidget *widget, GdkEventButton *event)
                                        sctree->dnd_select_pending = TRUE;
                                        sctree->dnd_select_pending_state = event->state;
                                        sctree->dnd_select_pending_row = row;
-                               } else
-                                       select_row (sctree, row, col, event->state);
-                       } else
+                               } else {
+                                       select_row (sctree, row, col, event->state, NULL);
+                               }
+                       } else {
+                               sctree->selecting_range = TRUE;
                                gtk_clist_unselect_all (clist);
+                               sctree->selecting_range = FALSE;
+                       }
 
                        retval = TRUE;
                } else if (event->button == 3) {
                        /* Emit *_popup_menu signal*/
                        if (on_row) {
                                if (!row_is_selected(sctree,row))
-                                       select_row (sctree, row, col, 0);
+                                       select_row (sctree, row, col, 0, NULL);
                                g_signal_emit (G_OBJECT (sctree),
                                                 sctree_signals[ROW_POPUP_MENU],
                                                 0, event);
                        } else {
+                               sctree->selecting_range = TRUE;
                                gtk_clist_unselect_all(clist);
+                               sctree->selecting_range = FALSE;
                                g_signal_emit (G_OBJECT (sctree),
                                                 sctree_signals[EMPTY_POPUP_MENU],
                                                 0, event);
@@ -398,7 +470,7 @@ gtk_sctree_button_release (GtkWidget *widget, GdkEventButton *event)
 
        if (on_row) {
                if (sctree->dnd_select_pending) {
-                       select_row (sctree, row, col, sctree->dnd_select_pending_state);
+                       select_row (sctree, row, col, sctree->dnd_select_pending_state, NULL);
                        sctree->dnd_select_pending = FALSE;
                        sctree->dnd_select_pending_state = 0;
                }
@@ -445,7 +517,8 @@ gtk_sctree_motion (GtkWidget *widget, GdkEventMotion *event)
                        select_row (sctree,
                                    sctree->dnd_select_pending_row,
                                    -1,
-                                   sctree->dnd_select_pending_state);
+                                   sctree->dnd_select_pending_state,
+                                   NULL);
 
                sctree->dnd_select_pending = FALSE;
                sctree->dnd_select_pending_state = 0;
@@ -548,16 +621,6 @@ gtk_sctree_collapse (GtkCTree *ctree, GtkCTreeNode *node)
 GtkWidget *gtk_sctree_new_with_titles (gint columns, gint tree_column, 
                                       gchar *titles[])
 {
-#if 0
-       GtkSCTree* sctree;
-
-       sctree = gtk_type_new (gtk_sctree_get_type ());
-       gtk_ctree_construct (GTK_CTREE (sctree), columns, tree_column, titles);
-       
-       gtk_clist_set_selection_mode(GTK_CLIST(sctree), GTK_SELECTION_EXTENDED);
-
-       return GTK_WIDGET (sctree);
-#else
        GtkWidget *widget;
                                                                                                             
        g_return_val_if_fail (columns > 0, NULL);
@@ -577,26 +640,29 @@ GtkWidget *gtk_sctree_new_with_titles (gint columns, gint tree_column,
        }
 
        return widget;
-#endif
 }
 
 void gtk_sctree_select (GtkSCTree *sctree, GtkCTreeNode *node)
 {
        select_row(sctree, 
                   g_list_position(GTK_CLIST(sctree)->row_list, (GList *)node),
-                  -1, 0);
+                  -1, 0, node);
 }
 
 void gtk_sctree_select_with_state (GtkSCTree *sctree, GtkCTreeNode *node, int state)
 {
        select_row(sctree, 
                   g_list_position(GTK_CLIST(sctree)->row_list, (GList *)node),
-                  -1, state);
+                  -1, state, node);
 }
 
 void gtk_sctree_unselect_all (GtkSCTree *sctree)
 {
+       sctree->selecting_range = TRUE;
+       gtk_clist_freeze(GTK_CLIST(sctree));
        gtk_clist_unselect_all(GTK_CLIST(sctree));
+       gtk_clist_thaw(GTK_CLIST(sctree));
+       sctree->selecting_range = FALSE;
        sctree->anchor_row = NULL;
 }
 
@@ -605,6 +671,13 @@ void gtk_sctree_set_anchor_row (GtkSCTree *sctree, GtkCTreeNode *node)
        sctree->anchor_row = node;
 }
 
+void gtk_sctree_remove_node (GtkSCTree *sctree, GtkCTreeNode *node)
+{
+       if (sctree->anchor_row == node)
+               sctree->anchor_row = NULL;
+       gtk_ctree_remove_node(GTK_CTREE(sctree), node);
+}
+
 /***********************************************************
  *             Tree sorting functions                      *
  ***********************************************************/
@@ -656,7 +729,7 @@ static void heap_sort(GtkCList *clist, GPtrArray *numbers, gint array_size)
 }
 
 static void
-tree_sort (GtkCTree    *ctree,
+stree_sort (GtkCTree    *ctree,
           GtkCTreeNode *node,
           gpointer      data)
 {
@@ -665,7 +738,6 @@ tree_sort (GtkCTree    *ctree,
        GtkCList *clist;
        gint i;
 
-
        clist = GTK_CLIST (ctree);
 
        if (node)
@@ -684,7 +756,7 @@ tree_sort (GtkCTree    *ctree,
                        if (GTK_CTREE_ROW (work)->parent && gtk_ctree_is_viewable( ctree, work))
                                g_ptr_array_add( viewable_array, GTK_CTREE_ROW (work)->parent);
                        next = GTK_CTREE_ROW (work)->sibling;
-                       gtk_ctree_unlink( ctree, work, FALSE);
+                       gtk_sctree_unlink( ctree, work, FALSE);
                        work = next;
                }
 
@@ -698,14 +770,14 @@ tree_sort (GtkCTree    *ctree,
                if (clist->sort_type == GTK_SORT_ASCENDING) {
                        for (i=(row_array->len)-1; i>=1; i--) {
                                work = g_ptr_array_index( row_array, i);
-                               gtk_ctree_link( ctree, work, node, list_start, FALSE);
+                               gtk_sctree_link( ctree, work, node, list_start, FALSE);
                                list_start = work;
                                /* insert work at the beginning of the list */
                        }
                } else {
                        for (i=1; i<row_array->len; i++) {
                                work = g_ptr_array_index( row_array, i);
-                               gtk_ctree_link( ctree, work, node, list_start, FALSE);
+                               gtk_sctree_link( ctree, work, node, list_start, FALSE);
                                list_start = work;
                                /* insert work at the beginning of the list */
                        }
@@ -716,7 +788,6 @@ tree_sort (GtkCTree    *ctree,
                }
                
        }
-
        g_ptr_array_free( row_array, TRUE);
        g_ptr_array_free( viewable_array, TRUE);
 }
@@ -747,10 +818,14 @@ gtk_sctree_sort_recursive (GtkCTree     *ctree,
        if (!node || (node && gtk_ctree_is_viewable (ctree, node)))
                focus_node = GTK_CTREE_NODE (g_list_nth (clist->row_list, clist->focus_row));
       
-       gtk_ctree_post_recursive (ctree, node, GTK_CTREE_FUNC (tree_sort), NULL);
+       GTK_SCTREE(ctree)->sorting = TRUE;
+
+       gtk_ctree_post_recursive (ctree, node, GTK_CTREE_FUNC (stree_sort), NULL);
 
        if (!node)
-               tree_sort (ctree, NULL, NULL);
+               stree_sort (ctree, NULL, NULL);
+
+       GTK_SCTREE(ctree)->sorting = FALSE;
 
        if (focus_node) {
                clist->focus_row = g_list_position (clist->row_list,(GList *)focus_node);
@@ -786,7 +861,11 @@ gtk_sctree_sort_node (GtkCTree     *ctree,
        if (!node || (node && gtk_ctree_is_viewable (ctree, node)))
                focus_node = GTK_CTREE_NODE (g_list_nth (clist->row_list, clist->focus_row));
 
-       tree_sort (ctree, node, NULL);
+       GTK_SCTREE(ctree)->sorting = TRUE;
+
+       stree_sort (ctree, node, NULL);
+
+       GTK_SCTREE(ctree)->sorting = FALSE;
 
        if (focus_node) {
                clist->focus_row = g_list_position (clist->row_list,(GList *)focus_node);
@@ -799,7 +878,7 @@ gtk_sctree_sort_node (GtkCTree     *ctree,
 /************************************************************************/
 
 static void
-gtk_ctree_unlink (GtkCTree     *ctree, 
+gtk_sctree_unlink (GtkCTree     *ctree, 
                  GtkCTreeNode *node,
                   gboolean      update_focus_row)
 {
@@ -848,7 +927,6 @@ gtk_ctree_unlink (GtkCTree     *ctree,
 
                if (update_focus_row) {
                        gint pos;
-
                        pos = g_list_position (clist->row_list, (GList *)node);
                        if (pos + rows < clist->focus_row)
                                clist->focus_row -= (rows + 1);
@@ -882,8 +960,6 @@ gtk_ctree_unlink (GtkCTree     *ctree,
        if (parent) {
                if (GTK_CTREE_ROW (parent)->children == node) {
                        GTK_CTREE_ROW (parent)->children = GTK_CTREE_ROW (node)->sibling;
-                       if (!GTK_CTREE_ROW (parent)->children)
-                               gtk_ctree_collapse (ctree, parent);
                }
                else {
                        GtkCTreeNode *sibling;
@@ -909,7 +985,7 @@ gtk_ctree_unlink (GtkCTree     *ctree,
 }
 
 static void
-gtk_ctree_link (GtkCTree     *ctree,
+gtk_sctree_link (GtkCTree     *ctree,
                GtkCTreeNode *node,
                GtkCTreeNode *parent,
                GtkCTreeNode *sibling,
@@ -988,7 +1064,7 @@ gtk_ctree_link (GtkCTree     *ctree,
                        GTK_CTREE_ROW (work)->sibling = node;
 
                        /* find last visible child of sibling */
-                       work = (GList *) gtk_ctree_last_visible (ctree,
+                       work = (GList *) gtk_sctree_last_visible (ctree,
                               GTK_CTREE_NODE (work));
 
                        list_end->next = work->next;
@@ -1024,7 +1100,7 @@ gtk_ctree_link (GtkCTree     *ctree,
                }
        }
 
-       gtk_ctree_pre_recursive (ctree, node, tree_update_level, NULL); 
+       gtk_ctree_pre_recursive (ctree, node, stree_update_level, NULL); 
 
        if (clist->row_list_end == NULL ||
            clist->row_list_end->next == (GList *)node)
@@ -1032,7 +1108,6 @@ gtk_ctree_link (GtkCTree     *ctree,
 
        if (visible && update_focus_row) {
                gint pos;
-         
                pos = g_list_position (clist->row_list, (GList *)node);
   
                if (pos <= clist->focus_row) {
@@ -1043,7 +1118,7 @@ gtk_ctree_link (GtkCTree     *ctree,
 }
 
 static void
-tree_update_level (GtkCTree     *ctree, 
+stree_update_level (GtkCTree     *ctree, 
                   GtkCTreeNode *node, 
                   gpointer      data)
 {
@@ -1058,7 +1133,7 @@ tree_update_level (GtkCTree     *ctree,
 }
 
 static GtkCTreeNode *
-gtk_ctree_last_visible (GtkCTree     *ctree,
+gtk_sctree_last_visible (GtkCTree     *ctree,
                        GtkCTreeNode *node)
 {
        GtkCTreeNode *work;
@@ -1074,5 +1149,741 @@ gtk_ctree_last_visible (GtkCTree     *ctree,
        while (GTK_CTREE_ROW (work)->sibling)
                work = GTK_CTREE_ROW (work)->sibling;
 
-       return gtk_ctree_last_visible (ctree, work);
+       return gtk_sctree_last_visible (ctree, work);
 }
+
+/* this wrapper simply replaces NULL pixmaps 
+ * with a transparent, 1x1 pixmap. This works
+ * around a memory problem deep inside gtk, 
+ * revealed by valgrind. 
+ */
+/*GtkCTreeNode* gtk_sctree_insert_node        (GtkCTree *ctree,
+                                             GtkCTreeNode *parent,
+                                             GtkCTreeNode *sibling,
+                                             gchar *text[],
+                                             guint8 spacing,
+                                             GdkPixmap *pixmap_closed,
+                                             GdkBitmap *mask_closed,
+                                             GdkPixmap *pixmap_opened,
+                                             GdkBitmap *mask_opened,
+                                             gboolean is_leaf,
+                                             gboolean expanded)
+{
+       if (!emptyxpm) {
+               stock_pixmap_gdk(GTK_WIDGET(ctree), STOCK_PIXMAP_EMPTY,
+                        &emptyxpm, &emptyxpmmask);
+       }
+       if (!pixmap_closed) {
+               pixmap_closed = emptyxpm;
+               mask_closed = emptyxpmmask;
+       }
+       if (!pixmap_opened) {
+               pixmap_opened = emptyxpm;
+               mask_opened = emptyxpmmask;
+       }
+       return gtk_ctree_insert_node(ctree, parent, sibling, text,spacing,
+               pixmap_closed, mask_closed, pixmap_opened, mask_opened,
+               is_leaf, expanded);
+}*/
+
+static void 
+sset_node_info (GtkCTree     *ctree,
+              GtkCTreeNode *node,
+              const gchar  *text,
+              guint8        spacing,
+              GdkPixmap    *pixmap_closed,
+              GdkBitmap    *mask_closed,
+              GdkPixmap    *pixmap_opened,
+              GdkBitmap    *mask_opened,
+              gboolean      is_leaf,
+              gboolean      expanded)
+{
+  if (GTK_CTREE_ROW (node)->pixmap_opened)
+    {
+      gdk_pixmap_unref (GTK_CTREE_ROW (node)->pixmap_opened);
+      if (GTK_CTREE_ROW (node)->mask_opened) 
+       gdk_bitmap_unref (GTK_CTREE_ROW (node)->mask_opened);
+    }
+  if (GTK_CTREE_ROW (node)->pixmap_closed)
+    {
+      gdk_pixmap_unref (GTK_CTREE_ROW (node)->pixmap_closed);
+      if (GTK_CTREE_ROW (node)->mask_closed) 
+       gdk_bitmap_unref (GTK_CTREE_ROW (node)->mask_closed);
+    }
+
+  GTK_CTREE_ROW (node)->pixmap_opened = NULL;
+  GTK_CTREE_ROW (node)->mask_opened   = NULL;
+  GTK_CTREE_ROW (node)->pixmap_closed = NULL;
+  GTK_CTREE_ROW (node)->mask_closed   = NULL;
+
+  if (pixmap_closed)
+    {
+      GTK_CTREE_ROW (node)->pixmap_closed = gdk_pixmap_ref (pixmap_closed);
+      if (mask_closed) 
+       GTK_CTREE_ROW (node)->mask_closed = gdk_bitmap_ref (mask_closed);
+    }
+  if (pixmap_opened)
+    {
+      GTK_CTREE_ROW (node)->pixmap_opened = gdk_pixmap_ref (pixmap_opened);
+      if (mask_opened) 
+       GTK_CTREE_ROW (node)->mask_opened = gdk_bitmap_ref (mask_opened);
+    }
+
+  GTK_CTREE_ROW (node)->is_leaf  = is_leaf;
+  GTK_CTREE_ROW (node)->expanded = (is_leaf) ? FALSE : expanded;
+
+  if (GTK_CTREE_ROW (node)->expanded)
+    gtk_ctree_node_set_pixtext (ctree, node, ctree->tree_column,
+                               text, spacing, pixmap_opened, mask_opened);
+  else 
+    gtk_ctree_node_set_pixtext (ctree, node, ctree->tree_column,
+                               text, spacing, pixmap_closed, mask_closed);
+}
+
+static void
+stree_draw_node (GtkCTree     *ctree, 
+               GtkCTreeNode *node)
+{
+  GtkCList *clist;
+  
+  clist = GTK_CLIST (ctree);
+
+  if (CLIST_UNFROZEN (clist) && gtk_ctree_is_viewable (ctree, node))
+    {
+      GtkCTreeNode *work;
+      gint num = 0;
+      
+      work = GTK_CTREE_NODE (clist->row_list);
+      while (work && work != node)
+       {
+         work = GTK_CTREE_NODE_NEXT (work);
+         num++;
+       }
+      if (work && gtk_clist_row_is_visible (clist, num) != GTK_VISIBILITY_NONE)
+       GTK_CLIST_GET_CLASS (clist)->draw_row
+         (clist, NULL, num, GTK_CLIST_ROW ((GList *) node));
+    }
+}
+
+/* this wrapper simply replaces NULL pixmaps 
+ * with a transparent, 1x1 pixmap. This works
+ * around a memory problem deep inside gtk, 
+ * revealed by valgrind. 
+ */
+void        gtk_sctree_set_node_info        (GtkCTree *ctree,
+                                             GtkCTreeNode *node,
+                                             const gchar *text,
+                                             guint8 spacing,
+                                             GdkPixmap *pixmap_closed,
+                                             GdkBitmap *mask_closed,
+                                             GdkPixmap *pixmap_opened,
+                                             GdkBitmap *mask_opened,
+                                             gboolean is_leaf,
+                                             gboolean expanded)
+{
+  gboolean old_leaf;
+  gboolean old_expanded;
+  GtkCTreeNode *work;
+  if (!emptyxpm) {
+         stock_pixmap_gdk(GTK_WIDGET(ctree), STOCK_PIXMAP_EMPTY,
+                  &emptyxpm, &emptyxpmmask);
+  }
+  if (!pixmap_closed) {
+         pixmap_closed = emptyxpm;
+         mask_closed = emptyxpmmask;
+  }
+  if (!pixmap_opened) {
+         pixmap_opened = emptyxpm;
+         mask_opened = emptyxpmmask;
+  }
+
+  if (!GTK_IS_CTREE (ctree) || !node) return;
+
+  old_leaf = GTK_CTREE_ROW (node)->is_leaf;
+  old_expanded = GTK_CTREE_ROW (node)->expanded;
+
+  if (is_leaf && (work = GTK_CTREE_ROW (node)->children) != NULL)
+    {
+      GtkCTreeNode *ptr;
+      
+      while (work)
+       {
+         ptr = work;
+         work = GTK_CTREE_ROW (work)->sibling;
+         gtk_ctree_remove_node (ctree, ptr);
+       }
+    }
+
+  sset_node_info (ctree, node, text, spacing, pixmap_closed, mask_closed,
+                pixmap_opened, mask_opened, is_leaf, expanded);
+
+  if (!is_leaf && !old_leaf)
+    {
+      GTK_CTREE_ROW (node)->expanded = old_expanded;
+      if (expanded && !old_expanded)
+       gtk_ctree_expand (ctree, node);
+      else if (!expanded && old_expanded)
+       gtk_ctree_collapse (ctree, node);
+    }
+
+  GTK_CTREE_ROW (node)->expanded = (is_leaf) ? FALSE : expanded;
+  
+  stree_draw_node (ctree, node);
+}
+
+static GtkCTreeRow *
+srow_new (GtkCTree *ctree)
+{
+  GtkCList *clist;
+  GtkCTreeRow *ctree_row;
+  int i;
+
+  clist = GTK_CLIST (ctree);
+#if GTK_CHECK_VERSION(2,9,0)
+  ctree_row = g_slice_new (GtkCTreeRow);
+  ctree_row->row.cell = g_slice_alloc (sizeof (GtkCell) * clist->columns);
+#else
+  ctree_row = g_chunk_new (GtkCTreeRow, (GMemChunk *)clist->row_mem_chunk);
+  ctree_row->row.cell = g_chunk_new (GtkCell, (GMemChunk *)clist->cell_mem_chunk);
+#endif
+  for (i = 0; i < clist->columns; i++)
+    {
+      ctree_row->row.cell[i].type = GTK_CELL_EMPTY;
+      ctree_row->row.cell[i].vertical = 0;
+      ctree_row->row.cell[i].horizontal = 0;
+      ctree_row->row.cell[i].style = NULL;
+    }
+
+  GTK_CELL_PIXTEXT (ctree_row->row.cell[ctree->tree_column])->text = NULL;
+
+  ctree_row->row.fg_set     = FALSE;
+  ctree_row->row.bg_set     = FALSE;
+  ctree_row->row.style      = NULL;
+  ctree_row->row.selectable = TRUE;
+  ctree_row->row.state      = GTK_STATE_NORMAL;
+  ctree_row->row.data       = NULL;
+  ctree_row->row.destroy    = NULL;
+
+  ctree_row->level         = 0;
+  ctree_row->expanded      = FALSE;
+  ctree_row->parent        = NULL;
+  ctree_row->sibling       = NULL;
+  ctree_row->children      = NULL;
+  ctree_row->pixmap_closed = NULL;
+  ctree_row->mask_closed   = NULL;
+  ctree_row->pixmap_opened = NULL;
+  ctree_row->mask_opened   = NULL;
+  
+  return ctree_row;
+}
+
+static void
+srow_delete (GtkCTree    *ctree,
+           GtkCTreeRow *ctree_row)
+{
+  GtkCList *clist;
+  gint i;
+
+  clist = GTK_CLIST (ctree);
+
+  for (i = 0; i < clist->columns; i++)
+    {
+      GTK_CLIST_GET_CLASS (clist)->set_cell_contents
+       (clist, &(ctree_row->row), i, GTK_CELL_EMPTY, NULL, 0, NULL, NULL);
+      if (ctree_row->row.cell[i].style)
+       {
+         if (GTK_WIDGET_REALIZED (ctree))
+           gtk_style_detach (ctree_row->row.cell[i].style);
+         g_object_unref (ctree_row->row.cell[i].style);
+       }
+    }
+
+  if (ctree_row->row.style)
+    {
+      if (GTK_WIDGET_REALIZED (ctree))
+       gtk_style_detach (ctree_row->row.style);
+      g_object_unref (ctree_row->row.style);
+    }
+
+  if (ctree_row->pixmap_closed)
+    {
+      gdk_pixmap_unref (ctree_row->pixmap_closed);
+      if (ctree_row->mask_closed)
+       gdk_bitmap_unref (ctree_row->mask_closed);
+    }
+
+  if (ctree_row->pixmap_opened)
+    {
+      gdk_pixmap_unref (ctree_row->pixmap_opened);
+      if (ctree_row->mask_opened)
+       gdk_bitmap_unref (ctree_row->mask_opened);
+    }
+
+  if (ctree_row->row.destroy)
+    {
+      GtkDestroyNotify dnotify = ctree_row->row.destroy;
+      gpointer ddata = ctree_row->row.data;
+
+      ctree_row->row.destroy = NULL;
+      ctree_row->row.data = NULL;
+
+      dnotify (ddata);
+    }
+
+#if GTK_CHECK_VERSION(2,9,0)  
+  g_slice_free1 (sizeof (GtkCell) * clist->columns, ctree_row->row.cell);
+  g_slice_free (GtkCTreeRow, ctree_row);
+#else
+  g_mem_chunk_free ((GMemChunk *)clist->cell_mem_chunk, ctree_row->row.cell);
+  g_mem_chunk_free ((GMemChunk *)clist->row_mem_chunk, ctree_row);
+#endif
+}
+
+static void
+stree_delete_row (GtkCTree     *ctree, 
+                GtkCTreeNode *node, 
+                gpointer      data)
+{
+  srow_delete (ctree, GTK_CTREE_ROW (node));
+  g_list_free_1 ((GList *)node);
+}
+
+static void
+gtk_sctree_column_auto_resize (GtkCList    *clist,
+                   GtkCListRow *clist_row,
+                   gint         column,
+                   gint         old_width)
+{
+  /* resize column if needed for auto_resize */
+  GtkRequisition requisition;
+
+  if (!clist->column[column].auto_resize ||
+      GTK_CLIST_AUTO_RESIZE_BLOCKED (clist))
+    return;
+
+  if (clist_row)
+    GTK_CLIST_GET_CLASS (clist)->cell_size_request (clist, clist_row,
+                                                  column, &requisition);
+  else
+    requisition.width = 0;
+
+  if (requisition.width > clist->column[column].width)
+    gtk_clist_set_column_width (clist, column, requisition.width);
+  else if (requisition.width < old_width &&
+          old_width == clist->column[column].width)
+    {
+      GList *list;
+      gint new_width;
+
+      /* run a "gtk_clist_optimal_column_width" but break, if
+       * the column doesn't shrink */
+      if (GTK_CLIST_SHOW_TITLES (clist) && clist->column[column].button)
+       new_width = (clist->column[column].button->requisition.width -
+                    (CELL_SPACING + (2 * COLUMN_INSET)));
+      else
+       new_width = 0;
+
+      for (list = clist->row_list; list; list = list->next)
+       {
+         GTK_CLIST_GET_CLASS (clist)->cell_size_request
+           (clist, GTK_CLIST_ROW (list), column, &requisition);
+         new_width = MAX (new_width, requisition.width);
+         if (new_width == clist->column[column].width)
+           break;
+       }
+      if (new_width < clist->column[column].width)
+       gtk_clist_set_column_width (clist, column, new_width);
+    }
+}
+
+
+static void 
+gtk_sctree_real_tree_expand (GtkCTree     *ctree,
+                 GtkCTreeNode *node)
+{
+  GtkCList *clist;
+  GtkCTreeNode *work;
+  GtkRequisition requisition;
+  gboolean visible;
+  gint level;
+
+  g_return_if_fail (GTK_IS_CTREE (ctree));
+
+  if (!node || GTK_CTREE_ROW (node)->expanded || GTK_CTREE_ROW (node)->is_leaf)
+    return;
+
+  clist = GTK_CLIST (ctree);
+  
+  GTK_CLIST_GET_CLASS (clist)->resync_selection (clist, NULL);
+
+  GTK_CTREE_ROW (node)->expanded = TRUE;
+  level = GTK_CTREE_ROW (node)->level;
+
+  visible = gtk_ctree_is_viewable (ctree, node);
+  /* get cell width if tree_column is auto resized */
+  if (visible && clist->column[ctree->tree_column].auto_resize &&
+      !GTK_CLIST_AUTO_RESIZE_BLOCKED (clist))
+    GTK_CLIST_GET_CLASS (clist)->cell_size_request
+      (clist, &GTK_CTREE_ROW (node)->row, ctree->tree_column, &requisition);
+
+  /* unref/unset closed pixmap */
+  if (GTK_CELL_PIXTEXT 
+      (GTK_CTREE_ROW (node)->row.cell[ctree->tree_column])->pixmap)
+    {
+      gdk_pixmap_unref
+       (GTK_CELL_PIXTEXT
+        (GTK_CTREE_ROW (node)->row.cell[ctree->tree_column])->pixmap);
+      
+      GTK_CELL_PIXTEXT
+       (GTK_CTREE_ROW (node)->row.cell[ctree->tree_column])->pixmap = NULL;
+      
+      if (GTK_CELL_PIXTEXT 
+         (GTK_CTREE_ROW (node)->row.cell[ctree->tree_column])->mask)
+       {
+         gdk_pixmap_unref
+           (GTK_CELL_PIXTEXT 
+            (GTK_CTREE_ROW (node)->row.cell[ctree->tree_column])->mask);
+         GTK_CELL_PIXTEXT 
+           (GTK_CTREE_ROW (node)->row.cell[ctree->tree_column])->mask = NULL;
+       }
+    }
+
+  /* set/ref opened pixmap */
+  if (GTK_CTREE_ROW (node)->pixmap_opened)
+    {
+      GTK_CELL_PIXTEXT 
+       (GTK_CTREE_ROW (node)->row.cell[ctree->tree_column])->pixmap = 
+       gdk_pixmap_ref (GTK_CTREE_ROW (node)->pixmap_opened);
+
+      if (GTK_CTREE_ROW (node)->mask_opened) 
+       GTK_CELL_PIXTEXT 
+         (GTK_CTREE_ROW (node)->row.cell[ctree->tree_column])->mask = 
+         gdk_pixmap_ref (GTK_CTREE_ROW (node)->mask_opened);
+    }
+
+
+  work = GTK_CTREE_ROW (node)->children;
+  if (work)
+    {
+      GList *list = (GList *)work;
+      gint *cell_width = NULL;
+      gint tmp = 0;
+      gint row;
+      gint i;
+      
+      if (visible && !GTK_CLIST_AUTO_RESIZE_BLOCKED (clist))
+       {
+         cell_width = g_new0 (gint, clist->columns);
+         if (clist->column[ctree->tree_column].auto_resize)
+             cell_width[ctree->tree_column] = requisition.width;
+
+         while (work)
+           {
+             /* search maximum cell widths of auto_resize columns */
+             for (i = 0; i < clist->columns; i++)
+               if (clist->column[i].auto_resize)
+                 {
+                   GTK_CLIST_GET_CLASS (clist)->cell_size_request
+                     (clist, &GTK_CTREE_ROW (work)->row, i, &requisition);
+                   cell_width[i] = MAX (requisition.width, cell_width[i]);
+                 }
+
+             list = (GList *)work;
+             work = GTK_CTREE_NODE_NEXT (work);
+             tmp++;
+           }
+       }
+      else
+       while (work)
+         {
+           list = (GList *)work;
+           work = GTK_CTREE_NODE_NEXT (work);
+           tmp++;
+         }
+
+      list->next = (GList *)GTK_CTREE_NODE_NEXT (node);
+
+      if (GTK_CTREE_NODE_NEXT (node))
+       {
+         GList *tmp_list;
+
+         tmp_list = (GList *)GTK_CTREE_NODE_NEXT (node);
+         tmp_list->prev = list;
+       }
+      else
+       clist->row_list_end = list;
+
+      list = (GList *)node;
+      list->next = (GList *)(GTK_CTREE_ROW (node)->children);
+
+      if (visible)
+       {
+         /* resize auto_resize columns if needed */
+         for (i = 0; i < clist->columns; i++)
+           if (clist->column[i].auto_resize &&
+               cell_width[i] > clist->column[i].width)
+             gtk_clist_set_column_width (clist, i, cell_width[i]);
+         g_free (cell_width);
+       
+         if (!GTK_SCTREE(ctree)->sorting) {
+                 /* update focus_row position */
+                 row = g_list_position (clist->row_list, (GList *)node);
+                 if (row < clist->focus_row)
+                   clist->focus_row += tmp;
+         }
+         clist->rows += tmp;
+         CLIST_REFRESH (clist);
+       }
+    }
+  else if (visible && clist->column[ctree->tree_column].auto_resize)
+    /* resize tree_column if needed */
+    gtk_sctree_column_auto_resize (clist, &GTK_CTREE_ROW (node)->row, ctree->tree_column,
+                       requisition.width);
+}
+
+GtkCTreeNode * 
+gtk_sctree_insert_node (GtkCTree     *ctree,
+                      GtkCTreeNode *parent, 
+                      GtkCTreeNode *sibling,
+                      gchar        *text[],
+                      guint8        spacing,
+                      GdkPixmap    *pixmap_closed,
+                      GdkBitmap    *mask_closed,
+                      GdkPixmap    *pixmap_opened,
+                      GdkBitmap    *mask_opened,
+                      gboolean      is_leaf,
+                      gboolean      expanded)
+{
+  GtkCList *clist;
+  GtkCTreeRow *new_row;
+  GtkCTreeNode *node;
+  GList *list;
+  gint i;
+
+  if (!emptyxpm) {
+         stock_pixmap_gdk(GTK_WIDGET(ctree), STOCK_PIXMAP_EMPTY,
+                  &emptyxpm, &emptyxpmmask);
+  }
+  if (!pixmap_closed) {
+         pixmap_closed = emptyxpm;
+         mask_closed = emptyxpmmask;
+  }
+  if (!pixmap_opened) {
+         pixmap_opened = emptyxpm;
+         mask_opened = emptyxpmmask;
+  }
+  g_return_val_if_fail (GTK_IS_CTREE (ctree), NULL);
+  if (sibling)
+    g_return_val_if_fail (GTK_CTREE_ROW (sibling)->parent == parent, NULL);
+
+  if (parent && GTK_CTREE_ROW (parent)->is_leaf)
+    return NULL;
+
+  clist = GTK_CLIST (ctree);
+
+  /* create the row */
+  new_row = srow_new (ctree);
+  list = g_list_alloc ();
+  list->data = new_row;
+  node = GTK_CTREE_NODE (list);
+
+  if (text)
+    for (i = 0; i < clist->columns; i++)
+      if (text[i] && i != ctree->tree_column)
+       GTK_CLIST_GET_CLASS (clist)->set_cell_contents
+         (clist, &(new_row->row), i, GTK_CELL_TEXT, text[i], 0, NULL, NULL);
+
+  sset_node_info (ctree, node, text ?
+                text[ctree->tree_column] : NULL, spacing, pixmap_closed,
+                mask_closed, pixmap_opened, mask_opened, is_leaf, expanded);
+
+  /* sorted insertion */
+  if (GTK_CLIST_AUTO_SORT (clist))
+    {
+      if (parent)
+       sibling = GTK_CTREE_ROW (parent)->children;
+      else
+       sibling = GTK_CTREE_NODE (clist->row_list);
+
+      while (sibling && clist->compare
+            (clist, GTK_CTREE_ROW (node), GTK_CTREE_ROW (sibling)) > 0)
+       sibling = GTK_CTREE_ROW (sibling)->sibling;
+    }
+
+  gtk_sctree_link (ctree, node, parent, sibling, FALSE);
+
+  if (text && !GTK_CLIST_AUTO_RESIZE_BLOCKED (clist) &&
+      gtk_ctree_is_viewable (ctree, node))
+    {
+      for (i = 0; i < clist->columns; i++)
+       if (clist->column[i].auto_resize)
+         gtk_sctree_column_auto_resize (clist, &(new_row->row), i, 0);
+    }
+
+  if (clist->rows == 1)
+    {
+      clist->focus_row = 0;
+      if (clist->selection_mode == GTK_SELECTION_BROWSE)
+       gtk_sctree_select (GTK_SCTREE(ctree), node);
+    }
+
+
+  CLIST_REFRESH (clist);
+
+  return node;
+}
+
+GtkCTreeNode *
+gtk_sctree_insert_gnode (GtkCTree          *ctree,
+                       GtkCTreeNode      *parent,
+                       GtkCTreeNode      *sibling,
+                       GNode             *gnode,
+                       GtkCTreeGNodeFunc  func,
+                       gpointer           data)
+{
+  GtkCList *clist;
+  GtkCTreeNode *cnode = NULL;
+  GtkCTreeNode *child = NULL;
+  GtkCTreeNode *new_child;
+  GList *list;
+  GNode *work;
+  guint depth = 1;
+
+  g_return_val_if_fail (GTK_IS_CTREE (ctree), NULL);
+  g_return_val_if_fail (gnode != NULL, NULL);
+  g_return_val_if_fail (func != NULL, NULL);
+  if (sibling)
+    g_return_val_if_fail (GTK_CTREE_ROW (sibling)->parent == parent, NULL);
+  
+  clist = GTK_CLIST (ctree);
+
+  if (parent)
+    depth = GTK_CTREE_ROW (parent)->level + 1;
+
+  list = g_list_alloc ();
+  list->data = srow_new (ctree);
+  cnode = GTK_CTREE_NODE (list);
+
+  gtk_clist_freeze (clist);
+
+  sset_node_info (ctree, cnode, "", 0, NULL, NULL, NULL, NULL, TRUE, FALSE);
+
+  if (!func (ctree, depth, gnode, cnode, data))
+    {
+      stree_delete_row (ctree, cnode, NULL);
+      gtk_clist_thaw (clist);
+      return NULL;
+    }
+
+  if (GTK_CLIST_AUTO_SORT (clist))
+    {
+      if (parent)
+       sibling = GTK_CTREE_ROW (parent)->children;
+      else
+       sibling = GTK_CTREE_NODE (clist->row_list);
+
+      while (sibling && clist->compare
+            (clist, GTK_CTREE_ROW (cnode), GTK_CTREE_ROW (sibling)) > 0)
+       sibling = GTK_CTREE_ROW (sibling)->sibling;
+    }
+
+  gtk_sctree_link (ctree, cnode, parent, sibling, FALSE);
+
+  for (work = g_node_last_child (gnode); work; work = work->prev)
+    {
+      new_child = gtk_sctree_insert_gnode (ctree, cnode, child,
+                                         work, func, data);
+      if (new_child)
+       child = new_child;
+    }  
+  
+  gtk_clist_thaw (clist);
+
+  return cnode;
+}
+
+static void
+sreal_tree_move (GtkCTree     *ctree,
+               GtkCTreeNode *node,
+               GtkCTreeNode *new_parent, 
+               GtkCTreeNode *new_sibling)
+{
+  GtkCList *clist;
+  GtkCTreeNode *work;
+  gboolean visible = FALSE;
+
+  g_return_if_fail (ctree != NULL);
+  g_return_if_fail (node != NULL);
+  g_return_if_fail (!new_sibling || 
+                   GTK_CTREE_ROW (new_sibling)->parent == new_parent);
+
+  if (new_parent && GTK_CTREE_ROW (new_parent)->is_leaf)
+    return;
+
+  /* new_parent != child of child */
+  for (work = new_parent; work; work = GTK_CTREE_ROW (work)->parent)
+    if (work == node)
+      return;
+
+  clist = GTK_CLIST (ctree);
+
+  visible = gtk_ctree_is_viewable (ctree, node);
+
+  if (clist->selection_mode == GTK_SELECTION_MULTIPLE)
+    {
+      GTK_CLIST_GET_CLASS (clist)->resync_selection (clist, NULL);
+      
+      g_list_free (clist->undo_selection);
+      g_list_free (clist->undo_unselection);
+      clist->undo_selection = NULL;
+      clist->undo_unselection = NULL;
+    }
+
+  if (GTK_CLIST_AUTO_SORT (clist))
+    {
+      if (new_parent == GTK_CTREE_ROW (node)->parent)
+       return;
+      
+      if (new_parent)
+       new_sibling = GTK_CTREE_ROW (new_parent)->children;
+      else
+       new_sibling = GTK_CTREE_NODE (clist->row_list);
+
+      while (new_sibling && clist->compare
+            (clist, GTK_CTREE_ROW (node), GTK_CTREE_ROW (new_sibling)) > 0)
+       new_sibling = GTK_CTREE_ROW (new_sibling)->sibling;
+    }
+
+  if (new_parent == GTK_CTREE_ROW (node)->parent && 
+      new_sibling == GTK_CTREE_ROW (node)->sibling)
+    return;
+
+  gtk_clist_freeze (clist);
+
+  work = NULL;
+
+  if (!GTK_SCTREE(ctree)->sorting && gtk_ctree_is_viewable (ctree, node))
+    work = GTK_CTREE_NODE (g_list_nth (clist->row_list, clist->focus_row));
+      
+  gtk_sctree_unlink (ctree, node, FALSE);
+  gtk_sctree_link (ctree, node, new_parent, new_sibling, FALSE);
+  
+  if (!GTK_SCTREE(ctree)->sorting && work)
+    {
+      while (work &&  !gtk_ctree_is_viewable (ctree, work))
+       work = GTK_CTREE_ROW (work)->parent;
+      clist->focus_row = g_list_position (clist->row_list, (GList *)work);
+      clist->undo_anchor = clist->focus_row;
+    }
+
+  if (clist->column[ctree->tree_column].auto_resize &&
+      !GTK_CLIST_AUTO_RESIZE_BLOCKED (clist) &&
+      (visible || gtk_ctree_is_viewable (ctree, node)))
+    gtk_clist_set_column_width
+      (clist, ctree->tree_column,
+       gtk_clist_optimal_column_width (clist, ctree->tree_column));
+
+  gtk_clist_thaw (clist);
+}
+