/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * pikalayertreeview.c * Copyright (C) 2001-2009 Michael Natterer * * 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 . */ #include "config.h" #include #include #include #include "libpikabase/pikabase.h" #include "libpikamath/pikamath.h" #include "libpikawidgets/pikawidgets.h" #include "widgets-types.h" #if 0 /* For mask features in pika_layer_tree_view_layer_clicked() */ #include "config/pikadialogconfig.h" #endif #include "core/pika.h" #include "core/pikachannel.h" #include "core/pikacontainer.h" #include "core/pikacontext.h" #include "core/pikaimage-undo.h" #include "core/pikaimage-undo-push.h" #include "core/pikaimage.h" #include "core/pikaitemlist.h" #include "core/pikaitemundo.h" #include "core/pikalayer.h" #include "core/pikalayer-floating-selection.h" #include "core/pikalayer-new.h" #include "core/pikalayermask.h" #include "core/pikatreehandler.h" #include "text/pikatextlayer.h" #include "file/file-open.h" #include "pikaactiongroup.h" #include "pikacellrendererviewable.h" #include "pikacontainertreestore.h" #include "pikacontainerview.h" #include "pikadnd.h" #include "pikahelp-ids.h" #include "pikalayermodebox.h" #include "pikalayertreeview.h" #include "pikatoggleaction.h" #include "pikauimanager.h" #include "pikaviewrenderer.h" #include "pikawidgets-utils.h" #include "pika-intl.h" struct _PikaLayerTreeViewPrivate { GtkWidget *layer_mode_box; GtkAdjustment *opacity_adjustment; GtkWidget *anchor_button; GtkWidget *link_button; GtkWidget *link_popover; GtkWidget *new_link_button; GtkWidget *link_list; GtkWidget *link_entry; GtkWidget *link_search_entry; PikaItemList *link_pattern_set; gint model_column_mask; gint model_column_mask_visible; GtkCellRenderer *mask_cell; PangoAttrList *italic_attrs; PangoAttrList *bold_attrs; PikaTreeHandler *mode_changed_handler; PikaTreeHandler *opacity_changed_handler; PikaTreeHandler *mask_changed_handler; PikaTreeHandler *alpha_changed_handler; }; static void pika_layer_tree_view_view_iface_init (PikaContainerViewInterface *iface); static void pika_layer_tree_view_constructed (GObject *object); static void pika_layer_tree_view_finalize (GObject *object); static void pika_layer_tree_view_style_updated (GtkWidget *widget); static void pika_layer_tree_view_set_container (PikaContainerView *view, PikaContainer *container); static void pika_layer_tree_view_set_context (PikaContainerView *view, PikaContext *context); static gpointer pika_layer_tree_view_insert_item (PikaContainerView *view, PikaViewable *viewable, gpointer parent_insert_data, gint index); static gboolean pika_layer_tree_view_select_items (PikaContainerView *view, GList *items, GList *paths); static void pika_layer_tree_view_set_view_size (PikaContainerView *view); static gboolean pika_layer_tree_view_drop_possible (PikaContainerTreeView *view, PikaDndType src_type, GList *src_viewables, PikaViewable *dest_viewable, GtkTreePath *drop_path, GtkTreeViewDropPosition drop_pos, GtkTreeViewDropPosition *return_drop_pos, GdkDragAction *return_drag_action); static void pika_layer_tree_view_drop_color (PikaContainerTreeView *view, const PikaRGB *color, PikaViewable *dest_viewable, GtkTreeViewDropPosition drop_pos); static void pika_layer_tree_view_drop_uri_list (PikaContainerTreeView *view, GList *uri_list, PikaViewable *dest_viewable, GtkTreeViewDropPosition drop_pos); static void pika_layer_tree_view_drop_component (PikaContainerTreeView *tree_view, PikaImage *image, PikaChannelType component, PikaViewable *dest_viewable, GtkTreeViewDropPosition drop_pos); static void pika_layer_tree_view_drop_pixbuf (PikaContainerTreeView *tree_view, GdkPixbuf *pixbuf, PikaViewable *dest_viewable, GtkTreeViewDropPosition drop_pos); static void pika_layer_tree_view_set_image (PikaItemTreeView *view, PikaImage *image); static PikaItem * pika_layer_tree_view_item_new (PikaImage *image); static void pika_layer_tree_view_floating_selection_changed (PikaImage *image, PikaLayerTreeView *view); static void pika_layer_tree_view_layer_links_changed (PikaImage *image, PikaLayerTreeView *view); static gboolean pika_layer_tree_view_link_clicked (GtkWidget *box, GdkEvent *event, PikaLayerTreeView *view); static void pika_layer_tree_view_link_popover_shown (GtkPopover *popover, PikaLayerTreeView *view); static gboolean pika_layer_tree_view_search_key_release (GtkWidget *widget, GdkEventKey *event, PikaLayerTreeView *view); static gboolean pika_layer_tree_view_start_interactive_search (GtkTreeView *tree_view, PikaLayerTreeView *layer_view); static void pika_layer_tree_view_new_link_exit (PikaLayerTreeView *view); static gboolean pika_layer_tree_view_new_link_clicked (PikaLayerTreeView *view); static gboolean pika_layer_tree_view_unlink_clicked (GtkWidget *widget, GdkEvent *event, PikaLayerTreeView *view); static void pika_layer_tree_view_layer_mode_box_callback (GtkWidget *widget, const GParamSpec *pspec, PikaLayerTreeView *view); static void pika_layer_tree_view_opacity_scale_changed (GtkAdjustment *adj, PikaLayerTreeView *view); static void pika_layer_tree_view_layer_signal_handler (PikaLayer *layer, PikaLayerTreeView *view); static void pika_layer_tree_view_update_options (PikaLayerTreeView *view, GList *layers); static void pika_layer_tree_view_update_menu (PikaLayerTreeView *view, GList *layers); static void pika_layer_tree_view_update_highlight (PikaLayerTreeView *view); static void pika_layer_tree_view_mask_update (PikaLayerTreeView *view, GtkTreeIter *iter, PikaLayer *layer); static void pika_layer_tree_view_mask_changed (PikaLayer *layer, PikaLayerTreeView *view); static void pika_layer_tree_view_renderer_update (PikaViewRenderer *renderer, PikaLayerTreeView *view); static void pika_layer_tree_view_update_borders (PikaLayerTreeView *view, GtkTreeIter *iter); static void pika_layer_tree_view_mask_callback (PikaLayer *mask, PikaLayerTreeView *view); static void pika_layer_tree_view_layer_clicked (PikaCellRendererViewable *cell, const gchar *path, GdkModifierType state, PikaLayerTreeView *view); static void pika_layer_tree_view_mask_clicked (PikaCellRendererViewable *cell, const gchar *path, GdkModifierType state, PikaLayerTreeView *view); static void pika_layer_tree_view_alpha_update (PikaLayerTreeView *view, GtkTreeIter *iter, PikaLayer *layer); static void pika_layer_tree_view_alpha_changed (PikaLayer *layer, PikaLayerTreeView *view); G_DEFINE_TYPE_WITH_CODE (PikaLayerTreeView, pika_layer_tree_view, PIKA_TYPE_DRAWABLE_TREE_VIEW, G_ADD_PRIVATE (PikaLayerTreeView) G_IMPLEMENT_INTERFACE (PIKA_TYPE_CONTAINER_VIEW, pika_layer_tree_view_view_iface_init)) #define parent_class pika_layer_tree_view_parent_class static PikaContainerViewInterface *parent_view_iface = NULL; static void pika_layer_tree_view_class_init (PikaLayerTreeViewClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaContainerTreeViewClass *tree_view_class; PikaItemTreeViewClass *item_view_class; GtkWidgetClass *widget_class; widget_class = GTK_WIDGET_CLASS (klass); tree_view_class = PIKA_CONTAINER_TREE_VIEW_CLASS (klass); item_view_class = PIKA_ITEM_TREE_VIEW_CLASS (klass); object_class->constructed = pika_layer_tree_view_constructed; object_class->finalize = pika_layer_tree_view_finalize; widget_class->style_updated = pika_layer_tree_view_style_updated; tree_view_class->drop_possible = pika_layer_tree_view_drop_possible; tree_view_class->drop_color = pika_layer_tree_view_drop_color; tree_view_class->drop_uri_list = pika_layer_tree_view_drop_uri_list; tree_view_class->drop_component = pika_layer_tree_view_drop_component; tree_view_class->drop_pixbuf = pika_layer_tree_view_drop_pixbuf; item_view_class->item_type = PIKA_TYPE_LAYER; item_view_class->signal_name = "selected-layers-changed"; item_view_class->set_image = pika_layer_tree_view_set_image; item_view_class->get_container = pika_image_get_layers; item_view_class->get_selected_items = (PikaGetItemsFunc) pika_image_get_selected_layers; item_view_class->set_selected_items = (PikaSetItemsFunc) pika_image_set_selected_layers; item_view_class->add_item = (PikaAddItemFunc) pika_image_add_layer; item_view_class->remove_item = (PikaRemoveItemFunc) pika_image_remove_layer; item_view_class->new_item = pika_layer_tree_view_item_new; item_view_class->action_group = "layers"; item_view_class->activate_action = "layers-edit"; item_view_class->new_action = "layers-new"; item_view_class->new_default_action = "layers-new-last-values"; item_view_class->raise_action = "layers-raise"; item_view_class->raise_top_action = "layers-raise-to-top"; item_view_class->lower_action = "layers-lower"; item_view_class->lower_bottom_action = "layers-lower-to-bottom"; item_view_class->duplicate_action = "layers-duplicate"; item_view_class->delete_action = "layers-delete"; item_view_class->lock_content_help_id = PIKA_HELP_LAYER_LOCK_PIXELS; item_view_class->lock_position_help_id = PIKA_HELP_LAYER_LOCK_POSITION; } static void pika_layer_tree_view_view_iface_init (PikaContainerViewInterface *iface) { parent_view_iface = g_type_interface_peek_parent (iface); iface->set_container = pika_layer_tree_view_set_container; iface->set_context = pika_layer_tree_view_set_context; iface->insert_item = pika_layer_tree_view_insert_item; iface->select_items = pika_layer_tree_view_select_items; iface->set_view_size = pika_layer_tree_view_set_view_size; iface->model_is_tree = TRUE; } static void pika_layer_tree_view_init (PikaLayerTreeView *view) { PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (view); GtkWidget *scale; PangoAttribute *attr; view->priv = pika_layer_tree_view_get_instance_private (view); view->priv->link_pattern_set = NULL; view->priv->model_column_mask = pika_container_tree_store_columns_add (tree_view->model_columns, &tree_view->n_model_columns, PIKA_TYPE_VIEW_RENDERER); view->priv->model_column_mask_visible = pika_container_tree_store_columns_add (tree_view->model_columns, &tree_view->n_model_columns, G_TYPE_BOOLEAN); /* Paint mode menu */ view->priv->layer_mode_box = pika_layer_mode_box_new (PIKA_LAYER_MODE_CONTEXT_LAYER); pika_layer_mode_box_set_label (PIKA_LAYER_MODE_BOX (view->priv->layer_mode_box), _("Mode")); pika_item_tree_view_add_options (PIKA_ITEM_TREE_VIEW (view), NULL, view->priv->layer_mode_box); g_signal_connect (view->priv->layer_mode_box, "notify::layer-mode", G_CALLBACK (pika_layer_tree_view_layer_mode_box_callback), view); pika_help_set_help_data (view->priv->layer_mode_box, NULL, PIKA_HELP_LAYER_DIALOG_PAINT_MODE_MENU); /* Opacity scale */ view->priv->opacity_adjustment = gtk_adjustment_new (100.0, 0.0, 100.0, 1.0, 10.0, 0.0); scale = pika_spin_scale_new (view->priv->opacity_adjustment, _("Opacity"), 1); pika_spin_scale_set_constrain_drag (PIKA_SPIN_SCALE (scale), TRUE); pika_help_set_help_data (scale, NULL, PIKA_HELP_LAYER_DIALOG_OPACITY_SCALE); pika_item_tree_view_add_options (PIKA_ITEM_TREE_VIEW (view), NULL, scale); g_signal_connect (view->priv->opacity_adjustment, "value-changed", G_CALLBACK (pika_layer_tree_view_opacity_scale_changed), view); view->priv->italic_attrs = pango_attr_list_new (); attr = pango_attr_style_new (PANGO_STYLE_ITALIC); attr->start_index = 0; attr->end_index = -1; pango_attr_list_insert (view->priv->italic_attrs, attr); view->priv->bold_attrs = pango_attr_list_new (); attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD); attr->start_index = 0; attr->end_index = -1; pango_attr_list_insert (view->priv->bold_attrs, attr); } static void pika_layer_tree_view_constructed (GObject *object) { PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (object); PikaLayerTreeView *layer_view = PIKA_LAYER_TREE_VIEW (object); GtkWidget *button; GtkWidget *placeholder; GtkWidget *grid; PangoAttrList *attrs; GtkIconSize button_size; G_OBJECT_CLASS (parent_class)->constructed (object); layer_view->priv->mask_cell = pika_cell_renderer_viewable_new (); gtk_tree_view_column_pack_start (tree_view->main_column, layer_view->priv->mask_cell, FALSE); gtk_tree_view_column_set_attributes (tree_view->main_column, layer_view->priv->mask_cell, "renderer", layer_view->priv->model_column_mask, "visible", layer_view->priv->model_column_mask_visible, NULL); pika_container_tree_view_add_renderer_cell (tree_view, layer_view->priv->mask_cell, layer_view->priv->model_column_mask); g_signal_connect (tree_view->renderer_cell, "clicked", G_CALLBACK (pika_layer_tree_view_layer_clicked), layer_view); g_signal_connect (layer_view->priv->mask_cell, "clicked", G_CALLBACK (pika_layer_tree_view_mask_clicked), layer_view); pika_dnd_component_dest_add (GTK_WIDGET (tree_view->view), NULL, tree_view); pika_dnd_viewable_dest_add (GTK_WIDGET (tree_view->view), PIKA_TYPE_CHANNEL, NULL, tree_view); pika_dnd_viewable_dest_add (GTK_WIDGET (tree_view->view), PIKA_TYPE_LAYER_MASK, NULL, tree_view); pika_dnd_uri_list_dest_add (GTK_WIDGET (tree_view->view), NULL, tree_view); pika_dnd_pixbuf_dest_add (GTK_WIDGET (tree_view->view), NULL, tree_view); button = pika_editor_add_action_button (PIKA_EDITOR (layer_view), "layers", "layers-new-group", NULL); gtk_box_reorder_child (pika_editor_get_button_box (PIKA_EDITOR (layer_view)), button, 1); button = pika_editor_add_action_button (PIKA_EDITOR (layer_view), "layers", "layers-anchor", NULL); layer_view->priv->anchor_button = button; pika_container_view_enable_dnd (PIKA_CONTAINER_VIEW (layer_view), GTK_BUTTON (button), PIKA_TYPE_LAYER); gtk_box_reorder_child (pika_editor_get_button_box (PIKA_EDITOR (layer_view)), button, 5); button = pika_editor_add_action_button (PIKA_EDITOR (layer_view), "layers", "layers-merge-down-button", "layers-merge-group", GDK_SHIFT_MASK, "layers-merge-layers", GDK_CONTROL_MASK, "layers-merge-layers-last-values", GDK_CONTROL_MASK | GDK_SHIFT_MASK, NULL); pika_container_view_enable_dnd (PIKA_CONTAINER_VIEW (layer_view), GTK_BUTTON (button), PIKA_TYPE_LAYER); gtk_box_reorder_child (pika_editor_get_button_box (PIKA_EDITOR (layer_view)), button, 6); button = pika_editor_add_action_button (PIKA_EDITOR (layer_view), "layers", "layers-mask-add-button", "layers-mask-add-last-values", pika_get_extend_selection_mask (), "layers-mask-delete", pika_get_modify_selection_mask (), "layers-mask-apply", pika_get_extend_selection_mask () | pika_get_modify_selection_mask (), NULL); pika_container_view_enable_dnd (PIKA_CONTAINER_VIEW (layer_view), GTK_BUTTON (button), PIKA_TYPE_LAYER); gtk_box_reorder_child (pika_editor_get_button_box (PIKA_EDITOR (layer_view)), button, 7); /* Link popover menu. */ layer_view->priv->link_button = gtk_menu_button_new (); gtk_widget_set_tooltip_text (layer_view->priv->link_button, _("Select layers by patterns and store layer sets")); gtk_widget_style_get (GTK_WIDGET (layer_view), "button-icon-size", &button_size, NULL); gtk_button_set_image (GTK_BUTTON (layer_view->priv->link_button), gtk_image_new_from_icon_name (PIKA_ICON_LINKED, button_size)); gtk_box_pack_start (pika_editor_get_button_box (PIKA_EDITOR (layer_view)), layer_view->priv->link_button, TRUE, TRUE, 0); gtk_widget_show (layer_view->priv->link_button); pika_container_view_enable_dnd (PIKA_CONTAINER_VIEW (layer_view), GTK_BUTTON (layer_view->priv->link_button), PIKA_TYPE_LAYER); gtk_box_reorder_child (pika_editor_get_button_box (PIKA_EDITOR (layer_view)), layer_view->priv->link_button, 8); layer_view->priv->link_popover = gtk_popover_new (layer_view->priv->link_button); gtk_popover_set_modal (GTK_POPOVER (layer_view->priv->link_popover), TRUE); gtk_menu_button_set_popover (GTK_MENU_BUTTON (layer_view->priv->link_button), layer_view->priv->link_popover); g_signal_connect (layer_view->priv->link_popover, "show", G_CALLBACK (pika_layer_tree_view_link_popover_shown), layer_view); grid = gtk_grid_new (); /* Link popover: regexp search. */ layer_view->priv->link_search_entry = gtk_entry_new (); gtk_entry_set_icon_from_icon_name (GTK_ENTRY (layer_view->priv->link_search_entry), GTK_ENTRY_ICON_SECONDARY, "system-search"); gtk_grid_attach (GTK_GRID (grid), layer_view->priv->link_search_entry, 0, 0, 2, 1); gtk_widget_show (layer_view->priv->link_search_entry); g_signal_connect (layer_view->priv->link_search_entry, "key-release-event", G_CALLBACK (pika_layer_tree_view_search_key_release), layer_view); g_signal_connect (PIKA_CONTAINER_TREE_VIEW (layer_view)->view, "start-interactive-search", G_CALLBACK (pika_layer_tree_view_start_interactive_search), layer_view); gtk_tree_view_set_search_entry (PIKA_CONTAINER_TREE_VIEW (layer_view)->view, GTK_ENTRY (layer_view->priv->link_search_entry)); /* Link popover: existing links. */ layer_view->priv->link_list = gtk_list_box_new (); placeholder = gtk_label_new (_("No layer set stored")); attrs = pango_attr_list_new (); gtk_label_set_attributes (GTK_LABEL (placeholder), attrs); pango_attr_list_insert (attrs, pango_attr_style_new (PANGO_STYLE_ITALIC)); pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_ULTRALIGHT)); pango_attr_list_unref (attrs); gtk_list_box_set_placeholder (GTK_LIST_BOX (layer_view->priv->link_list), placeholder); gtk_widget_show (placeholder); gtk_grid_attach (GTK_GRID (grid), layer_view->priv->link_list, 0, 1, 2, 1); gtk_widget_show (layer_view->priv->link_list); gtk_list_box_set_activate_on_single_click (GTK_LIST_BOX (layer_view->priv->link_list), TRUE); /* Link popover: new links. */ layer_view->priv->link_entry = gtk_entry_new (); gtk_entry_set_placeholder_text (GTK_ENTRY (layer_view->priv->link_entry), _("New layer set's name")); gtk_grid_attach (GTK_GRID (grid), layer_view->priv->link_entry, 0, 2, 1, 1); gtk_widget_show (layer_view->priv->link_entry); button = gtk_button_new_from_icon_name (PIKA_ICON_LIST_ADD, button_size); gtk_grid_attach (GTK_GRID (grid), button, 1, 2, 1, 1); g_signal_connect_swapped (button, "clicked", G_CALLBACK (pika_layer_tree_view_new_link_clicked), layer_view); gtk_widget_show (button); layer_view->priv->new_link_button = button; /* Enter on any entry activates the link creation then exits in case * of success. */ g_signal_connect_swapped (layer_view->priv->link_entry, "activate", G_CALLBACK (pika_layer_tree_view_new_link_exit), layer_view); gtk_container_add (GTK_CONTAINER (layer_view->priv->link_popover), grid); gtk_widget_show (grid); /* Lock alpha toggle */ pika_item_tree_view_add_lock (PIKA_ITEM_TREE_VIEW (tree_view), PIKA_ICON_LOCK_ALPHA, (PikaIsLockedFunc) pika_layer_get_lock_alpha, (PikaCanLockFunc) pika_layer_can_lock_alpha, (PikaSetLockFunc) pika_layer_set_lock_alpha, (PikaUndoLockPush) pika_image_undo_push_layer_lock_alpha, "lock-alpha-changed", PIKA_UNDO_LAYER_LOCK_ALPHA, PIKA_UNDO_GROUP_LAYER_LOCK_ALPHA, _("Lock alpha channel"), _("Unlock alpha channel"), _("Set Item Exclusive Alpha Channel lock"), _("Lock alpha channel"), PIKA_HELP_LAYER_LOCK_ALPHA); } static void pika_layer_tree_view_finalize (GObject *object) { PikaLayerTreeView *layer_view = PIKA_LAYER_TREE_VIEW (object); if (layer_view->priv->italic_attrs) { pango_attr_list_unref (layer_view->priv->italic_attrs); layer_view->priv->italic_attrs = NULL; } if (layer_view->priv->bold_attrs) { pango_attr_list_unref (layer_view->priv->bold_attrs); layer_view->priv->bold_attrs = NULL; } G_OBJECT_CLASS (parent_class)->finalize (object); } /* PikaWidget methods */ static void pika_layer_tree_view_style_updated (GtkWidget *widget) { PikaLayerTreeView *view = PIKA_LAYER_TREE_VIEW (widget); GtkWidget *image; const gchar *old_icon_name; GtkReliefStyle button_relief; GtkIconSize old_size; GtkIconSize button_size; gtk_widget_style_get (widget, "button-relief", &button_relief, "button-icon-size", &button_size, NULL); gtk_button_set_relief (GTK_BUTTON (view->priv->link_button), button_relief); image = gtk_button_get_image (GTK_BUTTON (view->priv->link_button)); gtk_image_get_icon_name (GTK_IMAGE (image), &old_icon_name, &old_size); if (button_size != old_size) { gchar *icon_name; /* Changing the link button in dockable button box. */ icon_name = g_strdup (old_icon_name); gtk_image_set_from_icon_name (GTK_IMAGE (image), icon_name, button_size); g_free (icon_name); /* Changing the new link button inside the popover. */ image = gtk_button_get_image (GTK_BUTTON (view->priv->new_link_button)); gtk_image_get_icon_name (GTK_IMAGE (image), &old_icon_name, &old_size); icon_name = g_strdup (old_icon_name); gtk_image_set_from_icon_name (GTK_IMAGE (image), icon_name, button_size); g_free (icon_name); } } /* PikaContainerView methods */ static void pika_layer_tree_view_set_container (PikaContainerView *view, PikaContainer *container) { PikaLayerTreeView *layer_view = PIKA_LAYER_TREE_VIEW (view); PikaContainer *old_container; old_container = pika_container_view_get_container (view); if (old_container) { pika_tree_handler_disconnect (layer_view->priv->mode_changed_handler); layer_view->priv->mode_changed_handler = NULL; pika_tree_handler_disconnect (layer_view->priv->opacity_changed_handler); layer_view->priv->opacity_changed_handler = NULL; pika_tree_handler_disconnect (layer_view->priv->mask_changed_handler); layer_view->priv->mask_changed_handler = NULL; pika_tree_handler_disconnect (layer_view->priv->alpha_changed_handler); layer_view->priv->alpha_changed_handler = NULL; } parent_view_iface->set_container (view, container); if (container) { layer_view->priv->mode_changed_handler = pika_tree_handler_connect (container, "mode-changed", G_CALLBACK (pika_layer_tree_view_layer_signal_handler), view); layer_view->priv->opacity_changed_handler = pika_tree_handler_connect (container, "opacity-changed", G_CALLBACK (pika_layer_tree_view_layer_signal_handler), view); layer_view->priv->mask_changed_handler = pika_tree_handler_connect (container, "mask-changed", G_CALLBACK (pika_layer_tree_view_mask_changed), view); layer_view->priv->alpha_changed_handler = pika_tree_handler_connect (container, "alpha-changed", G_CALLBACK (pika_layer_tree_view_alpha_changed), view); } } typedef struct { gint mask_column; PikaContext *context; } SetContextForeachData; static gboolean pika_layer_tree_view_set_context_foreach (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { SetContextForeachData *context_data = data; PikaViewRenderer *renderer; gtk_tree_model_get (model, iter, context_data->mask_column, &renderer, -1); if (renderer) { pika_view_renderer_set_context (renderer, context_data->context); g_object_unref (renderer); } return FALSE; } static void pika_layer_tree_view_set_context (PikaContainerView *view, PikaContext *context) { PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (view); PikaLayerTreeView *layer_view = PIKA_LAYER_TREE_VIEW (view); PikaContext *old_context; old_context = pika_container_view_get_context (view); if (old_context) { g_signal_handlers_disconnect_by_func (old_context->pika->config, G_CALLBACK (pika_layer_tree_view_style_updated), view); } parent_view_iface->set_context (view, context); if (context) { g_signal_connect_object (context->pika->config, "notify::theme", G_CALLBACK (pika_layer_tree_view_style_updated), view, G_CONNECT_AFTER | G_CONNECT_SWAPPED); g_signal_connect_object (context->pika->config, "notify::override-theme-icon-size", G_CALLBACK (pika_layer_tree_view_style_updated), view, G_CONNECT_AFTER | G_CONNECT_SWAPPED); g_signal_connect_object (context->pika->config, "notify::custom-icon-size", G_CALLBACK (pika_layer_tree_view_style_updated), view, G_CONNECT_AFTER | G_CONNECT_SWAPPED); } if (tree_view->model) { SetContextForeachData context_data = { layer_view->priv->model_column_mask, context }; gtk_tree_model_foreach (tree_view->model, pika_layer_tree_view_set_context_foreach, &context_data); } } static gpointer pika_layer_tree_view_insert_item (PikaContainerView *view, PikaViewable *viewable, gpointer parent_insert_data, gint index) { PikaLayerTreeView *layer_view = PIKA_LAYER_TREE_VIEW (view); PikaLayer *layer; GtkTreeIter *iter; iter = parent_view_iface->insert_item (view, viewable, parent_insert_data, index); layer = PIKA_LAYER (viewable); if (! pika_drawable_has_alpha (PIKA_DRAWABLE (layer))) pika_layer_tree_view_alpha_update (layer_view, iter, layer); pika_layer_tree_view_mask_update (layer_view, iter, layer); if (PIKA_IS_LAYER (viewable) && pika_layer_is_floating_sel (PIKA_LAYER (viewable))) { PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (view); PikaLayer *floating = PIKA_LAYER (viewable); PikaDrawable *drawable = pika_layer_get_floating_sel_drawable (floating); if (PIKA_IS_LAYER_MASK (drawable)) { /* Display floating mask in the mask column. */ PikaViewRenderer *renderer = NULL; gtk_tree_model_get (GTK_TREE_MODEL (tree_view->model), iter, PIKA_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, -1); if (renderer) { gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), iter, layer_view->priv->model_column_mask, renderer, layer_view->priv->model_column_mask_visible, TRUE, PIKA_CONTAINER_TREE_STORE_COLUMN_RENDERER, NULL, -1); g_object_unref (renderer); } } } return iter; } static gboolean pika_layer_tree_view_select_items (PikaContainerView *view, GList *items, GList *paths) { PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (view); PikaLayerTreeView *layer_view = PIKA_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) { if (g_list_length (items) == 1) { GtkTreeIter *iter = NULL; iter = pika_container_view_lookup (PIKA_CONTAINER_VIEW (layer_view), PIKA_VIEWABLE (layers->data)); if (iter) pika_layer_tree_view_update_borders (layer_view, iter); } else { 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); pika_layer_tree_view_update_borders (layer_view, &iter); } } pika_layer_tree_view_update_options (layer_view, items); pika_layer_tree_view_update_menu (layer_view, items); } } if (! success) { PikaEditor *editor = PIKA_EDITOR (view); /* currently, select_items() only ever fails when there is a floating * selection, which can be committed/canceled through the editor buttons. */ pika_widget_blink (GTK_WIDGET (pika_editor_get_button_box (editor))); } return success; } typedef struct { gint mask_column; gint view_size; gint border_width; } SetSizeForeachData; static gboolean pika_layer_tree_view_set_view_size_foreach (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { SetSizeForeachData *size_data = data; PikaViewRenderer *renderer; gtk_tree_model_get (model, iter, size_data->mask_column, &renderer, -1); if (renderer) { pika_view_renderer_set_size (renderer, size_data->view_size, size_data->border_width); g_object_unref (renderer); } return FALSE; } static void pika_layer_tree_view_set_view_size (PikaContainerView *view) { PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (view); if (tree_view->model) { PikaLayerTreeView *layer_view = PIKA_LAYER_TREE_VIEW (view); SetSizeForeachData size_data; size_data.mask_column = layer_view->priv->model_column_mask; size_data.view_size = pika_container_view_get_view_size (view, &size_data.border_width); gtk_tree_model_foreach (tree_view->model, pika_layer_tree_view_set_view_size_foreach, &size_data); } parent_view_iface->set_view_size (view); } /* PikaContainerTreeView methods */ static gboolean pika_layer_tree_view_drop_possible (PikaContainerTreeView *tree_view, PikaDndType src_type, GList *src_viewables, PikaViewable *dest_viewable, GtkTreePath *drop_path, GtkTreeViewDropPosition drop_pos, GtkTreeViewDropPosition *return_drop_pos, GdkDragAction *return_drag_action) { /* If we are dropping a new layer, check if the destination image * has a floating selection. */ if (src_type == PIKA_DND_TYPE_URI_LIST || src_type == PIKA_DND_TYPE_TEXT_PLAIN || src_type == PIKA_DND_TYPE_NETSCAPE_URL || src_type == PIKA_DND_TYPE_COMPONENT || src_type == PIKA_DND_TYPE_PIXBUF || g_list_length (src_viewables) > 0) { PikaImage *dest_image = pika_item_tree_view_get_image (PIKA_ITEM_TREE_VIEW (tree_view)); if (pika_image_get_floating_selection (dest_image)) return FALSE; } return PIKA_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); } static void pika_layer_tree_view_drop_color (PikaContainerTreeView *view, const PikaRGB *color, PikaViewable *dest_viewable, GtkTreeViewDropPosition drop_pos) { if (pika_item_is_text_layer (PIKA_ITEM (dest_viewable))) { pika_text_layer_set (PIKA_TEXT_LAYER (dest_viewable), NULL, "color", color, NULL); pika_image_flush (pika_item_tree_view_get_image (PIKA_ITEM_TREE_VIEW (view))); return; } PIKA_CONTAINER_TREE_VIEW_CLASS (parent_class)->drop_color (view, color, dest_viewable, drop_pos); } static void pika_layer_tree_view_drop_uri_list (PikaContainerTreeView *view, GList *uri_list, PikaViewable *dest_viewable, GtkTreeViewDropPosition drop_pos) { PikaItemTreeView *item_view = PIKA_ITEM_TREE_VIEW (view); PikaContainerView *cont_view = PIKA_CONTAINER_VIEW (view); PikaImage *image = pika_item_tree_view_get_image (item_view); PikaLayer *parent; gint index; GList *list; index = pika_item_tree_view_get_drop_index (item_view, dest_viewable, drop_pos, (PikaViewable **) &parent); g_object_ref (image); for (list = uri_list; list; list = g_list_next (list)) { const gchar *uri = list->data; GFile *file = g_file_new_for_uri (uri); GList *new_layers; PikaPDBStatusType status; GError *error = NULL; new_layers = file_open_layers (image->pika, pika_container_view_get_context (cont_view), NULL, image, FALSE, file, PIKA_RUN_INTERACTIVE, NULL, &status, &error); if (new_layers) { pika_image_add_layers (image, new_layers, parent, index, 0, 0, pika_image_get_width (image), pika_image_get_height (image), _("Drop layers")); index += g_list_length (new_layers); g_list_free (new_layers); } else if (status != PIKA_PDB_CANCEL) { pika_message (image->pika, G_OBJECT (view), PIKA_MESSAGE_ERROR, _("Opening '%s' failed:\n\n%s"), pika_file_get_utf8_name (file), error->message); g_clear_error (&error); } g_object_unref (file); } pika_image_flush (image); g_object_unref (image); } static void pika_layer_tree_view_drop_component (PikaContainerTreeView *tree_view, PikaImage *src_image, PikaChannelType component, PikaViewable *dest_viewable, GtkTreeViewDropPosition drop_pos) { PikaItemTreeView *item_view = PIKA_ITEM_TREE_VIEW (tree_view); PikaImage *image = pika_item_tree_view_get_image (item_view); PikaChannel *channel; PikaItem *new_item; PikaLayer *parent; gint index; const gchar *desc; index = pika_item_tree_view_get_drop_index (item_view, dest_viewable, drop_pos, (PikaViewable **) &parent); channel = pika_channel_new_from_component (src_image, component, NULL, NULL); new_item = pika_item_convert (PIKA_ITEM (channel), image, PIKA_TYPE_LAYER); g_object_unref (channel); pika_enum_get_value (PIKA_TYPE_CHANNEL_TYPE, component, NULL, NULL, &desc, NULL); pika_object_take_name (PIKA_OBJECT (new_item), g_strdup_printf (_("%s Channel Copy"), desc)); pika_image_add_layer (image, PIKA_LAYER (new_item), parent, index, TRUE); pika_image_flush (image); } static void pika_layer_tree_view_drop_pixbuf (PikaContainerTreeView *tree_view, GdkPixbuf *pixbuf, PikaViewable *dest_viewable, GtkTreeViewDropPosition drop_pos) { PikaItemTreeView *item_view = PIKA_ITEM_TREE_VIEW (tree_view); PikaImage *image = pika_item_tree_view_get_image (item_view); PikaLayer *new_layer; PikaLayer *parent; gint index; index = pika_item_tree_view_get_drop_index (item_view, dest_viewable, drop_pos, (PikaViewable **) &parent); new_layer = pika_layer_new_from_pixbuf (pixbuf, image, pika_image_get_layer_format (image, TRUE), _("Dropped Buffer"), PIKA_OPACITY_OPAQUE, pika_image_get_default_new_layer_mode (image)); pika_image_add_layer (image, new_layer, parent, index, TRUE); pika_image_flush (image); } /* PikaItemTreeView methods */ static void pika_layer_tree_view_set_image (PikaItemTreeView *view, PikaImage *image) { PikaLayerTreeView *layer_view = PIKA_LAYER_TREE_VIEW (view); if (pika_item_tree_view_get_image (view)) { g_signal_handlers_disconnect_by_func (pika_item_tree_view_get_image (view), pika_layer_tree_view_floating_selection_changed, view); g_signal_handlers_disconnect_by_func (pika_item_tree_view_get_image (view), G_CALLBACK (pika_layer_tree_view_layer_links_changed), view); } PIKA_ITEM_TREE_VIEW_CLASS (parent_class)->set_image (view, image); if (pika_item_tree_view_get_image (view)) { g_signal_connect (pika_item_tree_view_get_image (view), "floating-selection-changed", G_CALLBACK (pika_layer_tree_view_floating_selection_changed), view); g_signal_connect (pika_item_tree_view_get_image (view), "layer-sets-changed", G_CALLBACK (pika_layer_tree_view_layer_links_changed), view); /* call pika_layer_tree_view_floating_selection_changed() now, to update * the floating selection's row attributes. */ pika_layer_tree_view_floating_selection_changed ( pika_item_tree_view_get_image (view), layer_view); } /* Call this even with no image, allowing to empty the link list. */ pika_layer_tree_view_layer_links_changed (pika_item_tree_view_get_image (view), layer_view); pika_layer_tree_view_update_highlight (layer_view); } static PikaItem * pika_layer_tree_view_item_new (PikaImage *image) { PikaLayer *new_layer; pika_image_undo_group_start (image, PIKA_UNDO_GROUP_EDIT_PASTE, _("New Layer")); new_layer = pika_layer_new (image, pika_image_get_width (image), pika_image_get_height (image), pika_image_get_layer_format (image, TRUE), NULL, PIKA_OPACITY_OPAQUE, pika_image_get_default_new_layer_mode (image)); pika_image_add_layer (image, new_layer, PIKA_IMAGE_ACTIVE_PARENT, -1, TRUE); pika_image_undo_group_end (image); return PIKA_ITEM (new_layer); } /* callbacks */ static void pika_layer_tree_view_floating_selection_changed (PikaImage *image, PikaLayerTreeView *layer_view) { PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (layer_view); PikaContainerView *view = PIKA_CONTAINER_VIEW (layer_view); PikaLayer *floating_sel; GtkTreeIter *iter; floating_sel = pika_image_get_floating_selection (image); if (floating_sel) { iter = pika_container_view_lookup (view, (PikaViewable *) floating_sel); if (iter) gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), iter, PIKA_CONTAINER_TREE_STORE_COLUMN_NAME_ATTRIBUTES, layer_view->priv->italic_attrs, -1); } else { GList *all_layers; GList *list; all_layers = pika_image_get_layer_list (image); for (list = all_layers; list; list = g_list_next (list)) { PikaDrawable *drawable = list->data; iter = pika_container_view_lookup (view, (PikaViewable *) drawable); if (iter) gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), iter, PIKA_CONTAINER_TREE_STORE_COLUMN_NAME_ATTRIBUTES, pika_drawable_has_alpha (drawable) ? NULL : layer_view->priv->bold_attrs, -1); } g_list_free (all_layers); } gtk_widget_set_sensitive (layer_view->priv->link_button, ! floating_sel); pika_layer_tree_view_update_highlight (layer_view); } static void pika_layer_tree_view_layer_links_changed (PikaImage *image, PikaLayerTreeView *view) { GtkWidget *grid; GtkWidget *label; GtkWidget *event_box; GtkWidget *icon; GtkSizeGroup *label_size; GList *links; GList *iter; gtk_container_foreach (GTK_CONTAINER (view->priv->link_list), (GtkCallback) gtk_widget_destroy, NULL); gtk_widget_set_sensitive (view->priv->link_button, image != NULL); if (! image) return; links = pika_image_get_stored_item_sets (image, PIKA_TYPE_LAYER); label_size = gtk_size_group_new (GTK_SIZE_GROUP_BOTH); for (iter = links; iter; iter = iter->next) { PikaSelectMethod method; grid = gtk_grid_new (); label = gtk_label_new (pika_object_get_name (iter->data)); gtk_label_set_xalign (GTK_LABEL (label), 0.0); if (pika_item_list_is_pattern (iter->data, &method)) { gchar *display_name; PangoAttrList *attrs; display_name = g_strdup_printf ("[%s] %s", method == PIKA_SELECT_PLAIN_TEXT ? _("search") : (method == PIKA_SELECT_GLOB_PATTERN ? _("glob") : _("regexp")), pika_object_get_name (iter->data)); gtk_label_set_markup (GTK_LABEL (label), display_name); g_free (display_name); attrs = pango_attr_list_new (); pango_attr_list_insert (attrs, pango_attr_style_new (PANGO_STYLE_OBLIQUE)); gtk_label_set_attributes (GTK_LABEL (label), attrs); pango_attr_list_unref (attrs); } gtk_widget_set_hexpand (GTK_WIDGET (label), TRUE); gtk_widget_set_halign (GTK_WIDGET (label), GTK_ALIGN_START); gtk_size_group_add_widget (label_size, label); gtk_grid_attach (GTK_GRID (grid), label, 0, 1, 1, 1); gtk_widget_show (label); /* I don't use a GtkButton because the minimum size is 16 which is * weird and ugly here. And somehow if I force smaller GtkImage * size then add it to the GtkButton, I still get a giant button * with a small image in it, which is even worse. XXX */ event_box = gtk_event_box_new (); gtk_event_box_set_above_child (GTK_EVENT_BOX (event_box), TRUE); gtk_widget_add_events (event_box, GDK_BUTTON_RELEASE_MASK); g_object_set_data (G_OBJECT (event_box), "link-set", iter->data); g_signal_connect (event_box, "button-release-event", G_CALLBACK (pika_layer_tree_view_unlink_clicked), view); gtk_grid_attach (GTK_GRID (grid), event_box, 2, 0, 1, 1); gtk_widget_show (event_box); icon = gtk_image_new_from_icon_name (PIKA_ICON_EDIT_DELETE, GTK_ICON_SIZE_MENU); gtk_image_set_pixel_size (GTK_IMAGE (icon), 10); gtk_container_add (GTK_CONTAINER (event_box), icon); gtk_widget_show (icon); /* Now using again an event box on the whole grid, but behind its * child (so that the delete button is processed first. I do it * this way instead of using the "row-activated" of GtkListBox * because this signal does not give us event info, and in * particular modifier state. Yet I want to be able to process * Shift/Ctrl state for logical operations on layer sets. */ event_box = gtk_event_box_new (); gtk_event_box_set_above_child (GTK_EVENT_BOX (event_box), FALSE); gtk_widget_add_events (event_box, GDK_BUTTON_RELEASE_MASK); g_object_set_data (G_OBJECT (event_box), "link-set", iter->data); gtk_container_add (GTK_CONTAINER (event_box), grid); gtk_list_box_prepend (GTK_LIST_BOX (view->priv->link_list), event_box); gtk_widget_show (event_box); g_signal_connect (event_box, "button-release-event", G_CALLBACK (pika_layer_tree_view_link_clicked), view); gtk_widget_show (grid); } g_object_unref (label_size); gtk_list_box_unselect_all (GTK_LIST_BOX (view->priv->link_list)); } static gboolean pika_layer_tree_view_link_clicked (GtkWidget *box, GdkEvent *event, PikaLayerTreeView *view) { PikaImage *image; GdkEventButton *bevent = (GdkEventButton *) event; GdkModifierType modifiers; image = pika_item_tree_view_get_image (PIKA_ITEM_TREE_VIEW (view)); g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); g_return_val_if_fail (GTK_IS_EVENT_BOX (box), FALSE); modifiers = bevent->state & pika_get_all_modifiers_mask (); if (modifiers == GDK_SHIFT_MASK) pika_image_add_item_set (image, g_object_get_data (G_OBJECT (box), "link-set")); else if (modifiers == GDK_CONTROL_MASK) pika_image_remove_item_set (image, g_object_get_data (G_OBJECT (box), "link-set")); else if (modifiers == (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) pika_image_intersect_item_set (image, g_object_get_data (G_OBJECT (box), "link-set")); else pika_image_select_item_set (image, g_object_get_data (G_OBJECT (box), "link-set")); gtk_entry_set_text (GTK_ENTRY (view->priv->link_search_entry), ""); /* TODO: if clicking on pattern link, fill in the pattern field? */ return FALSE; } static void pika_layer_tree_view_link_popover_shown (GtkPopover *popover, PikaLayerTreeView *view) { PikaImage *image; PikaSelectMethod pattern_syntax; image = pika_item_tree_view_get_image (PIKA_ITEM_TREE_VIEW (view)); if (! image) { gtk_widget_set_tooltip_text (view->priv->link_search_entry, _("Select layers by text search")); gtk_entry_set_placeholder_text (GTK_ENTRY (view->priv->link_search_entry), _("Text search")); return; } g_object_get (image->pika->config, "items-select-method", &pattern_syntax, NULL); switch (pattern_syntax) { case PIKA_SELECT_PLAIN_TEXT: gtk_widget_set_tooltip_text (view->priv->link_search_entry, _("Select layers by text search")); gtk_entry_set_placeholder_text (GTK_ENTRY (view->priv->link_search_entry), _("Text search")); break; case PIKA_SELECT_GLOB_PATTERN: gtk_widget_set_tooltip_text (view->priv->link_search_entry, _("Select layers by glob patterns")); gtk_entry_set_placeholder_text (GTK_ENTRY (view->priv->link_search_entry), _("Glob pattern search")); break; case PIKA_SELECT_REGEX_PATTERN: gtk_widget_set_tooltip_text (view->priv->link_search_entry, _("Select layers by regular expressions")); gtk_entry_set_placeholder_text (GTK_ENTRY (view->priv->link_search_entry), _("Regular Expression search")); break; } } static gboolean pika_layer_tree_view_search_key_release (GtkWidget *widget, GdkEventKey *event, PikaLayerTreeView *view) { PikaImage *image; const gchar *pattern; PikaSelectMethod pattern_syntax; if (event->keyval == GDK_KEY_Escape || event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter || event->keyval == GDK_KEY_ISO_Enter) { if (event->state & GDK_SHIFT_MASK) { if (pika_layer_tree_view_new_link_clicked (view)) gtk_widget_hide (view->priv->link_popover); } else { gtk_widget_hide (view->priv->link_popover); } return TRUE; } gtk_entry_set_attributes (GTK_ENTRY (view->priv->link_search_entry), NULL); image = pika_item_tree_view_get_image (PIKA_ITEM_TREE_VIEW (view)); g_clear_object (&view->priv->link_pattern_set); if (! image) return TRUE; g_object_get (image->pika->config, "items-select-method", &pattern_syntax, NULL); pattern = gtk_entry_get_text (GTK_ENTRY (view->priv->link_search_entry)); if (pattern && strlen (pattern) > 0) { GList *items; GError *error = NULL; gtk_entry_set_text (GTK_ENTRY (view->priv->link_entry), ""); gtk_widget_set_sensitive (view->priv->link_entry, FALSE); view->priv->link_pattern_set = pika_item_list_pattern_new (image, PIKA_TYPE_LAYER, pattern_syntax, pattern); items = pika_item_list_get_items (view->priv->link_pattern_set, &error); if (error) { /* Invalid regular expression. */ PangoAttrList *attrs = pango_attr_list_new (); gchar *tooltip; pango_attr_list_insert (attrs, pango_attr_strikethrough_new (TRUE)); tooltip = g_strdup_printf (_("Invalid regular expression: %s\n"), error->message); gtk_widget_set_tooltip_text (view->priv->link_search_entry, tooltip); pika_image_set_selected_layers (image, NULL); gtk_entry_set_attributes (GTK_ENTRY (view->priv->link_search_entry), attrs); g_free (tooltip); g_error_free (error); pango_attr_list_unref (attrs); g_clear_object (&view->priv->link_pattern_set); } else if (items == NULL) { /* Pattern does not match any results. */ pika_image_set_selected_layers (image, NULL); pika_widget_blink (view->priv->link_search_entry); } else { pika_image_set_selected_layers (image, items); g_list_free (items); } } else { gtk_widget_set_sensitive (view->priv->link_entry, TRUE); } return TRUE; } static gboolean pika_layer_tree_view_start_interactive_search (GtkTreeView *tree_view, PikaLayerTreeView *layer_view) { gtk_widget_show (layer_view->priv->link_popover); gtk_widget_grab_focus (layer_view->priv->link_search_entry); return FALSE; } static void pika_layer_tree_view_new_link_exit (PikaLayerTreeView *view) { if (pika_layer_tree_view_new_link_clicked (view)) gtk_widget_hide (view->priv->link_popover); } static gboolean pika_layer_tree_view_new_link_clicked (PikaLayerTreeView *view) { PikaImage *image; const gchar *name; image = pika_item_tree_view_get_image (PIKA_ITEM_TREE_VIEW (view)); if (! image) return TRUE; name = gtk_entry_get_text (GTK_ENTRY (view->priv->link_entry)); if (name && strlen (name) > 0) { PikaItemList *set; set = pika_item_list_named_new (image, PIKA_TYPE_LAYER, name, NULL); if (set) { pika_image_store_item_set (image, set); gtk_entry_set_text (GTK_ENTRY (view->priv->link_entry), ""); } else { /* No existing selection. */ return FALSE; } } else if (view->priv->link_pattern_set != NULL) { pika_image_store_item_set (image, view->priv->link_pattern_set); view->priv->link_pattern_set = NULL; gtk_entry_set_text (GTK_ENTRY (view->priv->link_search_entry), ""); } else { pika_widget_blink (view->priv->link_entry); pika_widget_blink (view->priv->link_search_entry); return FALSE; } return TRUE; } static gboolean pika_layer_tree_view_unlink_clicked (GtkWidget *widget, GdkEvent *event, PikaLayerTreeView *view) { PikaImage *image; image = pika_item_tree_view_get_image (PIKA_ITEM_TREE_VIEW (view)); g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); pika_image_unlink_item_set (image, g_object_get_data (G_OBJECT (widget), "link-set")); return TRUE; } /* Paint Mode, Opacity and Lock alpha callbacks */ #define BLOCK() \ g_signal_handlers_block_by_func (layer, \ pika_layer_tree_view_layer_signal_handler, view) #define UNBLOCK() \ g_signal_handlers_unblock_by_func (layer, \ pika_layer_tree_view_layer_signal_handler, view) static void pika_layer_tree_view_layer_mode_box_callback (GtkWidget *widget, const GParamSpec *pspec, PikaLayerTreeView *view) { PikaImage *image; GList *layers = NULL; GList *iter; PikaUndo *undo; gboolean push_undo = TRUE; gint n_layers = 0; PikaLayerMode mode; image = pika_item_tree_view_get_image (PIKA_ITEM_TREE_VIEW (view)); mode = pika_layer_mode_box_get_mode (PIKA_LAYER_MODE_BOX (widget)); undo = pika_image_undo_can_compress (image, PIKA_TYPE_ITEM_UNDO, PIKA_UNDO_LAYER_MODE); if (image) layers = PIKA_ITEM_TREE_VIEW_GET_CLASS (view)->get_selected_items (image); for (iter = layers; iter; iter = iter->next) { if (pika_layer_get_mode (iter->data) != mode) { n_layers++; if (undo && PIKA_ITEM_UNDO (undo)->item == PIKA_ITEM (iter->data)) push_undo = FALSE; } } if (n_layers > 1) { /* Don't compress mode undos with more than 1 layer changed. */ push_undo = TRUE; pika_image_undo_group_start (image, PIKA_UNDO_GROUP_LAYER_OPACITY, _("Set layers mode")); } for (iter = layers; iter; iter = iter->next) { PikaLayer *layer = iter->data; if (pika_layer_get_mode (layer) != mode) { BLOCK(); pika_layer_set_mode (layer, (PikaLayerMode) mode, push_undo); UNBLOCK(); } } pika_image_flush (image); if (! push_undo) pika_undo_refresh_preview (undo, pika_container_view_get_context (PIKA_CONTAINER_VIEW (view))); if (n_layers > 1) pika_image_undo_group_end (image); } static void pika_layer_tree_view_opacity_scale_changed (GtkAdjustment *adjustment, PikaLayerTreeView *view) { PikaImage *image; GList *layers; GList *iter; PikaUndo *undo; gboolean push_undo = TRUE; gint n_layers = 0; gdouble opacity; image = pika_item_tree_view_get_image (PIKA_ITEM_TREE_VIEW (view)); layers = PIKA_ITEM_TREE_VIEW_GET_CLASS (view)->get_selected_items (image); undo = pika_image_undo_can_compress (image, PIKA_TYPE_ITEM_UNDO, PIKA_UNDO_LAYER_OPACITY); opacity = gtk_adjustment_get_value (adjustment) / 100.0; for (iter = layers; iter; iter = iter->next) { if (pika_layer_get_opacity (iter->data) != opacity) { n_layers++; if (undo && PIKA_ITEM_UNDO (undo)->item == PIKA_ITEM (iter->data)) push_undo = FALSE; } } if (n_layers > 1) { /* Don't compress opacity undos with more than 1 layer changed. */ push_undo = TRUE; pika_image_undo_group_start (image, PIKA_UNDO_GROUP_LAYER_OPACITY, _("Set layers opacity")); } for (iter = layers; iter; iter = iter->next) { PikaLayer *layer = iter->data; if (pika_layer_get_opacity (layer) != opacity) { BLOCK(); pika_layer_set_opacity (layer, opacity, push_undo); UNBLOCK(); } } pika_image_flush (image); if (! push_undo) pika_undo_refresh_preview (undo, pika_container_view_get_context (PIKA_CONTAINER_VIEW (view))); if (n_layers > 1) pika_image_undo_group_end (image); } #undef BLOCK #undef UNBLOCK static void pika_layer_tree_view_layer_signal_handler (PikaLayer *layer, PikaLayerTreeView *view) { PikaItemTreeView *item_view = PIKA_ITEM_TREE_VIEW (view); GList *selected_layers; selected_layers = PIKA_ITEM_TREE_VIEW_GET_CLASS (view)->get_selected_items (pika_item_tree_view_get_image (item_view)); if (g_list_find (selected_layers, layer)) pika_layer_tree_view_update_options (view, selected_layers); } #define BLOCK(object,function) \ g_signal_handlers_block_by_func ((object), (function), view) #define UNBLOCK(object,function) \ g_signal_handlers_unblock_by_func ((object), (function), view) static void pika_layer_tree_view_update_options (PikaLayerTreeView *view, GList *layers) { GList *iter; PikaLayerMode mode = PIKA_LAYER_MODE_SEPARATOR; PikaLayerModeContext context = 0; /*gboolean inconsistent_opacity = FALSE;*/ gboolean inconsistent_mode = FALSE; gdouble opacity = -1.0; for (iter = layers; iter; iter = iter->next) { #if 0 if (opacity != -1.0 && opacity != pika_layer_get_opacity (iter->data)) /* We don't really have a way to show an inconsistent * PikaSpinScale. This is currently unused. */ inconsistent_opacity = TRUE; #endif opacity = pika_layer_get_opacity (iter->data); if (pika_viewable_get_children (iter->data)) context |= PIKA_LAYER_MODE_CONTEXT_GROUP; else context |= PIKA_LAYER_MODE_CONTEXT_LAYER; if (mode != PIKA_LAYER_MODE_SEPARATOR && mode != pika_layer_get_mode (iter->data)) inconsistent_mode = TRUE; mode = pika_layer_get_mode (iter->data); } if (opacity == -1.0) opacity = 1.0; if (inconsistent_mode) mode = PIKA_LAYER_MODE_SEPARATOR; if (! context) context = PIKA_LAYER_MODE_CONTEXT_LAYER; BLOCK (view->priv->layer_mode_box, pika_layer_tree_view_layer_mode_box_callback); pika_layer_mode_box_set_context (PIKA_LAYER_MODE_BOX (view->priv->layer_mode_box), context); pika_layer_mode_box_set_mode (PIKA_LAYER_MODE_BOX (view->priv->layer_mode_box), mode); UNBLOCK (view->priv->layer_mode_box, pika_layer_tree_view_layer_mode_box_callback); if (opacity * 100.0 != gtk_adjustment_get_value (view->priv->opacity_adjustment)) { BLOCK (view->priv->opacity_adjustment, pika_layer_tree_view_opacity_scale_changed); gtk_adjustment_set_value (view->priv->opacity_adjustment, opacity * 100.0); UNBLOCK (view->priv->opacity_adjustment, pika_layer_tree_view_opacity_scale_changed); } } #undef BLOCK #undef UNBLOCK static void pika_layer_tree_view_update_menu (PikaLayerTreeView *layer_view, GList *layers) { PikaUIManager *ui_manager = pika_editor_get_ui_manager (PIKA_EDITOR (layer_view)); PikaActionGroup *group; GList *iter; gboolean have_masks = FALSE; gboolean all_masks_shown = TRUE; gboolean all_masks_disabled = TRUE; group = pika_ui_manager_get_action_group (ui_manager, "layers"); for (iter = layers; iter; iter = iter->next) { if (pika_layer_get_mask (iter->data)) { have_masks = TRUE; if (! pika_layer_get_show_mask (iter->data)) all_masks_shown = FALSE; if (pika_layer_get_apply_mask (iter->data)) all_masks_disabled = FALSE; } } pika_action_group_set_action_active (group, "layers-mask-show", have_masks && all_masks_shown); pika_action_group_set_action_active (group, "layers-mask-disable", have_masks && all_masks_disabled); /* Only one layer mask at a time can be edited. */ pika_action_group_set_action_active (group, "layers-mask-edit", g_list_length (layers) == 1 && pika_layer_get_mask (layers->data) && pika_layer_get_edit_mask (layers->data)); } static void pika_layer_tree_view_update_highlight (PikaLayerTreeView *layer_view) { PikaItemTreeView *item_view = PIKA_ITEM_TREE_VIEW (layer_view); PikaImage *image = pika_item_tree_view_get_image (item_view); PikaLayer *floating_sel = NULL; GtkReliefStyle default_relief; if (image) floating_sel = pika_image_get_floating_selection (image); gtk_widget_style_get (GTK_WIDGET (layer_view), "button-relief", &default_relief, NULL); pika_button_set_suggested (pika_item_tree_view_get_new_button (item_view), floating_sel && ! PIKA_IS_CHANNEL (pika_layer_get_floating_sel_drawable (floating_sel)), default_relief); pika_button_set_destructive (pika_item_tree_view_get_delete_button (item_view), floating_sel != NULL, default_relief); pika_button_set_suggested (layer_view->priv->anchor_button, floating_sel != NULL, default_relief); if (floating_sel != NULL) { if (PIKA_IS_LAYER_MASK (pika_layer_get_floating_sel_drawable (floating_sel))) gtk_widget_set_tooltip_text (layer_view->priv->anchor_button, C_("layers-action", "Anchor the floating mask")); else gtk_widget_set_tooltip_text (layer_view->priv->anchor_button, C_("layers-action", "Anchor the floating layer")); } else { gtk_widget_set_tooltip_text (layer_view->priv->anchor_button, C_("layers-action", "Anchor the floating layer or mask")); } } /* Layer Mask callbacks */ static void pika_layer_tree_view_mask_update (PikaLayerTreeView *layer_view, GtkTreeIter *iter, PikaLayer *layer) { PikaContainerView *view = PIKA_CONTAINER_VIEW (layer_view); PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (layer_view); PikaLayerMask *mask; PikaViewRenderer *renderer = NULL; gboolean mask_visible = FALSE; mask = pika_layer_get_mask (layer); if (mask) { GClosure *closure; gint view_size; gint border_width; view_size = pika_container_view_get_view_size (view, &border_width); mask_visible = TRUE; renderer = pika_view_renderer_new (pika_container_view_get_context (view), G_TYPE_FROM_INSTANCE (mask), view_size, border_width, FALSE); pika_view_renderer_set_viewable (renderer, PIKA_VIEWABLE (mask)); g_signal_connect (renderer, "update", G_CALLBACK (pika_layer_tree_view_renderer_update), layer_view); closure = g_cclosure_new (G_CALLBACK (pika_layer_tree_view_mask_callback), layer_view, NULL); g_object_watch_closure (G_OBJECT (renderer), closure); g_signal_connect_closure (layer, "apply-mask-changed", closure, FALSE); g_signal_connect_closure (layer, "edit-mask-changed", closure, FALSE); g_signal_connect_closure (layer, "show-mask-changed", closure, FALSE); } gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), iter, layer_view->priv->model_column_mask, renderer, layer_view->priv->model_column_mask_visible, mask_visible, -1); pika_layer_tree_view_update_borders (layer_view, iter); if (renderer) { pika_view_renderer_remove_idle (renderer); g_object_unref (renderer); } } static void pika_layer_tree_view_mask_changed (PikaLayer *layer, PikaLayerTreeView *layer_view) { PikaContainerView *view = PIKA_CONTAINER_VIEW (layer_view); GtkTreeIter *iter; iter = pika_container_view_lookup (view, PIKA_VIEWABLE (layer)); if (iter) pika_layer_tree_view_mask_update (layer_view, iter, layer); } static void pika_layer_tree_view_renderer_update (PikaViewRenderer *renderer, PikaLayerTreeView *layer_view) { PikaContainerView *view = PIKA_CONTAINER_VIEW (layer_view); PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (layer_view); PikaLayerMask *mask; GtkTreeIter *iter; mask = PIKA_LAYER_MASK (renderer->viewable); iter = pika_container_view_lookup (view, (PikaViewable *) pika_layer_mask_get_layer (mask)); if (iter) { GtkTreePath *path; path = gtk_tree_model_get_path (tree_view->model, iter); gtk_tree_model_row_changed (tree_view->model, path, iter); gtk_tree_path_free (path); } } static void pika_layer_tree_view_update_borders (PikaLayerTreeView *layer_view, GtkTreeIter *iter) { PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (layer_view); PikaViewRenderer *layer_renderer; PikaViewRenderer *mask_renderer; PikaLayer *layer = NULL; PikaLayerMask *mask = NULL; PikaViewBorderType layer_type = PIKA_VIEW_BORDER_BLACK; gtk_tree_model_get (tree_view->model, iter, PIKA_CONTAINER_TREE_STORE_COLUMN_RENDERER, &layer_renderer, layer_view->priv->model_column_mask, &mask_renderer, -1); if (layer_renderer) layer = PIKA_LAYER (layer_renderer->viewable); else if (mask_renderer && PIKA_IS_LAYER (mask_renderer->viewable)) /* This happens when floating masks are displayed (in the mask column). * In such a case, the mask_renderer will actually be a layer renderer * showing the floating mask. */ layer = PIKA_LAYER (mask_renderer->viewable); g_return_if_fail (layer != NULL); if (mask_renderer && PIKA_IS_LAYER_MASK (mask_renderer->viewable)) mask = PIKA_LAYER_MASK (mask_renderer->viewable); if (! mask || (mask && ! pika_layer_get_edit_mask (layer))) layer_type = PIKA_VIEW_BORDER_WHITE; if (layer_renderer) pika_view_renderer_set_border_type (layer_renderer, layer_type); if (mask) { PikaViewBorderType mask_color = PIKA_VIEW_BORDER_BLACK; if (pika_layer_get_show_mask (layer)) { mask_color = PIKA_VIEW_BORDER_GREEN; } else if (! pika_layer_get_apply_mask (layer)) { mask_color = PIKA_VIEW_BORDER_RED; } else if (pika_layer_get_edit_mask (layer)) { mask_color = PIKA_VIEW_BORDER_WHITE; } pika_view_renderer_set_border_type (mask_renderer, mask_color); } else if (mask_renderer) { /* Floating mask. */ pika_view_renderer_set_border_type (mask_renderer, PIKA_VIEW_BORDER_WHITE); } if (layer_renderer) g_object_unref (layer_renderer); if (mask_renderer) g_object_unref (mask_renderer); } static void pika_layer_tree_view_mask_callback (PikaLayer *layer, PikaLayerTreeView *layer_view) { PikaContainerView *view = PIKA_CONTAINER_VIEW (layer_view); GtkTreeIter *iter; iter = pika_container_view_lookup (view, (PikaViewable *) layer); pika_layer_tree_view_update_borders (layer_view, iter); } static void pika_layer_tree_view_layer_clicked (PikaCellRendererViewable *cell, const gchar *path_str, GdkModifierType state, PikaLayerTreeView *layer_view) { PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (layer_view); GtkTreePath *path = gtk_tree_path_new_from_string (path_str); GtkTreeIter iter; if (gtk_tree_model_get_iter (tree_view->model, &iter, path)) { PikaUIManager *ui_manager; PikaActionGroup *group; PikaViewRenderer *renderer; ui_manager = pika_editor_get_ui_manager (PIKA_EDITOR (tree_view)); group = pika_ui_manager_get_action_group (ui_manager, "layers"); gtk_tree_model_get (tree_view->model, &iter, PIKA_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, -1); if (renderer) { PikaLayer *layer = PIKA_LAYER (renderer->viewable); PikaLayerMask *mask = pika_layer_get_mask (layer); GdkModifierType modifiers = (state & pika_get_all_modifiers_mask ()); #if 0 /* This feature has been removed because it clashes with the * multi-selection (Shift/Ctrl are reserved), and we can't move it to * Alt+ because then it would clash with the alpha-to-selection features * (cf. pika_item_tree_view_item_pre_clicked()). * Just macro-ing them out for now, just in case, but I don't think * there is a chance to revive them as there is no infinite modifiers. */ PikaImage *image; image = pika_item_get_image (PIKA_ITEM (layer)); if ((modifiers & GDK_MOD1_MASK)) { /* Alternative functions (Alt key or equivalent) when * clicking on a layer preview. The actions happen only on * the clicked layer, not on selected layers. * * Note: Alt-click is already reserved for Alpha to * selection in PikaItemTreeView. */ if (modifiers == (GDK_MOD1_MASK | GDK_SHIFT_MASK)) { /* Alt-Shift-click adds a layer mask with last values */ PikaDialogConfig *config; PikaChannel *channel = NULL; if (! mask) { config = PIKA_DIALOG_CONFIG (image->pika->config); if (config->layer_add_mask_type == PIKA_ADD_MASK_CHANNEL) { channel = pika_image_get_active_channel (image); if (! channel) { PikaContainer *channels = pika_image_get_channels (image); channel = PIKA_CHANNEL (pika_container_get_first_child (channels)); } if (! channel) { /* No channel. We cannot perform the add * mask action. */ g_message (_("No channels to create a layer mask from.")); } } if (config->layer_add_mask_type != PIKA_ADD_MASK_CHANNEL || channel) { mask = pika_layer_create_mask (layer, config->layer_add_mask_type, channel); if (config->layer_add_mask_invert) pika_channel_invert (PIKA_CHANNEL (mask), FALSE); pika_layer_add_mask (layer, mask, TRUE, NULL); pika_image_flush (image); } } } else if (modifiers == (GDK_MOD1_MASK | GDK_CONTROL_MASK)) { /* Alt-Control-click removes a layer mask */ if (mask) { pika_layer_apply_mask (layer, PIKA_MASK_DISCARD, TRUE); pika_image_flush (image); } } else if (modifiers == (GDK_MOD1_MASK | GDK_CONTROL_MASK | GDK_SHIFT_MASK)) { /* Alt-Shift-Control-click applies a layer mask */ if (mask) { pika_layer_apply_mask (layer, PIKA_MASK_APPLY, TRUE); pika_image_flush (image); } } } else #endif if (! modifiers) { /* Simple clicks (without modifiers) activate the layer */ if (mask && pika_layer_get_edit_mask (layer)) pika_action_group_set_action_active (group, "layers-mask-edit", FALSE); else pika_layer_tree_view_update_borders (layer_view, &iter); } g_object_unref (renderer); } } gtk_tree_path_free (path); } static void pika_layer_tree_view_mask_clicked (PikaCellRendererViewable *cell, const gchar *path_str, GdkModifierType state, PikaLayerTreeView *layer_view) { PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (layer_view); GtkTreePath *path; GtkTreeIter iter; path = gtk_tree_path_new_from_string (path_str); if (gtk_tree_model_get_iter (tree_view->model, &iter, path)) { PikaViewRenderer *renderer; PikaUIManager *ui_manager; PikaActionGroup *group; ui_manager = pika_editor_get_ui_manager (PIKA_EDITOR (tree_view)); group = pika_ui_manager_get_action_group (ui_manager, "layers"); gtk_tree_model_get (tree_view->model, &iter, PIKA_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, -1); if (renderer) { PikaLayer *layer = PIKA_LAYER (renderer->viewable); GdkModifierType modifiers = pika_get_all_modifiers_mask (); if ((state & GDK_MOD1_MASK)) { PikaImage *image; image = pika_item_get_image (PIKA_ITEM (layer)); if ((state & modifiers) == GDK_MOD1_MASK) { /* Alt-click shows/hides a layer mask */ pika_layer_set_show_mask (layer, ! pika_layer_get_show_mask (layer), TRUE); pika_image_flush (image); } else if ((state & modifiers) == (GDK_MOD1_MASK | GDK_CONTROL_MASK)) { /* Alt-Control-click enables/disables a layer mask */ pika_layer_set_apply_mask (layer, ! pika_layer_get_apply_mask (layer), TRUE); pika_image_flush (image); } } else if (! pika_layer_get_edit_mask (layer)) { /* Simple click selects the mask for edition. */ pika_action_group_set_action_active (group, "layers-mask-edit", TRUE); } g_object_unref (renderer); } } gtk_tree_path_free (path); } /* PikaDrawable alpha callbacks */ static void pika_layer_tree_view_alpha_update (PikaLayerTreeView *view, GtkTreeIter *iter, PikaLayer *layer) { PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (view); gtk_tree_store_set (GTK_TREE_STORE (tree_view->model), iter, PIKA_CONTAINER_TREE_STORE_COLUMN_NAME_ATTRIBUTES, pika_drawable_has_alpha (PIKA_DRAWABLE (layer)) ? NULL : view->priv->bold_attrs, -1); } static void pika_layer_tree_view_alpha_changed (PikaLayer *layer, PikaLayerTreeView *layer_view) { PikaContainerView *view = PIKA_CONTAINER_VIEW (layer_view); GtkTreeIter *iter; iter = pika_container_view_lookup (view, (PikaViewable *) layer); if (iter) { PikaItemTreeView *item_view = PIKA_ITEM_TREE_VIEW (view); pika_layer_tree_view_alpha_update (layer_view, iter, layer); /* update button states */ if (g_list_find (pika_image_get_selected_layers (pika_item_tree_view_get_image (item_view)), layer)) pika_container_view_select_items (PIKA_CONTAINER_VIEW (view), pika_image_get_selected_layers (pika_item_tree_view_get_image (item_view))); } }