gtkctree.c 160 KB
Newer Older
Tim Janik's avatar
Tim Janik committed
1 2 3 4 5 6 7 8
/* GTK - The GIMP Toolkit
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball, Josh MacDonald, 
 * Copyright (C) 1997-1998 Jay Painter <jpaint@serv.net><jpaint@gimp.org>  
 *
 * GtkCTree widget for GTK+
 * Copyright (C) 1998 Lars Hamann and Stefan Jeske
 *
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
Tim Janik's avatar
Tim Janik committed
10 11 12 13 14 15
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 * Lesser General Public License for more details.
Tim Janik's avatar
Tim Janik committed
17
 *
18
 * You should have received a copy of the GNU Lesser General Public
Tim Janik's avatar
Tim Janik committed
19 20 21 22 23
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

24
/*
25
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
26 27 28 29 30
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
 */

Manish Singh's avatar
Manish Singh committed
31 32
#undef GDK_DISABLE_DEPRECATED

Tim Janik's avatar
Tim Janik committed
33 34
#include <stdlib.h>
#include "gtkctree.h"
35
#include "gtkbindings.h"
Lars Hamann's avatar
Lars Hamann committed
36
#include "gtkmain.h"
37
#include "gtkmarshalers.h"
38
#include "gtkdnd.h"
39
#include <gdk/gdkkeysyms.h>
Tim Janik's avatar
Tim Janik committed
40 41

#define PM_SIZE                    8
42
#define TAB_SIZE                   (PM_SIZE + 6)
Tim Janik's avatar
Tim Janik committed
43
#define CELL_SPACING               1
44
#define CLIST_OPTIMUM_SIZE         64
45 46
#define COLUMN_INSET               3
#define DRAG_WIDTH                 6
Tim Janik's avatar
Tim Janik committed
47 48 49 50 51 52 53 54

#define ROW_TOP_YPIXEL(clist, row) (((clist)->row_height * (row)) + \
				    (((row) + 1) * CELL_SPACING) + \
				    (clist)->voffset)
#define ROW_FROM_YPIXEL(clist, y)  (((y) - (clist)->voffset) / \
                                    ((clist)->row_height + CELL_SPACING))
#define COLUMN_LEFT_XPIXEL(clist, col)  ((clist)->column[(col)].area.x \
                                    + (clist)->hoffset)
55
#define COLUMN_LEFT(clist, column) ((clist)->column[(column)].area.x)
Tim Janik's avatar
Tim Janik committed
56

57 58 59 60 61
static inline gint
COLUMN_FROM_XPIXEL (GtkCList * clist,
		    gint x)
{
  gint i, cx;
62

63 64 65 66
  for (i = 0; i < clist->columns; i++)
    if (clist->column[i].visible)
      {
	cx = clist->column[i].area.x + clist->hoffset;
67

68 69 70 71
	if (x >= (cx - (COLUMN_INSET + CELL_SPACING)) &&
	    x <= (cx + clist->column[i].area.width + COLUMN_INSET))
	  return i;
      }
72

73 74 75 76
  /* no match */
  return -1;
}

77 78 79
#define CLIST_UNFROZEN(clist)     (((GtkCList*) (clist))->freeze_count == 0)
#define CLIST_REFRESH(clist)    G_STMT_START { \
  if (CLIST_UNFROZEN (clist)) \
80
    GTK_CLIST_GET_CLASS (clist)->refresh ((GtkCList*) (clist)); \
81 82
} G_STMT_END

83

84 85 86 87 88 89 90 91 92 93 94
enum {
  ARG_0,
  ARG_N_COLUMNS,
  ARG_TREE_COLUMN,
  ARG_INDENT,
  ARG_SPACING,
  ARG_SHOW_STUB,
  ARG_LINE_STYLE,
  ARG_EXPANDER_STYLE
};

95

96 97 98 99 100
static void     gtk_ctree_class_init    (GtkCTreeClass         *klass);
static void     gtk_ctree_init          (GtkCTree              *ctree);
static GObject* gtk_ctree_constructor   (GType                  type,
				         guint                  n_construct_properties,
				         GObjectConstructParam *construct_params);
101 102 103 104 105 106
static void gtk_ctree_set_arg		(GtkObject      *object,
					 GtkArg         *arg,
					 guint           arg_id);
static void gtk_ctree_get_arg      	(GtkObject      *object,
					 GtkArg         *arg,
					 guint           arg_id);
107 108
static void gtk_ctree_realize           (GtkWidget      *widget);
static void gtk_ctree_unrealize         (GtkWidget      *widget);
Tim Janik's avatar
Tim Janik committed
109 110
static gint gtk_ctree_button_press      (GtkWidget      *widget,
					 GdkEventButton *event);
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
static void ctree_attach_styles         (GtkCTree       *ctree,
					 GtkCTreeNode   *node,
					 gpointer        data);
static void ctree_detach_styles         (GtkCTree       *ctree,
					 GtkCTreeNode   *node, 
					 gpointer        data);
static gint draw_cell_pixmap            (GdkWindow      *window,
					 GdkRectangle   *clip_rectangle,
					 GdkGC          *fg_gc,
					 GdkPixmap      *pixmap,
					 GdkBitmap      *mask,
					 gint            x,
					 gint            y,
					 gint            width,
					 gint            height);
static void get_cell_style              (GtkCList       *clist,
					 GtkCListRow    *clist_row,
					 gint            state,
					 gint            column,
					 GtkStyle      **style,
					 GdkGC         **fg_gc,
					 GdkGC         **bg_gc);
static gint gtk_ctree_draw_expander     (GtkCTree       *ctree,
					 GtkCTreeRow    *ctree_row,
					 GtkStyle       *style,
					 GdkRectangle   *clip_rectangle,
					 gint            x);
static gint gtk_ctree_draw_lines        (GtkCTree       *ctree,
					 GtkCTreeRow    *ctree_row,
					 gint            row,
					 gint            column,
					 gint            state,
					 GdkRectangle   *clip_rectangle,
					 GdkRectangle   *cell_rectangle,
					 GdkRectangle   *crect,
					 GdkRectangle   *area,
					 GtkStyle       *style);
148 149 150
static void draw_row                    (GtkCList       *clist,
					 GdkRectangle   *area,
					 gint            row,
151 152 153 154 155
					 GtkCListRow    *clist_row);
static void draw_drag_highlight         (GtkCList        *clist,
					 GtkCListRow     *dest_row,
					 gint             dest_row_number,
					 GtkCListDragPos  drag_pos);
156 157
static void tree_draw_node              (GtkCTree      *ctree,
					 GtkCTreeNode  *node);
158
static void set_cell_contents           (GtkCList      *clist,
Tim Janik's avatar
Tim Janik committed
159 160
					 GtkCListRow   *clist_row,
					 gint           column,
161
					 GtkCellType    type,
Tim Janik's avatar
Tim Janik committed
162
					 const gchar   *text,
Tim Janik's avatar
Tim Janik committed
163 164 165
					 guint8         spacing,
					 GdkPixmap     *pixmap,
					 GdkBitmap     *mask);
166
static void set_node_info               (GtkCTree      *ctree,
167
					 GtkCTreeNode  *node,
Tim Janik's avatar
Tim Janik committed
168
					 const gchar   *text,
169 170 171 172 173 174 175
					 guint8         spacing,
					 GdkPixmap     *pixmap_closed,
					 GdkBitmap     *mask_closed,
					 GdkPixmap     *pixmap_opened,
					 GdkBitmap     *mask_opened,
					 gboolean       is_leaf,
					 gboolean       expanded);
176 177 178 179
static GtkCTreeRow *row_new             (GtkCTree      *ctree);
static void row_delete                  (GtkCTree      *ctree,
				 	 GtkCTreeRow   *ctree_row);
static void tree_delete                 (GtkCTree      *ctree, 
180
					 GtkCTreeNode  *node, 
181 182
					 gpointer       data);
static void tree_delete_row             (GtkCTree      *ctree, 
183
					 GtkCTreeNode  *node, 
184
					 gpointer       data);
185
static void real_clear                  (GtkCList      *clist);
186
static void tree_update_level           (GtkCTree      *ctree, 
187
					 GtkCTreeNode  *node, 
188 189
					 gpointer       data);
static void tree_select                 (GtkCTree      *ctree, 
190
					 GtkCTreeNode  *node, 
191 192
					 gpointer       data);
static void tree_unselect               (GtkCTree      *ctree, 
193
					 GtkCTreeNode  *node, 
194
				         gpointer       data);
195 196
static void real_select_all             (GtkCList      *clist);
static void real_unselect_all           (GtkCList      *clist);
197
static void tree_expand                 (GtkCTree      *ctree, 
198
					 GtkCTreeNode  *node,
199 200
					 gpointer       data);
static void tree_collapse               (GtkCTree      *ctree, 
201
					 GtkCTreeNode  *node,
202
					 gpointer       data);
203
static void tree_collapse_to_depth      (GtkCTree      *ctree, 
204
					 GtkCTreeNode  *node, 
205
					 gint           depth);
206
static void tree_toggle_expansion       (GtkCTree      *ctree,
207
					 GtkCTreeNode  *node,
208 209
					 gpointer       data);
static void change_focus_row_expansion  (GtkCTree      *ctree,
210
				         GtkCTreeExpansionType expansion);
211 212 213 214 215 216 217 218
static void real_select_row             (GtkCList      *clist,
					 gint           row,
					 gint           column,
					 GdkEvent      *event);
static void real_unselect_row           (GtkCList      *clist,
					 gint           row,
					 gint           column,
					 GdkEvent      *event);
219
static void real_tree_select            (GtkCTree      *ctree,
220
					 GtkCTreeNode  *node,
221 222
					 gint           column);
static void real_tree_unselect          (GtkCTree      *ctree,
223
					 GtkCTreeNode  *node,
224 225
					 gint           column);
static void real_tree_expand            (GtkCTree      *ctree,
226
					 GtkCTreeNode  *node);
227
static void real_tree_collapse          (GtkCTree      *ctree,
228
					 GtkCTreeNode  *node);
229
static void real_tree_move              (GtkCTree      *ctree,
230 231 232
					 GtkCTreeNode  *node,
					 GtkCTreeNode  *new_parent, 
					 GtkCTreeNode  *new_sibling);
233 234 235
static void real_row_move               (GtkCList      *clist,
					 gint           source_row,
					 gint           dest_row);
236
static void gtk_ctree_link              (GtkCTree      *ctree,
237 238 239
					 GtkCTreeNode  *node,
					 GtkCTreeNode  *parent,
					 GtkCTreeNode  *sibling,
240
					 gboolean       update_focus_row);
241
static void gtk_ctree_unlink            (GtkCTree      *ctree, 
242
					 GtkCTreeNode  *node,
243
					 gboolean       update_focus_row);
244 245
static GtkCTreeNode * gtk_ctree_last_visible (GtkCTree     *ctree,
					      GtkCTreeNode *node);
246
static gboolean ctree_is_hot_spot       (GtkCTree      *ctree, 
247
					 GtkCTreeNode  *node,
248 249 250 251
					 gint           row, 
					 gint           x, 
					 gint           y);
static void tree_sort                   (GtkCTree      *ctree,
252
					 GtkCTreeNode  *node,
253
					 gpointer       data);
254 255
static void fake_unselect_all           (GtkCList      *clist,
					 gint           row);
256
static GList * selection_find           (GtkCList      *clist,
257 258 259 260 261 262
					 gint           row_number,
					 GList         *row_list_element);
static void resync_selection            (GtkCList      *clist,
					 GdkEvent      *event);
static void real_undo_selection         (GtkCList      *clist);
static void select_row_recursive        (GtkCTree      *ctree, 
263
					 GtkCTreeNode  *node, 
264
					 gpointer       data);
265 266 267 268 269 270
static gint real_insert_row             (GtkCList      *clist,
					 gint           row,
					 gchar         *text[]);
static void real_remove_row             (GtkCList      *clist,
					 gint           row);
static void real_sort_list              (GtkCList      *clist);
271 272 273 274 275 276 277 278
static void cell_size_request           (GtkCList       *clist,
					 GtkCListRow    *clist_row,
					 gint            column,
					 GtkRequisition *requisition);
static void column_auto_resize          (GtkCList       *clist,
					 GtkCListRow    *clist_row,
					 gint            column,
					 gint            old_width);
Lars Hamann's avatar
Lars Hamann committed
279
static void auto_resize_columns         (GtkCList       *clist);
280

281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299

static gboolean check_drag               (GtkCTree         *ctree,
			                  GtkCTreeNode     *drag_source,
					  GtkCTreeNode     *drag_target,
					  GtkCListDragPos   insert_pos);
static void gtk_ctree_drag_begin         (GtkWidget        *widget,
					  GdkDragContext   *context);
static gint gtk_ctree_drag_motion        (GtkWidget        *widget,
					  GdkDragContext   *context,
					  gint              x,
					  gint              y,
					  guint             time);
static void gtk_ctree_drag_data_received (GtkWidget        *widget,
					  GdkDragContext   *context,
					  gint              x,
					  gint              y,
					  GtkSelectionData *selection_data,
					  guint             info,
					  guint32           time);
300
static void remove_grab                  (GtkCList         *clist);
Lars Hamann's avatar
Lars Hamann committed
301 302 303 304
static void drag_dest_cell               (GtkCList         *clist,
					  gint              x,
					  gint              y,
					  GtkCListDestInfo *dest_info);
305 306


Tim Janik's avatar
Tim Janik committed
307 308 309 310 311 312 313
enum
{
  TREE_SELECT_ROW,
  TREE_UNSELECT_ROW,
  TREE_EXPAND,
  TREE_COLLAPSE,
  TREE_MOVE,
314
  CHANGE_FOCUS_ROW_EXPANSION,
Tim Janik's avatar
Tim Janik committed
315 316 317 318 319 320 321 322 323
  LAST_SIGNAL
};

static GtkCListClass *parent_class = NULL;
static GtkContainerClass *container_class = NULL;
static guint ctree_signals[LAST_SIGNAL] = {0};


GtkType
324
gtk_ctree_get_type (void)
Tim Janik's avatar
Tim Janik committed
325 326
{
  static GtkType ctree_type = 0;
327

Tim Janik's avatar
Tim Janik committed
328 329
  if (!ctree_type)
    {
330
      static const GtkTypeInfo ctree_info =
Tim Janik's avatar
Tim Janik committed
331 332 333 334 335 336
      {
	"GtkCTree",
	sizeof (GtkCTree),
	sizeof (GtkCTreeClass),
	(GtkClassInitFunc) gtk_ctree_class_init,
	(GtkObjectInitFunc) gtk_ctree_init,
337 338
	/* reserved_1 */ NULL,
        /* reserved_2 */ NULL,
339
        (GtkClassInitFunc) NULL,
Tim Janik's avatar
Tim Janik committed
340
      };
341

342
      ctree_type = gtk_type_unique (GTK_TYPE_CLIST, &ctree_info);
Tim Janik's avatar
Tim Janik committed
343
    }
344

Tim Janik's avatar
Tim Janik committed
345 346 347 348 349 350
  return ctree_type;
}

static void
gtk_ctree_class_init (GtkCTreeClass *klass)
{
351
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
Tim Janik's avatar
Tim Janik committed
352 353 354
  GtkObjectClass *object_class;
  GtkWidgetClass *widget_class;
  GtkCListClass *clist_class;
355
  GtkBindingSet *binding_set;
356

357 358
  gobject_class->constructor = gtk_ctree_constructor;

Tim Janik's avatar
Tim Janik committed
359 360 361 362
  object_class = (GtkObjectClass *) klass;
  widget_class = (GtkWidgetClass *) klass;
  container_class = (GtkContainerClass *) klass;
  clist_class = (GtkCListClass *) klass;
363

364 365
  parent_class = gtk_type_class (GTK_TYPE_CLIST);
  container_class = gtk_type_class (GTK_TYPE_CONTAINER);
366

367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
  object_class->set_arg = gtk_ctree_set_arg;
  object_class->get_arg = gtk_ctree_get_arg;

  widget_class->realize = gtk_ctree_realize;
  widget_class->unrealize = gtk_ctree_unrealize;
  widget_class->button_press_event = gtk_ctree_button_press;

  widget_class->drag_begin = gtk_ctree_drag_begin;
  widget_class->drag_motion = gtk_ctree_drag_motion;
  widget_class->drag_data_received = gtk_ctree_drag_data_received;

  clist_class->select_row = real_select_row;
  clist_class->unselect_row = real_unselect_row;
  clist_class->row_move = real_row_move;
  clist_class->undo_selection = real_undo_selection;
  clist_class->resync_selection = resync_selection;
  clist_class->selection_find = selection_find;
  clist_class->click_column = NULL;
  clist_class->draw_row = draw_row;
  clist_class->draw_drag_highlight = draw_drag_highlight;
  clist_class->clear = real_clear;
  clist_class->select_all = real_select_all;
  clist_class->unselect_all = real_unselect_all;
  clist_class->fake_unselect_all = fake_unselect_all;
  clist_class->insert_row = real_insert_row;
  clist_class->remove_row = real_remove_row;
  clist_class->sort_list = real_sort_list;
  clist_class->set_cell_contents = set_cell_contents;
  clist_class->cell_size_request = cell_size_request;

  klass->tree_select_row = real_tree_select;
  klass->tree_unselect_row = real_tree_unselect;
  klass->tree_expand = real_tree_expand;
  klass->tree_collapse = real_tree_collapse;
  klass->tree_move = real_tree_move;
  klass->change_focus_row_expansion = change_focus_row_expansion;

  gtk_object_add_arg_type ("GtkCTree::n_columns", /* overrides GtkCList::n_columns!! */
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
			   GTK_TYPE_UINT,
			   GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT_ONLY,
			   ARG_N_COLUMNS);
  gtk_object_add_arg_type ("GtkCTree::tree_column",
			   GTK_TYPE_UINT,
			   GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT_ONLY,
			   ARG_TREE_COLUMN);
  gtk_object_add_arg_type ("GtkCTree::indent",
			   GTK_TYPE_UINT,
			   GTK_ARG_READWRITE,
			   ARG_INDENT);
  gtk_object_add_arg_type ("GtkCTree::spacing",
			   GTK_TYPE_UINT,
			   GTK_ARG_READWRITE,
			   ARG_SPACING);
  gtk_object_add_arg_type ("GtkCTree::show_stub",
			   GTK_TYPE_BOOL,
			   GTK_ARG_READWRITE,
			   ARG_SHOW_STUB);
  gtk_object_add_arg_type ("GtkCTree::line_style",
425
			   GTK_TYPE_CTREE_LINE_STYLE,
426 427 428
			   GTK_ARG_READWRITE,
			   ARG_LINE_STYLE);
  gtk_object_add_arg_type ("GtkCTree::expander_style",
429
			   GTK_TYPE_CTREE_EXPANDER_STYLE,
430 431
			   GTK_ARG_READWRITE,
			   ARG_EXPANDER_STYLE);
432

Tim Janik's avatar
Tim Janik committed
433 434 435
  ctree_signals[TREE_SELECT_ROW] =
    gtk_signal_new ("tree_select_row",
		    GTK_RUN_FIRST,
436
		    GTK_CLASS_TYPE (object_class),
Tim Janik's avatar
Tim Janik committed
437
		    GTK_SIGNAL_OFFSET (GtkCTreeClass, tree_select_row),
438
		    _gtk_marshal_VOID__POINTER_INT,
James Henstridge's avatar
James Henstridge committed
439
		    GTK_TYPE_NONE, 2,
440
		    GTK_TYPE_CTREE_NODE,
James Henstridge's avatar
James Henstridge committed
441
		    GTK_TYPE_INT);
Tim Janik's avatar
Tim Janik committed
442 443 444
  ctree_signals[TREE_UNSELECT_ROW] =
    gtk_signal_new ("tree_unselect_row",
		    GTK_RUN_FIRST,
445
		    GTK_CLASS_TYPE (object_class),
Tim Janik's avatar
Tim Janik committed
446
		    GTK_SIGNAL_OFFSET (GtkCTreeClass, tree_unselect_row),
447
		    _gtk_marshal_VOID__POINTER_INT,
James Henstridge's avatar
James Henstridge committed
448
		    GTK_TYPE_NONE, 2,
449
		    GTK_TYPE_CTREE_NODE,
James Henstridge's avatar
James Henstridge committed
450
		    GTK_TYPE_INT);
Tim Janik's avatar
Tim Janik committed
451 452 453
  ctree_signals[TREE_EXPAND] =
    gtk_signal_new ("tree_expand",
		    GTK_RUN_LAST,
454
		    GTK_CLASS_TYPE (object_class),
Tim Janik's avatar
Tim Janik committed
455
		    GTK_SIGNAL_OFFSET (GtkCTreeClass, tree_expand),
456
		    _gtk_marshal_VOID__POINTER,
James Henstridge's avatar
James Henstridge committed
457
		    GTK_TYPE_NONE, 1,
458
		    GTK_TYPE_CTREE_NODE);
Tim Janik's avatar
Tim Janik committed
459 460 461
  ctree_signals[TREE_COLLAPSE] =
    gtk_signal_new ("tree_collapse",
		    GTK_RUN_LAST,
462
		    GTK_CLASS_TYPE (object_class),
Tim Janik's avatar
Tim Janik committed
463
		    GTK_SIGNAL_OFFSET (GtkCTreeClass, tree_collapse),
464
		    _gtk_marshal_VOID__POINTER,
James Henstridge's avatar
James Henstridge committed
465
		    GTK_TYPE_NONE, 1,
466
		    GTK_TYPE_CTREE_NODE);
Tim Janik's avatar
Tim Janik committed
467 468 469
  ctree_signals[TREE_MOVE] =
    gtk_signal_new ("tree_move",
		    GTK_RUN_LAST,
470
		    GTK_CLASS_TYPE (object_class),
Tim Janik's avatar
Tim Janik committed
471
		    GTK_SIGNAL_OFFSET (GtkCTreeClass, tree_move),
472
		    _gtk_marshal_VOID__POINTER_POINTER_POINTER,
James Henstridge's avatar
James Henstridge committed
473
		    GTK_TYPE_NONE, 3,
474 475 476
		    GTK_TYPE_CTREE_NODE,
		    GTK_TYPE_CTREE_NODE,
		    GTK_TYPE_CTREE_NODE);
477 478 479
  ctree_signals[CHANGE_FOCUS_ROW_EXPANSION] =
    gtk_signal_new ("change_focus_row_expansion",
		    GTK_RUN_LAST | GTK_RUN_ACTION,
480
		    GTK_CLASS_TYPE (object_class),
481 482
		    GTK_SIGNAL_OFFSET (GtkCTreeClass,
				       change_focus_row_expansion),
483
		    _gtk_marshal_VOID__ENUM,
484
		    GTK_TYPE_NONE, 1, GTK_TYPE_CTREE_EXPANSION_TYPE);
485

486
  binding_set = gtk_binding_set_by_class (klass);
487
  gtk_binding_entry_add_signal (binding_set,
488
				GDK_plus, 0,
489 490 491
				"change_focus_row_expansion", 1,
				GTK_TYPE_ENUM, GTK_CTREE_EXPANSION_EXPAND);
  gtk_binding_entry_add_signal (binding_set,
492
				GDK_plus, GDK_CONTROL_MASK,
493 494
				"change_focus_row_expansion", 1,
				GTK_TYPE_ENUM, GTK_CTREE_EXPANSION_EXPAND_RECURSIVE);
495

496 497 498 499 500 501 502
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_Add, 0,
				"change_focus_row_expansion", 1,
				GTK_TYPE_ENUM, GTK_CTREE_EXPANSION_EXPAND);
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_Add, GDK_CONTROL_MASK,
				"change_focus_row_expansion", 1,
503 504
				GTK_TYPE_ENUM, GTK_CTREE_EXPANSION_EXPAND_RECURSIVE);
  
505
  gtk_binding_entry_add_signal (binding_set,
506
				GDK_minus, 0,
507 508
				"change_focus_row_expansion", 1,
				GTK_TYPE_ENUM, GTK_CTREE_EXPANSION_COLLAPSE);
509
  gtk_binding_entry_add_signal (binding_set,
510
                                GDK_minus, GDK_CONTROL_MASK,
511 512 513
				"change_focus_row_expansion", 1,
				GTK_TYPE_ENUM,
				GTK_CTREE_EXPANSION_COLLAPSE_RECURSIVE);
514 515 516 517 518 519 520 521 522 523
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_Subtract, 0,
				"change_focus_row_expansion", 1,
				GTK_TYPE_ENUM, GTK_CTREE_EXPANSION_COLLAPSE);
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_Subtract, GDK_CONTROL_MASK,
				"change_focus_row_expansion", 1,
				GTK_TYPE_ENUM,
				GTK_CTREE_EXPANSION_COLLAPSE_RECURSIVE);
  gtk_binding_entry_add_signal (binding_set,
524
				GDK_equal, 0,
525 526
				"change_focus_row_expansion", 1,
				GTK_TYPE_ENUM, GTK_CTREE_EXPANSION_TOGGLE);
527
  gtk_binding_entry_add_signal (binding_set,
528 529 530
				GDK_KP_Equal, 0,
				"change_focus_row_expansion", 1,
				GTK_TYPE_ENUM, GTK_CTREE_EXPANSION_TOGGLE);
531 532 533 534
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_Multiply, 0,
				"change_focus_row_expansion", 1,
				GTK_TYPE_ENUM, GTK_CTREE_EXPANSION_TOGGLE);
535 536 537 538
  gtk_binding_entry_add_signal (binding_set,
				GDK_asterisk, 0,
				"change_focus_row_expansion", 1,
				GTK_TYPE_ENUM, GTK_CTREE_EXPANSION_TOGGLE);
539 540 541 542 543
  gtk_binding_entry_add_signal (binding_set,
				GDK_KP_Multiply, GDK_CONTROL_MASK,
				"change_focus_row_expansion", 1,
				GTK_TYPE_ENUM,
				GTK_CTREE_EXPANSION_TOGGLE_RECURSIVE);
544 545 546 547 548
  gtk_binding_entry_add_signal (binding_set,
				GDK_asterisk, GDK_CONTROL_MASK,
				"change_focus_row_expansion", 1,
				GTK_TYPE_ENUM,
				GTK_CTREE_EXPANSION_TOGGLE_RECURSIVE);  
Tim Janik's avatar
Tim Janik committed
549 550
}

551 552 553 554 555 556
static void
gtk_ctree_set_arg (GtkObject      *object,
		   GtkArg         *arg,
		   guint           arg_id)
{
  GtkCTree *ctree;
557
  GtkCList *clist;
558

559
  ctree = GTK_CTREE (object);
560
  clist = GTK_CLIST (ctree);
561

562 563
  switch (arg_id)
    {
564 565 566 567 568 569 570 571 572 573 574 575 576 577
    case ARG_N_COLUMNS: /* construct-only arg, only set at construction time */
      g_return_if_fail (clist->row_mem_chunk == NULL);
      clist->columns = MAX (1, GTK_VALUE_UINT (*arg));
      clist->row_mem_chunk = g_mem_chunk_new ("ctree row mem chunk",
					      sizeof (GtkCTreeRow),
					      sizeof (GtkCTreeRow)
					      * CLIST_OPTIMUM_SIZE,
					      G_ALLOC_AND_FREE);
      clist->cell_mem_chunk = g_mem_chunk_new ("ctree cell mem chunk",
					       sizeof (GtkCell) * clist->columns,
					       sizeof (GtkCell) * clist->columns
					       * CLIST_OPTIMUM_SIZE,
					       G_ALLOC_AND_FREE);
      ctree->tree_column = CLAMP (ctree->tree_column, 0, clist->columns);
578
      break;
579 580 581 582
    case ARG_TREE_COLUMN: /* construct-only arg, only set at construction time */
      ctree->tree_column = GTK_VALUE_UINT (*arg);
      if (clist->row_mem_chunk)
	ctree->tree_column = CLAMP (ctree->tree_column, 0, clist->columns);
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609
      break;
    case ARG_INDENT:
      gtk_ctree_set_indent (ctree, GTK_VALUE_UINT (*arg));
      break;
    case ARG_SPACING:
      gtk_ctree_set_spacing (ctree, GTK_VALUE_UINT (*arg));
      break;
    case ARG_SHOW_STUB:
      gtk_ctree_set_show_stub (ctree, GTK_VALUE_BOOL (*arg));
      break;
    case ARG_LINE_STYLE:
      gtk_ctree_set_line_style (ctree, GTK_VALUE_ENUM (*arg));
      break;
    case ARG_EXPANDER_STYLE:
      gtk_ctree_set_expander_style (ctree, GTK_VALUE_ENUM (*arg));
      break;
    default:
      break;
    }
}

static void
gtk_ctree_get_arg (GtkObject      *object,
		   GtkArg         *arg,
		   guint           arg_id)
{
  GtkCTree *ctree;
610

611
  ctree = GTK_CTREE (object);
612

613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641
  switch (arg_id)
    {
    case ARG_N_COLUMNS:
      GTK_VALUE_UINT (*arg) = GTK_CLIST (ctree)->columns;
      break;
    case ARG_TREE_COLUMN:
      GTK_VALUE_UINT (*arg) = ctree->tree_column;
      break;
    case ARG_INDENT:
      GTK_VALUE_UINT (*arg) = ctree->tree_indent;
      break;
    case ARG_SPACING:
      GTK_VALUE_UINT (*arg) = ctree->tree_spacing;
      break;
    case ARG_SHOW_STUB:
      GTK_VALUE_BOOL (*arg) = ctree->show_stub;
      break;
    case ARG_LINE_STYLE:
      GTK_VALUE_ENUM (*arg) = ctree->line_style;
      break;
    case ARG_EXPANDER_STYLE:
      GTK_VALUE_ENUM (*arg) = ctree->expander_style;
      break;
    default:
      arg->type = GTK_TYPE_INVALID;
      break;
    }
}

Tim Janik's avatar
Tim Janik committed
642 643 644
static void
gtk_ctree_init (GtkCTree *ctree)
{
645
  GtkCList *clist;
646

647 648
  GTK_CLIST_SET_FLAG (ctree, CLIST_DRAW_DRAG_RECT);
  GTK_CLIST_SET_FLAG (ctree, CLIST_DRAW_DRAG_LINE);
649

650
  clist = GTK_CLIST (ctree);
651

Tim Janik's avatar
Tim Janik committed
652
  ctree->tree_indent    = 20;
653
  ctree->tree_spacing   = 5;
Tim Janik's avatar
Tim Janik committed
654
  ctree->tree_column    = 0;
655
  ctree->line_style     = GTK_CTREE_LINES_SOLID;
656
  ctree->expander_style = GTK_CTREE_EXPANDER_SQUARE;
657
  ctree->drag_compare   = NULL;
658
  ctree->show_stub      = TRUE;
659

660
  clist->button_actions[0] |= GTK_BUTTON_EXPANDS;
Tim Janik's avatar
Tim Janik committed
661 662
}

663 664 665 666 667 668 669
static void
ctree_attach_styles (GtkCTree     *ctree,
		     GtkCTreeNode *node,
		     gpointer      data)
{
  GtkCList *clist;
  gint i;
670

671
  clist = GTK_CLIST (ctree);
672

673 674 675
  if (GTK_CTREE_ROW (node)->row.style)
    GTK_CTREE_ROW (node)->row.style =
      gtk_style_attach (GTK_CTREE_ROW (node)->row.style, clist->clist_window);
676

677 678 679
  if (GTK_CTREE_ROW (node)->row.fg_set || GTK_CTREE_ROW (node)->row.bg_set)
    {
      GdkColormap *colormap;
680

681 682 683 684 685 686
      colormap = gtk_widget_get_colormap (GTK_WIDGET (ctree));
      if (GTK_CTREE_ROW (node)->row.fg_set)
	gdk_color_alloc (colormap, &(GTK_CTREE_ROW (node)->row.foreground));
      if (GTK_CTREE_ROW (node)->row.bg_set)
	gdk_color_alloc (colormap, &(GTK_CTREE_ROW (node)->row.background));
    }
687

688 689 690 691 692 693 694 695 696 697 698 699 700 701
  for (i = 0; i < clist->columns; i++)
    if  (GTK_CTREE_ROW (node)->row.cell[i].style)
      GTK_CTREE_ROW (node)->row.cell[i].style =
	gtk_style_attach (GTK_CTREE_ROW (node)->row.cell[i].style,
			  clist->clist_window);
}

static void
ctree_detach_styles (GtkCTree     *ctree,
		     GtkCTreeNode *node,
		     gpointer      data)
{
  GtkCList *clist;
  gint i;
702

703
  clist = GTK_CLIST (ctree);
704

705 706 707 708 709 710 711
  if (GTK_CTREE_ROW (node)->row.style)
    gtk_style_detach (GTK_CTREE_ROW (node)->row.style);
  for (i = 0; i < clist->columns; i++)
    if  (GTK_CTREE_ROW (node)->row.cell[i].style)
      gtk_style_detach (GTK_CTREE_ROW (node)->row.cell[i].style);
}

Tim Janik's avatar
Tim Janik committed
712 713 714 715
static void
gtk_ctree_realize (GtkWidget *widget)
{
  GtkCTree *ctree;
716
  GtkCList *clist;
Tim Janik's avatar
Tim Janik committed
717
  GdkGCValues values;
718 719 720
  GtkCTreeNode *node;
  GtkCTreeNode *child;
  gint i;
721

722
  g_return_if_fail (GTK_IS_CTREE (widget));
723

724
  GTK_WIDGET_CLASS (parent_class)->realize (widget);
725

726
  ctree = GTK_CTREE (widget);
727
  clist = GTK_CLIST (widget);
728

729 730 731 732 733 734 735 736 737
  node = GTK_CTREE_NODE (clist->row_list);
  for (i = 0; i < clist->rows; i++)
    {
      if (GTK_CTREE_ROW (node)->children && !GTK_CTREE_ROW (node)->expanded)
	for (child = GTK_CTREE_ROW (node)->children; child;
	     child = GTK_CTREE_ROW (child)->sibling)
	  gtk_ctree_pre_recursive (ctree, child, ctree_attach_styles, NULL);
      node = GTK_CTREE_NODE_NEXT (node);
    }
738

Tim Janik's avatar
Tim Janik committed
739
  values.foreground = widget->style->fg[GTK_STATE_NORMAL];
740
  values.background = widget->style->base[GTK_STATE_NORMAL];
Tim Janik's avatar
Tim Janik committed
741 742 743 744 745 746 747 748
  values.subwindow_mode = GDK_INCLUDE_INFERIORS;
  values.line_style = GDK_LINE_SOLID;
  ctree->lines_gc = gdk_gc_new_with_values (GTK_CLIST(widget)->clist_window, 
					    &values,
					    GDK_GC_FOREGROUND |
					    GDK_GC_BACKGROUND |
					    GDK_GC_SUBWINDOW |
					    GDK_GC_LINE_STYLE);
749

Tim Janik's avatar
Tim Janik committed
750 751 752
  if (ctree->line_style == GTK_CTREE_LINES_DOTTED)
    {
      gdk_gc_set_line_attributes (ctree->lines_gc, 1, 
753
				  GDK_LINE_ON_OFF_DASH, GDK_CAP_BUTT, GDK_JOIN_MITER);
754
      gdk_gc_set_dashes (ctree->lines_gc, 0, "\1\1", 2);
Tim Janik's avatar
Tim Janik committed
755 756 757 758 759 760 761
    }
}

static void
gtk_ctree_unrealize (GtkWidget *widget)
{
  GtkCTree *ctree;
762
  GtkCList *clist;
763

764
  g_return_if_fail (GTK_IS_CTREE (widget));
765

766
  GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
767

768
  ctree = GTK_CTREE (widget);
769
  clist = GTK_CLIST (widget);
770

771 772 773 774 775
  if (GTK_WIDGET_REALIZED (widget))
    {
      GtkCTreeNode *node;
      GtkCTreeNode *child;
      gint i;
776

777 778 779 780 781 782 783 784 785 786 787
      node = GTK_CTREE_NODE (clist->row_list);
      for (i = 0; i < clist->rows; i++)
	{
	  if (GTK_CTREE_ROW (node)->children &&
	      !GTK_CTREE_ROW (node)->expanded)
	    for (child = GTK_CTREE_ROW (node)->children; child;
		 child = GTK_CTREE_ROW (child)->sibling)
	      gtk_ctree_pre_recursive(ctree, child, ctree_detach_styles, NULL);
	  node = GTK_CTREE_NODE_NEXT (node);
	}
    }
788

Tim Janik's avatar
Tim Janik committed
789 790 791 792 793 794 795 796 797
  gdk_gc_destroy (ctree->lines_gc);
}

static gint
gtk_ctree_button_press (GtkWidget      *widget,
			GdkEventButton *event)
{
  GtkCTree *ctree;
  GtkCList *clist;
798
  gint button_actions;
799

Tim Janik's avatar
Tim Janik committed
800 801
  g_return_val_if_fail (GTK_IS_CTREE (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);
802

Tim Janik's avatar
Tim Janik committed
803 804
  ctree = GTK_CTREE (widget);
  clist = GTK_CLIST (widget);
805

806
  button_actions = clist->button_actions[event->button - 1];
807

808 809
  if (button_actions == GTK_BUTTON_IGNORED)
    return FALSE;
810

Tim Janik's avatar
Tim Janik committed
811 812
  if (event->window == clist->clist_window)
    {
813
      GtkCTreeNode *work;
Tim Janik's avatar
Tim Janik committed
814 815 816 817
      gint x;
      gint y;
      gint row;
      gint column;
818

Tim Janik's avatar
Tim Janik committed
819 820
      x = event->x;
      y = event->y;
821

822 823
      if (!gtk_clist_get_selection_info (clist, x, y, &row, &column))
	return FALSE;
824

825
      work = GTK_CTREE_NODE (g_list_nth (clist->row_list, row));
826
	  
827 828 829 830
      if (button_actions & GTK_BUTTON_EXPANDS &&
	  (GTK_CTREE_ROW (work)->children && !GTK_CTREE_ROW (work)->is_leaf  &&
	   (event->type == GDK_2BUTTON_PRESS ||
	    ctree_is_hot_spot (ctree, work, row, x, y))))
831 832 833 834 835
	{
	  if (GTK_CTREE_ROW (work)->expanded)
	    gtk_ctree_collapse (ctree, work);
	  else
	    gtk_ctree_expand (ctree, work);
836

837
	  return TRUE;
Tim Janik's avatar
Tim Janik committed
838 839
	}
    }
840
  
841
  return GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, event);
Tim Janik's avatar
Tim Janik committed
842 843 844
}

static void
845 846 847 848
draw_drag_highlight (GtkCList        *clist,
		     GtkCListRow     *dest_row,
		     gint             dest_row_number,
		     GtkCListDragPos  drag_pos)
Tim Janik's avatar
Tim Janik committed
849
{
850 851
  GtkCTree *ctree;
  GdkPoint points[4];
Tim Janik's avatar
Tim Janik committed
852
  gint level;
853
  gint i;
Tim Janik's avatar
Tim Janik committed
854
  gint y = 0;
855

856
  g_return_if_fail (GTK_IS_CTREE (clist));
857

858
  ctree = GTK_CTREE (clist);
859

860
  level = ((GtkCTreeRow *)(dest_row))->level;
861

862
  y = ROW_TOP_YPIXEL (clist, dest_row_number) - 1;
863

864 865 866 867 868 869 870
  switch (drag_pos)
    {
    case GTK_CLIST_DRAG_NONE:
      break;
    case GTK_CLIST_DRAG_AFTER:
      y += clist->row_height + 1;
    case GTK_CLIST_DRAG_BEFORE:
871
      
872 873 874 875 876 877 878 879 880 881 882 883
      if (clist->column[ctree->tree_column].visible)
	switch (clist->column[ctree->tree_column].justification)
	  {
	  case GTK_JUSTIFY_CENTER:
	  case GTK_JUSTIFY_FILL:
	  case GTK_JUSTIFY_LEFT:
	    if (ctree->tree_column > 0)
	      gdk_draw_line (clist->clist_window, clist->xor_gc, 
			     COLUMN_LEFT_XPIXEL(clist, 0), y,
			     COLUMN_LEFT_XPIXEL(clist, ctree->tree_column - 1)+
			     clist->column[ctree->tree_column - 1].area.width,
			     y);
884

885 886 887 888 889 890 891 892 893 894 895 896 897
	    gdk_draw_line (clist->clist_window, clist->xor_gc, 
			   COLUMN_LEFT_XPIXEL(clist, ctree->tree_column) + 
			   ctree->tree_indent * level -
			   (ctree->tree_indent - PM_SIZE) / 2, y,
			   GTK_WIDGET (ctree)->allocation.width, y);
	    break;
	  case GTK_JUSTIFY_RIGHT:
	    if (ctree->tree_column < clist->columns - 1)
	      gdk_draw_line (clist->clist_window, clist->xor_gc, 
			     COLUMN_LEFT_XPIXEL(clist, ctree->tree_column + 1),
			     y,
			     COLUMN_LEFT_XPIXEL(clist, clist->columns - 1) +
			     clist->column[clist->columns - 1].area.width, y);
898
      
899 900 901 902 903 904 905 906
	    gdk_draw_line (clist->clist_window, clist->xor_gc, 
			   0, y, COLUMN_LEFT_XPIXEL(clist, ctree->tree_column)
			   + clist->column[ctree->tree_column].area.width -
			   ctree->tree_indent * level +
			   (ctree->tree_indent - PM_SIZE) / 2, y);
	    break;
	  }
      else
907
	gdk_draw_line (clist->clist_window, clist->xor_gc, 
908 909 910 911
		       0, y, clist->clist_window_width, y);
      break;
    case GTK_CLIST_DRAG_INTO:
      y = ROW_TOP_YPIXEL (clist, dest_row_number) + clist->row_height;
912

913 914
      if (clist->column[ctree->tree_column].visible)
	switch (clist->column[ctree->tree_column].justification)
915
	  {
916 917 918 919 920
	  case GTK_JUSTIFY_CENTER:
	  case GTK_JUSTIFY_FILL:
	  case GTK_JUSTIFY_LEFT:
	    points[0].x =  COLUMN_LEFT_XPIXEL(clist, ctree->tree_column) + 
	      ctree->tree_indent * level - (ctree->tree_indent - PM_SIZE) / 2;
921 922 923
	    points[0].y = y;
	    points[3].x = points[0].x;
	    points[3].y = y - clist->row_height - 1;
924
	    points[1].x = clist->clist_window_width - 1;
925
	    points[1].y = points[0].y;
926
	    points[2].x = points[1].x;
927
	    points[2].y = points[3].y;
928

929
	    for (i = 0; i < 3; i++)
930
	      gdk_draw_line (clist->clist_window, clist->xor_gc,
931 932
			     points[i].x, points[i].y,
			     points[i+1].x, points[i+1].y);
933