Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
undo.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2013, 2016-2017, 2019-2021 Pascal Obry.
4 Copyright (C) 2014, 2016-2017 Tobias Ellinghaus.
5 Copyright (C) 2015-2017 Roman Lebedev.
6 Copyright (C) 2019 Edgardo Hoszowski.
7 Copyright (C) 2019 Heiko Bauke.
8 Copyright (C) 2019 Philippe Weyland.
9 Copyright (C) 2020-2021 Aldric Renaudin.
10 Copyright (C) 2021 Ralf Brown.
11 Copyright (C) 2022 Martin Bařinka.
12 Copyright (C) 2023, 2025 Aurélien PIERRE.
13
14 darktable is free software: you can redistribute it and/or modify
15 it under the terms of the GNU General Public License as published by
16 the Free Software Foundation, either version 3 of the License, or
17 (at your option) any later version.
18
19 darktable is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 GNU General Public License for more details.
23
24 You should have received a copy of the GNU General Public License
25 along with darktable. If not, see <http://www.gnu.org/licenses/>.
26*/
27
28#include "common/undo.h"
29#include "common/collection.h"
30#include "common/darktable.h"
31#include "common/image.h"
32#include "control/control.h"
33#include <glib.h> // for GList, gpointer, g_list_prepend
34#include <stdlib.h> // for NULL, malloc, free
35#include <sys/time.h>
36
37const double MAX_TIME_PERIOD = 0.5; // in second
38
49
51{
52 dt_undo_t *udata = malloc(sizeof(dt_undo_t));
53 udata->undo_list = NULL;
54 udata->redo_list = NULL;
55 udata->disable_next = FALSE;
56 udata->locked = FALSE;
57 dt_pthread_mutex_init(&udata->mutex, NULL);
58 udata->group = DT_UNDO_NONE;
59 udata->group_indent = 0;
60 dt_print(DT_DEBUG_UNDO, "[undo] init\n");
61 return udata;
62}
63
64#define LOCK \
65 dt_pthread_mutex_lock(&self->mutex); self->locked = TRUE
66
67#define UNLOCK \
68 self->locked = FALSE; dt_pthread_mutex_unlock(&self->mutex)
69
71{
72 self->disable_next = TRUE;
73 dt_print(DT_DEBUG_UNDO, "[undo] disable next\n");
74}
75
81
82static void _free_undo_data(void *p)
83{
85 if (item->free_data) item->free_data(item->data);
86 dt_free(item);
87}
88
89static void _undo_record(dt_undo_t *self, gpointer user_data, dt_undo_type_t type, dt_undo_data_t data,
90 gboolean is_group,
91 void (*undo)(gpointer user_data, dt_undo_type_t type, dt_undo_data_t item, dt_undo_action_t action, GList **imgs),
92 void (*free_data)(gpointer data))
93{
94 if(IS_NULL_PTR(self)) return;
95
96 if(self->disable_next)
97 {
98 if(free_data) free_data(data);
99 self->disable_next = FALSE;
100 }
101 else
102 {
103 // do not block, if an undo record is asked and there is a lock it means that this call has been done in un
104 // undo/redo callback. We just skip this event.
105
106 if(!self->locked)
107 {
108 LOCK;
109
110 dt_undo_item_t *item = malloc(sizeof(dt_undo_item_t));
111
112 item->user_data = user_data;
113 item->type = type;
114 item->data = data;
115 item->undo = undo;
116 item->free_data = free_data;
117 item->ts = dt_get_wtime();
118 item->is_group = is_group;
119
120 self->undo_list = g_list_prepend(self->undo_list, (gpointer)item);
121
122 // recording an undo data invalidate all the redo
123 g_list_free_full(self->redo_list, _free_undo_data);
124 self->redo_list = NULL;
125
126 dt_print(DT_DEBUG_UNDO, "[undo] record for type %d (length %d)\n",
127 type, g_list_length(self->undo_list));
128
129 UNLOCK;
130 }
131 }
132}
133
135{
136 if(IS_NULL_PTR(self)) return;
137
138 if(self->group == DT_UNDO_NONE)
139 {
140 dt_print(DT_DEBUG_UNDO, "[undo] start group for type %d\n", type);
141 self->group = type;
142 self->group_indent = 1;
143 _undo_record(self, NULL, type, NULL, TRUE, NULL, NULL);
144 }
145 else
146 self->group_indent++;
147}
148
150{
151 if(IS_NULL_PTR(self)) return;
152
153 assert(self->group_indent>0);
154 self->group_indent--;
155 if(self->group_indent == 0)
156 {
157 _undo_record(self, NULL, self->group, NULL, TRUE, NULL, NULL);
158 dt_print(DT_DEBUG_UNDO, "[undo] end group for type %d\n", self->group);
159 self->group = DT_UNDO_NONE;
160 }
161}
162
163void dt_undo_record(dt_undo_t *self, gpointer user_data, dt_undo_type_t type, dt_undo_data_t data,
164 void (*undo)(gpointer user_data, dt_undo_type_t type, dt_undo_data_t item, dt_undo_action_t action, GList **imgs),
165 void (*free_data)(gpointer data))
166{
167 _undo_record(self, user_data, type, data, FALSE, undo, free_data);
168}
169
170gint _images_list_cmp(gconstpointer a, gconstpointer b)
171{
172 return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
173}
174
175static void _undo_do_undo_redo(dt_undo_t *self, uint32_t filter, dt_undo_action_t action)
176{
177 if(IS_NULL_PTR(self)) return;
178
179 LOCK;
180
181 // we take/remove item from the FROM list and add them into the TO list:
182 GList **from = action == DT_ACTION_UNDO ? &self->undo_list : &self->redo_list;
183 GList **to = action == DT_ACTION_UNDO ? &self->redo_list : &self->undo_list;
184
185 GList *imgs = NULL;
186
187 // check for first item that is matching the given pattern
188
189 dt_print(DT_DEBUG_UNDO, "[undo] action %s for %d (from length %d -> to length %d)\n",
190 action == DT_ACTION_UNDO?"UNDO":"DO", filter, g_list_length(*from), g_list_length(*to));
191
192 for(GList *l = *from; l; l = g_list_next(l))
193 {
194 dt_undo_item_t *item = (dt_undo_item_t *)l->data;
195
196 if(item->type & filter)
197 {
198 if(item->is_group)
199 {
200 gboolean is_group = FALSE;
201
202 GList *next = g_list_next(l);
203
204 // first move the group item into the TO list
205 *from = g_list_remove(*from, item);
206 *to = g_list_prepend(*to, item);
207
208 while((l = next) && !is_group)
209 {
210 item = (dt_undo_item_t *)l->data;
211 next = g_list_next(l);
212
213 // first remove element from FROM list
214 *from = g_list_remove(*from, item);
215
216 // callback with undo or redo data
217 if(item->is_group)
218 is_group = TRUE;
219 else
220 item->undo(item->user_data, item->type, item->data, action, &imgs);
221
222 // add old position back into the TO list
223 *to = g_list_prepend(*to, item);
224 }
225 }
226 else
227 {
228 const double first_item_ts = item->ts;
229 gboolean in_group = FALSE;
230
231 // when found, handle all items of the same type and in the same time period
232
233 do
234 {
235 GList *next = g_list_next(l);
236
237 // first remove element from FROM list
238 *from = g_list_remove(*from, item);
239
240 if(item->is_group)
241 in_group = !in_group;
242 else
243 // callback with redo or redo data
244 item->undo(item->user_data, item->type, item->data, action, &imgs);
245
246 // add old position back into the TO list
247 *to = g_list_prepend(*to, item);
248
249 l = next;
250 if (l) item = (dt_undo_item_t *)l->data;
251 } while (l && (item->type & filter) && (in_group || (fabs(item->ts - first_item_ts) < MAX_TIME_PERIOD)));
252 }
253
254 break;
255 }
256 }
257 UNLOCK;
258
259 if(imgs)
260 {
261 imgs = g_list_sort(imgs, _images_list_cmp);
262 // remove duplicates
263 for(const GList *img = imgs; img; img = g_list_next(img))
264 while(img->next && img->data == img->next->data)
265 imgs = g_list_delete_link(imgs, img->next);
266 // udpate xmp for updated images
267
269 }
270}
271
272void dt_undo_do_redo(dt_undo_t *self, uint32_t filter)
273{
274 _undo_do_undo_redo(self, filter, DT_ACTION_REDO);
275}
276
277void dt_undo_do_undo(dt_undo_t *self, uint32_t filter)
278{
279 _undo_do_undo_redo(self, filter, DT_ACTION_UNDO);
280}
281
282static gboolean _is_do_undo_list_populated(dt_undo_t *self, uint32_t filter, dt_undo_action_t action)
283{
284 if(IS_NULL_PTR(self)) return FALSE;
285 gboolean found_something = FALSE;
286
287 LOCK;
288
289 GList **from = action == DT_ACTION_UNDO ? &self->undo_list : &self->redo_list;
290
291 for(GList *l = *from; l; l = g_list_next(l))
292 {
293 dt_undo_item_t *item = (dt_undo_item_t *)l->data;
294 if(item->type & filter)
295 {
296 found_something = TRUE;
297 break;
298 }
299 }
300
301 UNLOCK;
302
303 return found_something;
304}
305
306gboolean dt_is_undo_list_populated(dt_undo_t *self, uint32_t filter)
307{
308 return _is_do_undo_list_populated(self, filter, DT_ACTION_UNDO);
309}
310
311gboolean dt_is_redo_list_populated(dt_undo_t *self, uint32_t filter)
312{
313 return _is_do_undo_list_populated(self, filter, DT_ACTION_REDO);
314}
315
316static void _undo_clear_list(GList **list, uint32_t filter)
317{
318 // check for first item that is matching the given pattern
319
320 GList *next;
321 for(GList *l = *list; l; l = next)
322 {
323 dt_undo_item_t *item = (dt_undo_item_t *)l->data;
324 next = g_list_next(l); // get next node now, because we may delete the current one
325 if(item->type & filter)
326 {
327 // remove this element
328 *list = g_list_remove(*list, item);
329 _free_undo_data((void *)item);
330 }
331 };
332
333 dt_print(DT_DEBUG_UNDO, "[undo] clear list for %d (length %d)\n",
334 filter, g_list_length(*list));
335}
336
337void dt_undo_clear(dt_undo_t *self, uint32_t filter)
338{
339 if(IS_NULL_PTR(self)) return;
340
341 LOCK;
342 _undo_clear_list(&self->undo_list, filter);
343 _undo_clear_list(&self->redo_list, filter);
344 self->undo_list = NULL;
345 self->redo_list = NULL;
346 self->disable_next = FALSE;
347 UNLOCK;
348}
349
350static void _undo_iterate(GList *list, uint32_t filter, gpointer user_data,
351 void (*apply)(gpointer user_data, dt_undo_type_t type, dt_undo_data_t item))
352{
353 // check for first item that is matching the given pattern
354 for(GList *l = list; l; l = g_list_next(l))
355 {
356 dt_undo_item_t *item = (dt_undo_item_t *)l->data;
357 if(!item->is_group && (item->type & filter))
358 {
359 apply(user_data, item->type, item->data);
360 }
361 };
362}
363
364void dt_undo_iterate_internal(dt_undo_t *self, uint32_t filter, gpointer user_data,
365 void (*apply)(gpointer user_data, dt_undo_type_t type, dt_undo_data_t item))
366{
367 if(IS_NULL_PTR(self)) return;
368
369 _undo_iterate(self->undo_list, filter, user_data, apply);
370 _undo_iterate(self->redo_list, filter, user_data, apply);
371}
372
373
374void dt_undo_iterate(dt_undo_t *self, uint32_t filter, gpointer user_data,
375 void (*apply)(gpointer user_data, dt_undo_type_t type, dt_undo_data_t item))
376{
377 if(IS_NULL_PTR(self)) return;
378
379 LOCK;
380 dt_undo_iterate_internal(self, filter, user_data, apply);
381 UNLOCK;
382}
383
384// clang-format off
385// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
386// vim: shiftwidth=2 expandtab tabstop=2 cindent
387// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
388// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
void dt_image_synch_xmps(const GList *img)
int type
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
@ DT_DEBUG_UNDO
Definition darktable.h:734
#define dt_free(ptr)
Definition darktable.h:456
static double dt_get_wtime(void)
Definition darktable.h:914
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Definition dtpthread.h:359
static int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:379
void(* free_data)(gpointer data)
Definition undo.c:47
void(* undo)(gpointer user_data, dt_undo_type_t type, dt_undo_data_t data, dt_undo_action_t action, GList **imgs)
Definition undo.c:46
dt_undo_data_t data
Definition undo.c:43
double ts
Definition undo.c:44
gboolean is_group
Definition undo.c:45
gpointer user_data
Definition undo.c:41
dt_undo_type_t type
Definition undo.c:42
dt_pthread_mutex_t mutex
Definition undo.h:74
gboolean disable_next
Definition undo.h:76
gboolean locked
Definition undo.h:75
int group_indent
Definition undo.h:73
GList * undo_list
Definition undo.h:71
GList * redo_list
Definition undo.h:71
dt_undo_type_t group
Definition undo.h:72
#define LOCK
Definition undo.c:64
void dt_undo_disable_next(dt_undo_t *self)
Definition undo.c:70
void dt_undo_do_redo(dt_undo_t *self, uint32_t filter)
Definition undo.c:272
static void _undo_iterate(GList *list, uint32_t filter, gpointer user_data, void(*apply)(gpointer user_data, dt_undo_type_t type, dt_undo_data_t item))
Definition undo.c:350
gboolean dt_is_redo_list_populated(dt_undo_t *self, uint32_t filter)
Definition undo.c:311
static void _undo_clear_list(GList **list, uint32_t filter)
Definition undo.c:316
void dt_undo_end_group(dt_undo_t *self)
Definition undo.c:149
static void _undo_record(dt_undo_t *self, gpointer user_data, dt_undo_type_t type, dt_undo_data_t data, gboolean is_group, void(*undo)(gpointer user_data, dt_undo_type_t type, dt_undo_data_t item, dt_undo_action_t action, GList **imgs), void(*free_data)(gpointer data))
Definition undo.c:89
gint _images_list_cmp(gconstpointer a, gconstpointer b)
Definition undo.c:170
const double MAX_TIME_PERIOD
Definition undo.c:37
void dt_undo_cleanup(dt_undo_t *self)
Definition undo.c:76
void dt_undo_clear(dt_undo_t *self, uint32_t filter)
Definition undo.c:337
void dt_undo_iterate_internal(dt_undo_t *self, uint32_t filter, gpointer user_data, void(*apply)(gpointer user_data, dt_undo_type_t type, dt_undo_data_t item))
Definition undo.c:364
void dt_undo_iterate(dt_undo_t *self, uint32_t filter, gpointer user_data, void(*apply)(gpointer user_data, dt_undo_type_t type, dt_undo_data_t item))
Definition undo.c:374
void dt_undo_start_group(dt_undo_t *self, dt_undo_type_t type)
Definition undo.c:134
static void _free_undo_data(void *p)
Definition undo.c:82
static gboolean _is_do_undo_list_populated(dt_undo_t *self, uint32_t filter, dt_undo_action_t action)
Definition undo.c:282
#define UNLOCK
Definition undo.c:67
void dt_undo_do_undo(dt_undo_t *self, uint32_t filter)
Definition undo.c:277
static void _undo_do_undo_redo(dt_undo_t *self, uint32_t filter, dt_undo_action_t action)
Definition undo.c:175
dt_undo_t * dt_undo_init(void)
Definition undo.c:50
void dt_undo_record(dt_undo_t *self, gpointer user_data, dt_undo_type_t type, dt_undo_data_t data, void(*undo)(gpointer user_data, dt_undo_type_t type, dt_undo_data_t item, dt_undo_action_t action, GList **imgs), void(*free_data)(gpointer data))
Definition undo.c:163
gboolean dt_is_undo_list_populated(dt_undo_t *self, uint32_t filter)
Definition undo.c:306
dt_undo_type_t
Definition undo.h:39
@ DT_UNDO_NONE
Definition undo.h:40
@ DT_UNDO_ALL
Definition undo.h:58
void * dt_undo_data_t
Definition undo.h:67
dt_undo_action_t
Definition undo.h:62
@ DT_ACTION_REDO
Definition undo.h:64
@ DT_ACTION_UNDO
Definition undo.h:63