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