/* 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 . */ #include "config.h" #include #include #include #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"); }