app: implement GimpContainerListView, using a GtkListBox

It completely relies on GListModel and doesn't manage any list items
itself. The port should be pretty much feature-complete.

Add a ton of stuff to GimpRow in order to look and behave like a
GimpContainerTreeView row.

Add a playground switch to use the new widgets in all views that can
be switched between list and grid view (brushes, patterns etc.)

Please test!
This commit is contained in:
Michael Natterer 2025-08-05 16:08:55 +02:00
parent bf4cfd9932
commit 33bfd5a12b
14 changed files with 852 additions and 36 deletions

View file

@ -92,6 +92,7 @@ enum
PROP_PLAYGROUND_NPD_TOOL,
PROP_PLAYGROUND_SEAMLESS_CLONE_TOOL,
PROP_PLAYGROUND_PAINT_SELECT_TOOL,
PROP_PLAYGROUND_USE_LIST_BOX,
PROP_HIDE_DOCKS,
PROP_SINGLE_WINDOW_MODE,
@ -470,6 +471,14 @@ gimp_gui_config_class_init (GimpGuiConfigClass *klass)
GIMP_PARAM_STATIC_STRINGS |
GIMP_CONFIG_PARAM_RESTART);
GIMP_CONFIG_PROP_BOOLEAN (object_class,
PROP_PLAYGROUND_USE_LIST_BOX,
"playground-use-list-box",
"Playground Use List Box",
PLAYGROUND_USE_LIST_BOX_BLURB,
FALSE,
GIMP_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_HIDE_DOCKS,
g_param_spec_boolean ("hide-docks",
NULL,
@ -753,6 +762,9 @@ gimp_gui_config_set_property (GObject *object,
case PROP_PLAYGROUND_PAINT_SELECT_TOOL:
gui_config->playground_paint_select_tool = g_value_get_boolean (value);
break;
case PROP_PLAYGROUND_USE_LIST_BOX:
gui_config->playground_use_list_box = g_value_get_boolean (value);
break;
case PROP_HIDE_DOCKS:
gui_config->hide_docks = g_value_get_boolean (value);
@ -934,6 +946,9 @@ gimp_gui_config_get_property (GObject *object,
case PROP_PLAYGROUND_PAINT_SELECT_TOOL:
g_value_set_boolean (value, gui_config->playground_paint_select_tool);
break;
case PROP_PLAYGROUND_USE_LIST_BOX:
g_value_set_boolean (value, gui_config->playground_use_list_box);
break;
case PROP_HIDE_DOCKS:
g_value_set_boolean (value, gui_config->hide_docks);

View file

@ -86,6 +86,7 @@ struct _GimpGuiConfig
gboolean playground_npd_tool;
gboolean playground_seamless_clone_tool;
gboolean playground_paint_select_tool;
gboolean playground_use_list_box;
/* saved in sessionrc */
gboolean hide_docks;

View file

@ -510,6 +510,9 @@ _("Enable the Seamless Clone tool.")
#define PLAYGROUND_PAINT_SELECT_TOOL_BLURB \
_("Enable the Paint Select tool.")
#define PLAYGROUND_USE_LIST_BOX_BLURB \
_("Use the new GtkListBox widget for simple lists.")
#define SPACE_BAR_ACTION_BLURB \
_("What to do when the space bar is pressed in the image window.")

View file

@ -1695,6 +1695,9 @@ prefs_dialog_new (Gimp *gimp,
*/
gimp_help_set_help_data (button, "Missing GEGL operation 'gegl:paint-select'.", NULL);
}
button = prefs_check_button_add (object, "playground-use-list-box",
_("Use GtkListBox in simple lists"),
GTK_BOX (vbox2));
}

View file

@ -28,6 +28,9 @@
#include "widgets-types.h"
#include "config/gimpguiconfig.h" /* playground */
#include "core/gimp.h" /* playground */
#include "core/gimpasyncset.h"
#include "core/gimpcontext.h"
#include "core/gimplist.h"
@ -35,6 +38,7 @@
#include "gimpcontainereditor.h"
#include "gimpcontainericonview.h"
#include "gimpcontainerlistview.h"
#include "gimpcontainertreeview.h"
#include "gimpcontainerview.h"
#include "gimpdocked.h"
@ -230,11 +234,18 @@ gimp_container_editor_constructed (GObject *object)
break;
case GIMP_VIEW_TYPE_LIST:
editor->view =
GIMP_CONTAINER_VIEW (gimp_container_tree_view_new (editor->priv->container,
editor->priv->context,
editor->priv->view_size,
editor->priv->view_border_width));
if (GIMP_GUI_CONFIG (editor->priv->context->gimp->config)->playground_use_list_box)
editor->view =
GIMP_CONTAINER_VIEW (gimp_container_list_view_new (editor->priv->container,
editor->priv->context,
editor->priv->view_size,
editor->priv->view_border_width));
else
editor->view =
GIMP_CONTAINER_VIEW (gimp_container_tree_view_new (editor->priv->container,
editor->priv->context,
editor->priv->view_size,
editor->priv->view_border_width));
break;
default:

View file

@ -0,0 +1,517 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* gimpcontainerlistview.c
* Copyright (C) 2025 Michael Natterer <mitch@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <gegl.h>
#include <gtk/gtk.h>
#include "libgimpwidgets/gimpwidgets.h"
#include "widgets-types.h"
#include "core/gimpcontainer.h"
#include "core/gimpcontext.h"
#include "core/gimpviewable.h"
#include "gimpcontainerlistview.h"
#include "gimpcontainerview.h"
#include "gimprow.h"
#include "gimprow-utils.h"
#include "gimpviewrenderer.h"
#include "gimp-intl.h"
typedef struct _GimpContainerListViewPrivate GimpContainerListViewPrivate;
struct _GimpContainerListViewPrivate
{
GtkListBox *view;
};
#define GET_PRIVATE(obj) \
((GimpContainerListViewPrivate *) gimp_container_list_view_get_instance_private ((GimpContainerListView *) obj))
static void gimp_container_list_view_view_iface_init (GimpContainerViewInterface *iface);
static void gimp_container_list_view_constructed (GObject *object);
static void gimp_container_list_view_finalize (GObject *object);
static gboolean gimp_container_list_view_popup_menu (GtkWidget *widget);
static void gimp_container_list_view_set_container (GimpContainerView *view,
GimpContainer *container);
static void gimp_container_list_view_set_context (GimpContainerView *view,
GimpContext *context);
static void gimp_container_list_view_set_selection_mode(GimpContainerView *view,
GtkSelectionMode mode);
static void gimp_container_list_view_set_view_size (GimpContainerView *view);
static gboolean gimp_container_list_view_set_selected (GimpContainerView *view,
GList *items);
static gint gimp_container_list_view_get_selected (GimpContainerView *view,
GList **items);
static void gimp_container_list_view_selected_rows_changed
(GtkListBox *box,
GimpContainerListView *list_view);
static void gimp_container_list_view_row_activated (GtkListBox *box,
GimpRow *row,
GimpContainerListView *list_view);
static void gimp_container_list_view_monitor_changed (GimpContainerListView *view);
G_DEFINE_TYPE_WITH_CODE (GimpContainerListView,
gimp_container_list_view,
GIMP_TYPE_CONTAINER_BOX,
G_ADD_PRIVATE (GimpContainerListView)
G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONTAINER_VIEW,
gimp_container_list_view_view_iface_init))
#define parent_class gimp_container_list_view_parent_class
static GimpContainerViewInterface *parent_view_iface = NULL;
static void
gimp_container_list_view_class_init (GimpContainerListViewClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->constructed = gimp_container_list_view_constructed;
object_class->finalize = gimp_container_list_view_finalize;
widget_class->popup_menu = gimp_container_list_view_popup_menu;
}
static void
gimp_container_list_view_view_iface_init (GimpContainerViewInterface *iface)
{
parent_view_iface = g_type_interface_peek_parent (iface);
if (! parent_view_iface)
parent_view_iface = g_type_default_interface_peek (GIMP_TYPE_CONTAINER_VIEW);
iface->set_container = gimp_container_list_view_set_container;
iface->set_context = gimp_container_list_view_set_context;
iface->set_selection_mode = gimp_container_list_view_set_selection_mode;
iface->set_view_size = gimp_container_list_view_set_view_size;
iface->set_selected = gimp_container_list_view_set_selected;
iface->get_selected = gimp_container_list_view_get_selected;
iface->use_list_model = TRUE;
}
static void
gimp_container_list_view_init (GimpContainerListView *list_view)
{
GimpContainerListViewPrivate *priv = GET_PRIVATE (list_view);
GimpContainerBox *box = GIMP_CONTAINER_BOX (list_view);
priv->view = GTK_LIST_BOX (gtk_list_box_new ());
gtk_list_box_set_activate_on_single_click (priv->view, FALSE);
gtk_container_add (GTK_CONTAINER (box->scrolled_win),
GTK_WIDGET (priv->view));
gtk_widget_show (GTK_WIDGET (priv->view));
g_signal_connect (priv->view, "selected-rows-changed",
G_CALLBACK (gimp_container_list_view_selected_rows_changed),
list_view);
g_signal_connect (priv->view, "row-activated",
G_CALLBACK (gimp_container_list_view_row_activated),
list_view);
gimp_container_view_set_dnd_widget (GIMP_CONTAINER_VIEW (list_view),
GTK_WIDGET (priv->view));
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (box->scrolled_win),
GTK_SHADOW_IN);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (box->scrolled_win),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gimp_widget_track_monitor (GTK_WIDGET (list_view),
G_CALLBACK (gimp_container_list_view_monitor_changed),
NULL, NULL);
}
static void
gimp_container_list_view_constructed (GObject *object)
{
G_OBJECT_CLASS (parent_class)->constructed (object);
}
static void
gimp_container_list_view_finalize (GObject *object)
{
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static gboolean
gimp_container_list_view_popup_menu (GtkWidget *widget)
{
GimpContainerListViewPrivate *priv = GET_PRIVATE (widget);
GList *rows;
GtkWidget *row;
GtkAllocation allocation;
rows = gtk_list_box_get_selected_rows (priv->view);
if (g_list_length (rows) != 1)
{
g_list_free (rows);
return FALSE;
}
row = rows->data;
g_list_free (rows);
gtk_widget_get_allocation (row, &allocation);
return gimp_editor_popup_menu_at_rect (GIMP_EDITOR (widget),
gtk_widget_get_window (row),
(GdkRectangle *) &allocation,
GDK_GRAVITY_CENTER,
GDK_GRAVITY_NORTH_WEST,
NULL);
}
GtkWidget *
gimp_container_list_view_new (GimpContainer *container,
GimpContext *context,
gint view_size,
gint view_border_width)
{
GimpContainerListView *list_view;
GimpContainerView *view;
g_return_val_if_fail (container == NULL || GIMP_IS_CONTAINER (container),
NULL);
g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL);
g_return_val_if_fail (view_size > 0 &&
view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL);
g_return_val_if_fail (view_border_width >= 0 &&
view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH,
NULL);
list_view = g_object_new (GIMP_TYPE_CONTAINER_LIST_VIEW, NULL);
view = GIMP_CONTAINER_VIEW (list_view);
gimp_container_view_set_view_size (view, view_size, view_border_width);
if (container)
gimp_container_view_set_container (view, container);
if (context)
gimp_container_view_set_context (view, context);
return GTK_WIDGET (list_view);
}
/* GimpContainerView methods */
static void
gimp_container_list_view_set_container (GimpContainerView *view,
GimpContainer *container)
{
GimpContainerListViewPrivate *priv = GET_PRIVATE (view);
GimpContainer *old_container;
old_container = gimp_container_view_get_container (view);
if (old_container)
{
#if 0
list_view->priv->dnd_renderer = NULL;
g_signal_handlers_disconnect_by_func (list_view->view,
gimp_container_list_view_row_expanded,
list_view);
if (! container)
{
if (gimp_dnd_viewable_list_source_remove (GTK_WIDGET (list_view->view),
gimp_container_get_child_type (old_container)))
{
if (GIMP_VIEWABLE_CLASS (g_type_class_peek (gimp_container_get_child_type (old_container)))->get_size)
gimp_dnd_pixbuf_source_remove (GTK_WIDGET (list_view->view));
gtk_drag_source_unset (GTK_WIDGET (list_view->view));
}
g_signal_handlers_disconnect_by_func (list_view->view,
gimp_container_list_view_button,
list_view);
}
#endif
gtk_list_box_bind_model (priv->view, NULL, NULL, NULL, NULL);
}
else if (container)
{
#if 0
if (gimp_dnd_drag_source_set_by_type (GTK_WIDGET (list_view->view),
GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
gimp_container_get_child_type (container),
GDK_ACTION_COPY))
{
gimp_dnd_viewable_list_source_add (GTK_WIDGET (list_view->view),
gimp_container_get_child_type (container),
gimp_container_list_view_drag_viewable_list,
list_view);
gimp_dnd_viewable_source_add (GTK_WIDGET (list_view->view),
gimp_container_get_child_type (container),
gimp_container_list_view_drag_viewable,
list_view);
if (GIMP_VIEWABLE_CLASS (g_type_class_peek (gimp_container_get_child_type (container)))->get_size)
gimp_dnd_pixbuf_source_add (GTK_WIDGET (list_view->view),
gimp_container_list_view_drag_pixbuf,
list_view);
}
/* connect button_press_event after DND so we can keep the list from
* selecting the item on button2
*/
g_signal_connect (list_view->view, "button-press-event",
G_CALLBACK (gimp_container_list_view_button),
list_view);
g_signal_connect (list_view->view, "button-release-event",
G_CALLBACK (gimp_container_list_view_button),
list_view);
#endif
}
parent_view_iface->set_container (view, container);
if (container)
{
gtk_list_box_bind_model (priv->view, G_LIST_MODEL (container),
gimp_row_create_for_container_view,
view, NULL);
#if 0
gimp_container_list_view_expand_rows (list_view->model,
list_view->view,
NULL);
g_signal_connect (list_view->view,
"row-collapsed",
G_CALLBACK (gimp_container_list_view_row_expanded),
list_view);
g_signal_connect (list_view->view,
"row-expanded",
G_CALLBACK (gimp_container_list_view_row_expanded),
list_view);
#endif
}
#if 0
gtk_list_view_columns_autosize (list_view->view);
#endif
}
static void
gimp_container_list_view_set_context (GimpContainerView *view,
GimpContext *context)
{
GimpContainerListViewPrivate *priv = GET_PRIVATE (view);
GList *children;
GList *child;
parent_view_iface->set_context (view, context);
children = gtk_container_get_children (GTK_CONTAINER (priv->view));
for (child = children; child; child = g_list_next (child))
{
gimp_row_set_context (child->data, context);
}
g_list_free (children);
}
static void
gimp_container_list_view_set_selection_mode (GimpContainerView *view,
GtkSelectionMode mode)
{
GimpContainerListViewPrivate *priv = GET_PRIVATE (view);
parent_view_iface->set_selection_mode (view, mode);
gtk_list_box_set_selection_mode (priv->view, mode);
}
static void
gimp_container_list_view_set_view_size (GimpContainerView *view)
{
GimpContainerListViewPrivate *priv = GET_PRIVATE (view);
GList *children;
GList *child;
gint view_size;
gint border_width;
view_size = gimp_container_view_get_view_size (view, &border_width);
children = gtk_container_get_children (GTK_CONTAINER (priv->view));
for (child = children; child; child = g_list_next (child))
{
gimp_row_set_view_size (child->data, view_size, border_width);
}
g_list_free (children);
}
static gboolean
gimp_container_list_view_set_selected (GimpContainerView *view,
GList *items)
{
GimpContainerListViewPrivate *priv = GET_PRIVATE (view);
GtkAdjustment *adjustment;
GtkAllocation allocation;
GList *children;
GList *child;
gdouble scroll_offset;
gint top_selected = G_MAXINT;
gint bottom_selected = 0;
adjustment = gtk_list_box_get_adjustment (priv->view);
scroll_offset = gtk_adjustment_get_value (adjustment);
g_signal_handlers_block_by_func (priv->view,
gimp_container_list_view_selected_rows_changed,
view);
gtk_list_box_unselect_all (priv->view);
adjustment = gtk_list_box_get_adjustment (priv->view);
children = gtk_container_get_children (GTK_CONTAINER (priv->view));
for (child = children; child; child = g_list_next (child))
{
GimpViewable *viewable = gimp_row_get_viewable (child->data);
if (g_list_find (items, viewable))
{
gtk_list_box_select_row (priv->view, child->data);
gtk_widget_get_allocation (child->data, &allocation);
top_selected = MIN (top_selected, allocation.y);
bottom_selected = MAX (bottom_selected, allocation.y + allocation.height);
}
}
g_list_free (children);
g_signal_handlers_unblock_by_func (priv->view,
gimp_container_list_view_selected_rows_changed,
view);
gtk_widget_get_allocation (gtk_widget_get_parent (GTK_WIDGET (priv->view)),
&allocation);
if (top_selected < scroll_offset)
{
gtk_adjustment_set_value (adjustment, top_selected);
}
else if (bottom_selected > scroll_offset + allocation.height)
{
gtk_adjustment_set_value (adjustment, bottom_selected - allocation.height);
}
_gimp_container_view_selection_changed (view);
return TRUE;
}
static gint
gimp_container_list_view_get_selected (GimpContainerView *view,
GList **items)
{
GimpContainerListViewPrivate *priv = GET_PRIVATE (view);
GList *rows;
gint n_selected;
rows = gtk_list_box_get_selected_rows (priv->view);
n_selected = g_list_length (rows);
if (items)
{
GList *row;
for (row = rows; row; row = g_list_next (row))
{
*items = g_list_prepend (*items, gimp_row_get_viewable (row->data));
}
*items = g_list_reverse (*items);
}
g_list_free (rows);
return n_selected;
}
static void
gimp_container_list_view_selected_rows_changed (GtkListBox *box,
GimpContainerListView *list_view)
{
_gimp_container_view_selection_changed (GIMP_CONTAINER_VIEW (list_view));
}
static void
gimp_container_list_view_row_activated (GtkListBox *box,
GimpRow *row,
GimpContainerListView *list_view)
{
_gimp_container_view_item_activated (GIMP_CONTAINER_VIEW (list_view),
gimp_row_get_viewable (row));
}
static void
gimp_container_list_view_monitor_changed (GimpContainerListView *view)
{
GimpContainerListViewPrivate *priv = GET_PRIVATE (view);
GList *children;
GList *child;
children = gtk_container_get_children (GTK_CONTAINER (priv->view));
for (child = children; child; child = g_list_next (child))
{
gimp_row_monitor_changed (child->data);
}
g_list_free (children);
}

View file

@ -0,0 +1,52 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* gimpcontainerlistview.h
* Copyright (C) 2025 Michael Natterer <mitch@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "gimpcontainerbox.h"
#define GIMP_TYPE_CONTAINER_LIST_VIEW (gimp_container_list_view_get_type ())
#define GIMP_CONTAINER_LIST_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONTAINER_LIST_VIEW, GimpContainerListView))
#define GIMP_CONTAINER_LIST_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CONTAINER_LIST_VIEW, GimpContainerListViewClass))
#define GIMP_IS_CONTAINER_LIST_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONTAINER_LIST_VIEW))
#define GIMP_IS_CONTAINER_LIST_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONTAINER_LIST_VIEW))
#define GIMP_CONTAINER_LIST_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CONTAINER_LIST_VIEW, GimpContainerListViewClass))
typedef struct _GimpContainerListViewClass GimpContainerListViewClass;
struct _GimpContainerListView
{
GimpContainerBox parent_instance;
};
struct _GimpContainerListViewClass
{
GimpContainerBoxClass parent_class;
};
GType gimp_container_list_view_get_type (void) G_GNUC_CONST;
GtkWidget * gimp_container_list_view_new (GimpContainer *container,
GimpContext *context,
gint view_size,
gint view_border_width);

View file

@ -51,6 +51,7 @@
#include "gimpmessagedialog.h"
#include "gimppropwidgets.h"
#include "gimprow.h"
#include "gimprow-utils.h"
#include "gimpuimanager.h"
#include "gimpwidgets-utils.h"
@ -290,13 +291,13 @@ gimp_controller_list_constructed (GObject *object)
categories = gimp_controller_manager_get_categories (list->controller_manager);
gtk_list_box_bind_model (GTK_LIST_BOX (list->available_controllers),
categories,
gimp_row_create,
gimp_row_create_for_context,
gimp_get_user_context (gimp_controller_manager_get_gimp (list->controller_manager)),
NULL);
gtk_list_box_bind_model (GTK_LIST_BOX (list->active_controllers),
G_LIST_MODEL (list->controller_manager),
gimp_row_create,
gimp_row_create_for_context,
gimp_get_user_context (gimp_controller_manager_get_gimp (list->controller_manager)),
NULL);
}

View file

@ -0,0 +1,58 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* gimprow-utils.c
* Copyright (C) 2025 Michael Natterer <mitch@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gegl.h>
#include <gtk/gtk.h>
#include "widgets-types.h"
#include "core/gimpcontext.h"
#include "core/gimpviewable.h"
#include "gimpcontainerview.h"
#include "gimprow.h"
#include "gimprow-utils.h"
GtkWidget *
gimp_row_create_for_context (gpointer item,
gpointer context)
{
g_return_val_if_fail (GIMP_IS_VIEWABLE (item), NULL);
g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
return gimp_row_new (context, item);
}
GtkWidget *
gimp_row_create_for_container_view (gpointer item,
gpointer container_view)
{
GimpContext *context;
g_return_val_if_fail (GIMP_IS_VIEWABLE (item), NULL);
g_return_val_if_fail (GIMP_IS_CONTAINER_VIEW (container_view), NULL);
context = gimp_container_view_get_context (container_view);
return gimp_row_new (context, item);
}

View file

@ -0,0 +1,29 @@
/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* gimprow-utils.h
* Copyright (C) 2025 Michael Natterer <mitch@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
/* GtkListBoxCreateWidgetFunc */
GtkWidget * gimp_row_create_for_context (gpointer item,
gpointer context);
GtkWidget * gimp_row_create_for_container_view (gpointer item,
gpointer container_view);

View file

@ -30,6 +30,8 @@
#include "core/gimpcontext.h"
#include "core/gimpviewable.h"
#include "gimpdnd.h"
#include "gimpeditor.h"
#include "gimprow.h"
#include "gimpview.h"
#include "gimpviewrenderer.h"
@ -91,6 +93,8 @@ static void gimp_row_get_property (GObject *object,
GValue *value,
GParamSpec *pspec);
static gboolean gimp_row_button_press_event (GtkWidget *widget,
GdkEventButton *bevent);
static void gimp_row_style_updated (GtkWidget *widget);
static gboolean gimp_row_query_tooltip (GtkWidget *widget,
gint x,
@ -121,6 +125,13 @@ static void gimp_row_label_long_pressed (GtkGesture *gesture,
static void gimp_row_rename_entry_activate (GtkEntry *entry,
GimpRow *row);
static GimpViewable *
gimp_row_drag_viewable (GtkWidget *widget,
GimpContext **context,
gpointer data);
static GdkPixbuf *gimp_row_drag_pixbuf (GtkWidget *widget,
gpointer data);
G_DEFINE_TYPE_WITH_PRIVATE (GimpRow, gimp_row, GTK_TYPE_LIST_BOX_ROW)
@ -136,20 +147,21 @@ gimp_row_class_init (GimpRowClass *klass)
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GtkBindingSet *binding_set;
object_class->constructed = gimp_row_constructed;
object_class->dispose = gimp_row_dispose;
object_class->set_property = gimp_row_set_property;
object_class->get_property = gimp_row_get_property;
object_class->constructed = gimp_row_constructed;
object_class->dispose = gimp_row_dispose;
object_class->set_property = gimp_row_set_property;
object_class->get_property = gimp_row_get_property;
widget_class->style_updated = gimp_row_style_updated;
widget_class->query_tooltip = gimp_row_query_tooltip;
widget_class->button_press_event = gimp_row_button_press_event;
widget_class->style_updated = gimp_row_style_updated;
widget_class->query_tooltip = gimp_row_query_tooltip;
klass->set_context = gimp_row_real_set_context;
klass->set_viewable = gimp_row_real_set_viewable;
klass->set_view_size = gimp_row_real_set_view_size;
klass->monitor_changed = gimp_row_real_monitor_changed;
klass->edit_name = gimp_row_real_edit_name;
klass->name_edited = gimp_row_real_name_edited;
klass->set_context = gimp_row_real_set_context;
klass->set_viewable = gimp_row_real_set_viewable;
klass->set_view_size = gimp_row_real_set_view_size;
klass->monitor_changed = gimp_row_real_monitor_changed;
klass->edit_name = gimp_row_real_edit_name;
klass->name_edited = gimp_row_real_name_edited;
row_signals[EDIT_NAME] =
g_signal_new ("edit-name",
@ -235,7 +247,8 @@ gimp_row_init (GimpRow *row)
static void
gimp_row_constructed (GObject *object)
{
GimpRowPrivate *priv = GET_PRIVATE (object);
GimpRowPrivate *priv = GET_PRIVATE (object);
gboolean preview = FALSE;
G_OBJECT_CLASS (parent_class)->constructed (object);
@ -243,9 +256,16 @@ gimp_row_constructed (GObject *object)
priv->view_size,
priv->view_border_width,
FALSE);
GIMP_VIEW (priv->view)->eat_button_events = FALSE;
GIMP_VIEW (priv->view)->show_popup = TRUE;
gtk_box_pack_start (GTK_BOX (priv->box), priv->view, FALSE, FALSE, 0);
gtk_widget_set_visible (priv->view, TRUE);
if (priv->viewable)
preview = (GIMP_VIEWABLE_GET_CLASS (priv->viewable)->get_preview != NULL ||
GIMP_VIEWABLE_GET_CLASS (priv->viewable)->get_new_preview != NULL);
gtk_widget_set_visible (priv->icon, ! preview);
gtk_widget_set_visible (priv->view, preview);
}
static void
@ -340,6 +360,43 @@ gimp_row_get_property (GObject *object,
}
}
static gboolean
gimp_row_button_press_event (GtkWidget *widget,
GdkEventButton *bevent)
{
GdkEvent *event = (GdkEvent *) bevent;
gboolean context_menu;
GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, bevent);
context_menu = gdk_event_triggers_context_menu (event);
if (bevent->button == 1 || context_menu)
{
GtkWidget *list_box = gtk_widget_get_parent (widget);
if (GTK_IS_LIST_BOX (list_box))
{
gtk_list_box_select_row (GTK_LIST_BOX (list_box),
GTK_LIST_BOX_ROW (widget));
if (context_menu)
{
GtkWidget *editor = gtk_widget_get_ancestor (widget,
GIMP_TYPE_EDITOR);
if (editor)
{
return gimp_editor_popup_menu_at_pointer (GIMP_EDITOR (editor),
event);
}
}
}
}
return FALSE;
}
static void
gimp_row_style_updated (GtkWidget *widget)
{
@ -400,7 +457,12 @@ static void
gimp_row_real_set_viewable (GimpRow *row,
GimpViewable *viewable)
{
GimpRowPrivate *priv = GET_PRIVATE (row);
GimpRowPrivate *priv = GET_PRIVATE (row);
GType viewable_type = G_TYPE_NONE;
gboolean preview = FALSE;
if (viewable)
viewable_type = G_TYPE_FROM_INSTANCE (viewable);
if (priv->viewable)
{
@ -410,6 +472,36 @@ gimp_row_real_set_viewable (GimpRow *row,
g_signal_handlers_disconnect_by_func (priv->viewable,
gimp_row_viewable_name_changed,
row);
if (! viewable)
{
if (gimp_dnd_viewable_source_remove (GTK_WIDGET (row),
G_TYPE_FROM_INSTANCE (priv->viewable)))
{
if (gimp_viewable_get_size (priv->viewable, NULL, NULL))
gimp_dnd_pixbuf_source_remove (GTK_WIDGET (row));
gtk_drag_source_unset (GTK_WIDGET (row));
}
}
}
else if (viewable)
{
if (gimp_dnd_drag_source_set_by_type (GTK_WIDGET (row),
GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
viewable_type,
GDK_ACTION_COPY))
{
gimp_dnd_viewable_source_add (GTK_WIDGET (row),
viewable_type,
gimp_row_drag_viewable,
NULL);
if (gimp_viewable_get_size (viewable, NULL, NULL))
gimp_dnd_pixbuf_source_add (GTK_WIDGET (row),
gimp_row_drag_pixbuf,
NULL);
}
}
g_set_object (&priv->viewable, viewable);
@ -423,13 +515,24 @@ gimp_row_real_set_viewable (GimpRow *row,
g_signal_connect (priv->viewable, "notify::icon-name",
G_CALLBACK (gimp_row_viewable_icon_changed),
row);
preview = (GIMP_VIEWABLE_GET_CLASS (priv->viewable)->get_preview != NULL ||
GIMP_VIEWABLE_GET_CLASS (priv->viewable)->get_new_preview != NULL);
}
gimp_row_viewable_name_changed (priv->viewable, row);
gimp_row_viewable_icon_changed (priv->viewable, NULL, row);
if (priv->icon)
{
gtk_widget_set_visible (priv->icon, ! preview);
gimp_row_viewable_icon_changed (priv->viewable, NULL, row);
}
if (priv->view)
gimp_view_set_viewable (GIMP_VIEW (priv->view), priv->viewable);
{
gtk_widget_set_visible (priv->view, preview);
gimp_view_set_viewable (GIMP_VIEW (priv->view), priv->viewable);
}
}
static void
@ -634,13 +737,6 @@ gimp_row_monitor_changed (GimpRow *row)
GIMP_ROW_GET_CLASS (row)->monitor_changed (row);
}
GtkWidget *
gimp_row_create (gpointer item,
gpointer user_data)
{
return gimp_row_new (user_data, item);
}
/* private functions */
@ -702,3 +798,35 @@ gimp_row_rename_entry_activate (GtkEntry *entry,
gtk_popover_popdown (GTK_POPOVER (priv->popover));
}
static GimpViewable *
gimp_row_drag_viewable (GtkWidget *widget,
GimpContext **context,
gpointer data)
{
GimpRowPrivate *priv = GET_PRIVATE (widget);
if (context)
*context = priv->context;
return priv->viewable;
}
static GdkPixbuf *
gimp_row_drag_pixbuf (GtkWidget *widget,
gpointer data)
{
GimpRowPrivate *priv = GET_PRIVATE (widget);
gint width;
gint height;
if (priv->viewable &&
gimp_viewable_get_size (priv->viewable, &width, &height))
{
return gimp_viewable_get_new_pixbuf (priv->viewable,
priv->context,
width, height, NULL, NULL);
}
return NULL;
}

View file

@ -67,8 +67,3 @@ gint gimp_row_get_view_size (GimpRow *row,
gint *view_border_width);
void gimp_row_monitor_changed (GimpRow *row);
/* a generic GtkListBoxCreateWidgetFunc */
GtkWidget * gimp_row_create (gpointer item,
gpointer user_data);

View file

@ -65,6 +65,7 @@ libappwidgets_sources = [
'gimpcontainereditor.c',
'gimpcontainerentry.c',
'gimpcontainericonview.c',
'gimpcontainerlistview.c',
'gimpcontainerpopup.c',
'gimpcontainertreestore.c',
'gimpcontainertreeview-dnd.c',
@ -190,6 +191,7 @@ libappwidgets_sources = [
'gimpradioaction.c',
'gimprender.c',
'gimprow.c',
'gimprow-utils.c',
'gimpsamplepointeditor.c',
'gimpsavedialog.c',
'gimpsearchpopup.c',

View file

@ -88,6 +88,7 @@ typedef struct _GimpContainerBox GimpContainerBox;
typedef struct _GimpContainerComboBox GimpContainerComboBox;
typedef struct _GimpContainerEntry GimpContainerEntry;
typedef struct _GimpContainerIconView GimpContainerIconView;
typedef struct _GimpContainerListView GimpContainerListView;
typedef struct _GimpContainerTreeStore GimpContainerTreeStore;
typedef struct _GimpContainerTreeView GimpContainerTreeView;
typedef struct _GimpContainerView GimpContainerView; /* dummy typedef */