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