aboutsummaryrefslogtreecommitdiffstats
path: root/e-util
diff options
context:
space:
mode:
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;
+}
+