app: add a "select-items" signal to GimpContainerView.

Properly pass the multi-selection information through the container
classes. Previous implementation was incomplete (most code paths with
multiple item selected were just ignored) and data was passed through
the "select-item" signal with the GimpViewable to NULL and the data to a
list of items (instead of being a GtkTreeIter otherwise). Having a
pointer data which changes meaning in the same function/class is not the
best idea. So instead "select-items" will have 2 list as parameters: a
list of items and a list of GtkTreePath to be used similarly and with
less ambiguity.
This commit is contained in:
Jehan 2020-03-21 19:03:07 +01:00
parent 15113782fd
commit 288bec3b79
12 changed files with 290 additions and 49 deletions

View File

@ -480,7 +480,7 @@ action_data_sel_count (gpointer data)
GimpContainerEditor *editor;
editor = GIMP_CONTAINER_EDITOR (data);
return gimp_container_view_get_selected (editor->view, NULL);
return gimp_container_view_get_selected (editor->view, NULL, NULL);
}
else
{

View File

@ -117,7 +117,7 @@ palettes_merge_callback (GtkWidget *widget,
context = gimp_container_view_get_context (editor->view);
factory = gimp_data_factory_view_get_data_factory (view);
gimp_container_view_get_selected (editor->view, &selected);
gimp_container_view_get_selected (editor->view, &selected, NULL);
if (g_list_length (selected) < 2)
{

View File

@ -105,8 +105,9 @@ static GdkPixbuf * gimp_container_icon_view_drag_pixbuf (GtkWidget *wi
gpointer data);
static gboolean gimp_container_icon_view_get_selected_single (GimpContainerIconView *icon_view,
GtkTreeIter *iter);
static gint gimp_container_icon_view_get_selected (GimpContainerView *view,
GList **items);
static gint gimp_container_icon_view_get_selected (GimpContainerView *view,
GList **items,
GList **paths);
G_DEFINE_TYPE_WITH_CODE (GimpContainerIconView, gimp_container_icon_view,
@ -604,9 +605,11 @@ gimp_container_icon_view_selection_changed (GtkIconView *gtk_icon_view
{
GimpContainerView *view = GIMP_CONTAINER_VIEW (icon_view);
GList *items;
GList *paths;
gimp_container_icon_view_get_selected (view, &items);
gimp_container_view_multi_selected (view, items);
gimp_container_icon_view_get_selected (view, &items, &paths);
gimp_container_view_multi_selected (view, items, paths);
g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
g_list_free (items);
}
@ -773,22 +776,24 @@ gimp_container_icon_view_get_selected_single (GimpContainerIconView *icon_view,
static gint
gimp_container_icon_view_get_selected (GimpContainerView *view,
GList **items)
GList **items,
GList **paths)
{
GimpContainerIconView *icon_view = GIMP_CONTAINER_ICON_VIEW (view);
GList *selected_items;
GList *selected_paths;
gint selected_count;
selected_items = gtk_icon_view_get_selected_items (icon_view->view);
selected_count = g_list_length (selected_items);
selected_paths = gtk_icon_view_get_selected_items (icon_view->view);
selected_count = g_list_length (selected_paths);
if (items)
{
GList *removed_paths = NULL;
GList *list;
*items = NULL;
for (list = selected_items;
for (list = selected_paths;
list;
list = g_list_next (list))
{
@ -804,14 +809,30 @@ gimp_container_icon_view_get_selected (GimpContainerView *view,
if (renderer->viewable)
*items = g_list_prepend (*items, renderer->viewable);
else
/* Remove from the selected_paths list but at the end, in order not
* to break the for loop.
*/
removed_paths = g_list_prepend (removed_paths, list);
g_object_unref (renderer);
}
*items = g_list_reverse (*items);
for (list = removed_paths; list; list = list->next)
{
GList *remove_list = list->data;
selected_paths = g_list_remove_link (selected_paths, remove_list);
gtk_tree_path_free (remove_list->data);
}
g_list_free_full (removed_paths, (GDestroyNotify) g_list_free);
}
g_list_free_full (selected_items, (GDestroyNotify) gtk_tree_path_free);
if (paths)
*paths = selected_paths;
else
g_list_free_full (selected_paths, (GDestroyNotify) gtk_tree_path_free);
return selected_count;
}

View File

@ -91,6 +91,9 @@ static void gimp_container_tree_view_expand_item (GimpContainerVi
static gboolean gimp_container_tree_view_select_item (GimpContainerView *view,
GimpViewable *viewable,
gpointer insert_data);
static gboolean gimp_container_tree_view_select_items (GimpContainerView *view,
GList *viewables,
GList *paths);
static void gimp_container_tree_view_clear_items (GimpContainerView *view);
static void gimp_container_tree_view_set_view_size (GimpContainerView *view);
@ -126,7 +129,8 @@ static GdkPixbuf *gimp_container_tree_view_drag_pixbuf (GtkWidget
static gboolean gimp_container_tree_view_get_selected_single (GimpContainerTreeView *tree_view,
GtkTreeIter *iter);
static gint gimp_container_tree_view_get_selected (GimpContainerView *view,
GList **items);
GList **items,
GList **paths);
static void gimp_container_tree_view_row_expanded (GtkTreeView *tree_view,
GtkTreeIter *iter,
GtkTreePath *path,
@ -205,6 +209,7 @@ gimp_container_tree_view_view_iface_init (GimpContainerViewInterface *iface)
iface->rename_item = gimp_container_tree_view_rename_item;
iface->expand_item = gimp_container_tree_view_expand_item;
iface->select_item = gimp_container_tree_view_select_item;
iface->select_items = gimp_container_tree_view_select_items;
iface->clear_items = gimp_container_tree_view_clear_items;
iface->set_view_size = gimp_container_tree_view_set_view_size;
iface->get_selected = gimp_container_tree_view_get_selected;
@ -913,6 +918,47 @@ gimp_container_tree_view_select_item (GimpContainerView *view,
return TRUE;
}
static gboolean
gimp_container_tree_view_select_items (GimpContainerView *view,
GList *items,
GList *paths)
{
GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view);
GList *item;
GList *path;
/*gtk_tree_selection_unselect_all (tree_view->priv->selection);*/
for (item = items, path = paths; item && path; item = item->next, path = path->next)
{
GtkTreePath *parent_path;
/* Expand if necessary. */
parent_path = gtk_tree_path_copy (path->data);
if (gtk_tree_path_up (parent_path))
gtk_tree_view_expand_to_path (tree_view->view, parent_path);
gtk_tree_path_free (parent_path);
/* Add to the selection. */
g_signal_handlers_block_by_func (tree_view->priv->selection,
gimp_container_tree_view_selection_changed,
tree_view);
gtk_tree_selection_select_path (tree_view->priv->selection, path->data);
g_signal_handlers_unblock_by_func (tree_view->priv->selection,
gimp_container_tree_view_selection_changed,
tree_view);
/* Scroll to the top item. */
if (item == items)
gtk_tree_view_scroll_to_cell (tree_view->view, path->data,
NULL, FALSE, 0.0, 0.0);
/* TODO: better implementation: only scroll if none of the items
* are visible. */
}
return TRUE;
}
static void
gimp_container_tree_view_clear_items (GimpContainerView *view)
{
@ -1111,12 +1157,14 @@ static void
gimp_container_tree_view_selection_changed (GtkTreeSelection *selection,
GimpContainerTreeView *tree_view)
{
GimpContainerView *view = GIMP_CONTAINER_VIEW (tree_view);
GList *items;
GimpContainerView *view = GIMP_CONTAINER_VIEW (tree_view);
GList *items;
GList *paths;
gimp_container_tree_view_get_selected (view, &items);
gimp_container_view_multi_selected (view, items);
gimp_container_tree_view_get_selected (view, &items, &paths);
gimp_container_view_multi_selected (view, items, paths);
g_list_free (items);
g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
}
static GtkCellRenderer *
@ -1299,7 +1347,7 @@ gimp_container_tree_view_button_press (GtkWidget *widget,
{
if (multisel_mode)
{
/* let parent do the work */
/* Will be handled by gimp_container_tree_view_selection_changed() */
}
else
{
@ -1535,12 +1583,14 @@ gimp_container_tree_view_get_selected_single (GimpContainerTreeView *tree_view,
static gint
gimp_container_tree_view_get_selected (GimpContainerView *view,
GList **items)
GList **items,
GList **paths)
{
GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view);
GtkTreeSelection *selection;
gint selected_count;
GList *selected_rows;
GList *selected_paths;
GList *removed_paths = NULL;
GList *current_row;
GtkTreeIter iter;
GimpViewRenderer *renderer;
@ -1550,14 +1600,17 @@ gimp_container_tree_view_get_selected (GimpContainerView *view,
if (items == NULL)
{
if (paths)
*paths = NULL;
/* just provide selected count */
return selected_count;
}
selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
selected_paths = gtk_tree_selection_get_selected_rows (selection, NULL);
*items = NULL;
*items = NULL;
for (current_row = selected_rows;
for (current_row = selected_paths;
current_row;
current_row = g_list_next (current_row))
{
@ -1570,14 +1623,30 @@ gimp_container_tree_view_get_selected (GimpContainerView *view,
if (renderer->viewable)
*items = g_list_prepend (*items, renderer->viewable);
else
/* Remove from the selected_paths list but at the end, in order not
* to break the for loop.
*/
removed_paths = g_list_prepend (removed_paths, current_row);
g_object_unref (renderer);
}
g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free);
*items = g_list_reverse (*items);
for (current_row = removed_paths; current_row; current_row = current_row->next)
{
GList *remove_list = current_row->data;
selected_paths = g_list_remove_link (selected_paths, remove_list);
gtk_tree_path_free (remove_list->data);
}
g_list_free_full (removed_paths, (GDestroyNotify) g_list_free);
if (paths)
*paths = selected_paths;
else
g_list_free_full (selected_paths, (GDestroyNotify) gtk_tree_path_free);
return selected_count;
}

View File

@ -46,6 +46,7 @@
enum
{
SELECT_ITEM,
SELECT_ITEMS,
ACTIVATE_ITEM,
CONTEXT_ITEM,
LAST_SIGNAL
@ -139,7 +140,8 @@ static void gimp_container_view_button_viewable_dropped (GtkWidget *widget,
GimpViewable *viewable,
gpointer data);
static gint gimp_container_view_real_get_selected (GimpContainerView *view,
GList **list);
GList **list,
GList **paths);
G_DEFINE_INTERFACE (GimpContainerView, gimp_container_view, GTK_TYPE_WIDGET)
@ -162,6 +164,17 @@ gimp_container_view_default_init (GimpContainerViewInterface *iface)
GIMP_TYPE_OBJECT,
G_TYPE_POINTER);
view_signals[SELECT_ITEMS] =
g_signal_new ("select-items",
G_TYPE_FROM_INTERFACE (iface),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GimpContainerViewInterface, select_items),
NULL, NULL,
NULL,
G_TYPE_BOOLEAN, 2,
G_TYPE_POINTER,
G_TYPE_POINTER);
view_signals[ACTIVATE_ITEM] =
g_signal_new ("activate-item",
G_TYPE_FROM_INTERFACE (iface),
@ -185,6 +198,7 @@ gimp_container_view_default_init (GimpContainerViewInterface *iface)
G_TYPE_POINTER);
iface->select_item = NULL;
iface->select_items = NULL;
iface->activate_item = NULL;
iface->context_item = NULL;
@ -767,10 +781,11 @@ gimp_container_view_item_selected (GimpContainerView *view,
gboolean
gimp_container_view_multi_selected (GimpContainerView *view,
GList *items)
GList *items,
GList *items_data)
{
guint selected_count;
gboolean success = FALSE;
gboolean success = FALSE;
guint selected_count;
g_return_val_if_fail (GIMP_IS_CONTAINER_VIEW (view), FALSE);
@ -786,33 +801,64 @@ gimp_container_view_multi_selected (GimpContainerView *view,
}
else
{
success = FALSE;
g_signal_emit (view, view_signals[SELECT_ITEM], 0,
NULL, items, &success);
g_signal_emit (view, view_signals[SELECT_ITEMS], 0,
items, items_data, &success);
}
return success;
}
/**
* gimp_container_view_get_selected:
* @view:
* @items:
* @items_data:
*
* Get the selected items in @view.
*
* If @items is not %NULL, fills it with a newly allocated #GList of the
* selected items.
* If @items_data is not %NULL and if the implementing class associates
* data to its contents, it will be filled with a newly allocated #GList
* of the same size as @items, or will be %NULL otherwise. It is up to
* the class to decide what type of data is passed along.
*
* Note that by default, the interface only implements some basic single
* selection. Override select_item() and select_items() signals to get
* more complete selection support.
*
* Returns: the number of selected items.
*/
gint
gimp_container_view_get_selected (GimpContainerView *view,
GList **list)
GList **items,
GList **items_data)
{
g_return_val_if_fail (GIMP_IS_CONTAINER_VIEW (view), 0);
return GIMP_CONTAINER_VIEW_GET_IFACE (view)->get_selected (view, list);
return GIMP_CONTAINER_VIEW_GET_IFACE (view)->get_selected (view, items, items_data);
}
static gint
gimp_container_view_real_get_selected (GimpContainerView *view,
GList **list)
GList **items,
GList **items_data)
{
GimpContainerViewPrivate *private = GIMP_CONTAINER_VIEW_GET_PRIVATE (view);
GType children_type;
GimpObject *object;
if (list)
*list = NULL;
if (items)
*items = NULL;
/* In base interface, @items_data just stays NULL. We don't have a
* concept for it. Classes implementing this interface may want to
* store and pass data for their items, but they will have to
* implement themselves which data, and pass through with this
* parameter.
*/
if (items_data)
*items_data = NULL;
if (! private->container || ! private->context)
return 0;
@ -821,8 +867,12 @@ gimp_container_view_real_get_selected (GimpContainerView *view,
object = gimp_context_get_by_type (private->context,
children_type);
if (list && object)
*list = g_list_append (*list, object);
/* Base interface provides the API for multi-selection but only
* implements single selection. Classes must implement their own
* multi-selection.
*/
if (items && object)
*items = g_list_append (*items, object);
return object ? 1 : 0;
}

View File

@ -47,6 +47,10 @@ struct _GimpContainerViewInterface
gboolean (* select_item) (GimpContainerView *view,
GimpViewable *object,
gpointer insert_data);
gboolean (* select_items) (GimpContainerView *view,
GList *items,
GList *paths);
void (* activate_item) (GimpContainerView *view,
GimpViewable *object,
gpointer insert_data);
@ -85,7 +89,8 @@ struct _GimpContainerViewInterface
void (* clear_items) (GimpContainerView *view);
void (* set_view_size) (GimpContainerView *view);
gint (* get_selected) (GimpContainerView *view,
GList **items);
GList **items,
GList **insert_data);
/* the destroy notifier for private->hash_table's values */
@ -131,7 +136,8 @@ void gimp_container_view_activate_item (GimpContainerView *v
void gimp_container_view_context_item (GimpContainerView *view,
GimpViewable *viewable);
gint gimp_container_view_get_selected (GimpContainerView *view,
GList **list);
GList **items,
GList **items_data);
/* protected */
@ -141,7 +147,8 @@ gpointer gimp_container_view_lookup (GimpContainerView *v
gboolean gimp_container_view_item_selected (GimpContainerView *view,
GimpViewable *item);
gboolean gimp_container_view_multi_selected (GimpContainerView *view,
GList *items);
GList *items,
GList *paths);
void gimp_container_view_item_activated (GimpContainerView *view,
GimpViewable *item);
void gimp_container_view_item_context (GimpContainerView *view,

View File

@ -526,7 +526,7 @@ gimp_data_factory_view_select_item (GimpContainerEditor *editor,
GimpContainerView *container_view = GIMP_CONTAINER_VIEW (editor->view);
GList *active_items = NULL;
gimp_container_view_get_selected (container_view, &active_items);
gimp_container_view_get_selected (container_view, &active_items, NULL);
gimp_tag_entry_set_selected_items (GIMP_TAG_ENTRY (view->priv->assign_tag_entry),
active_items);
g_list_free (active_items);

View File

@ -455,7 +455,7 @@ gimp_device_editor_delete_response (GtkWidget *dialog,
GList *selected;
if (gimp_container_view_get_selected (GIMP_CONTAINER_VIEW (private->treeview),
&selected))
&selected, NULL))
{
GimpContainer *devices;
@ -479,7 +479,7 @@ gimp_device_editor_delete_clicked (GtkWidget *button,
GList *selected;
if (! gimp_container_view_get_selected (GIMP_CONTAINER_VIEW (private->treeview),
&selected))
&selected, NULL))
return;
dialog = gimp_message_dialog_new (_("Delete Device Settings"),

View File

@ -129,6 +129,9 @@ static void gimp_item_tree_view_insert_item_after (GimpContainerView *view,
static gboolean gimp_item_tree_view_select_item (GimpContainerView *view,
GimpViewable *item,
gpointer insert_data);
static gboolean gimp_item_tree_view_select_items (GimpContainerView *view,
GList *items,
GList *paths);
static void gimp_item_tree_view_activate_item (GimpContainerView *view,
GimpViewable *item,
gpointer insert_data);
@ -260,6 +263,7 @@ gimp_item_tree_view_class_init (GimpItemTreeViewClass *klass)
klass->get_container = NULL;
klass->get_active_item = NULL;
klass->set_active_item = NULL;
klass->set_selected_items = NULL;
klass->add_item = NULL;
klass->remove_item = NULL;
klass->new_item = NULL;
@ -293,6 +297,7 @@ gimp_item_tree_view_view_iface_init (GimpContainerViewInterface *iface)
iface->insert_item = gimp_item_tree_view_insert_item;
iface->insert_item_after = gimp_item_tree_view_insert_item_after;
iface->select_item = gimp_item_tree_view_select_item;
iface->select_items = gimp_item_tree_view_select_items;
iface->activate_item = gimp_item_tree_view_activate_item;
iface->context_item = gimp_item_tree_view_context_item;
}
@ -1069,6 +1074,45 @@ gimp_item_tree_view_select_item (GimpContainerView *view,
return success;
}
static gboolean
gimp_item_tree_view_select_items (GimpContainerView *view,
GList *selected_items,
GList *paths)
{
GimpItemTreeView *tree_view = GIMP_ITEM_TREE_VIEW (view);
GList *items = selected_items;
gboolean options_sensitive = FALSE;
gboolean success;
success = parent_view_iface->select_items (view, items, paths);
if (items)
{
GimpItemTreeViewClass *item_view_class;
GList *iter;
item_view_class = GIMP_ITEM_TREE_VIEW_GET_CLASS (tree_view);
if (TRUE) /* XXX: test if new selection same as old. */
{
item_view_class->set_selected_items (tree_view->priv->image,
selected_items);
gimp_image_flush (tree_view->priv->image);
}
options_sensitive = TRUE;
for (iter = items; iter; iter = iter->next)
gimp_item_tree_view_update_options (tree_view, iter->data);
}
gimp_ui_manager_update (gimp_editor_get_ui_manager (GIMP_EDITOR (tree_view)), tree_view);
if (tree_view->priv->options_box)
gtk_widget_set_sensitive (tree_view->priv->options_box, options_sensitive);
return success;
}
static void
gimp_item_tree_view_activate_item (GimpContainerView *view,
GimpViewable *item,

View File

@ -29,6 +29,8 @@ typedef GimpContainer * (* GimpGetContainerFunc) (GimpImage *image);
typedef GimpItem * (* GimpGetItemFunc) (GimpImage *image);
typedef void (* GimpSetItemFunc) (GimpImage *image,
GimpItem *item);
typedef void (* GimpSetItemsFunc) (GimpImage *image,
GList *items);
typedef void (* GimpAddItemFunc) (GimpImage *image,
GimpItem *item,
GimpItem *parent,
@ -74,6 +76,7 @@ struct _GimpItemTreeViewClass
GimpGetContainerFunc get_container;
GimpGetItemFunc get_active_item;
GimpSetItemFunc set_active_item;
GimpSetItemsFunc set_selected_items;
GimpAddItemFunc add_item;
GimpRemoveItemFunc remove_item;
GimpNewItemFunc new_item;

View File

@ -102,6 +102,9 @@ static gpointer gimp_layer_tree_view_insert_item (GimpContainer
static gboolean gimp_layer_tree_view_select_item (GimpContainerView *view,
GimpViewable *item,
gpointer insert_data);
static gboolean gimp_layer_tree_view_select_items (GimpContainerView *view,
GList *items,
GList *paths);
static void gimp_layer_tree_view_set_view_size (GimpContainerView *view);
static gboolean gimp_layer_tree_view_drop_possible (GimpContainerTreeView *view,
GimpDndType src_type,
@ -210,6 +213,7 @@ gimp_layer_tree_view_class_init (GimpLayerTreeViewClass *klass)
item_view_class->get_container = gimp_image_get_layers;
item_view_class->get_active_item = (GimpGetItemFunc) gimp_image_get_active_layer;
item_view_class->set_active_item = (GimpSetItemFunc) gimp_image_set_active_layer;
item_view_class->set_selected_items = (GimpSetItemsFunc) gimp_image_set_selected_layers;
item_view_class->add_item = (GimpAddItemFunc) gimp_image_add_layer;
item_view_class->remove_item = (GimpRemoveItemFunc) gimp_image_remove_layer;
item_view_class->new_item = gimp_layer_tree_view_item_new;
@ -237,6 +241,7 @@ gimp_layer_tree_view_view_iface_init (GimpContainerViewInterface *iface)
iface->set_context = gimp_layer_tree_view_set_context;
iface->insert_item = gimp_layer_tree_view_insert_item;
iface->select_item = gimp_layer_tree_view_select_item;
iface->select_items = gimp_layer_tree_view_select_items;
iface->set_view_size = gimp_layer_tree_view_set_view_size;
iface->model_is_tree = TRUE;
@ -608,6 +613,48 @@ gimp_layer_tree_view_select_item (GimpContainerView *view,
return success;
}
static gboolean
gimp_layer_tree_view_select_items (GimpContainerView *view,
GList *items,
GList *paths)
{
GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view);
GimpLayerTreeView *layer_view = GIMP_LAYER_TREE_VIEW (view);
GList *layers = items;
GList *path = paths;
gboolean success;
success = parent_view_iface->select_items (view, items, paths);
if (layers)
{
if (success)
{
for (layers = items, path = paths; layers && path; layers = layers->next, path = path->next)
{
GtkTreeIter iter;
gtk_tree_model_get_iter (tree_view->model, &iter, path->data);
gimp_layer_tree_view_update_borders (layer_view, &iter);
gimp_layer_tree_view_update_options (layer_view, GIMP_LAYER (layers->data));
gimp_layer_tree_view_update_menu (layer_view, GIMP_LAYER (layers->data));
}
}
}
if (! success)
{
GimpEditor *editor = GIMP_EDITOR (view);
/* currently, select_item() only ever fails when there is a floating
* selection, which can be committed/canceled through the editor buttons.
*/
gimp_widget_blink (GTK_WIDGET (gimp_editor_get_button_box (editor)));
}
return success;
}
typedef struct
{
gint mask_column;

View File

@ -372,7 +372,7 @@ gimp_pickable_popup_get_pickable (GimpPickablePopup *popup)
GList *selected;
if (gimp_container_view_get_selected (GIMP_CONTAINER_VIEW (popup->priv->layer_view),
&selected))
&selected, NULL))
{
pickable = selected->data;
g_list_free (selected);
@ -383,7 +383,7 @@ gimp_pickable_popup_get_pickable (GimpPickablePopup *popup)
GList *selected;
if (gimp_container_view_get_selected (GIMP_CONTAINER_VIEW (popup->priv->channel_view),
&selected))
&selected, NULL))
{
pickable = selected->data;
g_list_free (selected);