removed debugging info
[claws.git] / src / gtksctree.c
1 /*
2  * This program is based on gtkflist.c
3  */
4
5 #include "utils.h"
6 #include "gtkutils.h"
7 #include "gtksctree.h"
8
9
10 enum {
11         ROW_POPUP_MENU,
12         EMPTY_POPUP_MENU,
13         OPEN_ROW,
14         START_DRAG,
15         LAST_SIGNAL
16 };
17
18
19 static void gtk_sctree_class_init (GtkSCTreeClass *class);
20 static void gtk_sctree_init (GtkSCTree *sctree);
21
22 static gint gtk_sctree_button_press (GtkWidget *widget, GdkEventButton *event);
23 static gint gtk_sctree_button_release (GtkWidget *widget, GdkEventButton *event);
24 static gint gtk_sctree_motion (GtkWidget *widget, GdkEventMotion *event);
25 static void gtk_sctree_drag_begin (GtkWidget *widget, GdkDragContext *context);
26 static void gtk_sctree_drag_end (GtkWidget *widget, GdkDragContext *context);
27 static void gtk_sctree_drag_data_get (GtkWidget *widget, GdkDragContext *context,
28                                      GtkSelectionData *data, guint info, guint time);
29 static void gtk_sctree_drag_leave (GtkWidget *widget, GdkDragContext *context, guint time);
30 static gboolean gtk_sctree_drag_motion (GtkWidget *widget, GdkDragContext *context,
31                                        gint x, gint y, guint time);
32 static gboolean gtk_sctree_drag_drop (GtkWidget *widget, GdkDragContext *context,
33                                      gint x, gint y, guint time);
34 static void gtk_sctree_drag_data_received (GtkWidget *widget, GdkDragContext *context,
35                                           gint x, gint y, GtkSelectionData *data,
36                                           guint info, guint time);
37
38 static void gtk_sctree_clear (GtkCList *clist);
39 static void gtk_sctree_collapse (GtkCTree *ctree, GtkCTreeNode *node);
40        
41 static GtkCTreeClass *parent_class;
42
43 static guint sctree_signals[LAST_SIGNAL];
44
45
46 /**
47  * gtk_sctree_get_type:
48  * @void: 
49  * 
50  * Creates the GtkSCTree class and its type information
51  * 
52  * Return value: The type ID for GtkSCTreeClass
53  **/
54 GtkType
55 gtk_sctree_get_type (void)
56 {
57         static GtkType sctree_type = 0;
58
59         if (!sctree_type) {
60                 GtkTypeInfo sctree_info = {
61                         "GtkSCTree",
62                         sizeof (GtkSCTree),
63                         sizeof (GtkSCTreeClass),
64                         (GtkClassInitFunc) gtk_sctree_class_init,
65                         (GtkObjectInitFunc) gtk_sctree_init,
66                         NULL, /* reserved_1 */
67                         NULL, /* reserved_2 */
68                         (GtkClassInitFunc) NULL
69                 };
70
71                 sctree_type = gtk_type_unique (gtk_ctree_get_type (), &sctree_info);
72         }
73
74         return sctree_type;
75 }
76
77 /* Standard class initialization function */
78 static void
79 gtk_sctree_class_init (GtkSCTreeClass *klass)
80 {
81         GtkObjectClass *object_class;
82         GtkWidgetClass *widget_class;
83         GtkCListClass *clist_class;
84         GtkCTreeClass *ctree_class;
85
86         object_class = (GtkObjectClass *) klass;
87         widget_class = (GtkWidgetClass *) klass;
88         clist_class = (GtkCListClass *) klass;
89         ctree_class = (GtkCTreeClass *) klass;
90
91         parent_class = gtk_type_class (gtk_ctree_get_type ());
92
93         sctree_signals[ROW_POPUP_MENU] =
94                 gtk_signal_new ("row_popup_menu",
95                                 GTK_RUN_FIRST,
96                                 object_class->type,
97                                 GTK_SIGNAL_OFFSET (GtkSCTreeClass, row_popup_menu),
98                                 gtk_marshal_NONE__POINTER,
99                                 GTK_TYPE_NONE, 1,
100                                 GTK_TYPE_GDK_EVENT);
101         sctree_signals[EMPTY_POPUP_MENU] =
102                 gtk_signal_new ("empty_popup_menu",
103                                 GTK_RUN_FIRST,
104                                 object_class->type,
105                                 GTK_SIGNAL_OFFSET (GtkSCTreeClass, empty_popup_menu),
106                                 gtk_marshal_NONE__POINTER,
107                                 GTK_TYPE_NONE, 1,
108                                 GTK_TYPE_GDK_EVENT);
109         sctree_signals[OPEN_ROW] =
110                 gtk_signal_new ("open_row",
111                                 GTK_RUN_FIRST,
112                                 object_class->type,
113                                 GTK_SIGNAL_OFFSET (GtkSCTreeClass, open_row),
114                                 gtk_marshal_NONE__NONE,
115                                 GTK_TYPE_NONE, 0);
116         sctree_signals[START_DRAG] =
117                 gtk_signal_new ("start_drag",
118                                 GTK_RUN_FIRST,
119                                 object_class->type,
120                                 GTK_SIGNAL_OFFSET (GtkSCTreeClass, start_drag),
121                                 gtk_marshal_NONE__INT_POINTER,
122                                 GTK_TYPE_NONE, 2,
123                                 GTK_TYPE_INT,
124                                 GTK_TYPE_GDK_EVENT);
125
126         gtk_object_class_add_signals (object_class, sctree_signals, LAST_SIGNAL);
127
128         clist_class->clear = gtk_sctree_clear;
129         ctree_class->tree_collapse = gtk_sctree_collapse;
130         
131         widget_class->button_press_event = gtk_sctree_button_press;
132         widget_class->button_release_event = gtk_sctree_button_release;
133         widget_class->motion_notify_event = gtk_sctree_motion;
134         widget_class->drag_begin = gtk_sctree_drag_begin;
135         widget_class->drag_end = gtk_sctree_drag_end;
136         widget_class->drag_data_get = gtk_sctree_drag_data_get;
137         widget_class->drag_leave = gtk_sctree_drag_leave;
138         widget_class->drag_motion = gtk_sctree_drag_motion;
139         widget_class->drag_drop = gtk_sctree_drag_drop;
140         widget_class->drag_data_received = gtk_sctree_drag_data_received;
141 }
142
143 /* Standard object initialization function */
144 static void
145 gtk_sctree_init (GtkSCTree *sctree)
146 {
147         sctree->anchor_row = -1;
148
149         /* GtkCTree does not specify pointer motion by default */
150         gtk_widget_add_events (GTK_WIDGET (sctree), GDK_POINTER_MOTION_MASK);
151         gtk_widget_add_events (GTK_WIDGET (sctree), GDK_POINTER_MOTION_MASK);
152 }
153
154 /* Get information the specified row is selected. */
155
156 static gboolean
157 row_is_selected(GtkSCTree *sctree, gint row)
158 {
159         GtkCListRow *clist_row;
160         clist_row =  g_list_nth (GTK_CLIST(sctree)->row_list, row)->data;
161         return clist_row ? clist_row->state == GTK_STATE_SELECTED : FALSE;
162 }
163
164 /* Selects the rows between the anchor to the specified row, inclusive.  */
165 static void
166 select_range (GtkSCTree *sctree, gint row)
167 {
168         gint min, max;
169         gint i;
170
171         if (sctree->anchor_row == -1)
172                 sctree->anchor_row = row;
173
174         if (row < sctree->anchor_row) {
175                 min = row;
176                 max = sctree->anchor_row;
177         } else {
178                 min = sctree->anchor_row;
179                 max = row;
180         }
181         for (i = min; i <= max; i++)
182                 gtk_clist_select_row (GTK_CLIST (sctree), i, -1);
183 }
184
185 /* Handles row selection according to the specified modifier state */
186 static void
187 select_row (GtkSCTree *sctree, gint row, gint col, guint state)
188 {
189         gboolean range, additive;
190         g_return_if_fail (sctree != NULL);
191         g_return_if_fail (GTK_IS_SCTREE (sctree));
192     
193         range = ((state & GDK_SHIFT_MASK) != 0) &&
194                 (GTK_CLIST(sctree)->selection_mode != GTK_SELECTION_SINGLE) &&
195                 (GTK_CLIST(sctree)->selection_mode != GTK_SELECTION_BROWSE);
196         additive = ((state & GDK_CONTROL_MASK) != 0) &&
197                    (GTK_CLIST(sctree)->selection_mode != GTK_SELECTION_SINGLE) &&
198                    (GTK_CLIST(sctree)->selection_mode != GTK_SELECTION_BROWSE);
199
200         gtk_clist_freeze (GTK_CLIST (sctree));
201         gtkut_clist_set_focus_row(GTK_CLIST(sctree), row);
202         if (!additive)
203                 gtk_clist_unselect_all (GTK_CLIST (sctree));
204
205         if (!range) {
206                 /*No need to manage overlapped list*/
207                 if (additive) {
208                         if (row_is_selected(sctree, row))
209                                 gtk_clist_unselect_row (GTK_CLIST (sctree), row, col);
210                         else
211                                 gtk_signal_emit_by_name
212                                         (GTK_OBJECT (sctree),
213                                          "tree_select_row",
214                                          gtk_ctree_node_nth (GTK_CTREE(sctree), row),
215                                          col);
216                 } else {
217                         gtk_signal_emit_by_name
218                                 (GTK_OBJECT (sctree),
219                                  "tree_select_row",
220                                  gtk_ctree_node_nth (GTK_CTREE(sctree), row),
221                                  col);
222                 }
223                 sctree->anchor_row = row;
224         } else
225                 select_range (sctree, row);
226         gtk_clist_thaw (GTK_CLIST (sctree));
227 }
228
229 /* Our handler for button_press events.  We override all of GtkCList's broken
230  * behavior.
231  */
232 static gint
233 gtk_sctree_button_press (GtkWidget *widget, GdkEventButton *event)
234 {
235         GtkSCTree *sctree;
236         GtkCList *clist;
237         gboolean on_row;
238         gint row;
239         gint col;
240         gint retval;
241
242         g_return_val_if_fail (widget != NULL, FALSE);
243         g_return_val_if_fail (GTK_IS_SCTREE (widget), FALSE);
244         g_return_val_if_fail (event != NULL, FALSE);
245
246         sctree = GTK_SCTREE (widget);
247         clist = GTK_CLIST (widget);
248         retval = FALSE;
249
250         if (event->window != clist->clist_window)
251                 return (* GTK_WIDGET_CLASS (parent_class)->button_press_event) (widget, event);
252
253         on_row = gtk_clist_get_selection_info (clist, event->x, event->y, &row, &col);
254
255         if (on_row && !GTK_WIDGET_HAS_FOCUS(widget))
256                 gtk_widget_grab_focus (widget);
257
258         if (gtk_ctree_is_hot_spot (GTK_CTREE(sctree), event->x, event->y)) {
259                 gtk_ctree_toggle_expansion
260                         (GTK_CTREE(sctree), 
261                          gtk_ctree_node_nth(GTK_CTREE(sctree), row));
262                 return TRUE;
263         }
264
265         switch (event->type) {
266         case GDK_BUTTON_PRESS:
267                 if (event->button == 1 || event->button == 2) {
268                         if (event->button == 2)
269                                 event->state &= ~(GDK_SHIFT_MASK | GDK_CONTROL_MASK);
270                         if (on_row) {
271                                 /* Save the mouse info for DnD */
272                                 sctree->dnd_press_button = event->button;
273                                 sctree->dnd_press_x = event->x;
274                                 sctree->dnd_press_y = event->y;
275
276                                 /* Handle selection */
277                                 if ((row_is_selected (sctree, row)
278                                      && !(event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)))
279                                     || ((event->state & GDK_CONTROL_MASK)
280                                         && !(event->state & GDK_SHIFT_MASK))) {
281                                         sctree->dnd_select_pending = TRUE;
282                                         sctree->dnd_select_pending_state = event->state;
283                                         sctree->dnd_select_pending_row = row;
284                                 } else
285                                         select_row (sctree, row, col, event->state);
286                         } else
287                                 gtk_clist_unselect_all (clist);
288
289                         retval = TRUE;
290                 } else if (event->button == 3) {
291                         /* Emit *_popup_menu signal*/
292                         if (on_row) {
293                                 if (!row_is_selected(sctree,row))
294                                         select_row (sctree, row, col, 0);
295                                 gtk_signal_emit (GTK_OBJECT (sctree),
296                                                  sctree_signals[ROW_POPUP_MENU],
297                                                  event);
298                         } else {
299                                 gtk_clist_unselect_all(clist);
300                                 gtk_signal_emit (GTK_OBJECT (sctree),
301                                                  sctree_signals[EMPTY_POPUP_MENU],
302                                                  event);
303                         }
304                         retval = TRUE;
305                 }
306
307                 break;
308
309         case GDK_2BUTTON_PRESS:
310                 if (event->button != 1)
311                         break;
312
313                 sctree->dnd_select_pending = FALSE;
314                 sctree->dnd_select_pending_state = 0;
315
316                 if (on_row)
317                         gtk_signal_emit (GTK_OBJECT (sctree),
318                                          sctree_signals[OPEN_ROW]);
319
320                 retval = TRUE;
321                 break;
322
323         default:
324                 break;
325         }
326
327         return retval;
328 }
329
330 /* Our handler for button_release events.  We override all of GtkCList's broken
331  * behavior.
332  */
333 static gint
334 gtk_sctree_button_release (GtkWidget *widget, GdkEventButton *event)
335 {
336         GtkSCTree *sctree;
337         GtkCList *clist;
338         gint on_row;
339         gint row, col;
340         gint retval;
341
342         g_return_val_if_fail (widget != NULL, FALSE);
343         g_return_val_if_fail (GTK_IS_SCTREE (widget), FALSE);
344         g_return_val_if_fail (event != NULL, FALSE);
345
346         sctree = GTK_SCTREE (widget);
347         clist = GTK_CLIST (widget);
348         retval = FALSE;
349
350         if (event->window != clist->clist_window)
351                 return (* GTK_WIDGET_CLASS (parent_class)->button_release_event) (widget, event);
352
353         on_row = gtk_clist_get_selection_info (clist, event->x, event->y, &row, &col);
354
355         if (!(event->button == 1 || event->button == 2))
356                 return FALSE;
357
358         sctree->dnd_press_button = 0;
359         sctree->dnd_press_x = 0;
360         sctree->dnd_press_y = 0;
361
362         if (on_row) {
363                 if (sctree->dnd_select_pending) {
364                         select_row (sctree, row, col, sctree->dnd_select_pending_state);
365                         sctree->dnd_select_pending = FALSE;
366                         sctree->dnd_select_pending_state = 0;
367                 }
368
369                 retval = TRUE;
370         }
371
372         return retval;
373 }
374
375 /* Our handler for motion_notify events.  We override all of GtkCList's broken
376  * behavior.
377  */
378 static gint
379 gtk_sctree_motion (GtkWidget *widget, GdkEventMotion *event)
380 {
381         GtkSCTree *sctree;
382         GtkCList *clist;
383
384         g_return_val_if_fail (widget != NULL, FALSE);
385         g_return_val_if_fail (GTK_IS_SCTREE (widget), FALSE);
386         g_return_val_if_fail (event != NULL, FALSE);
387
388         sctree = GTK_SCTREE (widget);
389         clist = GTK_CLIST (widget);
390
391         if (event->window != clist->clist_window)
392                 return (* GTK_WIDGET_CLASS (parent_class)->motion_notify_event) (widget, event);
393
394         if (!((sctree->dnd_press_button == 1 && (event->state & GDK_BUTTON1_MASK))
395               || (sctree->dnd_press_button == 2 && (event->state & GDK_BUTTON2_MASK))))
396                 return FALSE;
397
398         /* This is the same threshold value that is used in gtkdnd.c */
399
400         if (MAX (abs (sctree->dnd_press_x - event->x),
401                  abs (sctree->dnd_press_y - event->y)) <= 3)
402                 return FALSE;
403
404         /* Handle any pending selections */
405
406         if (sctree->dnd_select_pending) {
407                 if (!row_is_selected(sctree,sctree->dnd_select_pending_row))
408                         select_row (sctree,
409                                     sctree->dnd_select_pending_row,
410                                     -1,
411                                     sctree->dnd_select_pending_state);
412
413                 sctree->dnd_select_pending = FALSE;
414                 sctree->dnd_select_pending_state = 0;
415         }
416
417         gtk_signal_emit (GTK_OBJECT (sctree),
418                          sctree_signals[START_DRAG],
419                          sctree->dnd_press_button,
420                          event);
421         return TRUE;
422 }
423
424 /* We override the drag_begin signal to do nothing */
425 static void
426 gtk_sctree_drag_begin (GtkWidget *widget, GdkDragContext *context)
427 {
428         /* nothing */
429 }
430
431 /* We override the drag_end signal to do nothing */
432 static void
433 gtk_sctree_drag_end (GtkWidget *widget, GdkDragContext *context)
434 {
435         /* nothing */
436 }
437
438 /* We override the drag_data_get signal to do nothing */
439 static void
440 gtk_sctree_drag_data_get (GtkWidget *widget, GdkDragContext *context,
441                                      GtkSelectionData *data, guint info, guint time)
442 {
443         /* nothing */
444 }
445
446 /* We override the drag_leave signal to do nothing */
447 static void
448 gtk_sctree_drag_leave (GtkWidget *widget, GdkDragContext *context, guint time)
449 {
450         /* nothing */
451 }
452
453 /* We override the drag_motion signal to do nothing */
454 static gboolean
455 gtk_sctree_drag_motion (GtkWidget *widget, GdkDragContext *context,
456                                    gint x, gint y, guint time)
457 {
458         return FALSE;
459 }
460
461 /* We override the drag_drop signal to do nothing */
462 static gboolean
463 gtk_sctree_drag_drop (GtkWidget *widget, GdkDragContext *context,
464                                  gint x, gint y, guint time)
465 {
466         return FALSE;
467 }
468
469 /* We override the drag_data_received signal to do nothing */
470 static void
471 gtk_sctree_drag_data_received (GtkWidget *widget, GdkDragContext *context,
472                                           gint x, gint y, GtkSelectionData *data,
473                                           guint info, guint time)
474 {
475         /* nothing */
476 }
477
478 /* Our handler for the clear signal of the clist.  We have to reset the anchor
479  * to null.
480  */
481 static void
482 gtk_sctree_clear (GtkCList *clist)
483 {
484         GtkSCTree *sctree;
485
486         g_return_if_fail (clist != NULL);
487         g_return_if_fail (GTK_IS_SCTREE (clist));
488
489         sctree = GTK_SCTREE (clist);
490         sctree->anchor_row = -1;
491
492         if (((GtkCListClass *)parent_class)->clear)
493                 (* ((GtkCListClass *)parent_class)->clear) (clist);
494 }
495
496 /* Our handler for the change_focus_row_expansion signal of the ctree.  
497  We have to set the anchor to parent visible node.
498  */
499 static void 
500 gtk_sctree_collapse (GtkCTree *ctree, GtkCTreeNode *node)
501 {
502         GtkSCTree *sctree;
503
504         g_return_if_fail (ctree != NULL);
505         g_return_if_fail (GTK_IS_SCTREE (ctree));
506
507         (* parent_class->tree_collapse) (ctree, node);
508         sctree = GTK_SCTREE (ctree);
509         sctree->anchor_row = GTK_CLIST(ctree)->focus_row;
510 }
511
512 GtkWidget *gtk_sctree_new_with_titles (gint columns, 
513                                        gint tree_column, 
514                                        gchar *titles[])
515 {
516         GtkSCTree* sctree;
517
518         sctree = gtk_type_new (gtk_sctree_get_type ());
519         gtk_ctree_construct (GTK_CTREE (sctree), columns, tree_column, titles);
520         gtk_clist_set_selection_mode(GTK_CLIST(sctree), GTK_SELECTION_EXTENDED);
521
522         return GTK_WIDGET (sctree);
523 }
524
525 void  gtk_sctree_select (GtkSCTree *sctree,
526                          GtkCTreeNode *node)
527 {
528         select_row(sctree, 
529                    gtkut_ctree_get_nth_from_node(GTK_CTREE(sctree),node),
530                    -1, 0);
531 }
532
533 void  gtk_sctree_unselect_all (GtkSCTree *sctree)
534 {
535         gtk_clist_unselect_all(GTK_CLIST(sctree));
536         sctree->anchor_row = -1;
537 }
538