1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
16  */
17 
18 #include "config.h"
19 
20 #include <time.h>
21 
22 #include <gdk-pixbuf/gdk-pixbuf.h>
23 #include <gegl.h>
24 
25 #include "libgimpbase/gimpbase.h"
26 
27 #include "core-types.h"
28 
29 #include "config/gimpcoreconfig.h"
30 
31 #include "gimp.h"
32 #include "gimpcontext.h"
33 #include "gimpimage.h"
34 #include "gimpimage-undo.h"
35 #include "gimpmarshal.h"
36 #include "gimptempbuf.h"
37 #include "gimpundo.h"
38 #include "gimpundostack.h"
39 
40 #include "gimp-priorities.h"
41 
42 #include "gimp-intl.h"
43 
44 
45 enum
46 {
47   POP,
48   FREE,
49   LAST_SIGNAL
50 };
51 
52 enum
53 {
54   PROP_0,
55   PROP_IMAGE,
56   PROP_TIME,
57   PROP_UNDO_TYPE,
58   PROP_DIRTY_MASK
59 };
60 
61 
62 static void          gimp_undo_constructed         (GObject             *object);
63 static void          gimp_undo_finalize            (GObject             *object);
64 static void          gimp_undo_set_property        (GObject             *object,
65                                                     guint                property_id,
66                                                     const GValue        *value,
67                                                     GParamSpec          *pspec);
68 static void          gimp_undo_get_property        (GObject             *object,
69                                                     guint                property_id,
70                                                     GValue              *value,
71                                                     GParamSpec          *pspec);
72 
73 static gint64        gimp_undo_get_memsize         (GimpObject          *object,
74                                                     gint64              *gui_size);
75 
76 static gboolean      gimp_undo_get_popup_size      (GimpViewable        *viewable,
77                                                     gint                 width,
78                                                     gint                 height,
79                                                     gboolean             dot_for_dot,
80                                                     gint                *popup_width,
81                                                     gint                *popup_height);
82 static GimpTempBuf * gimp_undo_get_new_preview     (GimpViewable        *viewable,
83                                                     GimpContext         *context,
84                                                     gint                 width,
85                                                     gint                 height);
86 
87 static void          gimp_undo_real_pop            (GimpUndo            *undo,
88                                                     GimpUndoMode         undo_mode,
89                                                     GimpUndoAccumulator *accum);
90 static void          gimp_undo_real_free           (GimpUndo            *undo,
91                                                     GimpUndoMode         undo_mode);
92 
93 static gboolean      gimp_undo_create_preview_idle (gpointer             data);
94 static void       gimp_undo_create_preview_private (GimpUndo            *undo,
95                                                     GimpContext         *context);
96 
97 
98 G_DEFINE_TYPE (GimpUndo, gimp_undo, GIMP_TYPE_VIEWABLE)
99 
100 #define parent_class gimp_undo_parent_class
101 
102 static guint undo_signals[LAST_SIGNAL] = { 0 };
103 
104 
105 static void
gimp_undo_class_init(GimpUndoClass * klass)106 gimp_undo_class_init (GimpUndoClass *klass)
107 {
108   GObjectClass      *object_class      = G_OBJECT_CLASS (klass);
109   GimpObjectClass   *gimp_object_class = GIMP_OBJECT_CLASS (klass);
110   GimpViewableClass *viewable_class    = GIMP_VIEWABLE_CLASS (klass);
111 
112   undo_signals[POP] =
113     g_signal_new ("pop",
114                   G_TYPE_FROM_CLASS (klass),
115                   G_SIGNAL_RUN_FIRST,
116                   G_STRUCT_OFFSET (GimpUndoClass, pop),
117                   NULL, NULL,
118                   gimp_marshal_VOID__ENUM_POINTER,
119                   G_TYPE_NONE, 2,
120                   GIMP_TYPE_UNDO_MODE,
121                   G_TYPE_POINTER);
122 
123   undo_signals[FREE] =
124     g_signal_new ("free",
125                   G_TYPE_FROM_CLASS (klass),
126                   G_SIGNAL_RUN_FIRST,
127                   G_STRUCT_OFFSET (GimpUndoClass, free),
128                   NULL, NULL,
129                   gimp_marshal_VOID__ENUM,
130                   G_TYPE_NONE, 1,
131                   GIMP_TYPE_UNDO_MODE);
132 
133   object_class->constructed         = gimp_undo_constructed;
134   object_class->finalize            = gimp_undo_finalize;
135   object_class->set_property        = gimp_undo_set_property;
136   object_class->get_property        = gimp_undo_get_property;
137 
138   gimp_object_class->get_memsize    = gimp_undo_get_memsize;
139 
140   viewable_class->default_icon_name = "edit-undo";
141   viewable_class->get_popup_size    = gimp_undo_get_popup_size;
142   viewable_class->get_new_preview   = gimp_undo_get_new_preview;
143 
144   klass->pop                        = gimp_undo_real_pop;
145   klass->free                       = gimp_undo_real_free;
146 
147   g_object_class_install_property (object_class, PROP_IMAGE,
148                                    g_param_spec_object ("image", NULL, NULL,
149                                                         GIMP_TYPE_IMAGE,
150                                                         GIMP_PARAM_READWRITE |
151                                                         G_PARAM_CONSTRUCT_ONLY));
152 
153   g_object_class_install_property (object_class, PROP_TIME,
154                                    g_param_spec_uint ("time", NULL, NULL,
155                                                       0, G_MAXUINT, 0,
156                                                       GIMP_PARAM_READWRITE));
157 
158   g_object_class_install_property (object_class, PROP_UNDO_TYPE,
159                                    g_param_spec_enum ("undo-type", NULL, NULL,
160                                                       GIMP_TYPE_UNDO_TYPE,
161                                                       GIMP_UNDO_GROUP_NONE,
162                                                       GIMP_PARAM_READWRITE |
163                                                       G_PARAM_CONSTRUCT_ONLY));
164 
165   g_object_class_install_property (object_class, PROP_DIRTY_MASK,
166                                    g_param_spec_flags ("dirty-mask",
167                                                        NULL, NULL,
168                                                        GIMP_TYPE_DIRTY_MASK,
169                                                        GIMP_DIRTY_NONE,
170                                                        GIMP_PARAM_READWRITE |
171                                                        G_PARAM_CONSTRUCT_ONLY));
172 }
173 
174 static void
gimp_undo_init(GimpUndo * undo)175 gimp_undo_init (GimpUndo *undo)
176 {
177   undo->time = time (NULL);
178 }
179 
180 static void
gimp_undo_constructed(GObject * object)181 gimp_undo_constructed (GObject *object)
182 {
183   GimpUndo *undo = GIMP_UNDO (object);
184 
185   G_OBJECT_CLASS (parent_class)->constructed (object);
186 
187   gimp_assert (GIMP_IS_IMAGE (undo->image));
188 }
189 
190 static void
gimp_undo_finalize(GObject * object)191 gimp_undo_finalize (GObject *object)
192 {
193   GimpUndo *undo = GIMP_UNDO (object);
194 
195   if (undo->preview_idle_id)
196     {
197       g_source_remove (undo->preview_idle_id);
198       undo->preview_idle_id = 0;
199     }
200 
201   g_clear_pointer (&undo->preview, gimp_temp_buf_unref);
202 
203   G_OBJECT_CLASS (parent_class)->finalize (object);
204 }
205 
206 static void
gimp_undo_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)207 gimp_undo_set_property (GObject      *object,
208                         guint         property_id,
209                         const GValue *value,
210                         GParamSpec   *pspec)
211 {
212   GimpUndo *undo = GIMP_UNDO (object);
213 
214   switch (property_id)
215     {
216     case PROP_IMAGE:
217       /* don't ref */
218       undo->image = g_value_get_object (value);
219       break;
220     case PROP_TIME:
221       undo->time = g_value_get_uint (value);
222       break;
223     case PROP_UNDO_TYPE:
224       undo->undo_type = g_value_get_enum (value);
225       break;
226     case PROP_DIRTY_MASK:
227       undo->dirty_mask = g_value_get_flags (value);
228       break;
229 
230     default:
231       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
232       break;
233     }
234 }
235 
236 static void
gimp_undo_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)237 gimp_undo_get_property (GObject    *object,
238                         guint       property_id,
239                         GValue     *value,
240                         GParamSpec *pspec)
241 {
242   GimpUndo *undo = GIMP_UNDO (object);
243 
244   switch (property_id)
245     {
246     case PROP_IMAGE:
247       g_value_set_object (value, undo->image);
248       break;
249     case PROP_TIME:
250       g_value_set_uint (value, undo->time);
251       break;
252     case PROP_UNDO_TYPE:
253       g_value_set_enum (value, undo->undo_type);
254       break;
255     case PROP_DIRTY_MASK:
256       g_value_set_flags (value, undo->dirty_mask);
257       break;
258 
259     default:
260       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
261       break;
262     }
263 }
264 
265 static gint64
gimp_undo_get_memsize(GimpObject * object,gint64 * gui_size)266 gimp_undo_get_memsize (GimpObject *object,
267                        gint64     *gui_size)
268 {
269   GimpUndo *undo    = GIMP_UNDO (object);
270   gint64    memsize = 0;
271 
272   *gui_size += gimp_temp_buf_get_memsize (undo->preview);
273 
274   return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
275                                                                   gui_size);
276 }
277 
278 static gboolean
gimp_undo_get_popup_size(GimpViewable * viewable,gint width,gint height,gboolean dot_for_dot,gint * popup_width,gint * popup_height)279 gimp_undo_get_popup_size (GimpViewable *viewable,
280                           gint          width,
281                           gint          height,
282                           gboolean      dot_for_dot,
283                           gint         *popup_width,
284                           gint         *popup_height)
285 {
286   GimpUndo *undo = GIMP_UNDO (viewable);
287 
288   if (undo->preview &&
289       (gimp_temp_buf_get_width  (undo->preview) > width ||
290        gimp_temp_buf_get_height (undo->preview) > height))
291     {
292       *popup_width  = gimp_temp_buf_get_width  (undo->preview);
293       *popup_height = gimp_temp_buf_get_height (undo->preview);
294 
295       return TRUE;
296     }
297 
298   return FALSE;
299 }
300 
301 static GimpTempBuf *
gimp_undo_get_new_preview(GimpViewable * viewable,GimpContext * context,gint width,gint height)302 gimp_undo_get_new_preview (GimpViewable *viewable,
303                            GimpContext  *context,
304                            gint          width,
305                            gint          height)
306 {
307   GimpUndo *undo = GIMP_UNDO (viewable);
308 
309   if (undo->preview)
310     {
311       gint preview_width;
312       gint preview_height;
313 
314       gimp_viewable_calc_preview_size (gimp_temp_buf_get_width  (undo->preview),
315                                        gimp_temp_buf_get_height (undo->preview),
316                                        width,
317                                        height,
318                                        TRUE, 1.0, 1.0,
319                                        &preview_width,
320                                        &preview_height,
321                                        NULL);
322 
323       if (preview_width  < gimp_temp_buf_get_width  (undo->preview) &&
324           preview_height < gimp_temp_buf_get_height (undo->preview))
325         {
326           return gimp_temp_buf_scale (undo->preview,
327                                       preview_width, preview_height);
328         }
329 
330       return gimp_temp_buf_copy (undo->preview);
331     }
332 
333   return NULL;
334 }
335 
336 static void
gimp_undo_real_pop(GimpUndo * undo,GimpUndoMode undo_mode,GimpUndoAccumulator * accum)337 gimp_undo_real_pop (GimpUndo            *undo,
338                     GimpUndoMode         undo_mode,
339                     GimpUndoAccumulator *accum)
340 {
341 }
342 
343 static void
gimp_undo_real_free(GimpUndo * undo,GimpUndoMode undo_mode)344 gimp_undo_real_free (GimpUndo     *undo,
345                      GimpUndoMode  undo_mode)
346 {
347 }
348 
349 void
gimp_undo_pop(GimpUndo * undo,GimpUndoMode undo_mode,GimpUndoAccumulator * accum)350 gimp_undo_pop (GimpUndo            *undo,
351                GimpUndoMode         undo_mode,
352                GimpUndoAccumulator *accum)
353 {
354   g_return_if_fail (GIMP_IS_UNDO (undo));
355   g_return_if_fail (accum != NULL);
356 
357   if (undo->dirty_mask != GIMP_DIRTY_NONE)
358     {
359       switch (undo_mode)
360         {
361         case GIMP_UNDO_MODE_UNDO:
362           gimp_image_clean (undo->image, undo->dirty_mask);
363           break;
364 
365         case GIMP_UNDO_MODE_REDO:
366           gimp_image_dirty (undo->image, undo->dirty_mask);
367           break;
368         }
369     }
370 
371   g_signal_emit (undo, undo_signals[POP], 0, undo_mode, accum);
372 }
373 
374 void
gimp_undo_free(GimpUndo * undo,GimpUndoMode undo_mode)375 gimp_undo_free (GimpUndo     *undo,
376                 GimpUndoMode  undo_mode)
377 {
378   g_return_if_fail (GIMP_IS_UNDO (undo));
379 
380   g_signal_emit (undo, undo_signals[FREE], 0, undo_mode);
381 }
382 
383 typedef struct _GimpUndoIdle GimpUndoIdle;
384 
385 struct _GimpUndoIdle
386 {
387   GimpUndo    *undo;
388   GimpContext *context;
389 };
390 
391 static void
gimp_undo_idle_free(GimpUndoIdle * idle)392 gimp_undo_idle_free (GimpUndoIdle *idle)
393 {
394   if (idle->context)
395     g_object_unref (idle->context);
396 
397   g_slice_free (GimpUndoIdle, idle);
398 }
399 
400 void
gimp_undo_create_preview(GimpUndo * undo,GimpContext * context,gboolean create_now)401 gimp_undo_create_preview (GimpUndo    *undo,
402                           GimpContext *context,
403                           gboolean     create_now)
404 {
405   g_return_if_fail (GIMP_IS_UNDO (undo));
406   g_return_if_fail (context == NULL || GIMP_IS_CONTEXT (context));
407 
408   if (undo->preview || undo->preview_idle_id)
409     return;
410 
411   if (create_now)
412     {
413       gimp_undo_create_preview_private (undo, context);
414     }
415   else
416     {
417       GimpUndoIdle *idle = g_slice_new0 (GimpUndoIdle);
418 
419       idle->undo = undo;
420 
421       if (context)
422         idle->context = g_object_ref (context);
423 
424       undo->preview_idle_id =
425         g_idle_add_full (GIMP_PRIORITY_VIEWABLE_IDLE,
426                          gimp_undo_create_preview_idle, idle,
427                          (GDestroyNotify) gimp_undo_idle_free);
428     }
429 }
430 
431 static gboolean
gimp_undo_create_preview_idle(gpointer data)432 gimp_undo_create_preview_idle (gpointer data)
433 {
434   GimpUndoIdle *idle   = data;
435   GimpUndoStack *stack = gimp_image_get_undo_stack (idle->undo->image);
436 
437   if (idle->undo == gimp_undo_stack_peek (stack))
438     {
439       gimp_undo_create_preview_private (idle->undo, idle->context);
440     }
441 
442   idle->undo->preview_idle_id = 0;
443 
444   return FALSE;
445 }
446 
447 static void
gimp_undo_create_preview_private(GimpUndo * undo,GimpContext * context)448 gimp_undo_create_preview_private (GimpUndo    *undo,
449                                   GimpContext *context)
450 {
451   GimpImage    *image = undo->image;
452   GimpViewable *preview_viewable;
453   GimpViewSize  preview_size;
454   gint          width;
455   gint          height;
456 
457   switch (undo->undo_type)
458     {
459     case GIMP_UNDO_GROUP_IMAGE_QUICK_MASK:
460     case GIMP_UNDO_GROUP_MASK:
461     case GIMP_UNDO_MASK:
462       preview_viewable = GIMP_VIEWABLE (gimp_image_get_mask (image));
463       break;
464 
465     default:
466       preview_viewable = GIMP_VIEWABLE (image);
467       break;
468     }
469 
470   preview_size = image->gimp->config->undo_preview_size;
471 
472   if (gimp_image_get_width  (image) <= preview_size &&
473       gimp_image_get_height (image) <= preview_size)
474     {
475       width  = gimp_image_get_width  (image);
476       height = gimp_image_get_height (image);
477     }
478   else
479     {
480       if (gimp_image_get_width (image) > gimp_image_get_height (image))
481         {
482           width  = preview_size;
483           height = MAX (1, (gimp_image_get_height (image) * preview_size /
484                             gimp_image_get_width (image)));
485         }
486       else
487         {
488           height = preview_size;
489           width  = MAX (1, (gimp_image_get_width (image) * preview_size /
490                             gimp_image_get_height (image)));
491         }
492     }
493 
494   undo->preview = gimp_viewable_get_new_preview (preview_viewable, context,
495                                                  width, height);
496 
497   gimp_viewable_invalidate_preview (GIMP_VIEWABLE (undo));
498 }
499 
500 void
gimp_undo_refresh_preview(GimpUndo * undo,GimpContext * context)501 gimp_undo_refresh_preview (GimpUndo    *undo,
502                            GimpContext *context)
503 {
504   g_return_if_fail (GIMP_IS_UNDO (undo));
505   g_return_if_fail (context == NULL || GIMP_IS_CONTEXT (context));
506 
507   if (undo->preview_idle_id)
508     return;
509 
510   if (undo->preview)
511     {
512       g_clear_pointer (&undo->preview, gimp_temp_buf_unref);
513       gimp_undo_create_preview (undo, context, FALSE);
514     }
515 }
516 
517 const gchar *
gimp_undo_type_to_name(GimpUndoType type)518 gimp_undo_type_to_name (GimpUndoType type)
519 {
520   const gchar *desc;
521 
522   if (gimp_enum_get_value (GIMP_TYPE_UNDO_TYPE, type, NULL, NULL, &desc, NULL))
523     return desc;
524   else
525     return "";
526 }
527 
528 gboolean
gimp_undo_is_weak(GimpUndo * undo)529 gimp_undo_is_weak (GimpUndo *undo)
530 {
531   if (! undo)
532     return FALSE;
533 
534   switch (undo->undo_type)
535     {
536     case GIMP_UNDO_GROUP_ITEM_VISIBILITY:
537     case GIMP_UNDO_GROUP_ITEM_PROPERTIES:
538     case GIMP_UNDO_GROUP_LAYER_APPLY_MASK:
539     case GIMP_UNDO_ITEM_VISIBILITY:
540     case GIMP_UNDO_LAYER_MODE:
541     case GIMP_UNDO_LAYER_OPACITY:
542     case GIMP_UNDO_LAYER_MASK_APPLY:
543     case GIMP_UNDO_LAYER_MASK_SHOW:
544       return TRUE;
545       break;
546 
547     default:
548       break;
549     }
550 
551   return FALSE;
552 }
553 
554 /**
555  * gimp_undo_get_age:
556  * @undo:
557  *
558  * Return value: the time in seconds since this undo item was created
559  */
560 gint
gimp_undo_get_age(GimpUndo * undo)561 gimp_undo_get_age (GimpUndo *undo)
562 {
563   guint now = time (NULL);
564 
565   g_return_val_if_fail (GIMP_IS_UNDO (undo), 0);
566   g_return_val_if_fail (now >= undo->time, 0);
567 
568   return now - undo->time;
569 }
570 
571 /**
572  * gimp_undo_reset_age:
573  * @undo:
574  *
575  * Changes the creation time of this undo item to the current time.
576  */
577 void
gimp_undo_reset_age(GimpUndo * undo)578 gimp_undo_reset_age (GimpUndo *undo)
579 {
580   g_return_if_fail (GIMP_IS_UNDO (undo));
581 
582   undo->time = time (NULL);
583 
584   g_object_notify (G_OBJECT (undo), "time");
585 }
586