1 /*
2 * e-tree-view-frame.c
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18 /**
19 * SECTION: e-tree-view-frame
20 * @include: e-util/e-util.h
21 * @short_description: A frame for #GtkTreeView
22 *
23 * #ETreeViewFrame embeds a #GtkTreeView in a scrolled window and adds an
24 * inline-style toolbar beneath the scrolled window which can be hidden.
25 *
26 * The inline-style toolbar supports "add" and "remove" actions, as well
27 * as move actions if the tree view is reorderable and selection actions
28 * if the tree view supports multiple selections. The action set can be
29 * extended through e_tree_view_frame_insert_toolbar_action().
30 **/
31
32 #include "evolution-config.h"
33
34 #include <libebackend/libebackend.h>
35
36 #include "e-misc-utils.h"
37
38 #include "e-tree-view-frame.h"
39
40 #define E_TREE_VIEW_FRAME_GET_PRIVATE(obj) \
41 (G_TYPE_INSTANCE_GET_PRIVATE \
42 ((obj), E_TYPE_TREE_VIEW_FRAME, ETreeViewFramePrivate))
43
44 /**
45 * E_TREE_VIEW_FRAME_ACTION_ADD:
46 *
47 * The #GtkAction name for the "add" toolbar button.
48 *
49 * Use e_tree_view_frame_lookup_toolbar_action() to obtain the #GtkAction.
50 **/
51
52 /**
53 * E_TREE_VIEW_FRAME_ACTION_REMOVE:
54 *
55 * The #GtkAction name for the "remove" toolbar button.
56 *
57 * Use e_tree_view_frame_lookup_toolbar_action() to obtain the #GtkAction.
58 **/
59
60 /**
61 * E_TREE_VIEW_FRAME_ACTION_MOVE_TOP:
62 *
63 * The #GtkAction name for the "move selected items to top" button.
64 *
65 * Use e_tree_view_frame_lookup_toolbar_action() to obtain the #GtkAction.
66 **/
67
68 /**
69 * E_TREE_VIEW_FRAME_ACTION_MOVE_UP:
70 *
71 * The #GtkAction name for the "move selected items up" button.
72 *
73 * Use e_tree_view_frame_lookup_toolbar_action() to obtain the #GtkAction.
74 **/
75
76 /**
77 * E_TREE_VIEW_FRAME_ACTION_MOVE_DOWN:
78 *
79 * The #GtkAction name for the "move selected items down" button.
80 *
81 * Use e_tree_view_frame_lookup_toolbar_action() to obtain the #GtkAction.
82 **/
83
84 /**
85 * E_TREE_VIEW_FRAME_ACTION_MOVE_BOTTOM:
86 *
87 * The #GtkAction name for the "move selected items to bottom" button.
88 *
89 * Use e_tree_view_frame_lookup_toolbar_action() to obtain the #GtkAction.
90 **/
91
92 /**
93 * E_TREE_VIEW_FRAME_ACTION_SELECT_ALL:
94 *
95 * The #GtkAction name for the "select all" button.
96 *
97 * Use e_tree_view_frame_lookup_toolbar_action() to obtain the #GtkAction.
98 **/
99
100 struct _ETreeViewFramePrivate {
101 GtkTreeView *tree_view;
102 gulong notify_reorderable_handler_id;
103 gulong notify_select_mode_handler_id;
104 gulong selection_changed_handler_id;
105
106 GtkWidget *scrolled_window;
107 GtkWidget *inline_toolbar;
108
109 GHashTable *tool_item_ht;
110
111 GtkPolicyType hscrollbar_policy;
112 GtkPolicyType vscrollbar_policy;
113
114 gboolean toolbar_visible;
115 };
116
117 enum {
118 PROP_0,
119 PROP_HSCROLLBAR_POLICY,
120 PROP_TREE_VIEW,
121 PROP_TOOLBAR_VISIBLE,
122 PROP_VSCROLLBAR_POLICY
123 };
124
125 enum {
126 TOOLBAR_ACTION_ACTIVATE,
127 UPDATE_TOOLBAR_ACTIONS,
128 LAST_SIGNAL
129 };
130
131 static guint signals[LAST_SIGNAL];
132
G_DEFINE_TYPE_WITH_CODE(ETreeViewFrame,e_tree_view_frame,GTK_TYPE_BOX,G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE,NULL))133 G_DEFINE_TYPE_WITH_CODE (
134 ETreeViewFrame,
135 e_tree_view_frame,
136 GTK_TYPE_BOX,
137 G_IMPLEMENT_INTERFACE (
138 E_TYPE_EXTENSIBLE, NULL))
139
140 static void
141 tree_view_frame_append_action (ETreeViewFrame *tree_view_frame,
142 const gchar *action_name,
143 const gchar *icon_name)
144 {
145 GtkAction *action;
146 GIcon *icon;
147
148 icon = g_themed_icon_new_with_default_fallbacks (icon_name);
149
150 action = g_object_new (
151 GTK_TYPE_ACTION,
152 "name", action_name, "gicon", icon, NULL);
153
154 e_tree_view_frame_insert_toolbar_action (tree_view_frame, action, -1);
155
156 g_object_unref (action);
157 g_object_unref (icon);
158 }
159
160 static void
tree_view_frame_dispose_tree_view(ETreeViewFramePrivate * priv)161 tree_view_frame_dispose_tree_view (ETreeViewFramePrivate *priv)
162 {
163 if (priv->notify_reorderable_handler_id > 0) {
164 g_signal_handler_disconnect (
165 priv->tree_view,
166 priv->notify_reorderable_handler_id);
167 priv->notify_reorderable_handler_id = 0;
168 }
169
170 if (priv->notify_select_mode_handler_id > 0) {
171 g_signal_handler_disconnect (
172 gtk_tree_view_get_selection (priv->tree_view),
173 priv->notify_select_mode_handler_id);
174 priv->notify_select_mode_handler_id = 0;
175 }
176
177 if (priv->selection_changed_handler_id > 0) {
178 g_signal_handler_disconnect (
179 gtk_tree_view_get_selection (priv->tree_view),
180 priv->selection_changed_handler_id);
181 priv->selection_changed_handler_id = 0;
182 }
183
184 g_clear_object (&priv->tree_view);
185 }
186
187 static gboolean
tree_view_frame_first_row_selected(GtkTreeView * tree_view)188 tree_view_frame_first_row_selected (GtkTreeView *tree_view)
189 {
190 GtkTreeModel *tree_model;
191 GtkTreeSelection *selection;
192 GtkTreeIter iter;
193
194 tree_model = gtk_tree_view_get_model (tree_view);
195 selection = gtk_tree_view_get_selection (tree_view);
196
197 if (tree_model == NULL)
198 return FALSE;
199
200 if (!gtk_tree_model_iter_nth_child (tree_model, &iter, NULL, 0))
201 return FALSE;
202
203 return gtk_tree_selection_iter_is_selected (selection, &iter);
204 }
205
206 static gboolean
tree_view_frame_last_row_selected(GtkTreeView * tree_view)207 tree_view_frame_last_row_selected (GtkTreeView *tree_view)
208 {
209 GtkTreeModel *tree_model;
210 GtkTreeSelection *selection;
211 GtkTreeIter iter;
212 gint last;
213
214 tree_model = gtk_tree_view_get_model (tree_view);
215 selection = gtk_tree_view_get_selection (tree_view);
216
217 if (tree_model == NULL)
218 return FALSE;
219
220 last = gtk_tree_model_iter_n_children (tree_model, NULL) - 1;
221 if (last < 0)
222 return FALSE;
223
224 if (!gtk_tree_model_iter_nth_child (tree_model, &iter, NULL, last))
225 return FALSE;
226
227 return gtk_tree_selection_iter_is_selected (selection, &iter);
228 }
229
230 static gboolean
tree_view_frame_move_selection_up(GtkTreeView * tree_view)231 tree_view_frame_move_selection_up (GtkTreeView *tree_view)
232 {
233 GtkTreeModel *tree_model;
234 GtkListStore *list_store;
235 GtkTreeSelection *selection;
236 GList *list, *link;
237
238 tree_model = gtk_tree_view_get_model (tree_view);
239 if (!GTK_IS_LIST_STORE (tree_model))
240 return FALSE;
241
242 if (tree_view_frame_first_row_selected (tree_view))
243 return FALSE;
244
245 list_store = GTK_LIST_STORE (tree_model);
246
247 selection = gtk_tree_view_get_selection (tree_view);
248 list = gtk_tree_selection_get_selected_rows (selection, NULL);
249
250 /* Move all selected rows up one, even
251 * if the selection is not contiguous. */
252
253 for (link = list; link != NULL; link = g_list_next (link)) {
254 GtkTreePath *path = link->data;
255 GtkTreeIter iter;
256 GtkTreeIter prev;
257
258 if (!gtk_tree_model_get_iter (tree_model, &iter, path)) {
259 g_warn_if_reached ();
260 continue;
261 }
262
263 prev = iter;
264 if (!gtk_tree_model_iter_previous (tree_model, &prev)) {
265 g_warn_if_reached ();
266 continue;
267 }
268
269 gtk_list_store_swap (list_store, &iter, &prev);
270 }
271
272 g_list_free_full (list, (GDestroyNotify) gtk_tree_path_free);
273
274 return TRUE;
275 }
276
277 static gboolean
tree_view_frame_move_selection_down(GtkTreeView * tree_view)278 tree_view_frame_move_selection_down (GtkTreeView *tree_view)
279 {
280 GtkTreeModel *tree_model;
281 GtkListStore *list_store;
282 GtkTreeSelection *selection;
283 GList *list, *link;
284
285 tree_model = gtk_tree_view_get_model (tree_view);
286 if (!GTK_IS_LIST_STORE (tree_model))
287 return FALSE;
288
289 if (tree_view_frame_last_row_selected (tree_view))
290 return FALSE;
291
292 list_store = GTK_LIST_STORE (tree_model);
293
294 selection = gtk_tree_view_get_selection (tree_view);
295 list = gtk_tree_selection_get_selected_rows (selection, NULL);
296
297 /* Reverse the list so we don't disturb rows we've already moved. */
298 list = g_list_reverse (list);
299
300 for (link = list; link != NULL; link = g_list_next (link)) {
301 GtkTreePath *path = link->data;
302 GtkTreeIter iter;
303 GtkTreeIter next;
304
305 if (!gtk_tree_model_get_iter (tree_model, &iter, path)) {
306 g_warn_if_reached ();
307 continue;
308 }
309
310 next = iter;
311 if (!gtk_tree_model_iter_next (tree_model, &next)) {
312 g_warn_if_reached ();
313 continue;
314 }
315
316 gtk_list_store_swap (list_store, &iter, &next);
317 }
318
319 g_list_free_full (list, (GDestroyNotify) gtk_tree_path_free);
320
321 return TRUE;
322 }
323
324 static void
tree_view_frame_scroll_to_cursor(GtkTreeView * tree_view)325 tree_view_frame_scroll_to_cursor (GtkTreeView *tree_view)
326 {
327 GtkTreePath *path = NULL;
328
329 gtk_tree_view_get_cursor (tree_view, &path, NULL);
330
331 if (path != NULL) {
332 gtk_tree_view_scroll_to_cell (
333 tree_view, path, NULL, FALSE, 0.0, 0.0);
334 gtk_tree_path_free (path);
335 }
336 }
337
338 static void
tree_view_frame_action_go_top(ETreeViewFrame * tree_view_frame)339 tree_view_frame_action_go_top (ETreeViewFrame *tree_view_frame)
340 {
341 GtkTreeView *tree_view;
342
343 tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
344
345 /* Not the most efficient method, but it's simple and works. */
346 while (tree_view_frame_move_selection_up (tree_view))
347 ;
348
349 tree_view_frame_scroll_to_cursor (tree_view);
350 e_tree_view_frame_update_toolbar_actions (tree_view_frame);
351 }
352
353 static void
tree_view_frame_action_go_up(ETreeViewFrame * tree_view_frame)354 tree_view_frame_action_go_up (ETreeViewFrame *tree_view_frame)
355 {
356 GtkTreeView *tree_view;
357
358 tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
359
360 tree_view_frame_move_selection_up (tree_view);
361
362 tree_view_frame_scroll_to_cursor (tree_view);
363 e_tree_view_frame_update_toolbar_actions (tree_view_frame);
364 }
365
366 static void
tree_view_frame_action_go_down(ETreeViewFrame * tree_view_frame)367 tree_view_frame_action_go_down (ETreeViewFrame *tree_view_frame)
368 {
369 GtkTreeView *tree_view;
370
371 tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
372
373 tree_view_frame_move_selection_down (tree_view);
374
375 tree_view_frame_scroll_to_cursor (tree_view);
376 e_tree_view_frame_update_toolbar_actions (tree_view_frame);
377 }
378
379 static void
tree_view_frame_action_go_bottom(ETreeViewFrame * tree_view_frame)380 tree_view_frame_action_go_bottom (ETreeViewFrame *tree_view_frame)
381 {
382 GtkTreeView *tree_view;
383
384 tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
385
386 /* Not the most efficient method, but it's simple and works. */
387 while (tree_view_frame_move_selection_down (tree_view))
388 ;
389
390 tree_view_frame_scroll_to_cursor (tree_view);
391 e_tree_view_frame_update_toolbar_actions (tree_view_frame);
392 }
393
394 static void
tree_view_frame_action_select_all(ETreeViewFrame * tree_view_frame)395 tree_view_frame_action_select_all (ETreeViewFrame *tree_view_frame)
396 {
397 GtkTreeView *tree_view;
398 GtkTreeSelection *selection;
399
400 tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
401 selection = gtk_tree_view_get_selection (tree_view);
402
403 gtk_tree_selection_select_all (selection);
404 }
405
406 static void
tree_view_frame_action_activate_cb(GtkAction * action,ETreeViewFrame * tree_view_frame)407 tree_view_frame_action_activate_cb (GtkAction *action,
408 ETreeViewFrame *tree_view_frame)
409 {
410 GQuark detail;
411 const gchar *action_name;
412 gboolean handled = FALSE;
413
414 action_name = gtk_action_get_name (action);
415 detail = g_quark_from_string (action_name);
416
417 g_signal_emit (
418 tree_view_frame,
419 signals[TOOLBAR_ACTION_ACTIVATE], detail,
420 action, &handled);
421 }
422
423 static void
tree_view_frame_notify_reorderable_cb(GtkTreeView * tree_view,GParamSpec * pspec,ETreeViewFrame * tree_view_frame)424 tree_view_frame_notify_reorderable_cb (GtkTreeView *tree_view,
425 GParamSpec *pspec,
426 ETreeViewFrame *tree_view_frame)
427 {
428 e_tree_view_frame_update_toolbar_actions (tree_view_frame);
429 }
430
431 static void
tree_view_frame_notify_select_mode_cb(GtkTreeSelection * selection,GParamSpec * pspec,ETreeViewFrame * tree_view_frame)432 tree_view_frame_notify_select_mode_cb (GtkTreeSelection *selection,
433 GParamSpec *pspec,
434 ETreeViewFrame *tree_view_frame)
435 {
436 e_tree_view_frame_update_toolbar_actions (tree_view_frame);
437 }
438
439 static void
tree_view_frame_selection_changed_cb(GtkTreeSelection * selection,ETreeViewFrame * tree_view_frame)440 tree_view_frame_selection_changed_cb (GtkTreeSelection *selection,
441 ETreeViewFrame *tree_view_frame)
442 {
443 e_tree_view_frame_update_toolbar_actions (tree_view_frame);
444 }
445
446 static void
tree_view_frame_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)447 tree_view_frame_set_property (GObject *object,
448 guint property_id,
449 const GValue *value,
450 GParamSpec *pspec)
451 {
452 switch (property_id) {
453 case PROP_HSCROLLBAR_POLICY:
454 e_tree_view_frame_set_hscrollbar_policy (
455 E_TREE_VIEW_FRAME (object),
456 g_value_get_enum (value));
457 return;
458
459 case PROP_TREE_VIEW:
460 e_tree_view_frame_set_tree_view (
461 E_TREE_VIEW_FRAME (object),
462 g_value_get_object (value));
463 return;
464
465 case PROP_TOOLBAR_VISIBLE:
466 e_tree_view_frame_set_toolbar_visible (
467 E_TREE_VIEW_FRAME (object),
468 g_value_get_boolean (value));
469 return;
470
471 case PROP_VSCROLLBAR_POLICY:
472 e_tree_view_frame_set_vscrollbar_policy (
473 E_TREE_VIEW_FRAME (object),
474 g_value_get_enum (value));
475 return;
476 }
477
478 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
479 }
480
481 static void
tree_view_frame_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)482 tree_view_frame_get_property (GObject *object,
483 guint property_id,
484 GValue *value,
485 GParamSpec *pspec)
486 {
487 switch (property_id) {
488 case PROP_HSCROLLBAR_POLICY:
489 g_value_set_enum (
490 value,
491 e_tree_view_frame_get_hscrollbar_policy (
492 E_TREE_VIEW_FRAME (object)));
493 return;
494
495 case PROP_TREE_VIEW:
496 g_value_set_object (
497 value,
498 e_tree_view_frame_get_tree_view (
499 E_TREE_VIEW_FRAME (object)));
500 return;
501
502 case PROP_TOOLBAR_VISIBLE:
503 g_value_set_boolean (
504 value,
505 e_tree_view_frame_get_toolbar_visible (
506 E_TREE_VIEW_FRAME (object)));
507 return;
508
509 case PROP_VSCROLLBAR_POLICY:
510 g_value_set_enum (
511 value,
512 e_tree_view_frame_get_vscrollbar_policy (
513 E_TREE_VIEW_FRAME (object)));
514 return;
515 }
516
517 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
518 }
519
520 static void
tree_view_frame_dispose(GObject * object)521 tree_view_frame_dispose (GObject *object)
522 {
523 ETreeViewFramePrivate *priv;
524
525 priv = E_TREE_VIEW_FRAME_GET_PRIVATE (object);
526
527 tree_view_frame_dispose_tree_view (priv);
528
529 g_clear_object (&priv->scrolled_window);
530 g_clear_object (&priv->inline_toolbar);
531
532 g_hash_table_remove_all (priv->tool_item_ht);
533
534 /* Chain up to parent's dispose() method. */
535 G_OBJECT_CLASS (e_tree_view_frame_parent_class)->dispose (object);
536 }
537
538 static void
tree_view_frame_finalize(GObject * object)539 tree_view_frame_finalize (GObject *object)
540 {
541 ETreeViewFramePrivate *priv;
542
543 priv = E_TREE_VIEW_FRAME_GET_PRIVATE (object);
544
545 g_hash_table_destroy (priv->tool_item_ht);
546
547 /* Chain up to parent's finalize() method. */
548 G_OBJECT_CLASS (e_tree_view_frame_parent_class)->finalize (object);
549 }
550
551 static void
tree_view_frame_constructed(GObject * object)552 tree_view_frame_constructed (GObject *object)
553 {
554 ETreeViewFrame *tree_view_frame;
555 GtkStyleContext *style_context;
556 GtkWidget *widget;
557
558 tree_view_frame = E_TREE_VIEW_FRAME (object);
559
560 /* Chain up to parent's constructed() method. */
561 G_OBJECT_CLASS (e_tree_view_frame_parent_class)->constructed (object);
562
563 gtk_orientable_set_orientation (
564 GTK_ORIENTABLE (tree_view_frame),
565 GTK_ORIENTATION_VERTICAL);
566
567 widget = gtk_scrolled_window_new (NULL, NULL);
568 gtk_scrolled_window_set_shadow_type (
569 GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
570 gtk_box_pack_start (GTK_BOX (tree_view_frame), widget, TRUE, TRUE, 0);
571 tree_view_frame->priv->scrolled_window = g_object_ref (widget);
572 gtk_widget_show (widget);
573
574 e_binding_bind_property (
575 tree_view_frame, "hscrollbar-policy",
576 widget, "hscrollbar-policy",
577 G_BINDING_SYNC_CREATE);
578
579 e_binding_bind_property (
580 tree_view_frame, "vscrollbar-policy",
581 widget, "vscrollbar-policy",
582 G_BINDING_SYNC_CREATE);
583
584 widget = gtk_toolbar_new ();
585 gtk_toolbar_set_show_arrow (GTK_TOOLBAR (widget), FALSE);
586 gtk_toolbar_set_style (GTK_TOOLBAR (widget), GTK_TOOLBAR_ICONS);
587 gtk_toolbar_set_icon_size (GTK_TOOLBAR (widget), GTK_ICON_SIZE_MENU);
588 gtk_box_pack_start (GTK_BOX (tree_view_frame), widget, FALSE, FALSE, 0);
589 tree_view_frame->priv->inline_toolbar = g_object_ref (widget);
590 gtk_widget_show (widget);
591
592 style_context = gtk_widget_get_style_context (widget);
593 gtk_style_context_add_class (
594 style_context, GTK_STYLE_CLASS_INLINE_TOOLBAR);
595 gtk_style_context_set_junction_sides (
596 style_context, GTK_JUNCTION_TOP);
597
598 e_binding_bind_property (
599 tree_view_frame, "toolbar-visible",
600 widget, "visible",
601 G_BINDING_SYNC_CREATE);
602
603 /* Define actions for toolbar items. */
604 tree_view_frame_append_action (
605 tree_view_frame,
606 E_TREE_VIEW_FRAME_ACTION_ADD,
607 "list-add-symbolic");
608 tree_view_frame_append_action (
609 tree_view_frame,
610 E_TREE_VIEW_FRAME_ACTION_REMOVE,
611 "list-remove-symbolic");
612 tree_view_frame_append_action (
613 tree_view_frame,
614 E_TREE_VIEW_FRAME_ACTION_MOVE_TOP,
615 "go-top-symbolic");
616 tree_view_frame_append_action (
617 tree_view_frame,
618 E_TREE_VIEW_FRAME_ACTION_MOVE_UP,
619 "go-up-symbolic");
620 tree_view_frame_append_action (
621 tree_view_frame,
622 E_TREE_VIEW_FRAME_ACTION_MOVE_DOWN,
623 "go-down-symbolic");
624 tree_view_frame_append_action (
625 tree_view_frame,
626 E_TREE_VIEW_FRAME_ACTION_MOVE_BOTTOM,
627 "go-bottom-symbolic");
628 tree_view_frame_append_action (
629 tree_view_frame,
630 E_TREE_VIEW_FRAME_ACTION_SELECT_ALL,
631 "edit-select-all-symbolic");
632
633 /* Install a default GtkTreeView. */
634 e_tree_view_frame_set_tree_view (tree_view_frame, NULL);
635 }
636
637 static gboolean
tree_view_frame_toolbar_action_activate(ETreeViewFrame * tree_view_frame,GtkAction * action)638 tree_view_frame_toolbar_action_activate (ETreeViewFrame *tree_view_frame,
639 GtkAction *action)
640 {
641 const gchar *action_name;
642
643 action_name = gtk_action_get_name (action);
644 g_return_val_if_fail (action_name != NULL, FALSE);
645
646 if (g_str_equal (action_name, E_TREE_VIEW_FRAME_ACTION_MOVE_TOP)) {
647 tree_view_frame_action_go_top (tree_view_frame);
648 return TRUE;
649 }
650
651 if (g_str_equal (action_name, E_TREE_VIEW_FRAME_ACTION_MOVE_UP)) {
652 tree_view_frame_action_go_up (tree_view_frame);
653 return TRUE;
654 }
655
656 if (g_str_equal (action_name, E_TREE_VIEW_FRAME_ACTION_MOVE_DOWN)) {
657 tree_view_frame_action_go_down (tree_view_frame);
658 return TRUE;
659 }
660
661 if (g_str_equal (action_name, E_TREE_VIEW_FRAME_ACTION_MOVE_BOTTOM)) {
662 tree_view_frame_action_go_bottom (tree_view_frame);
663 return TRUE;
664 }
665
666 if (g_str_equal (action_name, E_TREE_VIEW_FRAME_ACTION_SELECT_ALL)) {
667 tree_view_frame_action_select_all (tree_view_frame);
668 return TRUE;
669 }
670
671 return FALSE;
672 }
673
674 static void
tree_view_frame_update_toolbar_actions(ETreeViewFrame * tree_view_frame)675 tree_view_frame_update_toolbar_actions (ETreeViewFrame *tree_view_frame)
676 {
677 GtkAction *action;
678 GtkTreeView *tree_view;
679 GtkTreeModel *tree_model;
680 GtkTreeSelection *selection;
681 GtkSelectionMode selection_mode;
682 gboolean first_row_selected;
683 gboolean last_row_selected;
684 gboolean sensitive;
685 gboolean visible;
686 gint n_selected_rows;
687 gint n_rows = 0;
688
689 /* XXX This implementation assumes the tree model is a list store.
690 * A tree store will require special handling, although I don't
691 * yet know if there's even a use case for a tree store here. */
692
693 tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
694
695 tree_model = gtk_tree_view_get_model (tree_view);
696 if (tree_model != NULL)
697 n_rows = gtk_tree_model_iter_n_children (tree_model, NULL);
698
699 selection = gtk_tree_view_get_selection (tree_view);
700 selection_mode = gtk_tree_selection_get_mode (selection);
701 n_selected_rows = gtk_tree_selection_count_selected_rows (selection);
702
703 first_row_selected = tree_view_frame_first_row_selected (tree_view);
704 last_row_selected = tree_view_frame_last_row_selected (tree_view);
705
706 action = e_tree_view_frame_lookup_toolbar_action (
707 tree_view_frame, E_TREE_VIEW_FRAME_ACTION_MOVE_TOP);
708 visible = gtk_tree_view_get_reorderable (tree_view);
709 sensitive = (n_selected_rows > 0 && !first_row_selected);
710 gtk_action_set_visible (action, visible);
711 gtk_action_set_sensitive (action, sensitive);
712
713 action = e_tree_view_frame_lookup_toolbar_action (
714 tree_view_frame, E_TREE_VIEW_FRAME_ACTION_MOVE_UP);
715 visible = gtk_tree_view_get_reorderable (tree_view);
716 sensitive = (n_selected_rows > 0 && !first_row_selected);
717 gtk_action_set_visible (action, visible);
718 gtk_action_set_sensitive (action, sensitive);
719
720 action = e_tree_view_frame_lookup_toolbar_action (
721 tree_view_frame, E_TREE_VIEW_FRAME_ACTION_MOVE_DOWN);
722 visible = gtk_tree_view_get_reorderable (tree_view);
723 sensitive = (n_selected_rows > 0 && !last_row_selected);
724 gtk_action_set_visible (action, visible);
725 gtk_action_set_sensitive (action, sensitive);
726
727 action = e_tree_view_frame_lookup_toolbar_action (
728 tree_view_frame, E_TREE_VIEW_FRAME_ACTION_MOVE_BOTTOM);
729 visible = gtk_tree_view_get_reorderable (tree_view);
730 sensitive = (n_selected_rows > 0 && !last_row_selected);
731 gtk_action_set_visible (action, visible);
732 gtk_action_set_sensitive (action, sensitive);
733
734 action = e_tree_view_frame_lookup_toolbar_action (
735 tree_view_frame, E_TREE_VIEW_FRAME_ACTION_SELECT_ALL);
736 visible = (selection_mode == GTK_SELECTION_MULTIPLE);
737 sensitive = (n_selected_rows < n_rows);
738 gtk_action_set_visible (action, visible);
739 gtk_action_set_sensitive (action, sensitive);
740 }
741
742 static void
e_tree_view_frame_class_init(ETreeViewFrameClass * class)743 e_tree_view_frame_class_init (ETreeViewFrameClass *class)
744 {
745 GObjectClass *object_class;
746
747 g_type_class_add_private (class, sizeof (ETreeViewFramePrivate));
748
749 object_class = G_OBJECT_CLASS (class);
750 object_class->set_property = tree_view_frame_set_property;
751 object_class->get_property = tree_view_frame_get_property;
752 object_class->dispose = tree_view_frame_dispose;
753 object_class->finalize = tree_view_frame_finalize;
754 object_class->constructed = tree_view_frame_constructed;
755
756 class->toolbar_action_activate =
757 tree_view_frame_toolbar_action_activate;
758 class->update_toolbar_actions =
759 tree_view_frame_update_toolbar_actions;
760
761 g_object_class_install_property (
762 object_class,
763 PROP_HSCROLLBAR_POLICY,
764 g_param_spec_enum (
765 "hscrollbar-policy",
766 "Horizontal Scrollbar Policy",
767 "When the horizontal scrollbar is displayed",
768 GTK_TYPE_POLICY_TYPE,
769 GTK_POLICY_AUTOMATIC,
770 G_PARAM_READWRITE |
771 G_PARAM_CONSTRUCT |
772 G_PARAM_STATIC_STRINGS));
773
774 /* Don't use G_PARAM_CONSTRUCT here. Our constructed() method
775 * will install a default GtkTreeView once the scrolled window
776 * is set up. */
777 g_object_class_install_property (
778 object_class,
779 PROP_TREE_VIEW,
780 g_param_spec_object (
781 "tree-view",
782 "Tree View",
783 "The tree view widget",
784 GTK_TYPE_TREE_VIEW,
785 G_PARAM_READWRITE |
786 G_PARAM_STATIC_STRINGS));
787
788 g_object_class_install_property (
789 object_class,
790 PROP_TOOLBAR_VISIBLE,
791 g_param_spec_boolean (
792 "toolbar-visible",
793 "Toolbar Visible",
794 "Whether to show the inline toolbar",
795 TRUE,
796 G_PARAM_READWRITE |
797 G_PARAM_CONSTRUCT |
798 G_PARAM_STATIC_STRINGS));
799
800 g_object_class_install_property (
801 object_class,
802 PROP_VSCROLLBAR_POLICY,
803 g_param_spec_enum (
804 "vscrollbar-policy",
805 "Vertical Scrollbar Policy",
806 "When the vertical scrollbar is displayed",
807 GTK_TYPE_POLICY_TYPE,
808 GTK_POLICY_AUTOMATIC,
809 G_PARAM_READWRITE |
810 G_PARAM_CONSTRUCT |
811 G_PARAM_STATIC_STRINGS));
812
813 /**
814 * ETreeViewFrame::toolbar-action-activate:
815 * @tree_view_frame: the #ETreeViewFrame that received the signal
816 * @action: the #GtkAction that was activated
817 *
818 * Emitted when a toolbar action is activated.
819 *
820 * This signal supports "::detail" appendices to the signal name,
821 * where the "detail" part is the #GtkAction #GtkAction:name. So
822 * you can connect a signal handler to a particular action.
823 **/
824 signals[TOOLBAR_ACTION_ACTIVATE] = g_signal_new (
825 "toolbar-action-activate",
826 G_OBJECT_CLASS_TYPE (class),
827 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
828 G_STRUCT_OFFSET (
829 ETreeViewFrameClass,
830 toolbar_action_activate),
831 g_signal_accumulator_true_handled,
832 NULL, NULL,
833 G_TYPE_BOOLEAN, 1,
834 GTK_TYPE_ACTION);
835
836 /**
837 * ETreeViewFrame::update-toolbar-actions:
838 * @tree_view_frame: the #ETreeViewFrame that received the signal
839 *
840 * Requests toolbar actions be updated, usually in response to a
841 * #GtkTreeSelection change. Handlers should update #GtkAction
842 * properties like #GtkAction:visible and #GtkAction:sensitive
843 * based on the current #ETreeViewFrame:tree-view state.
844 **/
845 signals[UPDATE_TOOLBAR_ACTIONS] = g_signal_new (
846 "update-toolbar-actions",
847 G_OBJECT_CLASS_TYPE (class),
848 G_SIGNAL_RUN_LAST,
849 G_STRUCT_OFFSET (
850 ETreeViewFrameClass,
851 update_toolbar_actions),
852 NULL, NULL, NULL,
853 G_TYPE_NONE, 0);
854 }
855
856 static void
e_tree_view_frame_init(ETreeViewFrame * tree_view_frame)857 e_tree_view_frame_init (ETreeViewFrame *tree_view_frame)
858 {
859 GHashTable *tool_item_ht;
860
861 tool_item_ht = g_hash_table_new_full (
862 (GHashFunc) g_str_hash,
863 (GEqualFunc) g_str_equal,
864 (GDestroyNotify) g_free,
865 (GDestroyNotify) g_object_unref);
866
867 tree_view_frame->priv =
868 E_TREE_VIEW_FRAME_GET_PRIVATE (tree_view_frame);
869
870 tree_view_frame->priv->tool_item_ht = tool_item_ht;
871 }
872
873 /**
874 * e_tree_view_frame_new:
875 *
876 * Creates a new #ETreeViewFrame.
877 *
878 * Returns: an #ETreeViewFrame
879 **/
880 GtkWidget *
e_tree_view_frame_new(void)881 e_tree_view_frame_new (void)
882 {
883 return g_object_new (E_TYPE_TREE_VIEW_FRAME, NULL);
884 }
885
886 /**
887 * e_tree_view_frame_get_tree_view:
888 * @tree_view_frame: an #ETreeViewFrame
889 *
890 * Returns the #ETreeViewFrame:tree-view for @tree_view_frame.
891 *
892 * The @tree_view_frame creates its own #GtkTreeView by default, but
893 * that instance can be replaced with e_tree_view_frame_set_tree_view().
894 *
895 * Returns: a #GtkTreeView
896 **/
897 GtkTreeView *
e_tree_view_frame_get_tree_view(ETreeViewFrame * tree_view_frame)898 e_tree_view_frame_get_tree_view (ETreeViewFrame *tree_view_frame)
899 {
900 g_return_val_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame), NULL);
901
902 return tree_view_frame->priv->tree_view;
903 }
904
905 /**
906 * e_tree_view_frame_set_tree_view:
907 * @tree_view_frame: an #ETreeViewFrame
908 * @tree_view: a #GtkTreeView, or %NULL
909 *
910 * Replaces the previous #ETreeViewFrame:tree-view with the given @tree_view.
911 * If @tree_view is %NULL, the @tree_view_frame creates a new #GtkTreeView.
912 **/
913 void
e_tree_view_frame_set_tree_view(ETreeViewFrame * tree_view_frame,GtkTreeView * tree_view)914 e_tree_view_frame_set_tree_view (ETreeViewFrame *tree_view_frame,
915 GtkTreeView *tree_view)
916 {
917 GtkTreeSelection *selection;
918 GtkWidget *scrolled_window;
919 gulong handler_id;
920
921 g_return_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame));
922
923 if (tree_view != NULL) {
924 g_return_if_fail (GTK_IS_TREE_VIEW (tree_view));
925 g_object_ref (tree_view);
926 } else {
927 tree_view = (GtkTreeView *) gtk_tree_view_new ();
928 g_object_ref_sink (tree_view);
929 }
930
931 scrolled_window = tree_view_frame->priv->scrolled_window;
932
933 if (tree_view_frame->priv->tree_view != NULL) {
934 gtk_container_remove (
935 GTK_CONTAINER (scrolled_window),
936 GTK_WIDGET (tree_view_frame->priv->tree_view));
937 tree_view_frame_dispose_tree_view (tree_view_frame->priv);
938 }
939
940 tree_view_frame->priv->tree_view = tree_view;
941
942 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
943
944 handler_id = e_signal_connect_notify (
945 tree_view, "notify::reorderable",
946 G_CALLBACK (tree_view_frame_notify_reorderable_cb),
947 tree_view_frame);
948 tree_view_frame->priv->notify_reorderable_handler_id = handler_id;
949
950 handler_id = e_signal_connect_notify (
951 selection, "notify::mode",
952 G_CALLBACK (tree_view_frame_notify_select_mode_cb),
953 tree_view_frame);
954 tree_view_frame->priv->notify_select_mode_handler_id = handler_id;
955
956 handler_id = g_signal_connect (
957 selection, "changed",
958 G_CALLBACK (tree_view_frame_selection_changed_cb),
959 tree_view_frame);
960 tree_view_frame->priv->selection_changed_handler_id = handler_id;
961
962 gtk_container_add (
963 GTK_CONTAINER (scrolled_window),
964 GTK_WIDGET (tree_view));
965
966 gtk_widget_show (GTK_WIDGET (tree_view));
967
968 g_object_notify (G_OBJECT (tree_view_frame), "tree-view");
969
970 e_tree_view_frame_update_toolbar_actions (tree_view_frame);
971 }
972
973 /**
974 * e_tree_view_frame_get_toolbar_visible:
975 * @tree_view_frame: an #ETreeViewFrame
976 *
977 * Returns whether the inline toolbar in @tree_view_frame is visible.
978 *
979 * Returns: %TRUE if the toolbar is visible, %FALSE if invisible
980 **/
981 gboolean
e_tree_view_frame_get_toolbar_visible(ETreeViewFrame * tree_view_frame)982 e_tree_view_frame_get_toolbar_visible (ETreeViewFrame *tree_view_frame)
983 {
984 g_return_val_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame), FALSE);
985
986 return tree_view_frame->priv->toolbar_visible;
987 }
988
989 /**
990 * e_tree_view_frame_set_toolbar_visible:
991 * @tree_view_frame: an #ETreeViewFrame
992 * @toolbar_visible: whether to make the toolbar visible
993 *
994 * Shows or hides the inline toolbar in @tree_view_frame.
995 **/
996 void
e_tree_view_frame_set_toolbar_visible(ETreeViewFrame * tree_view_frame,gboolean toolbar_visible)997 e_tree_view_frame_set_toolbar_visible (ETreeViewFrame *tree_view_frame,
998 gboolean toolbar_visible)
999 {
1000 g_return_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame));
1001
1002 if (toolbar_visible == tree_view_frame->priv->toolbar_visible)
1003 return;
1004
1005 tree_view_frame->priv->toolbar_visible = toolbar_visible;
1006
1007 g_object_notify (G_OBJECT (tree_view_frame), "toolbar-visible");
1008 }
1009
1010 /**
1011 * e_tree_view_frame_get_hscrollbar_policy:
1012 * @tree_view_frame: an #ETreeViewFrame
1013 *
1014 * Returns the policy for the horizontal scrollbar in @tree_view_frame.
1015 *
1016 * Returns: the policy for the horizontal scrollbar
1017 **/
1018 GtkPolicyType
e_tree_view_frame_get_hscrollbar_policy(ETreeViewFrame * tree_view_frame)1019 e_tree_view_frame_get_hscrollbar_policy (ETreeViewFrame *tree_view_frame)
1020 {
1021 g_return_val_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame), 0);
1022
1023 return tree_view_frame->priv->hscrollbar_policy;
1024 }
1025
1026 /**
1027 * e_tree_view_frame_set_hscrollbar_policy:
1028 * @tree_view_frame: an #ETreeViewFrame
1029 * @hscrollbar_policy: the policy for the horizontal scrollbar
1030 *
1031 * Sets the policy for the horizontal scrollbar in @tree_view_frame.
1032 **/
1033 void
e_tree_view_frame_set_hscrollbar_policy(ETreeViewFrame * tree_view_frame,GtkPolicyType hscrollbar_policy)1034 e_tree_view_frame_set_hscrollbar_policy (ETreeViewFrame *tree_view_frame,
1035 GtkPolicyType hscrollbar_policy)
1036 {
1037 g_return_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame));
1038
1039 if (hscrollbar_policy == tree_view_frame->priv->hscrollbar_policy)
1040 return;
1041
1042 tree_view_frame->priv->hscrollbar_policy = hscrollbar_policy;
1043
1044 g_object_notify (G_OBJECT (tree_view_frame), "hscrollbar-policy");
1045 }
1046
1047 /**
1048 * e_tree_view_frame_get_vscrollbar_policy:
1049 * @tree_view_frame: an #ETreeViewFrame
1050 *
1051 * Returns the policy for the vertical scrollbar in @tree_view_frame.
1052 *
1053 * Returns: the policy for the vertical scrollbar
1054 **/
1055 GtkPolicyType
e_tree_view_frame_get_vscrollbar_policy(ETreeViewFrame * tree_view_frame)1056 e_tree_view_frame_get_vscrollbar_policy (ETreeViewFrame *tree_view_frame)
1057 {
1058 g_return_val_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame), 0);
1059
1060 return tree_view_frame->priv->vscrollbar_policy;
1061 }
1062
1063 /**
1064 * e_tree_view_frame_set_vscrollbar_policy:
1065 * @tree_view_frame: an #ETreeViewFrame
1066 * @vscrollbar_policy: the policy for the vertical scrollbar
1067 *
1068 * Sets the policy for the vertical scrollbar in @tree_view_frame.
1069 **/
1070 void
e_tree_view_frame_set_vscrollbar_policy(ETreeViewFrame * tree_view_frame,GtkPolicyType vscrollbar_policy)1071 e_tree_view_frame_set_vscrollbar_policy (ETreeViewFrame *tree_view_frame,
1072 GtkPolicyType vscrollbar_policy)
1073 {
1074 g_return_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame));
1075
1076 if (vscrollbar_policy == tree_view_frame->priv->vscrollbar_policy)
1077 return;
1078
1079 tree_view_frame->priv->vscrollbar_policy = vscrollbar_policy;
1080
1081 g_object_notify (G_OBJECT (tree_view_frame), "vscrollbar-policy");
1082 }
1083
1084 /**
1085 * e_tree_view_frame_insert_toolbar_action:
1086 * @tree_view_frame: an #ETreeViewFrame
1087 * @action: a #GtkAction
1088 * @position: the position of the new action
1089 *
1090 * Generates a #GtkToolItem from @action and inserts it into the inline
1091 * toolbar at the given @position. If @position is zero, the item is
1092 * prepended to the start of the toolbar. If @position is negative,
1093 * the item is appended to the end of the toolbar.
1094 **/
1095 void
e_tree_view_frame_insert_toolbar_action(ETreeViewFrame * tree_view_frame,GtkAction * action,gint position)1096 e_tree_view_frame_insert_toolbar_action (ETreeViewFrame *tree_view_frame,
1097 GtkAction *action,
1098 gint position)
1099 {
1100 GtkToolbar *toolbar;
1101 GtkWidget *tool_item;
1102 GHashTable *tool_item_ht;
1103 const gchar *action_name;
1104
1105 g_return_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame));
1106 g_return_if_fail (GTK_IS_ACTION (action));
1107
1108 action_name = gtk_action_get_name (action);
1109 g_return_if_fail (action_name != NULL);
1110
1111 tool_item_ht = tree_view_frame->priv->tool_item_ht;
1112 toolbar = GTK_TOOLBAR (tree_view_frame->priv->inline_toolbar);
1113
1114 if (g_hash_table_contains (tool_item_ht, action_name)) {
1115 g_warning (
1116 "%s: Duplicate action name '%s'",
1117 G_STRFUNC, action_name);
1118 return;
1119 }
1120
1121 tool_item = gtk_action_create_tool_item (action);
1122 g_return_if_fail (GTK_IS_TOOL_ITEM (tool_item));
1123
1124 gtk_toolbar_insert (toolbar, GTK_TOOL_ITEM (tool_item), position);
1125
1126 g_hash_table_insert (
1127 tool_item_ht,
1128 g_strdup (action_name),
1129 g_object_ref (tool_item));
1130
1131 g_signal_connect (
1132 action, "activate",
1133 G_CALLBACK (tree_view_frame_action_activate_cb),
1134 tree_view_frame);
1135 }
1136
1137 /**
1138 * e_tree_view_frame_lookup_toolbar_action:
1139 * @tree_view_frame: an #ETreeViewFrame
1140 * @action_name: a #GtkAction name
1141 *
1142 * Returns the toolbar action named @action_name, or %NULL if no such
1143 * toolbar action exists.
1144 *
1145 * Returns: a #GtkAction, or %NULL
1146 **/
1147 GtkAction *
e_tree_view_frame_lookup_toolbar_action(ETreeViewFrame * tree_view_frame,const gchar * action_name)1148 e_tree_view_frame_lookup_toolbar_action (ETreeViewFrame *tree_view_frame,
1149 const gchar *action_name)
1150 {
1151 GHashTable *tool_item_ht;
1152 GtkActivatable *activatable;
1153 GtkAction *action = NULL;
1154
1155 g_return_val_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame), NULL);
1156 g_return_val_if_fail (action_name != NULL, NULL);
1157
1158 tool_item_ht = tree_view_frame->priv->tool_item_ht;
1159 activatable = g_hash_table_lookup (tool_item_ht, action_name);
1160
1161 if (GTK_IS_ACTIVATABLE (activatable))
1162 action = gtk_activatable_get_related_action (activatable);
1163
1164 return action;
1165 }
1166
1167 /**
1168 * e_tree_view_frame_update_toolbar_actions:
1169 * @tree_view_frame: an #ETreeViewFrame
1170 *
1171 * Emits the #ETreeViewFrame::update-toolbar-actions signal.
1172 *
1173 * See the signal description for more details.
1174 **/
1175 void
e_tree_view_frame_update_toolbar_actions(ETreeViewFrame * tree_view_frame)1176 e_tree_view_frame_update_toolbar_actions (ETreeViewFrame *tree_view_frame)
1177 {
1178 g_return_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame));
1179
1180 g_signal_emit (tree_view_frame, signals[UPDATE_TOOLBAR_ACTIONS], 0);
1181 }
1182
1183