aboutsummaryrefslogtreecommitdiffstats
path: root/e-util
diff options
context:
space:
mode:
authorMatthew Barnes <mbarnes@redhat.com>2013-06-24 22:21:43 +0800
committerMatthew Barnes <mbarnes@redhat.com>2013-06-26 02:44:11 +0800
commit9ae72cb11c81a6eabc97655816c680186b33668c (patch)
tree7fbe6941ddc91ad368f30859198705a38b67708f /e-util
parenta8773bf1306ebc1cd81100ca8c8184a873ad6696 (diff)
downloadgsoc2013-evolution-9ae72cb11c81a6eabc97655816c680186b33668c.tar
gsoc2013-evolution-9ae72cb11c81a6eabc97655816c680186b33668c.tar.gz
gsoc2013-evolution-9ae72cb11c81a6eabc97655816c680186b33668c.tar.bz2
gsoc2013-evolution-9ae72cb11c81a6eabc97655816c680186b33668c.tar.lz
gsoc2013-evolution-9ae72cb11c81a6eabc97655816c680186b33668c.tar.xz
gsoc2013-evolution-9ae72cb11c81a6eabc97655816c680186b33668c.tar.zst
gsoc2013-evolution-9ae72cb11c81a6eabc97655816c680186b33668c.zip
Add ETreeViewFrame.
ETreeViewFrame embeds a GtkTreeView in a scrolled window and adds an inline-style toolbar beneath the scrolled window which can be hidden. The inline-style toolbar supports "add" and "remove" actions, as well as move actions if the tree view is reorderable and selection actions if the tree view supports multiple selections. The action set can be extended through e_tree_view_frame_insert_toolbar_action(). This also adds a small demo program: test-tree-view-frame
Diffstat (limited to 'e-util')
-rw-r--r--e-util/Makefile.am7
-rw-r--r--e-util/e-tree-view-frame.c1183
-rw-r--r--e-util/e-tree-view-frame.h119
-rw-r--r--e-util/e-util.h1
-rw-r--r--e-util/test-tree-view-frame.c375
5 files changed, 1685 insertions, 0 deletions
diff --git a/e-util/Makefile.am b/e-util/Makefile.am
index 0ebbc8a17a..a7123bbb96 100644
--- a/e-util/Makefile.am
+++ b/e-util/Makefile.am
@@ -70,6 +70,7 @@ noinst_PROGRAMS = \
test-source-combo-box \
test-source-config \
test-source-selector \
+ test-tree-view-frame \
$(NULL)
libevolution_util_la_CPPFLAGS = \
@@ -296,6 +297,7 @@ evolution_util_include_HEADERS = \
e-tree-model.h \
e-tree-selection-model.h \
e-tree-table-adapter.h \
+ e-tree-view-frame.h \
e-tree.h \
e-unicode.h \
e-url-entry.h \
@@ -538,6 +540,7 @@ libevolution_util_la_SOURCES = \
e-tree-model.c \
e-tree-selection-model.c \
e-tree-table-adapter.c \
+ e-tree-view-frame.c \
e-tree.c \
e-unicode.c \
e-url-entry.c \
@@ -648,6 +651,10 @@ test_source_selector_CPPFLAGS = $(TEST_CPPFLAGS)
test_source_selector_SOURCES = test-source-selector.c
test_source_selector_LDADD = $(TEST_LDADD)
+test_tree_view_frame_CPPFLAGS = $(TEST_CPPFLAGS)
+test_tree_view_frame_SOURCES = test-tree-view-frame.c
+test_tree_view_frame_LDADD = $(TEST_LDADD)
+
EXTRA_DIST = \
e-system.error.xml \
filter.error.xml \
diff --git a/e-util/e-tree-view-frame.c b/e-util/e-tree-view-frame.c
new file mode 100644
index 0000000000..3a1bca712e
--- /dev/null
+++ b/e-util/e-tree-view-frame.c
@@ -0,0 +1,1183 @@
+/*
+ * e-tree-view-frame.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/**
+ * SECTION: e-tree-view-frame
+ * @include: e-util/e-util.h
+ * @short_description: A frame for #GtkTreeView
+ *
+ * #ETreeViewFrame embeds a #GtkTreeView in a scrolled window and adds an
+ * inline-style toolbar beneath the scrolled window which can be hidden.
+ *
+ * The inline-style toolbar supports "add" and "remove" actions, as well
+ * as move actions if the tree view is reorderable and selection actions
+ * if the tree view supports multiple selections. The action set can be
+ * extended through e_tree_view_frame_insert_toolbar_action().
+ **/
+
+#include "e-tree-view-frame.h"
+
+#include <libebackend/libebackend.h>
+
+#define E_TREE_VIEW_FRAME_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_TREE_VIEW_FRAME, ETreeViewFramePrivate))
+
+/**
+ * E_TREE_VIEW_FRAME_ACTION_ADD:
+ *
+ * The #GtkAction name for the "add" toolbar button.
+ *
+ * Use e_tree_view_frame_lookup_toolbar_action() to obtain the #GtkAction.
+ **/
+
+/**
+ * E_TREE_VIEW_FRAME_ACTION_REMOVE:
+ *
+ * The #GtkAction name for the "remove" toolbar button.
+ *
+ * Use e_tree_view_frame_lookup_toolbar_action() to obtain the #GtkAction.
+ **/
+
+/**
+ * E_TREE_VIEW_FRAME_ACTION_MOVE_TOP:
+ *
+ * The #GtkAction name for the "move selected items to top" button.
+ *
+ * Use e_tree_view_frame_lookup_toolbar_action() to obtain the #GtkAction.
+ **/
+
+/**
+ * E_TREE_VIEW_FRAME_ACTION_MOVE_UP:
+ *
+ * The #GtkAction name for the "move selected items up" button.
+ *
+ * Use e_tree_view_frame_lookup_toolbar_action() to obtain the #GtkAction.
+ **/
+
+/**
+ * E_TREE_VIEW_FRAME_ACTION_MOVE_DOWN:
+ *
+ * The #GtkAction name for the "move selected items down" button.
+ *
+ * Use e_tree_view_frame_lookup_toolbar_action() to obtain the #GtkAction.
+ **/
+
+/**
+ * E_TREE_VIEW_FRAME_ACTION_MOVE_BOTTOM:
+ *
+ * The #GtkAction name for the "move selected items to bottom" button.
+ *
+ * Use e_tree_view_frame_lookup_toolbar_action() to obtain the #GtkAction.
+ **/
+
+/**
+ * E_TREE_VIEW_FRAME_ACTION_SELECT_ALL:
+ *
+ * The #GtkAction name for the "select all" button.
+ *
+ * Use e_tree_view_frame_lookup_toolbar_action() to obtain the #GtkAction.
+ **/
+
+struct _ETreeViewFramePrivate {
+ GtkTreeView *tree_view;
+ gulong notify_reorderable_handler_id;
+ gulong notify_select_mode_handler_id;
+ gulong selection_changed_handler_id;
+
+ GtkWidget *scrolled_window;
+ GtkWidget *inline_toolbar;
+
+ GHashTable *tool_item_ht;
+
+ GtkPolicyType hscrollbar_policy;
+ GtkPolicyType vscrollbar_policy;
+
+ gboolean toolbar_visible;
+};
+
+enum {
+ PROP_0,
+ PROP_HSCROLLBAR_POLICY,
+ PROP_TREE_VIEW,
+ PROP_TOOLBAR_VISIBLE,
+ PROP_VSCROLLBAR_POLICY
+};
+
+enum {
+ TOOLBAR_ACTION_ACTIVATE,
+ UPDATE_TOOLBAR_ACTIONS,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE_WITH_CODE (
+ ETreeViewFrame,
+ e_tree_view_frame,
+ GTK_TYPE_BOX,
+ G_IMPLEMENT_INTERFACE (
+ E_TYPE_EXTENSIBLE, NULL))
+
+static void
+tree_view_frame_append_action (ETreeViewFrame *tree_view_frame,
+ const gchar *action_name,
+ const gchar *icon_name)
+{
+ GtkAction *action;
+ GIcon *icon;
+
+ icon = g_themed_icon_new_with_default_fallbacks (icon_name);
+
+ action = g_object_new (
+ GTK_TYPE_ACTION,
+ "name", action_name, "gicon", icon, NULL);
+
+ e_tree_view_frame_insert_toolbar_action (tree_view_frame, action, -1);
+
+ g_object_unref (action);
+ g_object_unref (icon);
+}
+
+static void
+tree_view_frame_dispose_tree_view (ETreeViewFramePrivate *priv)
+{
+ GtkTreeSelection *selection;
+
+ selection = gtk_tree_view_get_selection (priv->tree_view);
+
+ if (priv->notify_reorderable_handler_id > 0) {
+ g_signal_handler_disconnect (
+ priv->tree_view,
+ priv->notify_reorderable_handler_id);
+ priv->notify_reorderable_handler_id = 0;
+ }
+
+ if (priv->notify_select_mode_handler_id > 0) {
+ g_signal_handler_disconnect (
+ selection,
+ priv->notify_select_mode_handler_id);
+ priv->notify_select_mode_handler_id = 0;
+ }
+
+ if (priv->selection_changed_handler_id > 0) {
+ g_signal_handler_disconnect (
+ selection,
+ priv->selection_changed_handler_id);
+ priv->selection_changed_handler_id = 0;
+ }
+
+ g_clear_object (&priv->tree_view);
+}
+
+static gboolean
+tree_view_frame_first_row_selected (GtkTreeView *tree_view)
+{
+ GtkTreeModel *tree_model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ tree_model = gtk_tree_view_get_model (tree_view);
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ if (tree_model == NULL)
+ return FALSE;
+
+ if (!gtk_tree_model_iter_nth_child (tree_model, &iter, NULL, 0))
+ return FALSE;
+
+ return gtk_tree_selection_iter_is_selected (selection, &iter);
+}
+
+static gboolean
+tree_view_frame_last_row_selected (GtkTreeView *tree_view)
+{
+ GtkTreeModel *tree_model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ gint last;
+
+ tree_model = gtk_tree_view_get_model (tree_view);
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ if (tree_model == NULL)
+ return FALSE;
+
+ last = gtk_tree_model_iter_n_children (tree_model, NULL) - 1;
+ if (last < 0)
+ return FALSE;
+
+ if (!gtk_tree_model_iter_nth_child (tree_model, &iter, NULL, last))
+ return FALSE;
+
+ return gtk_tree_selection_iter_is_selected (selection, &iter);
+}
+
+static gboolean
+tree_view_frame_move_selection_up (GtkTreeView *tree_view)
+{
+ GtkTreeModel *tree_model;
+ GtkListStore *list_store;
+ GtkTreeSelection *selection;
+ GList *list, *link;
+
+ tree_model = gtk_tree_view_get_model (tree_view);
+ if (!GTK_IS_LIST_STORE (tree_model))
+ return FALSE;
+
+ if (tree_view_frame_first_row_selected (tree_view))
+ return FALSE;
+
+ list_store = GTK_LIST_STORE (tree_model);
+
+ selection = gtk_tree_view_get_selection (tree_view);
+ list = gtk_tree_selection_get_selected_rows (selection, NULL);
+
+ /* Move all selected rows up one, even
+ * if the selection is not contiguous. */
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ GtkTreePath *path = link->data;
+ GtkTreeIter iter;
+ GtkTreeIter prev;
+
+ if (!gtk_tree_model_get_iter (tree_model, &iter, path)) {
+ g_warn_if_reached ();
+ continue;
+ }
+
+ prev = iter;
+ if (!gtk_tree_model_iter_previous (tree_model, &prev)) {
+ g_warn_if_reached ();
+ continue;
+ }
+
+ gtk_list_store_swap (list_store, &iter, &prev);
+ }
+
+ g_list_free_full (list, (GDestroyNotify) gtk_tree_path_free);
+
+ return TRUE;
+}
+
+static gboolean
+tree_view_frame_move_selection_down (GtkTreeView *tree_view)
+{
+ GtkTreeModel *tree_model;
+ GtkListStore *list_store;
+ GtkTreeSelection *selection;
+ GList *list, *link;
+
+ tree_model = gtk_tree_view_get_model (tree_view);
+ if (!GTK_IS_LIST_STORE (tree_model))
+ return FALSE;
+
+ if (tree_view_frame_last_row_selected (tree_view))
+ return FALSE;
+
+ list_store = GTK_LIST_STORE (tree_model);
+
+ selection = gtk_tree_view_get_selection (tree_view);
+ list = gtk_tree_selection_get_selected_rows (selection, NULL);
+
+ /* Reverse the list so we don't disturb rows we've already moved. */
+ list = g_list_reverse (list);
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ GtkTreePath *path = link->data;
+ GtkTreeIter iter;
+ GtkTreeIter next;
+
+ if (!gtk_tree_model_get_iter (tree_model, &iter, path)) {
+ g_warn_if_reached ();
+ continue;
+ }
+
+ next = iter;
+ if (!gtk_tree_model_iter_next (tree_model, &next)) {
+ g_warn_if_reached ();
+ continue;
+ }
+
+ gtk_list_store_swap (list_store, &iter, &next);
+ }
+
+ g_list_free_full (list, (GDestroyNotify) gtk_tree_path_free);
+
+ return TRUE;
+}
+
+static void
+tree_view_frame_scroll_to_cursor (GtkTreeView *tree_view)
+{
+ GtkTreePath *path = NULL;
+
+ gtk_tree_view_get_cursor (tree_view, &path, NULL);
+
+ if (path != NULL) {
+ gtk_tree_view_scroll_to_cell (
+ tree_view, path, NULL, FALSE, 0.0, 0.0);
+ gtk_tree_path_free (path);
+ }
+}
+
+static void
+tree_view_frame_action_go_top (ETreeViewFrame *tree_view_frame)
+{
+ GtkTreeView *tree_view;
+
+ tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
+
+ /* Not the most efficient method, but it's simple and works. */
+ while (tree_view_frame_move_selection_up (tree_view))
+ ;
+
+ tree_view_frame_scroll_to_cursor (tree_view);
+ e_tree_view_frame_update_toolbar_actions (tree_view_frame);
+}
+
+static void
+tree_view_frame_action_go_up (ETreeViewFrame *tree_view_frame)
+{
+ GtkTreeView *tree_view;
+
+ tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
+
+ tree_view_frame_move_selection_up (tree_view);
+
+ tree_view_frame_scroll_to_cursor (tree_view);
+ e_tree_view_frame_update_toolbar_actions (tree_view_frame);
+}
+
+static void
+tree_view_frame_action_go_down (ETreeViewFrame *tree_view_frame)
+{
+ GtkTreeView *tree_view;
+
+ tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
+
+ tree_view_frame_move_selection_down (tree_view);
+
+ tree_view_frame_scroll_to_cursor (tree_view);
+ e_tree_view_frame_update_toolbar_actions (tree_view_frame);
+}
+
+static void
+tree_view_frame_action_go_bottom (ETreeViewFrame *tree_view_frame)
+{
+ GtkTreeView *tree_view;
+
+ tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
+
+ /* Not the most efficient method, but it's simple and works. */
+ while (tree_view_frame_move_selection_down (tree_view))
+ ;
+
+ tree_view_frame_scroll_to_cursor (tree_view);
+ e_tree_view_frame_update_toolbar_actions (tree_view_frame);
+}
+
+static void
+tree_view_frame_action_select_all (ETreeViewFrame *tree_view_frame)
+{
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+
+ tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ gtk_tree_selection_select_all (selection);
+}
+
+static void
+tree_view_frame_action_activate_cb (GtkAction *action,
+ ETreeViewFrame *tree_view_frame)
+{
+ GQuark detail;
+ const gchar *action_name;
+ gboolean handled = FALSE;
+
+ action_name = gtk_action_get_name (action);
+ detail = g_quark_from_string (action_name);
+
+ g_signal_emit (
+ tree_view_frame,
+ signals[TOOLBAR_ACTION_ACTIVATE], detail,
+ action, &handled);
+}
+
+static void
+tree_view_frame_notify_reorderable_cb (GtkTreeView *tree_view,
+ GParamSpec *pspec,
+ ETreeViewFrame *tree_view_frame)
+{
+ e_tree_view_frame_update_toolbar_actions (tree_view_frame);
+}
+
+static void
+tree_view_frame_notify_select_mode_cb (GtkTreeSelection *selection,
+ GParamSpec *pspec,
+ ETreeViewFrame *tree_view_frame)
+{
+ e_tree_view_frame_update_toolbar_actions (tree_view_frame);
+}
+
+static void
+tree_view_frame_selection_changed_cb (GtkTreeSelection *selection,
+ ETreeViewFrame *tree_view_frame)
+{
+ e_tree_view_frame_update_toolbar_actions (tree_view_frame);
+}
+
+static void
+tree_view_frame_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_HSCROLLBAR_POLICY:
+ e_tree_view_frame_set_hscrollbar_policy (
+ E_TREE_VIEW_FRAME (object),
+ g_value_get_enum (value));
+ return;
+
+ case PROP_TREE_VIEW:
+ e_tree_view_frame_set_tree_view (
+ E_TREE_VIEW_FRAME (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_TOOLBAR_VISIBLE:
+ e_tree_view_frame_set_toolbar_visible (
+ E_TREE_VIEW_FRAME (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_VSCROLLBAR_POLICY:
+ e_tree_view_frame_set_vscrollbar_policy (
+ E_TREE_VIEW_FRAME (object),
+ g_value_get_enum (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+tree_view_frame_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_HSCROLLBAR_POLICY:
+ g_value_set_enum (
+ value,
+ e_tree_view_frame_get_hscrollbar_policy (
+ E_TREE_VIEW_FRAME (object)));
+ return;
+
+ case PROP_TREE_VIEW:
+ g_value_set_object (
+ value,
+ e_tree_view_frame_get_tree_view (
+ E_TREE_VIEW_FRAME (object)));
+ return;
+
+ case PROP_TOOLBAR_VISIBLE:
+ g_value_set_boolean (
+ value,
+ e_tree_view_frame_get_toolbar_visible (
+ E_TREE_VIEW_FRAME (object)));
+ return;
+
+ case PROP_VSCROLLBAR_POLICY:
+ g_value_set_enum (
+ value,
+ e_tree_view_frame_get_vscrollbar_policy (
+ E_TREE_VIEW_FRAME (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+tree_view_frame_dispose (GObject *object)
+{
+ ETreeViewFramePrivate *priv;
+
+ priv = E_TREE_VIEW_FRAME_GET_PRIVATE (object);
+
+ tree_view_frame_dispose_tree_view (priv);
+
+ g_clear_object (&priv->scrolled_window);
+ g_clear_object (&priv->inline_toolbar);
+
+ g_hash_table_remove_all (priv->tool_item_ht);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_tree_view_frame_parent_class)->dispose (object);
+}
+
+static void
+tree_view_frame_finalize (GObject *object)
+{
+ ETreeViewFramePrivate *priv;
+
+ priv = E_TREE_VIEW_FRAME_GET_PRIVATE (object);
+
+ g_hash_table_destroy (priv->tool_item_ht);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_tree_view_frame_parent_class)->finalize (object);
+}
+
+static void
+tree_view_frame_constructed (GObject *object)
+{
+ ETreeViewFrame *tree_view_frame;
+ GtkStyleContext *style_context;
+ GtkWidget *widget;
+
+ tree_view_frame = E_TREE_VIEW_FRAME (object);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_tree_view_frame_parent_class)->constructed (object);
+
+ gtk_orientable_set_orientation (
+ GTK_ORIENTABLE (tree_view_frame),
+ GTK_ORIENTATION_VERTICAL);
+
+ widget = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_shadow_type (
+ GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (tree_view_frame), widget, TRUE, TRUE, 0);
+ tree_view_frame->priv->scrolled_window = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ g_object_bind_property (
+ tree_view_frame, "hscrollbar-policy",
+ widget, "hscrollbar-policy",
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (
+ tree_view_frame, "vscrollbar-policy",
+ widget, "vscrollbar-policy",
+ G_BINDING_SYNC_CREATE);
+
+ widget = gtk_toolbar_new ();
+ gtk_toolbar_set_show_arrow (GTK_TOOLBAR (widget), FALSE);
+ gtk_toolbar_set_style (GTK_TOOLBAR (widget), GTK_TOOLBAR_ICONS);
+ gtk_toolbar_set_icon_size (GTK_TOOLBAR (widget), GTK_ICON_SIZE_MENU);
+ gtk_box_pack_start (GTK_BOX (tree_view_frame), widget, FALSE, FALSE, 0);
+ tree_view_frame->priv->inline_toolbar = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ style_context = gtk_widget_get_style_context (widget);
+ gtk_style_context_add_class (
+ style_context, GTK_STYLE_CLASS_INLINE_TOOLBAR);
+ gtk_style_context_set_junction_sides (
+ style_context, GTK_JUNCTION_TOP);
+
+ g_object_bind_property (
+ tree_view_frame, "toolbar-visible",
+ widget, "visible",
+ G_BINDING_SYNC_CREATE);
+
+ /* Define actions for toolbar items. */
+ tree_view_frame_append_action (
+ tree_view_frame,
+ E_TREE_VIEW_FRAME_ACTION_ADD,
+ "list-add-symbolic");
+ tree_view_frame_append_action (
+ tree_view_frame,
+ E_TREE_VIEW_FRAME_ACTION_REMOVE,
+ "list-remove-symbolic");
+ tree_view_frame_append_action (
+ tree_view_frame,
+ E_TREE_VIEW_FRAME_ACTION_MOVE_TOP,
+ "go-top-symbolic");
+ tree_view_frame_append_action (
+ tree_view_frame,
+ E_TREE_VIEW_FRAME_ACTION_MOVE_UP,
+ "go-up-symbolic");
+ tree_view_frame_append_action (
+ tree_view_frame,
+ E_TREE_VIEW_FRAME_ACTION_MOVE_DOWN,
+ "go-down-symbolic");
+ tree_view_frame_append_action (
+ tree_view_frame,
+ E_TREE_VIEW_FRAME_ACTION_MOVE_BOTTOM,
+ "go-bottom-symbolic");
+ tree_view_frame_append_action (
+ tree_view_frame,
+ E_TREE_VIEW_FRAME_ACTION_SELECT_ALL,
+ "edit-select-all-symbolic");
+
+ /* Install a default GtkTreeView. */
+ e_tree_view_frame_set_tree_view (tree_view_frame, NULL);
+}
+
+static gboolean
+tree_view_frame_toolbar_action_activate (ETreeViewFrame *tree_view_frame,
+ GtkAction *action)
+{
+ const gchar *action_name;
+
+ action_name = gtk_action_get_name (action);
+ g_return_val_if_fail (action_name != NULL, FALSE);
+
+ if (g_str_equal (action_name, E_TREE_VIEW_FRAME_ACTION_MOVE_TOP)) {
+ tree_view_frame_action_go_top (tree_view_frame);
+ return TRUE;
+ }
+
+ if (g_str_equal (action_name, E_TREE_VIEW_FRAME_ACTION_MOVE_UP)) {
+ tree_view_frame_action_go_up (tree_view_frame);
+ return TRUE;
+ }
+
+ if (g_str_equal (action_name, E_TREE_VIEW_FRAME_ACTION_MOVE_DOWN)) {
+ tree_view_frame_action_go_down (tree_view_frame);
+ return TRUE;
+ }
+
+ if (g_str_equal (action_name, E_TREE_VIEW_FRAME_ACTION_MOVE_BOTTOM)) {
+ tree_view_frame_action_go_bottom (tree_view_frame);
+ return TRUE;
+ }
+
+ if (g_str_equal (action_name, E_TREE_VIEW_FRAME_ACTION_SELECT_ALL)) {
+ tree_view_frame_action_select_all (tree_view_frame);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+tree_view_frame_update_toolbar_actions (ETreeViewFrame *tree_view_frame)
+{
+ GtkAction *action;
+ GtkTreeView *tree_view;
+ GtkTreeModel *tree_model;
+ GtkTreeSelection *selection;
+ GtkSelectionMode selection_mode;
+ gboolean first_row_selected;
+ gboolean last_row_selected;
+ gboolean sensitive;
+ gboolean visible;
+ gint n_selected_rows;
+ gint n_rows = 0;
+
+ /* XXX This implementation assumes the tree model is a list store.
+ * A tree store will require special handling, although I don't
+ * yet know if there's even a use case for a tree store here. */
+
+ tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
+
+ tree_model = gtk_tree_view_get_model (tree_view);
+ if (tree_model != NULL)
+ n_rows = gtk_tree_model_iter_n_children (tree_model, NULL);
+
+ selection = gtk_tree_view_get_selection (tree_view);
+ selection_mode = gtk_tree_selection_get_mode (selection);
+ n_selected_rows = gtk_tree_selection_count_selected_rows (selection);
+
+ first_row_selected = tree_view_frame_first_row_selected (tree_view);
+ last_row_selected = tree_view_frame_last_row_selected (tree_view);
+
+ action = e_tree_view_frame_lookup_toolbar_action (
+ tree_view_frame, E_TREE_VIEW_FRAME_ACTION_MOVE_TOP);
+ visible = gtk_tree_view_get_reorderable (tree_view);
+ sensitive = (n_selected_rows > 0 && !first_row_selected);
+ gtk_action_set_visible (action, visible);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = e_tree_view_frame_lookup_toolbar_action (
+ tree_view_frame, E_TREE_VIEW_FRAME_ACTION_MOVE_UP);
+ visible = gtk_tree_view_get_reorderable (tree_view);
+ sensitive = (n_selected_rows > 0 && !first_row_selected);
+ gtk_action_set_visible (action, visible);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = e_tree_view_frame_lookup_toolbar_action (
+ tree_view_frame, E_TREE_VIEW_FRAME_ACTION_MOVE_DOWN);
+ visible = gtk_tree_view_get_reorderable (tree_view);
+ sensitive = (n_selected_rows > 0 && !last_row_selected);
+ gtk_action_set_visible (action, visible);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = e_tree_view_frame_lookup_toolbar_action (
+ tree_view_frame, E_TREE_VIEW_FRAME_ACTION_MOVE_BOTTOM);
+ visible = gtk_tree_view_get_reorderable (tree_view);
+ sensitive = (n_selected_rows > 0 && !last_row_selected);
+ gtk_action_set_visible (action, visible);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = e_tree_view_frame_lookup_toolbar_action (
+ tree_view_frame, E_TREE_VIEW_FRAME_ACTION_SELECT_ALL);
+ visible = (selection_mode == GTK_SELECTION_MULTIPLE);
+ sensitive = (n_selected_rows < n_rows);
+ gtk_action_set_visible (action, visible);
+ gtk_action_set_sensitive (action, sensitive);
+}
+
+static void
+e_tree_view_frame_class_init (ETreeViewFrameClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (ETreeViewFramePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = tree_view_frame_set_property;
+ object_class->get_property = tree_view_frame_get_property;
+ object_class->dispose = tree_view_frame_dispose;
+ object_class->finalize = tree_view_frame_finalize;
+ object_class->constructed = tree_view_frame_constructed;
+
+ class->toolbar_action_activate =
+ tree_view_frame_toolbar_action_activate;
+ class->update_toolbar_actions =
+ tree_view_frame_update_toolbar_actions;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_HSCROLLBAR_POLICY,
+ g_param_spec_enum (
+ "hscrollbar-policy",
+ "Horizontal Scrollbar Policy",
+ "When the horizontal scrollbar is displayed",
+ GTK_TYPE_POLICY_TYPE,
+ GTK_POLICY_AUTOMATIC,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ /* Don't use G_PARAM_CONSTRUCT here. Our constructed() method
+ * will install a default GtkTreeView once the scrolled window
+ * is set up. */
+ g_object_class_install_property (
+ object_class,
+ PROP_TREE_VIEW,
+ g_param_spec_object (
+ "tree-view",
+ "Tree View",
+ "The tree view widget",
+ GTK_TYPE_TREE_VIEW,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_TOOLBAR_VISIBLE,
+ g_param_spec_boolean (
+ "toolbar-visible",
+ "Toolbar Visible",
+ "Whether to show the inline toolbar",
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_VSCROLLBAR_POLICY,
+ g_param_spec_enum (
+ "vscrollbar-policy",
+ "Vertical Scrollbar Policy",
+ "When the vertical scrollbar is displayed",
+ GTK_TYPE_POLICY_TYPE,
+ GTK_POLICY_AUTOMATIC,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * ETreeViewFrame::toolbar-action-activate:
+ * @tree_view_frame: the #ETreeViewFrame that received the signal
+ * @action: the #GtkAction that was activated
+ *
+ * Emitted when a toolbar action is activated.
+ *
+ * This signal supports "::detail" appendices to the signal name,
+ * where the "detail" part is the #GtkAction #GtkAction:name. So
+ * you can connect a signal handler to a particular action.
+ **/
+ signals[TOOLBAR_ACTION_ACTIVATE] = g_signal_new (
+ "toolbar-action-activate",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ G_STRUCT_OFFSET (
+ ETreeViewFrameClass,
+ toolbar_action_activate),
+ g_signal_accumulator_true_handled,
+ NULL, NULL,
+ G_TYPE_BOOLEAN, 1,
+ GTK_TYPE_ACTION);
+
+ /**
+ * ETreeViewFrame::update-toolbar-actions:
+ * @tree_view_frame: the #ETreeViewFrame that received the signal
+ *
+ * Requests toolbar actions be updated, usually in response to a
+ * #GtkTreeSelection change. Handlers should update #GtkAction
+ * properties like #GtkAction:visible and #GtkAction:sensitive
+ * based on the current #ETreeViewFrame:tree-view state.
+ **/
+ signals[UPDATE_TOOLBAR_ACTIONS] = g_signal_new (
+ "update-toolbar-actions",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (
+ ETreeViewFrameClass,
+ update_toolbar_actions),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+e_tree_view_frame_init (ETreeViewFrame *tree_view_frame)
+{
+ GHashTable *tool_item_ht;
+
+ tool_item_ht = g_hash_table_new_full (
+ (GHashFunc) g_str_hash,
+ (GEqualFunc) g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_object_unref);
+
+ tree_view_frame->priv =
+ E_TREE_VIEW_FRAME_GET_PRIVATE (tree_view_frame);
+
+ tree_view_frame->priv->tool_item_ht = tool_item_ht;
+}
+
+/**
+ * e_tree_view_frame_new:
+ *
+ * Creates a new #ETreeViewFrame.
+ *
+ * Returns: an #ETreeViewFrame
+ **/
+GtkWidget *
+e_tree_view_frame_new (void)
+{
+ return g_object_new (E_TYPE_TREE_VIEW_FRAME, NULL);
+}
+
+/**
+ * e_tree_view_frame_get_tree_view:
+ * @tree_view_frame: an #ETreeViewFrame
+ *
+ * Returns the #ETreeViewFrame:tree-view for @tree_view_frame.
+ *
+ * The @tree_view_frame creates its own #GtkTreeView by default, but
+ * that instance can be replaced with e_tree_view_frame_set_tree_view().
+ *
+ * Returns: a #GtkTreeView
+ **/
+GtkTreeView *
+e_tree_view_frame_get_tree_view (ETreeViewFrame *tree_view_frame)
+{
+ g_return_val_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame), NULL);
+
+ return tree_view_frame->priv->tree_view;
+}
+
+/**
+ * e_tree_view_frame_set_tree_view:
+ * @tree_view_frame: an #ETreeViewFrame
+ * @tree_view: a #GtkTreeView, or %NULL
+ *
+ * Replaces the previous #ETreeViewFrame:tree-view with the given @tree_view.
+ * If @tree_view is %NULL, the @tree_view_frame creates a new #GtkTreeView.
+ **/
+void
+e_tree_view_frame_set_tree_view (ETreeViewFrame *tree_view_frame,
+ GtkTreeView *tree_view)
+{
+ GtkTreeSelection *selection;
+ GtkWidget *scrolled_window;
+ gulong handler_id;
+
+ g_return_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame));
+
+ if (tree_view != NULL) {
+ g_return_if_fail (GTK_IS_TREE_VIEW (tree_view));
+ g_object_ref (tree_view);
+ } else {
+ tree_view = (GtkTreeView *) gtk_tree_view_new ();
+ g_object_ref_sink (tree_view);
+ }
+
+ scrolled_window = tree_view_frame->priv->scrolled_window;
+
+ if (tree_view_frame->priv->tree_view != NULL) {
+ gtk_container_remove (
+ GTK_CONTAINER (scrolled_window),
+ GTK_WIDGET (tree_view_frame->priv->tree_view));
+ tree_view_frame_dispose_tree_view (tree_view_frame->priv);
+ }
+
+ tree_view_frame->priv->tree_view = tree_view;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
+
+ handler_id = g_signal_connect (
+ tree_view, "notify::reorderable",
+ G_CALLBACK (tree_view_frame_notify_reorderable_cb),
+ tree_view_frame);
+ tree_view_frame->priv->notify_reorderable_handler_id = handler_id;
+
+ handler_id = g_signal_connect (
+ selection, "notify::mode",
+ G_CALLBACK (tree_view_frame_notify_select_mode_cb),
+ tree_view_frame);
+ tree_view_frame->priv->notify_select_mode_handler_id = handler_id;
+
+ handler_id = g_signal_connect (
+ selection, "changed",
+ G_CALLBACK (tree_view_frame_selection_changed_cb),
+ tree_view_frame);
+ tree_view_frame->priv->selection_changed_handler_id = handler_id;
+
+ gtk_container_add (
+ GTK_CONTAINER (scrolled_window),
+ GTK_WIDGET (tree_view));
+
+ gtk_widget_show (GTK_WIDGET (tree_view));
+
+ g_object_notify (G_OBJECT (tree_view_frame), "tree-view");
+
+ e_tree_view_frame_update_toolbar_actions (tree_view_frame);
+}
+
+/**
+ * e_tree_view_frame_get_toolbar_visible:
+ * @tree_view_frame: an #ETreeViewFrame
+ *
+ * Returns whether the inline toolbar in @tree_view_frame is visible.
+ *
+ * Returns: %TRUE if the toolbar is visible, %FALSE if invisible
+ **/
+gboolean
+e_tree_view_frame_get_toolbar_visible (ETreeViewFrame *tree_view_frame)
+{
+ g_return_val_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame), FALSE);
+
+ return tree_view_frame->priv->toolbar_visible;
+}
+
+/**
+ * e_tree_view_frame_set_toolbar_visible:
+ * @tree_view_frame: an #ETreeViewFrame
+ * @toolbar_visible: whether to make the toolbar visible
+ *
+ * Shows or hides the inline toolbar in @tree_view_frame.
+ **/
+void
+e_tree_view_frame_set_toolbar_visible (ETreeViewFrame *tree_view_frame,
+ gboolean toolbar_visible)
+{
+ g_return_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame));
+
+ if (toolbar_visible == tree_view_frame->priv->toolbar_visible)
+ return;
+
+ tree_view_frame->priv->toolbar_visible = toolbar_visible;
+
+ g_object_notify (G_OBJECT (tree_view_frame), "toolbar-visible");
+}
+
+/**
+ * e_tree_view_frame_get_hscrollbar_policy:
+ * @tree_view_frame: an #ETreeViewFrame
+ *
+ * Returns the policy for the horizontal scrollbar in @tree_view_frame.
+ *
+ * Returns: the policy for the horizontal scrollbar
+ **/
+GtkPolicyType
+e_tree_view_frame_get_hscrollbar_policy (ETreeViewFrame *tree_view_frame)
+{
+ g_return_val_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame), 0);
+
+ return tree_view_frame->priv->hscrollbar_policy;
+}
+
+/**
+ * e_tree_view_frame_set_hscrollbar_policy:
+ * @tree_view_frame: an #ETreeViewFrame
+ * @hscrollbar_policy: the policy for the horizontal scrollbar
+ *
+ * Sets the policy for the horizontal scrollbar in @tree_view_frame.
+ **/
+void
+e_tree_view_frame_set_hscrollbar_policy (ETreeViewFrame *tree_view_frame,
+ GtkPolicyType hscrollbar_policy)
+{
+ g_return_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame));
+
+ if (hscrollbar_policy == tree_view_frame->priv->hscrollbar_policy)
+ return;
+
+ tree_view_frame->priv->hscrollbar_policy = hscrollbar_policy;
+
+ g_object_notify (G_OBJECT (tree_view_frame), "hscrollbar-policy");
+}
+
+/**
+ * e_tree_view_frame_get_vscrollbar_policy:
+ * @tree_view_frame: an #ETreeViewFrame
+ *
+ * Returns the policy for the vertical scrollbar in @tree_view_frame.
+ *
+ * Returns: the policy for the vertical scrollbar
+ **/
+GtkPolicyType
+e_tree_view_frame_get_vscrollbar_policy (ETreeViewFrame *tree_view_frame)
+{
+ g_return_val_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame), 0);
+
+ return tree_view_frame->priv->vscrollbar_policy;
+}
+
+/**
+ * e_tree_view_frame_set_vscrollbar_policy:
+ * @tree_view_frame: an #ETreeViewFrame
+ * @vscrollbar_policy: the policy for the vertical scrollbar
+ *
+ * Sets the policy for the vertical scrollbar in @tree_view_frame.
+ **/
+void
+e_tree_view_frame_set_vscrollbar_policy (ETreeViewFrame *tree_view_frame,
+ GtkPolicyType vscrollbar_policy)
+{
+ g_return_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame));
+
+ if (vscrollbar_policy == tree_view_frame->priv->vscrollbar_policy)
+ return;
+
+ tree_view_frame->priv->vscrollbar_policy = vscrollbar_policy;
+
+ g_object_notify (G_OBJECT (tree_view_frame), "vscrollbar-policy");
+}
+
+/**
+ * e_tree_view_frame_insert_toolbar_action:
+ * @tree_view_frame: an #ETreeViewFrame
+ * @action: a #GtkAction
+ * @position: the position of the new action
+ *
+ * Generates a #GtkToolItem from @action and inserts it into the inline
+ * toolbar at the given @position. If @position is zero, the item is
+ * prepended to the start of the toolbar. If @position is negative,
+ * the item is appended to the end of the toolbar.
+ **/
+void
+e_tree_view_frame_insert_toolbar_action (ETreeViewFrame *tree_view_frame,
+ GtkAction *action,
+ gint position)
+{
+ GtkToolbar *toolbar;
+ GtkWidget *tool_item;
+ GHashTable *tool_item_ht;
+ const gchar *action_name;
+
+ g_return_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame));
+ g_return_if_fail (GTK_IS_ACTION (action));
+
+ action_name = gtk_action_get_name (action);
+ g_return_if_fail (action_name != NULL);
+
+ tool_item_ht = tree_view_frame->priv->tool_item_ht;
+ toolbar = GTK_TOOLBAR (tree_view_frame->priv->inline_toolbar);
+
+ if (g_hash_table_contains (tool_item_ht, action_name)) {
+ g_warning (
+ "%s: Duplicate action name '%s'",
+ G_STRFUNC, action_name);
+ return;
+ }
+
+ tool_item = gtk_action_create_tool_item (action);
+ g_return_if_fail (GTK_IS_TOOL_ITEM (tool_item));
+
+ gtk_toolbar_insert (toolbar, GTK_TOOL_ITEM (tool_item), position);
+
+ g_hash_table_insert (
+ tool_item_ht,
+ g_strdup (action_name),
+ g_object_ref (tool_item));
+
+ g_signal_connect (
+ action, "activate",
+ G_CALLBACK (tree_view_frame_action_activate_cb),
+ tree_view_frame);
+}
+
+/**
+ * e_tree_view_frame_lookup_toolbar_action:
+ * @tree_view_frame: an #ETreeViewFrame
+ * @action_name: a #GtkAction name
+ *
+ * Returns the toolbar action named @action_name, or %NULL if no such
+ * toolbar action exists.
+ *
+ * Returns: a #GtkAction, or %NULL
+ **/
+GtkAction *
+e_tree_view_frame_lookup_toolbar_action (ETreeViewFrame *tree_view_frame,
+ const gchar *action_name)
+{
+ GHashTable *tool_item_ht;
+ GtkActivatable *activatable;
+ GtkAction *action = NULL;
+
+ g_return_val_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame), NULL);
+ g_return_val_if_fail (action_name != NULL, NULL);
+
+ tool_item_ht = tree_view_frame->priv->tool_item_ht;
+ activatable = g_hash_table_lookup (tool_item_ht, action_name);
+
+ if (GTK_IS_ACTIVATABLE (activatable))
+ action = gtk_activatable_get_related_action (activatable);
+
+ return action;
+}
+
+/**
+ * e_tree_view_frame_update_toolbar_actions:
+ * @tree_view_frame: an #ETreeViewFrame
+ *
+ * Emits the #ETreeViewFrame::update-toolbar-actions signal.
+ *
+ * See the signal description for more details.
+ **/
+void
+e_tree_view_frame_update_toolbar_actions (ETreeViewFrame *tree_view_frame)
+{
+ g_return_if_fail (E_IS_TREE_VIEW_FRAME (tree_view_frame));
+
+ g_signal_emit (tree_view_frame, signals[UPDATE_TOOLBAR_ACTIONS], 0);
+}
+
diff --git a/e-util/e-tree-view-frame.h b/e-util/e-tree-view-frame.h
new file mode 100644
index 0000000000..503fa620ed
--- /dev/null
+++ b/e-util/e-tree-view-frame.h
@@ -0,0 +1,119 @@
+/*
+ * e-tree-view-frame.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_TREE_VIEW_FRAME_H
+#define E_TREE_VIEW_FRAME_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TREE_VIEW_FRAME \
+ (e_tree_view_frame_get_type ())
+#define E_TREE_VIEW_FRAME(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_TREE_VIEW_FRAME, ETreeViewFrame))
+#define E_TREE_VIEW_FRAME_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_TREE_VIEW_FRAME, ETreeViewFrameClass))
+#define E_IS_TREE_VIEW_FRAME(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_TREE_VIEW_FRAME))
+#define E_IS_TREE_VIEW_FRAME_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_TREE_VIEW_FRAME))
+#define E_TREE_VIEW_FRAME_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_TREE_VIEW_FRAME, ETreeViewFrameClass))
+
+#define E_TREE_VIEW_FRAME_ACTION_ADD "e-tree-view-frame-add"
+#define E_TREE_VIEW_FRAME_ACTION_REMOVE "e-tree-view-frame-remove"
+#define E_TREE_VIEW_FRAME_ACTION_MOVE_TOP "e-tree-view-frame-move-top"
+#define E_TREE_VIEW_FRAME_ACTION_MOVE_UP "e-tree-view-frame-move-up"
+#define E_TREE_VIEW_FRAME_ACTION_MOVE_DOWN "e-tree-view-frame-move-down"
+#define E_TREE_VIEW_FRAME_ACTION_MOVE_BOTTOM "e-tree-view-frame-move-bottom"
+#define E_TREE_VIEW_FRAME_ACTION_SELECT_ALL "e-tree-view-frame-select-all"
+
+G_BEGIN_DECLS
+
+typedef struct _ETreeViewFrame ETreeViewFrame;
+typedef struct _ETreeViewFrameClass ETreeViewFrameClass;
+typedef struct _ETreeViewFramePrivate ETreeViewFramePrivate;
+
+/**
+ * ETreeViewFrame:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ **/
+struct _ETreeViewFrame {
+ GtkBox parent;
+ ETreeViewFramePrivate *priv;
+};
+
+struct _ETreeViewFrameClass {
+ GtkBoxClass parent_class;
+
+ /* Signals */
+ gboolean (*toolbar_action_activate)
+ (ETreeViewFrame *tree_view_frame,
+ GtkAction *action);
+ void (*update_toolbar_actions)
+ (ETreeViewFrame *tree_view_frame);
+};
+
+GType e_tree_view_frame_get_type
+ (void) G_GNUC_CONST;
+GtkWidget * e_tree_view_frame_new (void);
+GtkTreeView * e_tree_view_frame_get_tree_view
+ (ETreeViewFrame *tree_view_frame);
+void e_tree_view_frame_set_tree_view
+ (ETreeViewFrame *tree_view_frame,
+ GtkTreeView *tree_view);
+gboolean e_tree_view_frame_get_toolbar_visible
+ (ETreeViewFrame *tree_view_frame);
+void e_tree_view_frame_set_toolbar_visible
+ (ETreeViewFrame *tree_view_frame,
+ gboolean toolbar_visible);
+GtkPolicyType e_tree_view_frame_get_hscrollbar_policy
+ (ETreeViewFrame *tree_view_frame);
+void e_tree_view_frame_set_hscrollbar_policy
+ (ETreeViewFrame *tree_view_frame,
+ GtkPolicyType hscrollbar_policy);
+GtkPolicyType e_tree_view_frame_get_vscrollbar_policy
+ (ETreeViewFrame *tree_view_frame);
+void e_tree_view_frame_set_vscrollbar_policy
+ (ETreeViewFrame *tree_view_frame,
+ GtkPolicyType vscrollbar_policy);
+void e_tree_view_frame_insert_toolbar_action
+ (ETreeViewFrame *tree_view_frame,
+ GtkAction *action,
+ gint position);
+GtkAction * e_tree_view_frame_lookup_toolbar_action
+ (ETreeViewFrame *tree_view_frame,
+ const gchar *action_name);
+void e_tree_view_frame_update_toolbar_actions
+ (ETreeViewFrame *tree_view_frame);
+
+G_END_DECLS
+
+#endif /* E_TREE_VIEW_FRAME_H */
+
diff --git a/e-util/e-util.h b/e-util/e-util.h
index 012c91d790..980c27932c 100644
--- a/e-util/e-util.h
+++ b/e-util/e-util.h
@@ -212,6 +212,7 @@
#include <e-util/e-tree-model.h>
#include <e-util/e-tree-selection-model.h>
#include <e-util/e-tree-table-adapter.h>
+#include <e-util/e-tree-view-frame.h>
#include <e-util/e-tree.h>
#include <e-util/e-unicode.h>
#include <e-util/e-url-entry.h>
diff --git a/e-util/test-tree-view-frame.c b/e-util/test-tree-view-frame.c
new file mode 100644
index 0000000000..136c45fba8
--- /dev/null
+++ b/e-util/test-tree-view-frame.c
@@ -0,0 +1,375 @@
+#include <gtk/gtk.h>
+#include <libedataserver/libedataserver.h>
+
+#include "e-tree-view-frame.h"
+
+static GtkTreeView *tree_view;
+static ETreeViewFrame *tree_view_frame;
+
+static gboolean
+delete_event_cb (GtkWidget *widget,
+ GdkEvent *event)
+{
+ gtk_main_quit ();
+
+ return TRUE;
+}
+
+static void
+action_add_cb (ETreeViewFrame *tree_view_frame,
+ GtkAction *action)
+{
+ GtkTreeView *tree_view;
+ GtkTreeModel *tree_model;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ GList *list;
+
+ tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
+
+ tree_model = gtk_tree_view_get_model (tree_view);
+ gtk_list_store_append (GTK_LIST_STORE (tree_model), &iter);
+ path = gtk_tree_model_get_path (tree_model, &iter);
+
+ column = gtk_tree_view_get_column (tree_view, 0);
+ list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
+ renderer = GTK_CELL_RENDERER (list->data);
+ g_list_free (list);
+
+ g_object_set (renderer, "editable", TRUE, NULL);
+ gtk_tree_view_set_cursor_on_cell (
+ tree_view, path, column, renderer, TRUE);
+ g_object_set (renderer, "editable", FALSE, NULL);
+
+ gtk_tree_path_free (path);
+}
+
+static void
+action_remove_cb (ETreeViewFrame *tree_view_frame,
+ GtkAction *action)
+{
+ GtkTreeView *tree_view;
+ GtkTreeModel *tree_model;
+ GtkTreeSelection *selection;
+ GtkListStore *list_store;
+ GList *list, *link;
+
+ tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
+
+ selection = gtk_tree_view_get_selection (tree_view);
+ list = gtk_tree_selection_get_selected_rows (selection, &tree_model);
+
+ /* Reverse the list so we don't invalidate paths. */
+ list = g_list_reverse (list);
+
+ list_store = GTK_LIST_STORE (tree_model);
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ GtkTreePath *path = link->data;
+ GtkTreeIter iter;
+
+ if (gtk_tree_model_get_iter (tree_model, &iter, path))
+ gtk_list_store_remove (list_store, &iter);
+ }
+
+ g_list_free_full (list, (GDestroyNotify) gtk_tree_path_free);
+}
+
+static void
+update_toolbar_actions_cb (ETreeViewFrame *tree_view_frame)
+{
+ GtkAction *action;
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+ gint n_selected_rows;
+
+ tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
+
+ selection = gtk_tree_view_get_selection (tree_view);
+ n_selected_rows = gtk_tree_selection_count_selected_rows (selection);
+
+ action = e_tree_view_frame_lookup_toolbar_action (
+ tree_view_frame, E_TREE_VIEW_FRAME_ACTION_REMOVE);
+ gtk_action_set_sensitive (action, n_selected_rows > 0);
+}
+
+static void
+cell_edited_cb (GtkCellRendererText *renderer,
+ const gchar *path_string,
+ const gchar *new_text,
+ GtkTreeView *tree_view)
+{
+ GtkTreeModel *tree_model;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ path = gtk_tree_path_new_from_string (path_string);
+
+ tree_model = gtk_tree_view_get_model (tree_view);
+ gtk_tree_model_get_iter (tree_model, &iter, path);
+ gtk_list_store_set (
+ GTK_LIST_STORE (tree_model), &iter, 0, new_text, -1);
+
+ gtk_tree_path_free (path);
+}
+
+static void
+editing_canceled_cb (GtkCellRenderer *renderer,
+ GtkTreeView *tree_view)
+{
+ GtkTreeModel *tree_model;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ gtk_tree_view_get_cursor (tree_view, &path, NULL);
+
+ tree_model = gtk_tree_view_get_model (tree_view);
+ gtk_tree_model_get_iter (tree_model, &iter, path);
+ gtk_list_store_remove (GTK_LIST_STORE (tree_model), &iter);
+
+ gtk_tree_path_free (path);
+}
+
+static void
+build_tree_view (void)
+{
+ GtkListStore *list_store;
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+ GtkTreeIter iter;
+ guint ii;
+
+ /* Somebody's a child of the 80's */
+ const gchar *items[] = {
+ "Cherry",
+ "Strawberry",
+ "Peach",
+ "Pretzel",
+ "Apple",
+ "Pear",
+ "Banana"
+ };
+
+ tree_view = (GtkTreeView *) gtk_tree_view_new ();
+ gtk_tree_view_set_headers_visible (tree_view, FALSE);
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes (
+ "Bonus Item", renderer, "text", 0, NULL);
+ gtk_tree_view_append_column (tree_view, column);
+
+ g_signal_connect (
+ renderer, "edited",
+ G_CALLBACK (cell_edited_cb), tree_view);
+
+ g_signal_connect (
+ renderer, "editing-canceled",
+ G_CALLBACK (editing_canceled_cb), tree_view);
+
+ list_store = gtk_list_store_new (1, G_TYPE_STRING);
+ for (ii = 0; ii < G_N_ELEMENTS (items); ii++) {
+ gtk_list_store_append (list_store, &iter);
+ gtk_list_store_set (list_store, &iter, 0, items[ii], -1);
+ }
+ gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (list_store));
+ g_object_unref (list_store);
+}
+
+static void
+build_test_window (void)
+{
+ GtkTreeSelection *selection;
+ GtkWidget *widget;
+ GtkWidget *container;
+ GtkWidget *grid;
+ const gchar *text;
+
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ widget = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_default_size (GTK_WINDOW (widget), 500, 300);
+ gtk_window_set_title (GTK_WINDOW (widget), "ETreeViewFrame");
+ gtk_widget_show (widget);
+
+ g_signal_connect (
+ widget, "delete-event",
+ G_CALLBACK (delete_event_cb), NULL);
+
+ container = widget;
+
+ widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (widget), 12);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = e_tree_view_frame_new ();
+ e_tree_view_frame_set_tree_view (
+ E_TREE_VIEW_FRAME (widget), tree_view);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ gtk_widget_show (widget);
+
+ tree_view_frame = E_TREE_VIEW_FRAME (widget);
+
+ widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ text = "Inline toolbar is visible";
+ widget = gtk_check_button_new_with_label (text);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ g_object_bind_property (
+ tree_view_frame, "toolbar-visible",
+ widget, "active",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ text = "Tree view is reorderable";
+ widget = gtk_check_button_new_with_label (text);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ g_object_bind_property (
+ tree_view, "reorderable",
+ widget, "active",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ gtk_widget_set_margin_bottom (widget, 6);
+
+ widget = gtk_grid_new ();
+ gtk_grid_set_row_spacing (GTK_GRID (widget), 6);
+ gtk_grid_set_column_spacing (GTK_GRID (widget), 6);
+ gtk_widget_set_margin_bottom (widget, 6);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ grid = widget;
+
+ widget = gtk_label_new ("Tree view selection mode:");
+ gtk_misc_set_alignment (GTK_MISC (widget), 1.0, 0.5);
+ gtk_widget_set_halign (GTK_WIDGET (widget), GTK_ALIGN_END);
+ gtk_grid_attach (GTK_GRID (grid), widget, 0, 0, 1, 1);
+ gtk_widget_show (widget);
+
+ widget = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append (
+ GTK_COMBO_BOX_TEXT (widget),
+ "none", "None");
+ gtk_combo_box_text_append (
+ GTK_COMBO_BOX_TEXT (widget),
+ "single", "Single");
+ gtk_combo_box_text_append (
+ GTK_COMBO_BOX_TEXT (widget),
+ "browse", "Browse");
+ gtk_combo_box_text_append (
+ GTK_COMBO_BOX_TEXT (widget),
+ "multiple", "Multiple");
+ gtk_grid_attach (GTK_GRID (grid), widget, 1, 0, 1, 1);
+ gtk_widget_show (widget);
+
+ g_object_bind_property_full (
+ selection, "mode",
+ widget, "active-id",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE,
+ e_binding_transform_enum_value_to_nick,
+ e_binding_transform_enum_nick_to_value,
+ NULL, (GDestroyNotify) NULL);
+
+ widget = gtk_label_new ("Horizontal scrollbar policy:");
+ gtk_misc_set_alignment (GTK_MISC (widget), 1.0, 0.5);
+ gtk_widget_set_halign (GTK_WIDGET (widget), GTK_ALIGN_END);
+ gtk_grid_attach (GTK_GRID (grid), widget, 0, 1, 1, 1);
+ gtk_widget_show (widget);
+
+ widget = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append (
+ GTK_COMBO_BOX_TEXT (widget),
+ "always", "Always");
+ gtk_combo_box_text_append (
+ GTK_COMBO_BOX_TEXT (widget),
+ "automatic", "Automatic");
+ gtk_combo_box_text_append (
+ GTK_COMBO_BOX_TEXT (widget),
+ "never", "Never");
+ gtk_grid_attach (GTK_GRID (grid), widget, 1, 1, 1, 1);
+ gtk_widget_show (widget);
+
+ g_object_bind_property_full (
+ tree_view_frame, "hscrollbar-policy",
+ widget, "active-id",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE,
+ e_binding_transform_enum_value_to_nick,
+ e_binding_transform_enum_nick_to_value,
+ NULL, (GDestroyNotify) NULL);
+
+ widget = gtk_label_new ("Vertical scrollbar policy:");
+ gtk_misc_set_alignment (GTK_MISC (widget), 1.0, 0.5);
+ gtk_widget_set_halign (GTK_WIDGET (widget), GTK_ALIGN_END);
+ gtk_grid_attach (GTK_GRID (grid), widget, 0, 2, 1, 1);
+ gtk_widget_show (widget);
+
+ widget = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append (
+ GTK_COMBO_BOX_TEXT (widget),
+ "always", "Always");
+ gtk_combo_box_text_append (
+ GTK_COMBO_BOX_TEXT (widget),
+ "automatic", "Automatic");
+ gtk_combo_box_text_append (
+ GTK_COMBO_BOX_TEXT (widget),
+ "never", "Never");
+ gtk_grid_attach (GTK_GRID (grid), widget, 1, 2, 1, 1);
+ gtk_widget_show (widget);
+
+ g_object_bind_property_full (
+ tree_view_frame, "vscrollbar-policy",
+ widget, "active-id",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE,
+ e_binding_transform_enum_value_to_nick,
+ e_binding_transform_enum_nick_to_value,
+ NULL, (GDestroyNotify) NULL);
+
+ g_signal_connect (
+ tree_view_frame,
+ "toolbar-action-activate::"
+ E_TREE_VIEW_FRAME_ACTION_ADD,
+ G_CALLBACK (action_add_cb), NULL);
+
+ g_signal_connect (
+ tree_view_frame,
+ "toolbar-action-activate::"
+ E_TREE_VIEW_FRAME_ACTION_REMOVE,
+ G_CALLBACK (action_remove_cb), NULL);
+
+ g_signal_connect (
+ tree_view_frame, "update-toolbar-actions",
+ G_CALLBACK (update_toolbar_actions_cb), NULL);
+
+ e_tree_view_frame_update_toolbar_actions (tree_view_frame);
+}
+
+gint
+main (gint argc,
+ gchar **argv)
+{
+ gtk_init (&argc, &argv);
+
+ build_tree_view ();
+ build_test_window ();
+
+ gtk_main ();
+
+ return 0;
+}
+