diff options
Diffstat (limited to 'e-util/e-tree-view-frame.c')
-rw-r--r-- | e-util/e-tree-view-frame.c | 1183 |
1 files changed, 1183 insertions, 0 deletions
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); +} + |