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