1180 lines
32 KiB
C
1180 lines
32 KiB
C
/*
|
|
* 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)
|
|
{
|
|
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 (
|
|
gtk_tree_view_get_selection (priv->tree_view),
|
|
priv->notify_select_mode_handler_id);
|
|
priv->notify_select_mode_handler_id = 0;
|
|
}
|
|
|
|
if (priv->selection_changed_handler_id > 0) {
|
|
g_signal_handler_disconnect (
|
|
gtk_tree_view_get_selection (priv->tree_view),
|
|
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);
|
|
}
|
|
|