diff options
Diffstat (limited to 'app/core/gimpundo.c')
-rw-r--r-- | app/core/gimpundo.c | 585 |
1 files changed, 585 insertions, 0 deletions
diff --git a/app/core/gimpundo.c b/app/core/gimpundo.c new file mode 100644 index 0000000..68bbae0 --- /dev/null +++ b/app/core/gimpundo.c @@ -0,0 +1,585 @@ +/* GIMP - The GNU Image Manipulation Program + * 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 "libgimpbase/gimpbase.h" + +#include "core-types.h" + +#include "config/gimpcoreconfig.h" + +#include "gimp.h" +#include "gimpcontext.h" +#include "gimpimage.h" +#include "gimpimage-undo.h" +#include "gimpmarshal.h" +#include "gimptempbuf.h" +#include "gimpundo.h" +#include "gimpundostack.h" + +#include "gimp-priorities.h" + +#include "gimp-intl.h" + + +enum +{ + POP, + FREE, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_IMAGE, + PROP_TIME, + PROP_UNDO_TYPE, + PROP_DIRTY_MASK +}; + + +static void gimp_undo_constructed (GObject *object); +static void gimp_undo_finalize (GObject *object); +static void gimp_undo_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_undo_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gint64 gimp_undo_get_memsize (GimpObject *object, + gint64 *gui_size); + +static gboolean gimp_undo_get_popup_size (GimpViewable *viewable, + gint width, + gint height, + gboolean dot_for_dot, + gint *popup_width, + gint *popup_height); +static GimpTempBuf * gimp_undo_get_new_preview (GimpViewable *viewable, + GimpContext *context, + gint width, + gint height); + +static void gimp_undo_real_pop (GimpUndo *undo, + GimpUndoMode undo_mode, + GimpUndoAccumulator *accum); +static void gimp_undo_real_free (GimpUndo *undo, + GimpUndoMode undo_mode); + +static gboolean gimp_undo_create_preview_idle (gpointer data); +static void gimp_undo_create_preview_private (GimpUndo *undo, + GimpContext *context); + + +G_DEFINE_TYPE (GimpUndo, gimp_undo, GIMP_TYPE_VIEWABLE) + +#define parent_class gimp_undo_parent_class + +static guint undo_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_undo_class_init (GimpUndoClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass); + GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass); + + undo_signals[POP] = + g_signal_new ("pop", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpUndoClass, pop), + NULL, NULL, + gimp_marshal_VOID__ENUM_POINTER, + G_TYPE_NONE, 2, + GIMP_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 (GimpUndoClass, free), + NULL, NULL, + gimp_marshal_VOID__ENUM, + G_TYPE_NONE, 1, + GIMP_TYPE_UNDO_MODE); + + object_class->constructed = gimp_undo_constructed; + object_class->finalize = gimp_undo_finalize; + object_class->set_property = gimp_undo_set_property; + object_class->get_property = gimp_undo_get_property; + + gimp_object_class->get_memsize = gimp_undo_get_memsize; + + viewable_class->default_icon_name = "edit-undo"; + viewable_class->get_popup_size = gimp_undo_get_popup_size; + viewable_class->get_new_preview = gimp_undo_get_new_preview; + + klass->pop = gimp_undo_real_pop; + klass->free = gimp_undo_real_free; + + g_object_class_install_property (object_class, PROP_IMAGE, + g_param_spec_object ("image", NULL, NULL, + GIMP_TYPE_IMAGE, + GIMP_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, + GIMP_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_UNDO_TYPE, + g_param_spec_enum ("undo-type", NULL, NULL, + GIMP_TYPE_UNDO_TYPE, + GIMP_UNDO_GROUP_NONE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_DIRTY_MASK, + g_param_spec_flags ("dirty-mask", + NULL, NULL, + GIMP_TYPE_DIRTY_MASK, + GIMP_DIRTY_NONE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_undo_init (GimpUndo *undo) +{ + undo->time = time (NULL); +} + +static void +gimp_undo_constructed (GObject *object) +{ + GimpUndo *undo = GIMP_UNDO (object); + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_IMAGE (undo->image)); +} + +static void +gimp_undo_finalize (GObject *object) +{ + GimpUndo *undo = GIMP_UNDO (object); + + if (undo->preview_idle_id) + { + g_source_remove (undo->preview_idle_id); + undo->preview_idle_id = 0; + } + + g_clear_pointer (&undo->preview, gimp_temp_buf_unref); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_undo_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpUndo *undo = GIMP_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 +gimp_undo_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpUndo *undo = GIMP_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 +gimp_undo_get_memsize (GimpObject *object, + gint64 *gui_size) +{ + GimpUndo *undo = GIMP_UNDO (object); + gint64 memsize = 0; + + *gui_size += gimp_temp_buf_get_memsize (undo->preview); + + return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object, + gui_size); +} + +static gboolean +gimp_undo_get_popup_size (GimpViewable *viewable, + gint width, + gint height, + gboolean dot_for_dot, + gint *popup_width, + gint *popup_height) +{ + GimpUndo *undo = GIMP_UNDO (viewable); + + if (undo->preview && + (gimp_temp_buf_get_width (undo->preview) > width || + gimp_temp_buf_get_height (undo->preview) > height)) + { + *popup_width = gimp_temp_buf_get_width (undo->preview); + *popup_height = gimp_temp_buf_get_height (undo->preview); + + return TRUE; + } + + return FALSE; +} + +static GimpTempBuf * +gimp_undo_get_new_preview (GimpViewable *viewable, + GimpContext *context, + gint width, + gint height) +{ + GimpUndo *undo = GIMP_UNDO (viewable); + + if (undo->preview) + { + gint preview_width; + gint preview_height; + + gimp_viewable_calc_preview_size (gimp_temp_buf_get_width (undo->preview), + gimp_temp_buf_get_height (undo->preview), + width, + height, + TRUE, 1.0, 1.0, + &preview_width, + &preview_height, + NULL); + + if (preview_width < gimp_temp_buf_get_width (undo->preview) && + preview_height < gimp_temp_buf_get_height (undo->preview)) + { + return gimp_temp_buf_scale (undo->preview, + preview_width, preview_height); + } + + return gimp_temp_buf_copy (undo->preview); + } + + return NULL; +} + +static void +gimp_undo_real_pop (GimpUndo *undo, + GimpUndoMode undo_mode, + GimpUndoAccumulator *accum) +{ +} + +static void +gimp_undo_real_free (GimpUndo *undo, + GimpUndoMode undo_mode) +{ +} + +void +gimp_undo_pop (GimpUndo *undo, + GimpUndoMode undo_mode, + GimpUndoAccumulator *accum) +{ + g_return_if_fail (GIMP_IS_UNDO (undo)); + g_return_if_fail (accum != NULL); + + if (undo->dirty_mask != GIMP_DIRTY_NONE) + { + switch (undo_mode) + { + case GIMP_UNDO_MODE_UNDO: + gimp_image_clean (undo->image, undo->dirty_mask); + break; + + case GIMP_UNDO_MODE_REDO: + gimp_image_dirty (undo->image, undo->dirty_mask); + break; + } + } + + g_signal_emit (undo, undo_signals[POP], 0, undo_mode, accum); +} + +void +gimp_undo_free (GimpUndo *undo, + GimpUndoMode undo_mode) +{ + g_return_if_fail (GIMP_IS_UNDO (undo)); + + g_signal_emit (undo, undo_signals[FREE], 0, undo_mode); +} + +typedef struct _GimpUndoIdle GimpUndoIdle; + +struct _GimpUndoIdle +{ + GimpUndo *undo; + GimpContext *context; +}; + +static void +gimp_undo_idle_free (GimpUndoIdle *idle) +{ + if (idle->context) + g_object_unref (idle->context); + + g_slice_free (GimpUndoIdle, idle); +} + +void +gimp_undo_create_preview (GimpUndo *undo, + GimpContext *context, + gboolean create_now) +{ + g_return_if_fail (GIMP_IS_UNDO (undo)); + g_return_if_fail (context == NULL || GIMP_IS_CONTEXT (context)); + + if (undo->preview || undo->preview_idle_id) + return; + + if (create_now) + { + gimp_undo_create_preview_private (undo, context); + } + else + { + GimpUndoIdle *idle = g_slice_new0 (GimpUndoIdle); + + idle->undo = undo; + + if (context) + idle->context = g_object_ref (context); + + undo->preview_idle_id = + g_idle_add_full (GIMP_PRIORITY_VIEWABLE_IDLE, + gimp_undo_create_preview_idle, idle, + (GDestroyNotify) gimp_undo_idle_free); + } +} + +static gboolean +gimp_undo_create_preview_idle (gpointer data) +{ + GimpUndoIdle *idle = data; + GimpUndoStack *stack = gimp_image_get_undo_stack (idle->undo->image); + + if (idle->undo == gimp_undo_stack_peek (stack)) + { + gimp_undo_create_preview_private (idle->undo, idle->context); + } + + idle->undo->preview_idle_id = 0; + + return FALSE; +} + +static void +gimp_undo_create_preview_private (GimpUndo *undo, + GimpContext *context) +{ + GimpImage *image = undo->image; + GimpViewable *preview_viewable; + GimpViewSize preview_size; + gint width; + gint height; + + switch (undo->undo_type) + { + case GIMP_UNDO_GROUP_IMAGE_QUICK_MASK: + case GIMP_UNDO_GROUP_MASK: + case GIMP_UNDO_MASK: + preview_viewable = GIMP_VIEWABLE (gimp_image_get_mask (image)); + break; + + default: + preview_viewable = GIMP_VIEWABLE (image); + break; + } + + preview_size = image->gimp->config->undo_preview_size; + + if (gimp_image_get_width (image) <= preview_size && + gimp_image_get_height (image) <= preview_size) + { + width = gimp_image_get_width (image); + height = gimp_image_get_height (image); + } + else + { + if (gimp_image_get_width (image) > gimp_image_get_height (image)) + { + width = preview_size; + height = MAX (1, (gimp_image_get_height (image) * preview_size / + gimp_image_get_width (image))); + } + else + { + height = preview_size; + width = MAX (1, (gimp_image_get_width (image) * preview_size / + gimp_image_get_height (image))); + } + } + + undo->preview = gimp_viewable_get_new_preview (preview_viewable, context, + width, height); + + gimp_viewable_invalidate_preview (GIMP_VIEWABLE (undo)); +} + +void +gimp_undo_refresh_preview (GimpUndo *undo, + GimpContext *context) +{ + g_return_if_fail (GIMP_IS_UNDO (undo)); + g_return_if_fail (context == NULL || GIMP_IS_CONTEXT (context)); + + if (undo->preview_idle_id) + return; + + if (undo->preview) + { + g_clear_pointer (&undo->preview, gimp_temp_buf_unref); + gimp_undo_create_preview (undo, context, FALSE); + } +} + +const gchar * +gimp_undo_type_to_name (GimpUndoType type) +{ + const gchar *desc; + + if (gimp_enum_get_value (GIMP_TYPE_UNDO_TYPE, type, NULL, NULL, &desc, NULL)) + return desc; + else + return ""; +} + +gboolean +gimp_undo_is_weak (GimpUndo *undo) +{ + if (! undo) + return FALSE; + + switch (undo->undo_type) + { + case GIMP_UNDO_GROUP_ITEM_VISIBILITY: + case GIMP_UNDO_GROUP_ITEM_PROPERTIES: + case GIMP_UNDO_GROUP_LAYER_APPLY_MASK: + case GIMP_UNDO_ITEM_VISIBILITY: + case GIMP_UNDO_LAYER_MODE: + case GIMP_UNDO_LAYER_OPACITY: + case GIMP_UNDO_LAYER_MASK_APPLY: + case GIMP_UNDO_LAYER_MASK_SHOW: + return TRUE; + break; + + default: + break; + } + + return FALSE; +} + +/** + * gimp_undo_get_age: + * @undo: + * + * Return value: the time in seconds since this undo item was created + */ +gint +gimp_undo_get_age (GimpUndo *undo) +{ + guint now = time (NULL); + + g_return_val_if_fail (GIMP_IS_UNDO (undo), 0); + g_return_val_if_fail (now >= undo->time, 0); + + return now - undo->time; +} + +/** + * gimp_undo_reset_age: + * @undo: + * + * Changes the creation time of this undo item to the current time. + */ +void +gimp_undo_reset_age (GimpUndo *undo) +{ + g_return_if_fail (GIMP_IS_UNDO (undo)); + + undo->time = time (NULL); + + g_object_notify (G_OBJECT (undo), "time"); +} |