diff options
-rw-r--r-- | doc/reference/evolution-util/evolution-util-docs.sgml | 1 | ||||
-rw-r--r-- | doc/reference/evolution-util/evolution-util-sections.txt | 36 | ||||
-rw-r--r-- | doc/reference/evolution-util/evolution-util.types | 1 | ||||
-rw-r--r-- | e-util/Makefile.am | 7 | ||||
-rw-r--r-- | e-util/e-tree-view-frame.c | 1183 | ||||
-rw-r--r-- | e-util/e-tree-view-frame.h | 119 | ||||
-rw-r--r-- | e-util/e-util.h | 1 | ||||
-rw-r--r-- | e-util/test-tree-view-frame.c | 375 |
8 files changed, 1723 insertions, 0 deletions
diff --git a/doc/reference/evolution-util/evolution-util-docs.sgml b/doc/reference/evolution-util/evolution-util-docs.sgml index 3830450604..57917bd28e 100644 --- a/doc/reference/evolution-util/evolution-util-docs.sgml +++ b/doc/reference/evolution-util/evolution-util-docs.sgml @@ -265,6 +265,7 @@ <xi:include href="xml/e-spell-entry.xml"/> <xi:include href="xml/e-timezone-dialog.xml"/> <xi:include href="xml/e-tree-model-generator.xml"/> + <xi:include href="xml/e-tree-view-frame.xml"/> <xi:include href="xml/e-url-entry.xml"/> </chapter> diff --git a/doc/reference/evolution-util/evolution-util-sections.txt b/doc/reference/evolution-util/evolution-util-sections.txt index 77f380f03e..6399c8c3ba 100644 --- a/doc/reference/evolution-util/evolution-util-sections.txt +++ b/doc/reference/evolution-util/evolution-util-sections.txt @@ -4346,6 +4346,42 @@ ETreeTableAdapterPrivate </SECTION> <SECTION> +<FILE>e-tree-view-frame</FILE> +<TITLE>ETreeViewFrame</TITLE> +ETreeViewFrame +e_tree_view_frame_new +e_tree_view_frame_get_tree_view +e_tree_view_frame_set_tree_view +e_tree_view_frame_get_toolbar_visible +e_tree_view_frame_set_toolbar_visible +e_tree_view_frame_get_hscrollbar_policy +e_tree_view_frame_set_hscrollbar_policy +e_tree_view_frame_get_vscrollbar_policy +e_tree_view_frame_set_vscrollbar_policy +e_tree_view_frame_insert_toolbar_action +E_TREE_VIEW_FRAME_ACTION_ADD +E_TREE_VIEW_FRAME_ACTION_REMOVE +E_TREE_VIEW_FRAME_ACTION_MOVE_TOP +E_TREE_VIEW_FRAME_ACTION_MOVE_UP +E_TREE_VIEW_FRAME_ACTION_MOVE_DOWN +E_TREE_VIEW_FRAME_ACTION_MOVE_BOTTOM +E_TREE_VIEW_FRAME_ACTION_SELECT_ALL +e_tree_view_frame_lookup_toolbar_action +e_tree_view_frame_update_toolbar_actions +<SUBSECTION Standard> +E_TREE_VIEW_FRAME +E_IS_TREE_VIEW_FRAME +E_TYPE_TREE_VIEW_FRAME +E_TREE_VIEW_FRAME_CLASS +E_IS_TREE_VIEW_FRAME_CLASS +E_TREE_VIEW_FRAME_GET_CLASS +ETreeViewFrameClass +e_tree_view_frame_get_type +<SUBSECTION Private> +ETreeViewFramePrivate +</SECTION> + +<SECTION> <FILE>e-unicode</FILE> <TITLE>Unicode Utilities</TITLE> e_utf8_from_gtk_event_key diff --git a/doc/reference/evolution-util/evolution-util.types b/doc/reference/evolution-util/evolution-util.types index bc64175e6d..764fd3cfba 100644 --- a/doc/reference/evolution-util/evolution-util.types +++ b/doc/reference/evolution-util/evolution-util.types @@ -158,6 +158,7 @@ e_tree_model_generator_get_type e_tree_model_get_type e_tree_selection_model_get_type e_tree_table_adapter_get_type +e_tree_view_frame_get_type e_url_entry_get_type e_web_view_get_type e_web_view_gtkhtml_get_type 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; +} + |