PIKApp/app/dialogs/layer-options-dialog.c

578 lines
24 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
*
* 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 "libpikamath/pikamath.h"
#include "libpikawidgets/pikawidgets.h"
#include "dialogs-types.h"
#include "operations/layer-modes/pika-layer-modes.h"
#include "core/pikacontext.h"
#include "core/pikadrawable-filters.h"
#include "core/pikaimage.h"
#include "core/pikalayer.h"
#include "text/pikatext.h"
#include "text/pikatextlayer.h"
#include "widgets/pikacontainertreeview.h"
#include "widgets/pikalayermodebox.h"
#include "widgets/pikaviewabledialog.h"
#include "item-options-dialog.h"
#include "layer-options-dialog.h"
#include "pika-intl.h"
typedef struct _LayerOptionsDialog LayerOptionsDialog;
struct _LayerOptionsDialog
{
PikaLayer *layer;
PikaLayerMode mode;
PikaLayerColorSpace blend_space;
PikaLayerColorSpace composite_space;
PikaLayerCompositeMode composite_mode;
gdouble opacity;
PikaFillType fill_type;
gboolean lock_alpha;
gboolean rename_text_layers;
PikaLayerOptionsCallback callback;
gpointer user_data;
GtkWidget *mode_box;
GtkWidget *blend_space_combo;
GtkWidget *composite_space_combo;
GtkWidget *composite_mode_combo;
GtkWidget *size_se;
GtkWidget *offset_se;
};
/* local function prototypes */
static void layer_options_dialog_free (LayerOptionsDialog *private);
static void layer_options_dialog_callback (GtkWidget *dialog,
PikaImage *image,
PikaItem *item,
PikaContext *context,
const gchar *item_name,
gboolean item_visible,
PikaColorTag item_color_tag,
gboolean item_lock_content,
gboolean item_lock_position,
gboolean item_lock_visibility,
gpointer user_data);
static void
layer_options_dialog_update_mode_sensitivity (LayerOptionsDialog *private);
static void layer_options_dialog_mode_notify (GtkWidget *widget,
const GParamSpec *pspec,
LayerOptionsDialog *private);
static void layer_options_dialog_rename_toggled (GtkWidget *widget,
LayerOptionsDialog *private);
/* public functions */
GtkWidget *
layer_options_dialog_new (PikaImage *image,
PikaLayer *layer,
PikaContext *context,
GtkWidget *parent,
const gchar *title,
const gchar *role,
const gchar *icon_name,
const gchar *desc,
const gchar *help_id,
const gchar *layer_name,
PikaLayerMode layer_mode,
PikaLayerColorSpace layer_blend_space,
PikaLayerColorSpace layer_composite_space,
PikaLayerCompositeMode layer_composite_mode,
gdouble layer_opacity,
PikaFillType layer_fill_type,
gboolean layer_visible,
PikaColorTag layer_color_tag,
gboolean layer_lock_content,
gboolean layer_lock_position,
gboolean layer_lock_visibility,
gboolean layer_lock_alpha,
PikaLayerOptionsCallback callback,
gpointer user_data)
{
LayerOptionsDialog *private;
GtkWidget *dialog;
GtkWidget *grid;
GtkListStore *space_model;
GtkWidget *combo;
GtkWidget *scale;
GtkWidget *label;
GtkAdjustment *adjustment;
GtkWidget *spinbutton;
GtkWidget *button;
PikaLayerModeContext mode_context;
gdouble xres;
gdouble yres;
gint row = 0;
g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL);
g_return_val_if_fail (layer == NULL || PIKA_IS_LAYER (layer), NULL);
g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);
g_return_val_if_fail (GTK_IS_WIDGET (parent), NULL);
private = g_slice_new0 (LayerOptionsDialog);
private->layer = layer;
private->mode = layer_mode;
private->blend_space = layer_blend_space;
private->composite_space = layer_composite_space;
private->composite_mode = layer_composite_mode;
private->opacity = layer_opacity * 100.0;
private->fill_type = layer_fill_type;
private->lock_alpha = layer_lock_alpha;
private->rename_text_layers = FALSE;
private->callback = callback;
private->user_data = user_data;
if (layer && pika_item_is_text_layer (PIKA_ITEM (layer)))
private->rename_text_layers = PIKA_TEXT_LAYER (layer)->auto_rename;
dialog = item_options_dialog_new (image, PIKA_ITEM (layer), context,
parent, title, role,
icon_name, desc, help_id,
_("Layer _name:"),
PIKA_ICON_LOCK_CONTENT,
_("Lock _pixels"),
_("Lock position and _size"),
_("Lock visibility"),
layer_name,
layer_visible,
layer_color_tag,
layer_lock_content,
layer_lock_position,
layer_lock_visibility,
layer_options_dialog_callback,
private);
g_object_weak_ref (G_OBJECT (dialog),
(GWeakNotify) layer_options_dialog_free, private);
if (! layer || pika_viewable_get_children (PIKA_VIEWABLE (layer)) == NULL)
mode_context = PIKA_LAYER_MODE_CONTEXT_LAYER;
else
mode_context = PIKA_LAYER_MODE_CONTEXT_GROUP;
private->mode_box = pika_layer_mode_box_new (mode_context);
item_options_dialog_add_widget (dialog, _("_Mode:"), private->mode_box);
pika_layer_mode_box_set_mode (PIKA_LAYER_MODE_BOX (private->mode_box),
private->mode);
g_signal_connect (private->mode_box, "notify::layer-mode",
G_CALLBACK (layer_options_dialog_mode_notify),
private);
space_model =
pika_enum_store_new_with_range (PIKA_TYPE_LAYER_COLOR_SPACE,
PIKA_LAYER_COLOR_SPACE_AUTO,
PIKA_LAYER_COLOR_SPACE_RGB_PERCEPTUAL);
private->blend_space_combo = combo =
pika_enum_combo_box_new_with_model (PIKA_ENUM_STORE (space_model));
item_options_dialog_add_widget (dialog, _("_Blend space:"), combo);
pika_enum_combo_box_set_icon_prefix (PIKA_ENUM_COMBO_BOX (combo),
"pika-layer-color-space");
pika_int_combo_box_connect (PIKA_INT_COMBO_BOX (combo),
private->blend_space,
G_CALLBACK (pika_int_combo_box_get_active),
&private->blend_space, NULL);
private->composite_space_combo = combo =
pika_enum_combo_box_new_with_model (PIKA_ENUM_STORE (space_model));
item_options_dialog_add_widget (dialog, _("Compos_ite space:"), combo);
pika_enum_combo_box_set_icon_prefix (PIKA_ENUM_COMBO_BOX (combo),
"pika-layer-color-space");
pika_int_combo_box_connect (PIKA_INT_COMBO_BOX (combo),
private->composite_space,
G_CALLBACK (pika_int_combo_box_get_active),
&private->composite_space, NULL);
g_object_unref (space_model);
private->composite_mode_combo = combo =
pika_enum_combo_box_new (PIKA_TYPE_LAYER_COMPOSITE_MODE);
item_options_dialog_add_widget (dialog, _("Composite mo_de:"), combo);
pika_enum_combo_box_set_icon_prefix (PIKA_ENUM_COMBO_BOX (combo),
"pika-layer-composite");
pika_int_combo_box_connect (PIKA_INT_COMBO_BOX (combo),
private->composite_mode,
G_CALLBACK (pika_int_combo_box_get_active),
&private->composite_mode, NULL);
/* set the sensitivity of above 3 menus */
layer_options_dialog_update_mode_sensitivity (private);
adjustment = gtk_adjustment_new (private->opacity, 0.0, 100.0,
1.0, 10.0, 0.0);
scale = pika_spin_scale_new (adjustment, NULL, 1);
item_options_dialog_add_widget (dialog, _("_Opacity:"), scale);
g_signal_connect (adjustment, "value-changed",
G_CALLBACK (pika_double_adjustment_update),
&private->opacity);
grid = item_options_dialog_get_grid (dialog, &row);
pika_image_get_resolution (image, &xres, &yres);
if (! layer)
{
/* The size labels */
label = gtk_label_new (_("Width:"));
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_grid_attach (GTK_GRID (grid), label, 0, row, 1, 1);
gtk_widget_show (label);
label = gtk_label_new (_("Height:"));
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_grid_attach (GTK_GRID (grid), label, 0, row + 1, 1, 1);
gtk_widget_show (label);
/* The size sizeentry */
adjustment = gtk_adjustment_new (1, 1, 1, 1, 10, 0);
spinbutton = pika_spin_button_new (adjustment, 1.0, 2);
gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), 10);
private->size_se = pika_size_entry_new (1, PIKA_UNIT_PIXEL, "%a",
TRUE, TRUE, FALSE, 10,
PIKA_SIZE_ENTRY_UPDATE_SIZE);
pika_size_entry_add_field (PIKA_SIZE_ENTRY (private->size_se),
GTK_SPIN_BUTTON (spinbutton), NULL);
gtk_grid_attach (GTK_GRID (private->size_se), spinbutton, 1, 0, 1, 1);
gtk_widget_show (spinbutton);
gtk_grid_attach (GTK_GRID (grid), private->size_se, 1, row, 1, 2);
gtk_widget_show (private->size_se);
pika_size_entry_set_unit (PIKA_SIZE_ENTRY (private->size_se),
PIKA_UNIT_PIXEL);
pika_size_entry_set_resolution (PIKA_SIZE_ENTRY (private->size_se), 0,
xres, FALSE);
pika_size_entry_set_resolution (PIKA_SIZE_ENTRY (private->size_se), 1,
yres, FALSE);
pika_size_entry_set_refval_boundaries (PIKA_SIZE_ENTRY (private->size_se), 0,
PIKA_MIN_IMAGE_SIZE,
PIKA_MAX_IMAGE_SIZE);
pika_size_entry_set_refval_boundaries (PIKA_SIZE_ENTRY (private->size_se), 1,
PIKA_MIN_IMAGE_SIZE,
PIKA_MAX_IMAGE_SIZE);
pika_size_entry_set_size (PIKA_SIZE_ENTRY (private->size_se), 0,
0, pika_image_get_width (image));
pika_size_entry_set_size (PIKA_SIZE_ENTRY (private->size_se), 1,
0, pika_image_get_height (image));
pika_size_entry_set_refval (PIKA_SIZE_ENTRY (private->size_se), 0,
pika_image_get_width (image));
pika_size_entry_set_refval (PIKA_SIZE_ENTRY (private->size_se), 1,
pika_image_get_height (image));
row += 2;
}
/* The offset labels */
label = gtk_label_new (_("Offset X:"));
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_grid_attach (GTK_GRID (grid), label, 0, row, 1, 1);
gtk_widget_show (label);
label = gtk_label_new (_("Offset Y:"));
gtk_label_set_xalign (GTK_LABEL (label), 0.0);
gtk_grid_attach (GTK_GRID (grid), label, 0, row + 1, 1, 1);
gtk_widget_show (label);
/* The offset sizeentry */
adjustment = gtk_adjustment_new (0, 1, 1, 1, 10, 0);
spinbutton = pika_spin_button_new (adjustment, 1.0, 2);
gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
gtk_entry_set_width_chars (GTK_ENTRY (spinbutton), 10);
private->offset_se = pika_size_entry_new (1, PIKA_UNIT_PIXEL, "%a",
TRUE, TRUE, FALSE, 10,
PIKA_SIZE_ENTRY_UPDATE_SIZE);
pika_size_entry_add_field (PIKA_SIZE_ENTRY (private->offset_se),
GTK_SPIN_BUTTON (spinbutton), NULL);
gtk_grid_attach (GTK_GRID (private->offset_se), spinbutton, 1, 0, 1, 1);
gtk_widget_show (spinbutton);
gtk_grid_attach (GTK_GRID (grid), private->offset_se, 1, row, 1, 2);
gtk_widget_show (private->offset_se);
pika_size_entry_set_unit (PIKA_SIZE_ENTRY (private->offset_se),
PIKA_UNIT_PIXEL);
pika_size_entry_set_resolution (PIKA_SIZE_ENTRY (private->offset_se), 0,
xres, FALSE);
pika_size_entry_set_resolution (PIKA_SIZE_ENTRY (private->offset_se), 1,
yres, FALSE);
pika_size_entry_set_refval_boundaries (PIKA_SIZE_ENTRY (private->offset_se), 0,
-PIKA_MAX_IMAGE_SIZE,
PIKA_MAX_IMAGE_SIZE);
pika_size_entry_set_refval_boundaries (PIKA_SIZE_ENTRY (private->offset_se), 1,
-PIKA_MAX_IMAGE_SIZE,
PIKA_MAX_IMAGE_SIZE);
pika_size_entry_set_size (PIKA_SIZE_ENTRY (private->offset_se), 0,
0, pika_image_get_width (image));
pika_size_entry_set_size (PIKA_SIZE_ENTRY (private->offset_se), 1,
0, pika_image_get_height (image));
if (layer)
{
pika_size_entry_set_refval (PIKA_SIZE_ENTRY (private->offset_se), 0,
pika_item_get_offset_x (PIKA_ITEM (layer)));
pika_size_entry_set_refval (PIKA_SIZE_ENTRY (private->offset_se), 1,
pika_item_get_offset_y (PIKA_ITEM (layer)));
}
else
{
pika_size_entry_set_refval (PIKA_SIZE_ENTRY (private->offset_se), 0, 0);
pika_size_entry_set_refval (PIKA_SIZE_ENTRY (private->offset_se), 1, 0);
}
row += 2;
if (! layer)
{
/* The fill type */
combo = pika_enum_combo_box_new (PIKA_TYPE_FILL_TYPE);
pika_grid_attach_aligned (GTK_GRID (grid), 0, row++,
_("_Fill with:"), 0.0, 0.5,
combo, 1);
pika_int_combo_box_connect (PIKA_INT_COMBO_BOX (combo),
private->fill_type,
G_CALLBACK (pika_int_combo_box_get_active),
&private->fill_type, NULL);
}
if (layer)
{
GtkWidget *left_vbox = item_options_dialog_get_vbox (dialog);
GtkWidget *frame;
PikaContainer *filters;
GtkWidget *view;
frame = pika_frame_new (_("Active Filters"));
gtk_box_pack_start (GTK_BOX (left_vbox), frame, TRUE, TRUE, 0);
gtk_widget_show (frame);
filters = pika_drawable_get_filters (PIKA_DRAWABLE (layer));
view = pika_container_tree_view_new (filters, context,
PIKA_VIEW_SIZE_SMALL, 0);
gtk_container_add (GTK_CONTAINER (frame), view);
gtk_widget_show (view);
}
button = item_options_dialog_get_lock_position (dialog);
if (private->size_se)
g_object_bind_property (G_OBJECT (button), "active",
G_OBJECT (private->size_se), "sensitive",
G_BINDING_SYNC_CREATE |
G_BINDING_INVERT_BOOLEAN);
g_object_bind_property (G_OBJECT (button), "active",
G_OBJECT (private->offset_se), "sensitive",
G_BINDING_SYNC_CREATE |
G_BINDING_INVERT_BOOLEAN);
button = item_options_dialog_add_switch (dialog,
PIKA_ICON_LOCK_ALPHA,
_("Lock _alpha"));
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
private->lock_alpha);
g_signal_connect (button, "toggled",
G_CALLBACK (pika_toggle_button_update),
&private->lock_alpha);
/* For text layers add a toggle to control "auto-rename" */
if (layer && pika_item_is_text_layer (PIKA_ITEM (layer)))
{
button = item_options_dialog_add_switch (dialog,
PIKA_ICON_TOOL_TEXT,
_("Set name from _text"));
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
private->rename_text_layers);
g_signal_connect (button, "toggled",
G_CALLBACK (pika_toggle_button_update),
&private->rename_text_layers);
g_signal_connect (button, "toggled",
G_CALLBACK (layer_options_dialog_rename_toggled),
private);
}
return dialog;
}
/* private functions */
static void
layer_options_dialog_free (LayerOptionsDialog *private)
{
g_slice_free (LayerOptionsDialog, private);
}
static void
layer_options_dialog_callback (GtkWidget *dialog,
PikaImage *image,
PikaItem *item,
PikaContext *context,
const gchar *item_name,
gboolean item_visible,
PikaColorTag item_color_tag,
gboolean item_lock_content,
gboolean item_lock_position,
gboolean item_lock_visibility,
gpointer user_data)
{
LayerOptionsDialog *private = user_data;
gint width = 0;
gint height = 0;
gint offset_x;
gint offset_y;
if (private->size_se)
{
width =
RINT (pika_size_entry_get_refval (PIKA_SIZE_ENTRY (private->size_se),
0));
height =
RINT (pika_size_entry_get_refval (PIKA_SIZE_ENTRY (private->size_se),
1));
}
offset_x =
RINT (pika_size_entry_get_refval (PIKA_SIZE_ENTRY (private->offset_se),
0));
offset_y =
RINT (pika_size_entry_get_refval (PIKA_SIZE_ENTRY (private->offset_se),
1));
private->callback (dialog,
image,
PIKA_LAYER (item),
context,
item_name,
private->mode,
private->blend_space,
private->composite_space,
private->composite_mode,
private->opacity / 100.0,
private->fill_type,
width,
height,
offset_x,
offset_y,
item_visible,
item_color_tag,
item_lock_content,
item_lock_position,
item_lock_visibility,
private->lock_alpha,
private->rename_text_layers,
private->user_data);
}
static void
layer_options_dialog_update_mode_sensitivity (LayerOptionsDialog *private)
{
gboolean mutable;
mutable = pika_layer_mode_is_blend_space_mutable (private->mode);
gtk_widget_set_sensitive (private->blend_space_combo, mutable);
mutable = pika_layer_mode_is_composite_space_mutable (private->mode);
gtk_widget_set_sensitive (private->composite_space_combo, mutable);
mutable = pika_layer_mode_is_composite_mode_mutable (private->mode);
gtk_widget_set_sensitive (private->composite_mode_combo, mutable);
}
static void
layer_options_dialog_mode_notify (GtkWidget *widget,
const GParamSpec *pspec,
LayerOptionsDialog *private)
{
private->mode = pika_layer_mode_box_get_mode (PIKA_LAYER_MODE_BOX (widget));
pika_int_combo_box_set_active (PIKA_INT_COMBO_BOX (private->blend_space_combo),
PIKA_LAYER_COLOR_SPACE_AUTO);
pika_int_combo_box_set_active (PIKA_INT_COMBO_BOX (private->composite_space_combo),
PIKA_LAYER_COLOR_SPACE_AUTO);
pika_int_combo_box_set_active (PIKA_INT_COMBO_BOX (private->composite_mode_combo),
PIKA_LAYER_COMPOSITE_AUTO);
layer_options_dialog_update_mode_sensitivity (private);
}
static void
layer_options_dialog_rename_toggled (GtkWidget *widget,
LayerOptionsDialog *private)
{
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)) &&
pika_item_is_text_layer (PIKA_ITEM (private->layer)))
{
PikaTextLayer *text_layer = PIKA_TEXT_LAYER (private->layer);
PikaText *text = pika_text_layer_get_text (text_layer);
if (text && text->text)
{
GtkWidget *dialog;
GtkWidget *name_entry;
gchar *name = pika_utf8_strtrim (text->text, 30);
dialog = gtk_widget_get_toplevel (widget);
name_entry = item_options_dialog_get_name_entry (dialog);
gtk_entry_set_text (GTK_ENTRY (name_entry), name);
g_free (name);
}
}
}