Gimp/app/widgets/gimpdrawabletreeview.c
Alx Sa dd28bae44c widgets: Make Fx icon invisible when no filters
We use a GimpCellRenderToggle for the Fx icon
in the layer and channel docks. If you hover over it
when there's no filter, a square "checkbox" will appear
and give the impression you can click on it, even though
you currently can not.

This patch connects the "visible" property of the toggle to
the number of filters attached to the drawable, similar to
what we did for "active". This prevents the checkbox from
appearing, and will hopefully reduce user confusion.
2026-01-22 02:06:01 +00:00

701 lines
28 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* gimpdrawabletreeview.c
* Copyright (C) 2001-2009 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 "libgimpwidgets/gimpwidgets.h"
#include "libgimpwidgets/gimpwidgets-private.h"
#include "gimphelp-ids.h"
#include "widgets-types.h"
#include "config/gimpguiconfig.h"
#include "core/gimp.h"
#include "core/gimpcontext.h"
#include "core/gimpdrawable.h"
#include "core/gimpdrawable-edit.h"
#include "core/gimpdrawable-filters.h"
#include "core/gimpdrawablefilter.h"
#include "core/gimpfilloptions.h"
#include "core/gimplist.h"
#include "core/gimpimage.h"
#include "core/gimpimage-undo.h"
#include "core/gimpimage-undo-push.h"
#include "core/gimppattern.h"
#include "core/gimptreehandler.h"
#include "gimpcontainertreestore.h"
#include "gimpcontainerview.h"
#include "gimpcontainerview-cruft.h"
#include "gimpdnd.h"
#include "gimpdrawabletreeview.h"
#include "gimpdrawabletreeview-filters.h"
#include "gimpviewrenderer.h"
#include "gimp-intl.h"
struct _GimpDrawableTreeViewPrivate
{
GtkWidget *filters_header_image;
gint model_column_filters;
GtkCellRenderer *filters_cell;
GimpTreeHandler *filters_changed_handler;
};
static void gimp_drawable_tree_view_view_iface_init (GimpContainerViewInterface *iface);
static void gimp_drawable_tree_view_constructed (GObject *object);
static void gimp_drawable_tree_view_dispose (GObject *object);
static void gimp_drawable_tree_view_unmap (GtkWidget *widget);
static void gimp_drawable_tree_view_style_updated (GtkWidget *widget);
static void gimp_drawable_tree_view_set_container (GimpContainerView *view,
GimpContainer *container);
static gboolean gimp_drawable_tree_view_set_selected (GimpContainerView *view,
GList *items);
static gpointer gimp_drawable_tree_view_insert_item (GimpContainerView *view,
GimpViewable *viewable,
gpointer parent_insert_data,
gint index);
static gboolean gimp_drawable_tree_view_drop_possible (GimpContainerTreeView *view,
GimpDndType src_type,
GList *src_viewables,
GimpViewable *dest_viewable,
GtkTreePath *drop_path,
GtkTreeViewDropPosition drop_pos,
GtkTreeViewDropPosition *return_drop_pos,
GdkDragAction *return_drag_action);
static void gimp_drawable_tree_view_drop_viewables (GimpContainerTreeView *view,
GList *src_viewables,
GimpViewable *dest_viewable,
GtkTreeViewDropPosition drop_pos);
static void gimp_drawable_tree_view_drop_color (GimpContainerTreeView *view,
GeglColor *color,
GimpViewable *dest_viewable,
GtkTreeViewDropPosition drop_pos);
static void gimp_drawable_tree_view_set_image (GimpItemTreeView *view,
GimpImage *image);
static void gimp_drawable_tree_view_floating_selection_changed
(GimpImage *image,
GimpDrawableTreeView *view);
static void gimp_drawable_tree_view_filters_changed (GimpDrawable *drawable,
GimpDrawableTreeView *view);
static void gimp_drawable_tree_view_filters_cell_clicked
(GtkCellRendererToggle *toggle,
gchar *path,
GdkModifierType state,
GimpDrawableTreeView *view);
static void gimp_drawable_tree_view_new_pattern_dropped
(GtkWidget *widget,
gint x,
gint y,
GimpViewable *viewable,
gpointer data);
static void gimp_drawable_tree_view_new_color_dropped
(GtkWidget *widget,
gint x,
gint y,
GeglColor *color,
gpointer data);
G_DEFINE_TYPE_WITH_CODE (GimpDrawableTreeView, gimp_drawable_tree_view,
GIMP_TYPE_ITEM_TREE_VIEW,
G_ADD_PRIVATE (GimpDrawableTreeView)
G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONTAINER_VIEW,
gimp_drawable_tree_view_view_iface_init))
#define parent_class gimp_drawable_tree_view_parent_class
static GimpContainerViewInterface *parent_view_iface = NULL;
static void
gimp_drawable_tree_view_class_init (GimpDrawableTreeViewClass *klass)
{
GObjectClass *object_class;
GtkWidgetClass *widget_class;
GimpContainerTreeViewClass *tree_view_class;
GimpItemTreeViewClass *item_view_class;
object_class = G_OBJECT_CLASS (klass);
widget_class = GTK_WIDGET_CLASS (klass);
tree_view_class = GIMP_CONTAINER_TREE_VIEW_CLASS (klass);
item_view_class = GIMP_ITEM_TREE_VIEW_CLASS (klass);
object_class->constructed = gimp_drawable_tree_view_constructed;
object_class->dispose = gimp_drawable_tree_view_dispose;
widget_class->unmap = gimp_drawable_tree_view_unmap;
widget_class->style_updated = gimp_drawable_tree_view_style_updated;
tree_view_class->drop_possible = gimp_drawable_tree_view_drop_possible;
tree_view_class->drop_viewables = gimp_drawable_tree_view_drop_viewables;
tree_view_class->drop_color = gimp_drawable_tree_view_drop_color;
item_view_class->set_image = gimp_drawable_tree_view_set_image;
item_view_class->lock_content_icon_name = GIMP_ICON_LOCK_CONTENT;
item_view_class->lock_content_tooltip = _("Lock pixels");
item_view_class->lock_position_icon_name = GIMP_ICON_LOCK_POSITION;
item_view_class->lock_position_tooltip = _("Lock position and size");
item_view_class->lock_visibility_icon_name = GIMP_ICON_LOCK_VISIBILITY;
item_view_class->lock_visibility_tooltip = _("Lock visibility");
}
static void
gimp_drawable_tree_view_view_iface_init (GimpContainerViewInterface *iface)
{
parent_view_iface = g_type_interface_peek_parent (iface);
iface->set_container = gimp_drawable_tree_view_set_container;
iface->set_selected = gimp_drawable_tree_view_set_selected;
iface->insert_item = gimp_drawable_tree_view_insert_item;
}
static void
gimp_drawable_tree_view_init (GimpDrawableTreeView *view)
{
GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view);
view->priv = gimp_drawable_tree_view_get_instance_private (view);
view->priv->model_column_filters =
gimp_container_tree_store_columns_add (tree_view->model_columns,
&tree_view->n_model_columns,
G_TYPE_BOOLEAN);
}
static void
gimp_drawable_tree_view_constructed (GObject *object)
{
GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (object);
GimpItemTreeView *item_view = GIMP_ITEM_TREE_VIEW (object);
GimpDrawableTreeView *view = GIMP_DRAWABLE_TREE_VIEW (object);
GtkIconSize button_icon_size = GTK_ICON_SIZE_SMALL_TOOLBAR;
GtkTreeViewColumn *column;
gint pixel_icon_size = 16;
gint button_spacing;
G_OBJECT_CLASS (parent_class)->constructed (object);
gtk_widget_style_get (GTK_WIDGET (view),
"button-icon-size", &button_icon_size,
"button-spacing", &button_spacing,
NULL);
gtk_icon_size_lookup (button_icon_size, &pixel_icon_size, NULL);
column = gtk_tree_view_column_new ();
view->priv->filters_header_image =
gtk_image_new_from_icon_name (GIMP_ICON_EFFECT, button_icon_size);
gimp_widget_set_identifier (view->priv->filters_header_image,
"item-effect-header-icon");
gtk_tree_view_column_set_widget (column, view->priv->filters_header_image);
gtk_widget_show (view->priv->filters_header_image);
gtk_tree_view_insert_column (tree_view->view, column, 2);
view->priv->filters_cell = gimp_cell_renderer_toggle_new (GIMP_ICON_EFFECT);
g_object_set (view->priv->filters_cell,
"xpad", 0,
"ypad", 0,
"icon-size", pixel_icon_size,
NULL);
gtk_tree_view_column_pack_start (column, view->priv->filters_cell, FALSE);
gtk_tree_view_column_set_attributes (column, view->priv->filters_cell,
"active",
view->priv->model_column_filters,
"visible",
view->priv->model_column_filters,
NULL);
gimp_container_tree_view_add_toggle_cell (tree_view,
view->priv->filters_cell);
g_signal_connect (view->priv->filters_cell, "clicked",
G_CALLBACK (gimp_drawable_tree_view_filters_cell_clicked),
item_view);
gimp_dnd_viewable_dest_add (gimp_item_tree_view_get_new_button (item_view),
GIMP_TYPE_PATTERN,
gimp_drawable_tree_view_new_pattern_dropped,
item_view);
gimp_dnd_color_dest_add (gimp_item_tree_view_get_new_button (item_view),
gimp_drawable_tree_view_new_color_dropped,
item_view);
gimp_dnd_color_dest_add (GTK_WIDGET (tree_view->view),
NULL, tree_view);
gimp_dnd_viewable_dest_add (GTK_WIDGET (tree_view->view), GIMP_TYPE_PATTERN,
NULL, tree_view);
}
static void
gimp_drawable_tree_view_dispose (GObject *object)
{
GimpDrawableTreeView *view = GIMP_DRAWABLE_TREE_VIEW (object);
_gimp_drawable_tree_view_filter_editor_destroy (view);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gimp_drawable_tree_view_unmap (GtkWidget *widget)
{
GimpDrawableTreeView *view = GIMP_DRAWABLE_TREE_VIEW (widget);
_gimp_drawable_tree_view_filter_editor_hide (view);
GTK_WIDGET_CLASS (parent_class)->unmap (widget);
}
static void
gimp_drawable_tree_view_style_updated (GtkWidget *widget)
{
GimpDrawableTreeView *view = GIMP_DRAWABLE_TREE_VIEW (widget);
const gchar *old_icon_name;
gint pixel_icon_size;
GtkIconSize old_size;
GtkIconSize button_icon_size = GTK_ICON_SIZE_SMALL_TOOLBAR;
gtk_widget_style_get (widget,
"button-icon-size", &button_icon_size,
NULL);
gtk_icon_size_lookup (button_icon_size, &pixel_icon_size, NULL);
gtk_image_get_icon_name (GTK_IMAGE (view->priv->filters_header_image),
&old_icon_name, &old_size);
if (button_icon_size != old_size)
{
gchar *icon_name = g_strdup (old_icon_name);
gtk_image_set_from_icon_name (GTK_IMAGE (view->priv->filters_header_image),
icon_name, button_icon_size);
g_free (icon_name);
}
g_object_set (view->priv->filters_cell,
"icon-name", GIMP_ICON_EFFECT,
"icon-size", pixel_icon_size,
NULL);
GTK_WIDGET_CLASS (parent_class)->style_updated (widget);
}
/* GimpContainerView methods */
static void
gimp_drawable_tree_view_set_container (GimpContainerView *view,
GimpContainer *container)
{
GimpDrawableTreeView *drawable_view = GIMP_DRAWABLE_TREE_VIEW (view);
GimpContainer *old_container;
old_container = gimp_container_view_get_container (view);
if (old_container)
g_clear_pointer (&drawable_view->priv->filters_changed_handler,
gimp_tree_handler_disconnect);
parent_view_iface->set_container (view, container);
if (container)
drawable_view->priv->filters_changed_handler =
gimp_tree_handler_connect (container, "filters-changed",
G_CALLBACK (gimp_drawable_tree_view_filters_changed),
view);
}
static gboolean
gimp_drawable_tree_view_set_selected (GimpContainerView *view,
GList *items)
{
GimpItemTreeView *item_view = GIMP_ITEM_TREE_VIEW (view);
GimpImage *image = gimp_item_tree_view_get_image (item_view);
gboolean success = TRUE;
if (image)
{
GimpLayer *floating_sel = gimp_image_get_floating_selection (image);
success = (items == NULL ||
floating_sel == NULL ||
(g_list_length (items) == 1 &&
items->data == GIMP_VIEWABLE (floating_sel)));
if (! success)
{
Gimp *gimp = image->gimp;
GimpContext *context = gimp_get_user_context (gimp);
GimpDisplay *display = gimp_context_get_display (context);
gimp_message_literal (gimp, G_OBJECT (display), GIMP_MESSAGE_WARNING,
_("Cannot select items while a floating "
"selection is active."));
}
}
if (success)
success = parent_view_iface->set_selected (view, items);
return success;
}
static gpointer
gimp_drawable_tree_view_insert_item (GimpContainerView *view,
GimpViewable *viewable,
gpointer parent_insert_data,
gint index)
{
GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view);
GimpDrawableTreeView *drawable_view = GIMP_DRAWABLE_TREE_VIEW (view);
GimpContainer *filters;
GtkTreeIter *iter;
gint n_filters;
iter = parent_view_iface->insert_item (view, viewable,
parent_insert_data, index);
filters = gimp_drawable_get_filters (GIMP_DRAWABLE (viewable));
n_filters = gimp_container_get_n_children (filters);
gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), iter,
drawable_view->priv->model_column_filters,
n_filters > 0,
-1);
return iter;
}
/* GimpContainerTreeView methods */
static gboolean
gimp_drawable_tree_view_drop_possible (GimpContainerTreeView *tree_view,
GimpDndType src_type,
GList *src_viewables,
GimpViewable *dest_viewable,
GtkTreePath *drop_path,
GtkTreeViewDropPosition drop_pos,
GtkTreeViewDropPosition *return_drop_pos,
GdkDragAction *return_drag_action)
{
if (GIMP_CONTAINER_TREE_VIEW_CLASS (parent_class)->drop_possible (tree_view,
src_type,
src_viewables,
dest_viewable,
drop_path,
drop_pos,
return_drop_pos,
return_drag_action))
{
if (src_type == GIMP_DND_TYPE_COLOR ||
src_type == GIMP_DND_TYPE_PATTERN)
{
if (! dest_viewable ||
gimp_item_is_content_locked (GIMP_ITEM (dest_viewable), NULL) ||
gimp_viewable_get_children (GIMP_VIEWABLE (dest_viewable)))
return FALSE;
if (return_drop_pos)
{
*return_drop_pos = GTK_TREE_VIEW_DROP_INTO_OR_AFTER;
}
}
return TRUE;
}
return FALSE;
}
static void
gimp_drawable_tree_view_drop_viewables (GimpContainerTreeView *view,
GList *src_viewables,
GimpViewable *dest_viewable,
GtkTreeViewDropPosition drop_pos)
{
GList *iter;
for (iter = src_viewables; iter; iter = iter->next)
{
GimpViewable *src_viewable = iter->data;
if (dest_viewable && GIMP_IS_PATTERN (src_viewable))
{
GimpImage *image = gimp_item_get_image (GIMP_ITEM (dest_viewable));
GimpFillOptions *options = gimp_fill_options_new (image->gimp, NULL, FALSE);
gimp_fill_options_set_style (options, GIMP_FILL_STYLE_PATTERN);
gimp_context_set_pattern (GIMP_CONTEXT (options),
GIMP_PATTERN (src_viewable));
gimp_drawable_edit_fill (GIMP_DRAWABLE (dest_viewable),
options,
C_("undo-type", "Drop pattern to layer"));
g_object_unref (options);
gimp_image_flush (image);
return;
}
}
GIMP_CONTAINER_TREE_VIEW_CLASS (parent_class)->drop_viewables (view,
src_viewables,
dest_viewable,
drop_pos);
}
static void
gimp_drawable_tree_view_drop_color (GimpContainerTreeView *view,
GeglColor *color,
GimpViewable *dest_viewable,
GtkTreeViewDropPosition drop_pos)
{
if (dest_viewable)
{
GimpImage *image = gimp_item_get_image (GIMP_ITEM (dest_viewable));
GimpFillOptions *options = gimp_fill_options_new (image->gimp, NULL, FALSE);
gimp_fill_options_set_style (options, GIMP_FILL_STYLE_FG_COLOR);
gimp_context_set_foreground (GIMP_CONTEXT (options), color);
gimp_drawable_edit_fill (GIMP_DRAWABLE (dest_viewable),
options,
C_("undo-type", "Drop color to layer"));
g_object_unref (options);
gimp_image_flush (image);
}
}
/* GimpItemTreeView methods */
static void
gimp_drawable_tree_view_set_image (GimpItemTreeView *view,
GimpImage *image)
{
GimpDrawableTreeView *drawable_view = GIMP_DRAWABLE_TREE_VIEW (view);
if (gimp_item_tree_view_get_image (view))
{
_gimp_drawable_tree_view_filter_editor_hide (drawable_view);
g_signal_handlers_disconnect_by_func (gimp_item_tree_view_get_image (view),
gimp_drawable_tree_view_floating_selection_changed,
view);
g_signal_handlers_disconnect_by_func (gimp_item_tree_view_get_image (view)->gimp->config,
G_CALLBACK (gimp_drawable_tree_view_style_updated),
view);
}
GIMP_ITEM_TREE_VIEW_CLASS (parent_class)->set_image (view, image);
if (gimp_item_tree_view_get_image (view))
{
g_signal_connect (gimp_item_tree_view_get_image (view),
"floating-selection-changed",
G_CALLBACK (gimp_drawable_tree_view_floating_selection_changed),
view);
g_signal_connect_object (image->gimp->config,
"notify::theme",
G_CALLBACK (gimp_drawable_tree_view_style_updated),
view, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
g_signal_connect_object (image->gimp->config,
"notify::override-theme-icon-size",
G_CALLBACK (gimp_drawable_tree_view_style_updated),
view, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
g_signal_connect_object (image->gimp->config,
"notify::custom-icon-size",
G_CALLBACK (gimp_drawable_tree_view_style_updated),
view, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
}
}
/* callbacks */
static void
gimp_drawable_tree_view_floating_selection_changed (GimpImage *image,
GimpDrawableTreeView *view)
{
GType item_type;
GList *items;
item_type = GIMP_ITEM_TREE_VIEW_GET_CLASS (view)->item_type;
items = gimp_image_get_selected_items (image, item_type);
items = g_list_copy (items);
/* update button states */
gimp_container_view_set_selected (GIMP_CONTAINER_VIEW (view), items);
g_list_free (items);
}
/* filters callbacks */
static void
gimp_drawable_tree_view_filters_changed (GimpDrawable *drawable,
GimpDrawableTreeView *view)
{
GimpContainerTreeView *tree_view = GIMP_CONTAINER_TREE_VIEW (view);
GtkTreeIter *iter;
iter = _gimp_container_view_lookup (GIMP_CONTAINER_VIEW (view),
(GimpViewable *) drawable);
if (iter)
{
gint n_editable;
gimp_drawable_n_editable_filters (drawable, &n_editable, NULL, NULL);
gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), iter,
view->priv->model_column_filters,
n_editable > 0,
-1);
}
}
static void
gimp_drawable_tree_view_filters_cell_clicked (GtkCellRendererToggle *toggle,
gchar *path_str,
GdkModifierType state,
GimpDrawableTreeView *view)
{
GtkTreePath *path;
GtkTreeIter iter;
path = gtk_tree_path_new_from_string (path_str);
if (gtk_tree_model_get_iter (GIMP_CONTAINER_TREE_VIEW (view)->model,
&iter, path))
{
GimpViewRenderer *renderer;
GimpContainerTreeStore *store;
GimpDrawable *drawable;
GdkRectangle rect;
/* Update the filter state. */
store = GIMP_CONTAINER_TREE_STORE (GIMP_CONTAINER_TREE_VIEW (view)->model);
renderer = gimp_container_tree_store_get_renderer (store, &iter);
drawable = GIMP_DRAWABLE (renderer->viewable);
g_object_unref (renderer);
gtk_tree_view_get_cell_area (GIMP_CONTAINER_TREE_VIEW (view)->view, path,
gtk_tree_view_get_column (GIMP_CONTAINER_TREE_VIEW (view)->view, 2),
&rect);
gtk_tree_view_convert_bin_window_to_widget_coords (GIMP_CONTAINER_TREE_VIEW (view)->view,
rect.x, rect.y, &rect.x, &rect.y);
_gimp_drawable_tree_view_filter_editor_show (view, drawable, &rect);
}
}
/* dnd callbacks */
static void
gimp_drawable_tree_view_new_dropped (GimpItemTreeView *view,
GimpFillOptions *options,
const gchar *undo_desc)
{
GimpImage *image = gimp_item_tree_view_get_image (view);
GimpItem *item;
gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_EDIT_PASTE,
C_("singular", "New Layer"));
item = GIMP_ITEM_TREE_VIEW_GET_CLASS (view)->new_item (image);
if (item)
gimp_drawable_edit_fill (GIMP_DRAWABLE (item), options, undo_desc);
gimp_image_undo_group_end (image);
gimp_image_flush (image);
}
static void
gimp_drawable_tree_view_new_pattern_dropped (GtkWidget *widget,
gint x,
gint y,
GimpViewable *viewable,
gpointer data)
{
GimpItemTreeView *view = GIMP_ITEM_TREE_VIEW (data);
GimpImage *image = gimp_item_tree_view_get_image (view);
GimpFillOptions *options = gimp_fill_options_new (image->gimp, NULL, FALSE);
gimp_fill_options_set_style (options, GIMP_FILL_STYLE_PATTERN);
gimp_context_set_pattern (GIMP_CONTEXT (options), GIMP_PATTERN (viewable));
gimp_drawable_tree_view_new_dropped (view, options,
C_("undo-type", "Drop pattern to layer"));
g_object_unref (options);
}
static void
gimp_drawable_tree_view_new_color_dropped (GtkWidget *widget,
gint x,
gint y,
GeglColor *color,
gpointer data)
{
GimpItemTreeView *view = GIMP_ITEM_TREE_VIEW (data);
GimpImage *image = gimp_item_tree_view_get_image (view);
GimpFillOptions *options = gimp_fill_options_new (image->gimp, NULL, FALSE);
gimp_fill_options_set_style (options, GIMP_FILL_STYLE_FG_COLOR);
gimp_context_set_foreground (GIMP_CONTEXT (options), color);
gimp_drawable_tree_view_new_dropped (view, options,
C_("undo-type", "Drop color to layer"));
g_object_unref (options);
}