PIKApp/app/core/pikaundo.c

589 lines
17 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 <time.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#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");
}