PIKApp/app/widgets/pikacomponenteditor.c

635 lines
22 KiB
C

/* 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
*
* pikacomponenteditor.c
* Copyright (C) 2003-2005 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 "libpikabase/pikabase.h"
#include "libpikawidgets/pikawidgets.h"
#include "widgets-types.h"
#include "core/pikachannel.h"
#include "core/pikaimage.h"
#include "pikacellrendererviewable.h"
#include "pikacomponenteditor.h"
#include "pikadnd.h"
#include "pikadocked.h"
#include "pikamenufactory.h"
#include "pikaviewrendererimage.h"
#include "pikawidgets-utils.h"
#include "pika-intl.h"
enum
{
COLUMN_CHANNEL,
COLUMN_VISIBLE,
COLUMN_RENDERER,
COLUMN_NAME,
N_COLUMNS
};
static void pika_component_editor_docked_iface_init (PikaDockedInterface *iface);
static void pika_component_editor_set_context (PikaDocked *docked,
PikaContext *context);
static void pika_component_editor_set_image (PikaImageEditor *editor,
PikaImage *image);
static void pika_component_editor_create_components (PikaComponentEditor *editor);
static void pika_component_editor_clear_components (PikaComponentEditor *editor);
static void pika_component_editor_clicked (GtkCellRendererToggle *cellrenderertoggle,
gchar *path,
GdkModifierType state,
PikaComponentEditor *editor);
static gboolean pika_component_editor_select (GtkTreeSelection *selection,
GtkTreeModel *model,
GtkTreePath *path,
gboolean path_currently_selected,
gpointer data);
static gboolean pika_component_editor_button_press (GtkWidget *widget,
GdkEventButton *bevent,
PikaComponentEditor *editor);
static void pika_component_editor_renderer_update (PikaViewRenderer *renderer,
PikaComponentEditor *editor);
static void pika_component_editor_mode_changed (PikaImage *image,
PikaComponentEditor *editor);
static void pika_component_editor_alpha_changed (PikaImage *image,
PikaComponentEditor *editor);
static void pika_component_editor_visibility_changed(PikaImage *image,
PikaChannelType channel,
PikaComponentEditor *editor);
static void pika_component_editor_active_changed (PikaImage *image,
PikaChannelType channel,
PikaComponentEditor *editor);
static PikaImage * pika_component_editor_drag_component (GtkWidget *widget,
PikaContext **context,
PikaChannelType *channel,
gpointer data);
G_DEFINE_TYPE_WITH_CODE (PikaComponentEditor, pika_component_editor,
PIKA_TYPE_IMAGE_EDITOR,
G_IMPLEMENT_INTERFACE (PIKA_TYPE_DOCKED,
pika_component_editor_docked_iface_init))
#define parent_class pika_component_editor_parent_class
static PikaDockedInterface *parent_docked_iface = NULL;
static void
pika_component_editor_class_init (PikaComponentEditorClass *klass)
{
PikaImageEditorClass *image_editor_class = PIKA_IMAGE_EDITOR_CLASS (klass);
image_editor_class->set_image = pika_component_editor_set_image;
}
static void
pika_component_editor_init (PikaComponentEditor *editor)
{
GtkWidget *frame;
GtkListStore *list;
frame = gtk_frame_new (NULL);
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
gtk_box_pack_start (GTK_BOX (editor), frame, FALSE, FALSE, 0);
gtk_widget_show (frame);
list = gtk_list_store_new (N_COLUMNS,
G_TYPE_INT,
G_TYPE_BOOLEAN,
PIKA_TYPE_VIEW_RENDERER,
G_TYPE_STRING);
editor->model = GTK_TREE_MODEL (list);
editor->view = GTK_TREE_VIEW (gtk_tree_view_new_with_model (editor->model));
g_object_unref (list);
gtk_tree_view_set_headers_visible (editor->view, FALSE);
editor->eye_column = gtk_tree_view_column_new ();
gtk_tree_view_append_column (editor->view, editor->eye_column);
editor->eye_cell = pika_cell_renderer_toggle_new (PIKA_ICON_VISIBLE);
gtk_tree_view_column_pack_start (editor->eye_column, editor->eye_cell,
FALSE);
gtk_tree_view_column_set_attributes (editor->eye_column, editor->eye_cell,
"active", COLUMN_VISIBLE,
NULL);
g_signal_connect (editor->eye_cell, "clicked",
G_CALLBACK (pika_component_editor_clicked),
editor);
editor->renderer_cell = pika_cell_renderer_viewable_new ();
gtk_tree_view_insert_column_with_attributes (editor->view,
-1, NULL,
editor->renderer_cell,
"renderer", COLUMN_RENDERER,
NULL);
gtk_tree_view_insert_column_with_attributes (editor->view,
-1, NULL,
gtk_cell_renderer_text_new (),
"text", COLUMN_NAME,
NULL);
gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (editor->view));
gtk_widget_show (GTK_WIDGET (editor->view));
g_signal_connect (editor->view, "button-press-event",
G_CALLBACK (pika_component_editor_button_press),
editor);
editor->selection = gtk_tree_view_get_selection (editor->view);
gtk_tree_selection_set_mode (editor->selection, GTK_SELECTION_MULTIPLE);
gtk_tree_selection_set_select_function (editor->selection,
pika_component_editor_select,
editor, NULL);
pika_dnd_component_source_add (GTK_WIDGET (editor->view),
pika_component_editor_drag_component,
editor);
}
static void
pika_component_editor_docked_iface_init (PikaDockedInterface *iface)
{
parent_docked_iface = g_type_interface_peek_parent (iface);
if (! parent_docked_iface)
parent_docked_iface = g_type_default_interface_peek (PIKA_TYPE_DOCKED);
iface->set_context = pika_component_editor_set_context;
}
static void
pika_component_editor_set_context (PikaDocked *docked,
PikaContext *context)
{
PikaComponentEditor *editor = PIKA_COMPONENT_EDITOR (docked);
GtkTreeIter iter;
gboolean iter_valid;
parent_docked_iface->set_context (docked, context);
for (iter_valid = gtk_tree_model_get_iter_first (editor->model, &iter);
iter_valid;
iter_valid = gtk_tree_model_iter_next (editor->model, &iter))
{
PikaViewRenderer *renderer;
gtk_tree_model_get (editor->model, &iter,
COLUMN_RENDERER, &renderer,
-1);
pika_view_renderer_set_context (renderer, context);
g_object_unref (renderer);
}
}
static void
pika_component_editor_set_image (PikaImageEditor *editor,
PikaImage *image)
{
PikaComponentEditor *component_editor = PIKA_COMPONENT_EDITOR (editor);
if (editor->image)
{
pika_component_editor_clear_components (component_editor);
g_signal_handlers_disconnect_by_func (editor->image,
pika_component_editor_mode_changed,
component_editor);
g_signal_handlers_disconnect_by_func (editor->image,
pika_component_editor_alpha_changed,
component_editor);
g_signal_handlers_disconnect_by_func (editor->image,
pika_component_editor_visibility_changed,
component_editor);
g_signal_handlers_disconnect_by_func (editor->image,
pika_component_editor_active_changed,
component_editor);
}
PIKA_IMAGE_EDITOR_CLASS (parent_class)->set_image (editor, image);
if (editor->image)
{
pika_component_editor_create_components (component_editor);
g_signal_connect (editor->image, "mode-changed",
G_CALLBACK (pika_component_editor_mode_changed),
component_editor);
g_signal_connect (editor->image, "alpha-changed",
G_CALLBACK (pika_component_editor_alpha_changed),
component_editor);
g_signal_connect (editor->image, "component-visibility-changed",
G_CALLBACK (pika_component_editor_visibility_changed),
component_editor);
g_signal_connect (editor->image, "component-active-changed",
G_CALLBACK (pika_component_editor_active_changed),
component_editor);
}
}
GtkWidget *
pika_component_editor_new (gint view_size,
PikaMenuFactory *menu_factory)
{
PikaComponentEditor *editor;
g_return_val_if_fail (view_size > 0 &&
view_size <= PIKA_VIEWABLE_MAX_PREVIEW_SIZE, NULL);
g_return_val_if_fail (PIKA_IS_MENU_FACTORY (menu_factory), NULL);
editor = g_object_new (PIKA_TYPE_COMPONENT_EDITOR,
"menu-factory", menu_factory,
"menu-identifier", "<Channels>",
"ui-path", "/channels-popup",
NULL);
pika_component_editor_set_view_size (editor, view_size);
return GTK_WIDGET (editor);
}
void
pika_component_editor_set_view_size (PikaComponentEditor *editor,
gint view_size)
{
GtkWidget *tree_widget;
GtkStyleContext *tree_style;
GtkBorder border;
GtkTreeIter iter;
gboolean iter_valid;
gint icon_size;
g_return_if_fail (PIKA_IS_COMPONENT_EDITOR (editor));
g_return_if_fail (view_size > 0 &&
view_size <= PIKA_VIEWABLE_MAX_PREVIEW_SIZE);
tree_widget = GTK_WIDGET (editor->view);
tree_style = gtk_widget_get_style_context (tree_widget);
gtk_style_context_save (tree_style);
gtk_style_context_add_class (tree_style, GTK_STYLE_CLASS_BUTTON);
gtk_style_context_get_border (tree_style, 0, &border);
gtk_style_context_restore (tree_style);
g_object_get (editor->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 (editor->eye_cell, "icon-size", icon_size, NULL);
for (iter_valid = gtk_tree_model_get_iter_first (editor->model, &iter);
iter_valid;
iter_valid = gtk_tree_model_iter_next (editor->model, &iter))
{
PikaViewRenderer *renderer;
gtk_tree_model_get (editor->model, &iter,
COLUMN_RENDERER, &renderer,
-1);
pika_view_renderer_set_size (renderer, view_size, 1);
g_object_unref (renderer);
}
editor->view_size = view_size;
gtk_tree_view_columns_autosize (editor->view);
}
static void
pika_component_editor_create_components (PikaComponentEditor *editor)
{
PikaImage *image = PIKA_IMAGE_EDITOR (editor)->image;
gint n_components = 0;
PikaChannelType components[MAX_CHANNELS];
GEnumClass *enum_class;
gint i;
switch (pika_image_get_base_type (image))
{
case PIKA_RGB:
n_components = 3;
components[0] = PIKA_CHANNEL_RED;
components[1] = PIKA_CHANNEL_GREEN;
components[2] = PIKA_CHANNEL_BLUE;
break;
case PIKA_GRAY:
n_components = 1;
components[0] = PIKA_CHANNEL_GRAY;
break;
case PIKA_INDEXED:
n_components = 1;
components[0] = PIKA_CHANNEL_INDEXED;
break;
}
if (pika_image_has_alpha (image))
components[n_components++] = PIKA_CHANNEL_ALPHA;
enum_class = g_type_class_ref (PIKA_TYPE_CHANNEL_TYPE);
for (i = 0; i < n_components; i++)
{
PikaViewRenderer *renderer;
GtkTreeIter iter;
GEnumValue *enum_value;
const gchar *desc;
gboolean visible;
visible = pika_image_get_component_visible (image, components[i]);
renderer = pika_view_renderer_new (PIKA_IMAGE_EDITOR (editor)->context,
G_TYPE_FROM_INSTANCE (image),
editor->view_size, 1, FALSE);
pika_view_renderer_set_viewable (renderer, PIKA_VIEWABLE (image));
pika_view_renderer_remove_idle (renderer);
PIKA_VIEW_RENDERER_IMAGE (renderer)->channel = components[i];
g_signal_connect (renderer, "update",
G_CALLBACK (pika_component_editor_renderer_update),
editor);
enum_value = g_enum_get_value (enum_class, components[i]);
desc = pika_enum_value_get_desc (enum_class, enum_value);
gtk_list_store_append (GTK_LIST_STORE (editor->model), &iter);
gtk_list_store_set (GTK_LIST_STORE (editor->model), &iter,
COLUMN_CHANNEL, components[i],
COLUMN_VISIBLE, visible,
COLUMN_RENDERER, renderer,
COLUMN_NAME, desc,
-1);
g_object_unref (renderer);
if (pika_image_get_component_active (image, components[i]))
gtk_tree_selection_select_iter (editor->selection, &iter);
}
g_type_class_unref (enum_class);
}
static void
pika_component_editor_clear_components (PikaComponentEditor *editor)
{
gtk_list_store_clear (GTK_LIST_STORE (editor->model));
/* Clear the renderer so that it don't reference the viewable.
* See bug #149906.
*/
g_object_set (editor->renderer_cell, "renderer", NULL, NULL);
}
static void
pika_component_editor_clicked (GtkCellRendererToggle *cellrenderertoggle,
gchar *path_str,
GdkModifierType state,
PikaComponentEditor *editor)
{
GtkTreePath *path;
GtkTreeIter iter;
path = gtk_tree_path_new_from_string (path_str);
if (gtk_tree_model_get_iter (editor->model, &iter, path))
{
PikaImage *image = PIKA_IMAGE_EDITOR (editor)->image;
PikaChannelType channel;
gboolean active;
gtk_tree_model_get (editor->model, &iter,
COLUMN_CHANNEL, &channel,
-1);
g_object_get (cellrenderertoggle,
"active", &active,
NULL);
pika_image_set_component_visible (image, channel, !active);
pika_image_flush (image);
}
gtk_tree_path_free (path);
}
static gboolean
pika_component_editor_select (GtkTreeSelection *selection,
GtkTreeModel *model,
GtkTreePath *path,
gboolean path_currently_selected,
gpointer data)
{
PikaComponentEditor *editor = PIKA_COMPONENT_EDITOR (data);
GtkTreeIter iter;
PikaChannelType channel;
gboolean active;
gtk_tree_model_get_iter (editor->model, &iter, path);
gtk_tree_model_get (editor->model, &iter,
COLUMN_CHANNEL, &channel,
-1);
active = pika_image_get_component_active (PIKA_IMAGE_EDITOR (editor)->image,
channel);
return active != path_currently_selected;
}
static gboolean
pika_component_editor_button_press (GtkWidget *widget,
GdkEventButton *bevent,
PikaComponentEditor *editor)
{
GtkTreeViewColumn *column;
GtkTreePath *path;
editor->clicked_component = -1;
if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
bevent->x,
bevent->y,
&path, &column, NULL, NULL))
{
GtkTreeIter iter;
PikaChannelType channel;
gboolean active;
active = gtk_tree_selection_path_is_selected (editor->selection, path);
gtk_tree_model_get_iter (editor->model, &iter, path);
gtk_tree_path_free (path);
gtk_tree_model_get (editor->model, &iter,
COLUMN_CHANNEL, &channel,
-1);
editor->clicked_component = channel;
if (gdk_event_triggers_context_menu ((GdkEvent *) bevent))
{
pika_editor_popup_menu_at_pointer (PIKA_EDITOR (editor), (GdkEvent *) bevent);
}
else if (bevent->type == GDK_BUTTON_PRESS && bevent->button == 1 &&
column != editor->eye_column)
{
PikaImage *image = PIKA_IMAGE_EDITOR (editor)->image;
pika_image_set_component_active (image, channel, ! active);
pika_image_flush (image);
}
}
return FALSE;
}
static gboolean
pika_component_editor_get_iter (PikaComponentEditor *editor,
PikaChannelType channel,
GtkTreeIter *iter)
{
gint index;
index = pika_image_get_component_index (PIKA_IMAGE_EDITOR (editor)->image,
channel);
if (index != -1)
return gtk_tree_model_iter_nth_child (editor->model, iter, NULL, index);
return FALSE;
}
static void
pika_component_editor_renderer_update (PikaViewRenderer *renderer,
PikaComponentEditor *editor)
{
PikaChannelType channel = PIKA_VIEW_RENDERER_IMAGE (renderer)->channel;
GtkTreeIter iter;
if (pika_component_editor_get_iter (editor, channel, &iter))
{
GtkTreePath *path;
path = gtk_tree_model_get_path (editor->model, &iter);
gtk_tree_model_row_changed (editor->model, path, &iter);
gtk_tree_path_free (path);
}
}
static void
pika_component_editor_mode_changed (PikaImage *image,
PikaComponentEditor *editor)
{
pika_component_editor_clear_components (editor);
pika_component_editor_create_components (editor);
}
static void
pika_component_editor_alpha_changed (PikaImage *image,
PikaComponentEditor *editor)
{
pika_component_editor_clear_components (editor);
pika_component_editor_create_components (editor);
}
static void
pika_component_editor_visibility_changed (PikaImage *image,
PikaChannelType channel,
PikaComponentEditor *editor)
{
GtkTreeIter iter;
if (pika_component_editor_get_iter (editor, channel, &iter))
{
gboolean visible = pika_image_get_component_visible (image, channel);
gtk_list_store_set (GTK_LIST_STORE (editor->model), &iter,
COLUMN_VISIBLE, visible,
-1);
}
}
static void
pika_component_editor_active_changed (PikaImage *image,
PikaChannelType channel,
PikaComponentEditor *editor)
{
GtkTreeIter iter;
if (pika_component_editor_get_iter (editor, channel, &iter))
{
gboolean active = pika_image_get_component_active (image, channel);
if (gtk_tree_selection_iter_is_selected (editor->selection, &iter) !=
active)
{
if (active)
gtk_tree_selection_select_iter (editor->selection, &iter);
else
gtk_tree_selection_unselect_iter (editor->selection, &iter);
}
}
}
static PikaImage *
pika_component_editor_drag_component (GtkWidget *widget,
PikaContext **context,
PikaChannelType *channel,
gpointer data)
{
PikaComponentEditor *editor = PIKA_COMPONENT_EDITOR (data);
if (PIKA_IMAGE_EDITOR (editor)->image &&
editor->clicked_component != -1)
{
if (channel)
*channel = editor->clicked_component;
if (context)
*context = PIKA_IMAGE_EDITOR (editor)->context;
return PIKA_IMAGE_EDITOR (editor)->image;
}
return NULL;
}