/* 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 . */ #include "config.h" #include #include #include #include "libpikabase/pikabase.h" #include "core-types.h" #include "config/pikacoreconfig.h" #include "pika.h" #include "pikacontext.h" #include "pikaimage.h" #include "pikaimage-undo.h" #include "pikamarshal.h" #include "pikatempbuf.h" #include "pikaundo.h" #include "pikaundostack.h" #include "pika-priorities.h" #include "pika-intl.h" enum { POP, FREE, LAST_SIGNAL }; enum { PROP_0, PROP_IMAGE, PROP_TIME, PROP_UNDO_TYPE, PROP_DIRTY_MASK }; static void pika_undo_constructed (GObject *object); static void pika_undo_finalize (GObject *object); static void pika_undo_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void pika_undo_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static gint64 pika_undo_get_memsize (PikaObject *object, gint64 *gui_size); static gboolean pika_undo_get_popup_size (PikaViewable *viewable, gint width, gint height, gboolean dot_for_dot, gint *popup_width, gint *popup_height); static PikaTempBuf * pika_undo_get_new_preview (PikaViewable *viewable, PikaContext *context, gint width, gint height); static void pika_undo_real_pop (PikaUndo *undo, PikaUndoMode undo_mode, PikaUndoAccumulator *accum); static void pika_undo_real_free (PikaUndo *undo, PikaUndoMode undo_mode); static gboolean pika_undo_create_preview_idle (gpointer data); static void pika_undo_create_preview_private (PikaUndo *undo, PikaContext *context); G_DEFINE_TYPE (PikaUndo, pika_undo, PIKA_TYPE_VIEWABLE) #define parent_class pika_undo_parent_class static guint undo_signals[LAST_SIGNAL] = { 0 }; static void pika_undo_class_init (PikaUndoClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaObjectClass *pika_object_class = PIKA_OBJECT_CLASS (klass); PikaViewableClass *viewable_class = PIKA_VIEWABLE_CLASS (klass); undo_signals[POP] = g_signal_new ("pop", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaUndoClass, pop), NULL, NULL, pika_marshal_VOID__ENUM_POINTER, G_TYPE_NONE, 2, PIKA_TYPE_UNDO_MODE, G_TYPE_POINTER); undo_signals[FREE] = g_signal_new ("free", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaUndoClass, free), NULL, NULL, NULL, G_TYPE_NONE, 1, PIKA_TYPE_UNDO_MODE); object_class->constructed = pika_undo_constructed; object_class->finalize = pika_undo_finalize; object_class->set_property = pika_undo_set_property; object_class->get_property = pika_undo_get_property; pika_object_class->get_memsize = pika_undo_get_memsize; viewable_class->default_icon_name = "edit-undo"; viewable_class->get_popup_size = pika_undo_get_popup_size; viewable_class->get_new_preview = pika_undo_get_new_preview; klass->pop = pika_undo_real_pop; klass->free = pika_undo_real_free; g_object_class_install_property (object_class, PROP_IMAGE, g_param_spec_object ("image", NULL, NULL, PIKA_TYPE_IMAGE, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_TIME, g_param_spec_uint ("time", NULL, NULL, 0, G_MAXUINT, 0, PIKA_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_UNDO_TYPE, g_param_spec_enum ("undo-type", NULL, NULL, PIKA_TYPE_UNDO_TYPE, PIKA_UNDO_GROUP_NONE, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_DIRTY_MASK, g_param_spec_flags ("dirty-mask", NULL, NULL, PIKA_TYPE_DIRTY_MASK, PIKA_DIRTY_NONE, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } static void pika_undo_init (PikaUndo *undo) { undo->time = time (NULL); } static void pika_undo_constructed (GObject *object) { PikaUndo *undo = PIKA_UNDO (object); G_OBJECT_CLASS (parent_class)->constructed (object); pika_assert (PIKA_IS_IMAGE (undo->image)); } static void pika_undo_finalize (GObject *object) { PikaUndo *undo = PIKA_UNDO (object); if (undo->preview_idle_id) { g_source_remove (undo->preview_idle_id); undo->preview_idle_id = 0; } g_clear_pointer (&undo->preview, pika_temp_buf_unref); G_OBJECT_CLASS (parent_class)->finalize (object); } static void pika_undo_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PikaUndo *undo = PIKA_UNDO (object); switch (property_id) { case PROP_IMAGE: /* don't ref */ undo->image = g_value_get_object (value); break; case PROP_TIME: undo->time = g_value_get_uint (value); break; case PROP_UNDO_TYPE: undo->undo_type = g_value_get_enum (value); break; case PROP_DIRTY_MASK: undo->dirty_mask = g_value_get_flags (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void pika_undo_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PikaUndo *undo = PIKA_UNDO (object); switch (property_id) { case PROP_IMAGE: g_value_set_object (value, undo->image); break; case PROP_TIME: g_value_set_uint (value, undo->time); break; case PROP_UNDO_TYPE: g_value_set_enum (value, undo->undo_type); break; case PROP_DIRTY_MASK: g_value_set_flags (value, undo->dirty_mask); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static gint64 pika_undo_get_memsize (PikaObject *object, gint64 *gui_size) { PikaUndo *undo = PIKA_UNDO (object); gint64 memsize = 0; *gui_size += pika_temp_buf_get_memsize (undo->preview); return memsize + PIKA_OBJECT_CLASS (parent_class)->get_memsize (object, gui_size); } static gboolean pika_undo_get_popup_size (PikaViewable *viewable, gint width, gint height, gboolean dot_for_dot, gint *popup_width, gint *popup_height) { PikaUndo *undo = PIKA_UNDO (viewable); if (undo->preview && (pika_temp_buf_get_width (undo->preview) > width || pika_temp_buf_get_height (undo->preview) > height)) { *popup_width = pika_temp_buf_get_width (undo->preview); *popup_height = pika_temp_buf_get_height (undo->preview); return TRUE; } return FALSE; } static PikaTempBuf * pika_undo_get_new_preview (PikaViewable *viewable, PikaContext *context, gint width, gint height) { PikaUndo *undo = PIKA_UNDO (viewable); if (undo->preview) { gint preview_width; gint preview_height; pika_viewable_calc_preview_size (pika_temp_buf_get_width (undo->preview), pika_temp_buf_get_height (undo->preview), width, height, TRUE, 1.0, 1.0, &preview_width, &preview_height, NULL); if (preview_width < pika_temp_buf_get_width (undo->preview) && preview_height < pika_temp_buf_get_height (undo->preview)) { return pika_temp_buf_scale (undo->preview, preview_width, preview_height); } return pika_temp_buf_copy (undo->preview); } return NULL; } static void pika_undo_real_pop (PikaUndo *undo, PikaUndoMode undo_mode, PikaUndoAccumulator *accum) { } static void pika_undo_real_free (PikaUndo *undo, PikaUndoMode undo_mode) { } void pika_undo_pop (PikaUndo *undo, PikaUndoMode undo_mode, PikaUndoAccumulator *accum) { g_return_if_fail (PIKA_IS_UNDO (undo)); g_return_if_fail (accum != NULL); if (undo->dirty_mask != PIKA_DIRTY_NONE) { switch (undo_mode) { case PIKA_UNDO_MODE_UNDO: pika_image_clean (undo->image, undo->dirty_mask); break; case PIKA_UNDO_MODE_REDO: pika_image_dirty (undo->image, undo->dirty_mask); break; } } g_signal_emit (undo, undo_signals[POP], 0, undo_mode, accum); } void pika_undo_free (PikaUndo *undo, PikaUndoMode undo_mode) { g_return_if_fail (PIKA_IS_UNDO (undo)); g_signal_emit (undo, undo_signals[FREE], 0, undo_mode); } typedef struct _PikaUndoIdle PikaUndoIdle; struct _PikaUndoIdle { PikaUndo *undo; PikaContext *context; }; static void pika_undo_idle_free (PikaUndoIdle *idle) { if (idle->context) g_object_unref (idle->context); g_slice_free (PikaUndoIdle, idle); } void pika_undo_create_preview (PikaUndo *undo, PikaContext *context, gboolean create_now) { g_return_if_fail (PIKA_IS_UNDO (undo)); g_return_if_fail (context == NULL || PIKA_IS_CONTEXT (context)); if (undo->preview || undo->preview_idle_id) return; if (create_now) { pika_undo_create_preview_private (undo, context); } else { PikaUndoIdle *idle = g_slice_new0 (PikaUndoIdle); idle->undo = undo; if (context) idle->context = g_object_ref (context); undo->preview_idle_id = g_idle_add_full (PIKA_PRIORITY_VIEWABLE_IDLE, pika_undo_create_preview_idle, idle, (GDestroyNotify) pika_undo_idle_free); } } static gboolean pika_undo_create_preview_idle (gpointer data) { PikaUndoIdle *idle = data; PikaUndoStack *stack = pika_image_get_undo_stack (idle->undo->image); if (idle->undo == pika_undo_stack_peek (stack)) { pika_undo_create_preview_private (idle->undo, idle->context); } idle->undo->preview_idle_id = 0; return FALSE; } static void pika_undo_create_preview_private (PikaUndo *undo, PikaContext *context) { PikaImage *image = undo->image; PikaViewable *preview_viewable; PikaViewSize preview_size; gint width; gint height; switch (undo->undo_type) { case PIKA_UNDO_GROUP_IMAGE_QUICK_MASK: case PIKA_UNDO_GROUP_MASK: case PIKA_UNDO_MASK: preview_viewable = PIKA_VIEWABLE (pika_image_get_mask (image)); break; default: preview_viewable = PIKA_VIEWABLE (image); break; } preview_size = image->pika->config->undo_preview_size; if (pika_image_get_width (image) <= preview_size && pika_image_get_height (image) <= preview_size) { width = pika_image_get_width (image); height = pika_image_get_height (image); } else { if (pika_image_get_width (image) > pika_image_get_height (image)) { width = preview_size; height = MAX (1, (pika_image_get_height (image) * preview_size / pika_image_get_width (image))); } else { height = preview_size; width = MAX (1, (pika_image_get_width (image) * preview_size / pika_image_get_height (image))); } } undo->preview = pika_viewable_get_new_preview (preview_viewable, context, width, height); pika_viewable_invalidate_preview (PIKA_VIEWABLE (undo)); } void pika_undo_refresh_preview (PikaUndo *undo, PikaContext *context) { g_return_if_fail (PIKA_IS_UNDO (undo)); g_return_if_fail (context == NULL || PIKA_IS_CONTEXT (context)); if (undo->preview_idle_id) return; if (undo->preview) { g_clear_pointer (&undo->preview, pika_temp_buf_unref); pika_undo_create_preview (undo, context, FALSE); } } const gchar * pika_undo_type_to_name (PikaUndoType type) { const gchar *desc; if (pika_enum_get_value (PIKA_TYPE_UNDO_TYPE, type, NULL, NULL, &desc, NULL)) return desc; else return ""; } gboolean pika_undo_is_weak (PikaUndo *undo) { if (! undo) return FALSE; switch (undo->undo_type) { case PIKA_UNDO_GROUP_ITEM_VISIBILITY: case PIKA_UNDO_GROUP_ITEM_PROPERTIES: case PIKA_UNDO_GROUP_LAYER_APPLY_MASK: case PIKA_UNDO_ITEM_VISIBILITY: case PIKA_UNDO_LAYER_MODE: case PIKA_UNDO_LAYER_OPACITY: case PIKA_UNDO_LAYER_MASK_APPLY: case PIKA_UNDO_LAYER_MASK_SHOW: return TRUE; break; default: break; } return FALSE; } /** * pika_undo_get_age: * @undo: * * Returns: the time in seconds since this undo item was created */ gint pika_undo_get_age (PikaUndo *undo) { guint now = time (NULL); g_return_val_if_fail (PIKA_IS_UNDO (undo), 0); g_return_val_if_fail (now >= undo->time, 0); return now - undo->time; } /** * pika_undo_reset_age: * @undo: * * Changes the creation time of this undo item to the current time. */ void pika_undo_reset_age (PikaUndo *undo) { g_return_if_fail (PIKA_IS_UNDO (undo)); undo->time = time (NULL); g_object_notify (G_OBJECT (undo), "time"); }