/* 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 * * pikatooleditor.c * Copyright (C) 2001-2009 Michael Natterer * Stephen Griffiths * * 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 "libpikaconfig/pikaconfig.h" #include "libpikawidgets/pikawidgets.h" #include "widgets-types.h" #include "core/pika.h" #include "core/pikacontainer.h" #include "core/pikacontext.h" #include "core/pikatoolgroup.h" #include "core/pikatreehandler.h" #include "tools/pika-tools.h" #include "pikacontainertreestore.h" #include "pikacontainerview.h" #include "pikadnd.h" #include "pikaviewrenderer.h" #include "pikatooleditor.h" #include "pikahelp-ids.h" #include "pikawidgets-utils.h" #include "pika-intl.h" struct _PikaToolEditorPrivate { PikaContainer *container; PikaContext *context; GtkWidget *scrolled; GtkWidget *new_group_button; GtkWidget *raise_button; GtkWidget *lower_button; GtkWidget *delete_button; GtkWidget *reset_button; PikaTreeHandler *tool_item_notify_handler; /* State of tools at creation of the editor, stored to support * reverting changes */ gchar *initial_tool_state; }; /* local function prototypes */ static void pika_tool_editor_view_iface_init (PikaContainerViewInterface *iface); static void pika_tool_editor_constructed (GObject *object); static gboolean pika_tool_editor_select_items (PikaContainerView *view, GList *items, GList *paths); static void pika_tool_editor_set_container (PikaContainerView *container_view, PikaContainer *container); static void pika_tool_editor_set_context (PikaContainerView *container_view, PikaContext *context); static gboolean pika_tool_editor_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); static void pika_tool_editor_drop_viewables (PikaContainerTreeView *tree_view, GList *src_viewables, PikaViewable *dest_viewable, GtkTreeViewDropPosition drop_pos); static void pika_tool_editor_tool_item_notify (PikaToolItem *tool_item, GParamSpec *pspec, PikaToolEditor *tool_editor); static void pika_tool_editor_eye_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data); static void pika_tool_editor_eye_clicked (GtkCellRendererToggle *toggle, gchar *path_str, GdkModifierType state, PikaToolEditor *tool_editor); static void pika_tool_editor_new_group_clicked (GtkButton *button, PikaToolEditor *tool_editor); static void pika_tool_editor_raise_clicked (GtkButton *button, PikaToolEditor *tool_editor); static void pika_tool_editor_raise_extend_clicked (GtkButton *button, GdkModifierType mask, PikaToolEditor *tool_editor); static void pika_tool_editor_lower_clicked (GtkButton *button, PikaToolEditor *tool_editor); static void pika_tool_editor_lower_extend_clicked (GtkButton *button, GdkModifierType mask, PikaToolEditor *tool_editor); static void pika_tool_editor_delete_clicked (GtkButton *button, PikaToolEditor *tool_editor); static void pika_tool_editor_reset_clicked (GtkButton *button, PikaToolEditor *tool_editor); static PikaToolItem * pika_tool_editor_get_selected_tool_item (PikaToolEditor *tool_editor); static PikaContainer * pika_tool_editor_get_tool_item_container (PikaToolEditor *tool_editor, PikaToolItem *tool_item); static void pika_tool_editor_update_container (PikaToolEditor *tool_editor); static void pika_tool_editor_update_sensitivity (PikaToolEditor *tool_editor); G_DEFINE_TYPE_WITH_CODE (PikaToolEditor, pika_tool_editor, PIKA_TYPE_CONTAINER_TREE_VIEW, G_ADD_PRIVATE (PikaToolEditor) G_IMPLEMENT_INTERFACE (PIKA_TYPE_CONTAINER_VIEW, pika_tool_editor_view_iface_init)) #define parent_class pika_tool_editor_parent_class static PikaContainerViewInterface *parent_view_iface = NULL; /* private functions */ static void pika_tool_editor_class_init (PikaToolEditorClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaContainerTreeViewClass *tree_view_class = PIKA_CONTAINER_TREE_VIEW_CLASS (klass); object_class->constructed = pika_tool_editor_constructed; tree_view_class->drop_possible = pika_tool_editor_drop_possible; tree_view_class->drop_viewables = pika_tool_editor_drop_viewables; } static void pika_tool_editor_view_iface_init (PikaContainerViewInterface *iface) { parent_view_iface = g_type_interface_peek_parent (iface); if (! parent_view_iface) parent_view_iface = g_type_default_interface_peek (PIKA_TYPE_CONTAINER_VIEW); iface->select_items = pika_tool_editor_select_items; iface->set_container = pika_tool_editor_set_container; iface->set_context = pika_tool_editor_set_context; } static void pika_tool_editor_init (PikaToolEditor *tool_editor) { tool_editor->priv = pika_tool_editor_get_instance_private (tool_editor); } static void pika_tool_editor_constructed (GObject *object) { PikaToolEditor *tool_editor = PIKA_TOOL_EDITOR (object); PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (object); PikaContainerView *container_view = PIKA_CONTAINER_VIEW (object); gint view_size; gint border_width; G_OBJECT_CLASS (parent_class)->constructed (object); view_size = pika_container_view_get_view_size (container_view, &border_width); pika_editor_set_show_name (PIKA_EDITOR (tool_editor), FALSE); gtk_tree_view_set_level_indentation (tree_view->view, 0.8 * (view_size + 2 * border_width)); pika_dnd_viewable_dest_add (GTK_WIDGET (tree_view->view), PIKA_TYPE_TOOL_ITEM, NULL, NULL); /* construct tree view */ { GtkTreeViewColumn *column; GtkCellRenderer *eye_cell; GtkStyleContext *tree_style; GtkBorder border; gint icon_size; tree_style = gtk_widget_get_style_context (GTK_WIDGET (tool_editor)); gtk_style_context_get_border (tree_style, 0, &border); column = gtk_tree_view_column_new (); gtk_tree_view_insert_column (tree_view->view, column, 0); eye_cell = pika_cell_renderer_toggle_new (PIKA_ICON_VISIBLE); g_object_get (eye_cell, "icon-size", &icon_size, NULL); icon_size = MIN (icon_size, MAX (view_size - (border.left + border.right), view_size - (border.top + border.bottom))); g_object_set (eye_cell, "icon-size", icon_size, NULL); gtk_tree_view_column_pack_start (column, eye_cell, FALSE); gtk_tree_view_column_set_cell_data_func (column, eye_cell, pika_tool_editor_eye_data_func, tree_view, NULL); pika_container_tree_view_add_toggle_cell (tree_view, eye_cell); g_signal_connect (eye_cell, "clicked", G_CALLBACK (pika_tool_editor_eye_clicked), tool_editor); } /* buttons */ tool_editor->priv->new_group_button = pika_editor_add_button (PIKA_EDITOR (tool_editor), PIKA_ICON_FOLDER_NEW, _("Create a new tool group"), NULL, G_CALLBACK (pika_tool_editor_new_group_clicked), NULL, G_OBJECT (tool_editor)); tool_editor->priv->raise_button = pika_editor_add_button (PIKA_EDITOR (tool_editor), PIKA_ICON_GO_UP, _("Raise this item"), _("Raise this item to the top"), G_CALLBACK (pika_tool_editor_raise_clicked), G_CALLBACK (pika_tool_editor_raise_extend_clicked), G_OBJECT (tool_editor)); tool_editor->priv->lower_button = pika_editor_add_button (PIKA_EDITOR (tool_editor), PIKA_ICON_GO_DOWN, _("Lower this item"), _("Lower this item to the bottom"), G_CALLBACK (pika_tool_editor_lower_clicked), G_CALLBACK (pika_tool_editor_lower_extend_clicked), G_OBJECT (tool_editor)); tool_editor->priv->delete_button = pika_editor_add_button (PIKA_EDITOR (tool_editor), PIKA_ICON_EDIT_DELETE, _("Delete this tool group"), NULL, G_CALLBACK (pika_tool_editor_delete_clicked), NULL, G_OBJECT (tool_editor)); tool_editor->priv->reset_button = pika_editor_add_button (PIKA_EDITOR (tool_editor), PIKA_ICON_RESET, _("Reset tool order and visibility"), NULL, G_CALLBACK (pika_tool_editor_reset_clicked), NULL, G_OBJECT (tool_editor)); pika_tool_editor_update_sensitivity (tool_editor); } static gboolean pika_tool_editor_select_items (PikaContainerView *container_view, GList *viewables, GList *paths) { PikaToolEditor *tool_editor = PIKA_TOOL_EDITOR (container_view); gboolean result; result = parent_view_iface->select_items (container_view, viewables, paths); pika_tool_editor_update_sensitivity (tool_editor); return result; } static void pika_tool_editor_set_container (PikaContainerView *container_view, PikaContainer *container) { PikaToolEditor *tool_editor = PIKA_TOOL_EDITOR (container_view); parent_view_iface->set_container (container_view, container); pika_tool_editor_update_container (tool_editor); } static void pika_tool_editor_set_context (PikaContainerView *container_view, PikaContext *context) { PikaToolEditor *tool_editor = PIKA_TOOL_EDITOR (container_view); parent_view_iface->set_context (container_view, context); pika_tool_editor_update_container (tool_editor); } static gboolean pika_tool_editor_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 (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)) { if (pika_viewable_get_parent (dest_viewable) || (pika_viewable_get_children (dest_viewable) && (drop_pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER || drop_pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE))) { GList *iter; for (iter = src_viewables; iter; iter = iter->next) { PikaViewable *src_viewable = iter->data; if (pika_viewable_get_children (src_viewable)) return FALSE; } } return TRUE; } return FALSE; } static void pika_tool_editor_drop_viewables (PikaContainerTreeView *tree_view, GList *src_viewables, PikaViewable *dest_viewable, GtkTreeViewDropPosition drop_pos) { PikaContainerView *container_view = PIKA_CONTAINER_VIEW (tree_view); PIKA_CONTAINER_TREE_VIEW_CLASS (parent_class)->drop_viewables (tree_view, src_viewables, dest_viewable, drop_pos); if (src_viewables) pika_container_view_select_items (container_view, src_viewables); } static void pika_tool_editor_new_group_clicked (GtkButton *button, PikaToolEditor *tool_editor) { PikaContainerView *container_view = PIKA_CONTAINER_VIEW (tool_editor); PikaContainer *container; PikaToolItem *tool_item; PikaToolGroup *group; gint index = 0; tool_item = pika_tool_editor_get_selected_tool_item (tool_editor); if (tool_item) { if (pika_viewable_get_parent (PIKA_VIEWABLE (tool_item)) != NULL) return; container = pika_tool_editor_get_tool_item_container (tool_editor, tool_item); index = pika_container_get_child_index (container, PIKA_OBJECT (tool_item)); } else { container = tool_editor->priv->container; } if (container) { group = pika_tool_group_new (); pika_container_insert (container, PIKA_OBJECT (group), index); g_object_unref (group); pika_container_view_select_item (container_view, PIKA_VIEWABLE (group)); } } static void pika_tool_editor_raise_clicked (GtkButton *button, PikaToolEditor *tool_editor) { PikaToolItem *tool_item; tool_item = pika_tool_editor_get_selected_tool_item (tool_editor); if (tool_item) { PikaContainer *container; gint index; container = pika_tool_editor_get_tool_item_container (tool_editor, tool_item); index = pika_container_get_child_index (container, PIKA_OBJECT (tool_item)); if (index > 0) { pika_container_reorder (container, PIKA_OBJECT (tool_item), index - 1); } } } static void pika_tool_editor_raise_extend_clicked (GtkButton *button, GdkModifierType mask, PikaToolEditor *tool_editor) { PikaToolItem *tool_item; tool_item = pika_tool_editor_get_selected_tool_item (tool_editor); if (tool_item && (mask & GDK_SHIFT_MASK)) { PikaContainer *container; gint index; container = pika_tool_editor_get_tool_item_container (tool_editor, tool_item); index = pika_container_get_child_index (container, PIKA_OBJECT (tool_item)); if (index > 0) { pika_container_reorder (container, PIKA_OBJECT (tool_item), 0); } } } static void pika_tool_editor_lower_clicked (GtkButton *button, PikaToolEditor *tool_editor) { PikaToolItem *tool_item; tool_item = pika_tool_editor_get_selected_tool_item (tool_editor); if (tool_item) { PikaContainer *container; gint index; container = pika_tool_editor_get_tool_item_container (tool_editor, tool_item); index = pika_container_get_child_index (container, PIKA_OBJECT (tool_item)); if (index + 1 < pika_container_get_n_children (container)) { pika_container_reorder (container, PIKA_OBJECT (tool_item), index + 1); } } } static void pika_tool_editor_lower_extend_clicked (GtkButton *button, GdkModifierType mask, PikaToolEditor *tool_editor) { PikaToolItem *tool_item; tool_item = pika_tool_editor_get_selected_tool_item (tool_editor); if (tool_item && (mask & GDK_SHIFT_MASK)) { PikaContainer *container; gint index; container = pika_tool_editor_get_tool_item_container (tool_editor, tool_item); index = pika_container_get_n_children (container) - 1; index = MAX (index, 0); pika_container_reorder (container, PIKA_OBJECT (tool_item), index); } } static void pika_tool_editor_delete_clicked (GtkButton *button, PikaToolEditor *tool_editor) { PikaContainerView *container_view = PIKA_CONTAINER_VIEW (tool_editor); PikaToolItem *tool_item; tool_item = pika_tool_editor_get_selected_tool_item (tool_editor); if (tool_item) { PikaContainer *src_container; PikaContainer *dest_container; gint index; gint dest_index; src_container = pika_viewable_get_children (PIKA_VIEWABLE (tool_item)); dest_container = pika_tool_editor_get_tool_item_container (tool_editor, tool_item); if (! src_container) return; index = pika_container_get_child_index (dest_container, PIKA_OBJECT (tool_item)); dest_index = index; g_object_ref (tool_item); pika_container_freeze (src_container); pika_container_freeze (dest_container); pika_container_remove (dest_container, PIKA_OBJECT (tool_item)); while (! pika_container_is_empty (src_container)) { PikaObject *object = pika_container_get_first_child (src_container); g_object_ref (object); pika_container_remove (src_container, object); pika_container_insert (dest_container, object, dest_index++); g_object_unref (object); } pika_container_thaw (dest_container); pika_container_thaw (src_container); pika_container_view_select_item ( container_view, PIKA_VIEWABLE (pika_container_get_child_by_index (dest_container, index))); g_object_unref (tool_item); } } static void pika_tool_editor_reset_clicked (GtkButton *button, PikaToolEditor *tool_editor) { pika_tools_reset (tool_editor->priv->context->pika, tool_editor->priv->container, FALSE); } static void pika_tool_editor_tool_item_notify (PikaToolItem *tool_item, GParamSpec *pspec, PikaToolEditor *tool_editor) { PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (tool_editor); PikaContainerView *container_view = PIKA_CONTAINER_VIEW (tool_editor); GtkTreeIter *iter; iter = pika_container_view_lookup (container_view, PIKA_VIEWABLE (tool_item)); 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_tool_editor_eye_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { PikaViewRenderer *renderer; PikaToolItem *tool_item; gtk_tree_model_get (tree_model, iter, PIKA_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, -1); tool_item = PIKA_TOOL_ITEM (renderer->viewable); g_object_set (cell, "active", pika_tool_item_get_visible (tool_item), "inconsistent", pika_tool_item_get_visible (tool_item) && ! pika_tool_item_get_shown (tool_item), NULL); g_object_unref (renderer); } static void pika_tool_editor_eye_clicked (GtkCellRendererToggle *toggle, gchar *path_str, GdkModifierType state, PikaToolEditor *tool_editor) { PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (tool_editor); 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; PikaToolItem *tool_item; gboolean active; gtk_tree_model_get (tree_view->model, &iter, PIKA_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, -1); tool_item = PIKA_TOOL_ITEM (renderer->viewable); g_object_get (toggle, "active", &active, NULL); pika_tool_item_set_visible (tool_item, ! active); g_object_unref (renderer); } gtk_tree_path_free (path); } static PikaToolItem * pika_tool_editor_get_selected_tool_item (PikaToolEditor *tool_editor) { PikaContainerTreeView *tree_view = PIKA_CONTAINER_TREE_VIEW (tool_editor); if (tool_editor->priv->container) { PikaViewRenderer *renderer; PikaToolItem *tool_item; GtkTreeSelection *selection; GtkTreeModel *model; GtkTreeIter iter; selection = gtk_tree_view_get_selection (tree_view->view); if (! gtk_tree_selection_get_selected (selection, &model, &iter)) return NULL; gtk_tree_model_get (model, &iter, PIKA_CONTAINER_TREE_STORE_COLUMN_RENDERER, &renderer, -1); tool_item = PIKA_TOOL_ITEM (renderer->viewable); g_object_unref (renderer); return tool_item; } return NULL; } static PikaContainer * pika_tool_editor_get_tool_item_container (PikaToolEditor *tool_editor, PikaToolItem *tool_item) { PikaViewable *parent; parent = pika_viewable_get_parent (PIKA_VIEWABLE (tool_item)); if (parent) { return pika_viewable_get_children (parent); } else { return tool_editor->priv->container; } } static void pika_tool_editor_update_container (PikaToolEditor *tool_editor) { PikaContainerView *container_view = PIKA_CONTAINER_VIEW (tool_editor); PikaContainer *container; PikaContext *context; g_clear_pointer (&tool_editor->priv->tool_item_notify_handler, pika_tree_handler_disconnect); g_clear_pointer (&tool_editor->priv->initial_tool_state, g_free); container = pika_container_view_get_container (container_view); context = pika_container_view_get_context (container_view); if (container && context) { GString *string; PikaConfigWriter *writer; tool_editor->priv->container = container; tool_editor->priv->context = context; tool_editor->priv->tool_item_notify_handler = pika_tree_handler_connect ( container, "notify", G_CALLBACK (pika_tool_editor_tool_item_notify), tool_editor); /* save initial tool order */ string = g_string_new (NULL); writer = pika_config_writer_new_from_string (string); pika_tools_serialize (context->pika, container, writer); pika_config_writer_finish (writer, NULL, NULL); tool_editor->priv->initial_tool_state = g_string_free (string, FALSE); } } static void pika_tool_editor_update_sensitivity (PikaToolEditor *tool_editor) { PikaToolItem *tool_item; tool_item = pika_tool_editor_get_selected_tool_item (tool_editor); if (tool_item) { PikaContainer *container; gint index; container = pika_tool_editor_get_tool_item_container (tool_editor, tool_item); index = pika_container_get_child_index (container, PIKA_OBJECT (tool_item)); gtk_widget_set_sensitive ( tool_editor->priv->new_group_button, pika_viewable_get_parent (PIKA_VIEWABLE (tool_item)) == NULL); gtk_widget_set_sensitive ( tool_editor->priv->raise_button, index > 0); gtk_widget_set_sensitive ( tool_editor->priv->lower_button, index < pika_container_get_n_children (container) - 1); gtk_widget_set_sensitive ( tool_editor->priv->delete_button, pika_viewable_get_children (PIKA_VIEWABLE (tool_item)) != NULL); } else { gtk_widget_set_sensitive (tool_editor->priv->new_group_button, TRUE); gtk_widget_set_sensitive (tool_editor->priv->raise_button, FALSE); gtk_widget_set_sensitive (tool_editor->priv->lower_button, FALSE); gtk_widget_set_sensitive (tool_editor->priv->delete_button, FALSE); } } /* public functions */ GtkWidget * pika_tool_editor_new (PikaContainer *container, PikaContext *context, gint view_size, gint view_border_width) { PikaContainerView *container_view; g_return_val_if_fail (PIKA_IS_CONTAINER (container), NULL); g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL); container_view = g_object_new (PIKA_TYPE_TOOL_EDITOR, "view-size", view_size, "view-border-width", view_border_width, NULL); pika_container_view_set_context (container_view, context); pika_container_view_set_container (container_view, container); pika_container_view_set_reorderable (container_view, TRUE); return GTK_WIDGET (container_view); } /** * pika_tool_editor_revert_changes: * @tool_editor: * * Reverts the tool order and visibility to the state at creation. **/ void pika_tool_editor_revert_changes (PikaToolEditor *tool_editor) { GScanner *scanner; g_return_if_fail (PIKA_IS_TOOL_EDITOR (tool_editor)); scanner = pika_scanner_new_string (tool_editor->priv->initial_tool_state, -1, NULL); pika_tools_deserialize (tool_editor->priv->context->pika, tool_editor->priv->container, scanner); pika_scanner_unref (scanner); }