Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
history_actions.c
Go to the documentation of this file.
1/*
2 This file is part of Ansel,
3 Copyright (C) 2026 Aurélien PIERRE.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18
20
21#include "common/darktable.h"
22#include "common/debug.h"
23#include "common/exif.h"
24#include "common/history.h"
26#include "common/image.h"
27#include "common/image_cache.h"
28#include "common/mipmap_cache.h"
29#include "common/styles.h"
30#include "common/tags.h"
31#include "common/undo.h"
32#include "control/conf.h"
33#include "control/control.h"
34#include "develop/dev_history.h"
35#include "develop/develop.h"
36#include "develop/imageop.h"
37#include "dtgtk/thumbtable.h"
38#include "gui/hist_dialog.h"
39
40static void _history_action_finalize_list(const GList *list, const gboolean changed)
41{
42 if(!changed) return;
43
46}
47
48typedef gboolean (*dt_history_action_fn)(const int32_t imgid, void *user_data);
49
50static gboolean _history_action_on_list_with_undo(const GList *list, dt_history_action_fn action, void *user_data,
51 const gboolean use_undo)
52{
53 if(IS_NULL_PTR(list)) return FALSE;
54
56 gboolean changed = FALSE;
57 for(const GList *l = list; l; l = g_list_next(l))
58 {
59 const int32_t imgid = GPOINTER_TO_INT(l->data);
60 changed |= action(imgid, user_data);
61 }
62 if(use_undo) dt_undo_end_group(darktable.undo);
63
64 _history_action_finalize_list(list, changed);
65 return changed;
66}
67
68static gboolean _history_action_on_list(const GList *list, dt_history_action_fn action, void *user_data)
69{
70 return _history_action_on_list_with_undo(list, action, user_data, TRUE);
71}
72
83static GList *_get_user_mod_list(dt_develop_t *dev_src, GList *ops, gboolean copy_full)
84{
85 GList *mod_list = NULL;
86
87 if(ops)
88 {
89 dt_print(DT_DEBUG_PARAMS, "[_history_copy_and_paste_on_image_merge] pasting selected IOP\n");
90
91 // copy only selected history entries
92 for(const GList *l = g_list_last(ops); l; l = g_list_previous(l))
93 {
94 const unsigned int num = GPOINTER_TO_UINT(l->data);
95 const dt_dev_history_item_t *hist = NULL;
96 for(GList *h = g_list_last(dev_src->history); h; h = g_list_previous(h))
97 {
98 const dt_dev_history_item_t *item = (dt_dev_history_item_t *)h->data;
99 if(item && item->num == (int)num)
100 {
101 hist = item;
102 break;
103 }
104 }
105
106 if(hist)
107 {
108 dt_iop_module_t *mod = hist->module;
109 if(mod && !dt_iop_is_hidden(mod))
110 {
111 dt_print(DT_DEBUG_HISTORY, "selected for copy/pasting : module %20s, multiprio %i", mod->op, mod->multi_priority);
112
113 mod_list = g_list_prepend(mod_list, mod);
114 }
115 }
116 }
117 }
118 else
119 {
120 dt_print(DT_DEBUG_PARAMS, "[_history_copy_and_paste_on_image_merge] pasting all IOP\n");
121
122 // we will copy all modules
123 for(GList *modules_src = g_list_first(dev_src->iop); modules_src; modules_src = g_list_next(modules_src))
124 {
125 dt_iop_module_t *mod_src = (dt_iop_module_t *)(modules_src->data);
126
127 // copy from history only if
128 if((dt_dev_history_get_first_item_by_module(dev_src->history, mod_src) != NULL) // module is in history of source image
129 && !dt_iop_is_hidden(mod_src) // hidden modules are technical and special
130 && (copy_full || !dt_history_module_skip_copy(mod_src->flags()))
131 )
132 {
133 // Note: we prepend to GList because it's more efficient
134 mod_list = g_list_prepend(mod_list, mod_src);
135 }
136 }
137 }
138
139 return g_list_reverse(mod_list);
140}
141
154static int _history_copy_and_paste_on_image_merge(int32_t imgid, int32_t dest_imgid, GList *ops,
155 const gboolean copy_full, const dt_history_merge_strategy_t mode,
156 const gboolean copy_iop_order, dt_hm_batch_state_t *batch)
157{
158 // Init source history + pipeline
159 dt_develop_t _dev_src = { 0 };
160 dt_develop_t *dev_src = &_dev_src;
161 dt_dev_init(dev_src, FALSE);
162 dt_dev_reload_history_items(dev_src, imgid);
163
164 int ret_val = 0;
165
166 if(mode == DT_HISTORY_MERGE_REPLACE)
167 {
168 // Dumb mode : keep dev_src intact but swap destination imgid and image info into it.
169 //
170 // NOTE: when pasting from LDR/JPEG onto RAW images, the source iop-order list might not be compatible
171 // with the destination pipeline. We need to re-init the iop-order list for the destination image and
172 // reload module defaults for the destination image, but we must not apply auto-presets.
173 ret_val = dt_dev_replace_history_on_image(dev_src, dest_imgid, TRUE, "_history_copy_and_paste_on_image_merge");
174 }
175 else
176 {
177 // Merge
178 GList *mod_list = _get_user_mod_list(dev_src, ops, copy_full);
179 ret_val = dt_dev_merge_history_into_image(dev_src, dest_imgid, mod_list,
180 copy_iop_order, mode,
181 dt_conf_get_bool("history/paste_instances"), NULL, batch);
182 g_list_free(mod_list);
183 mod_list = NULL;
184 }
185 dt_dev_cleanup(dev_src);
186
187 return ret_val;
188}
189
190gboolean dt_history_copy_and_paste_on_image(const int32_t imgid, const int32_t dest_imgid, GList *ops,
191 const gboolean copy_full, const dt_history_merge_strategy_t mode,
192 const gboolean copy_iop_order, dt_hm_batch_state_t *batch)
193{
194 if(imgid == dest_imgid) return 1;
195
196 if(imgid == UNKNOWN_IMAGE)
197 {
198 dt_control_log(_("you need to copy history from an image before you paste it onto another"));
199 return 1;
200 }
201
203 hist->imgid = dest_imgid;
205
206 int ret_val = _history_copy_and_paste_on_image_merge(imgid, dest_imgid, ops, copy_full, mode, copy_iop_order,
207 batch);
208
212
213 return ret_val;
214}
215
216gboolean dt_history_copy(int32_t imgid)
217{
218 // note that this routine does not copy anything, it just setup the copy_paste proxy
219 // with the needed information that will be used while pasting.
220
221 if(imgid <= 0) return FALSE;
222
224
225 return TRUE;
226}
227
228gboolean dt_history_copy_parts(int32_t imgid)
229{
230 if(dt_history_copy(imgid))
231 {
232 // run dialog, it will insert into selops the selected moduel
233
234 if(dt_gui_hist_dialog_new(&(darktable.view_manager->copy_paste), imgid, TRUE) == GTK_RESPONSE_CANCEL)
235 return FALSE;
236 return TRUE;
237 }
238 else
239 return FALSE;
240}
241
246
247static gboolean _history_paste_apply(const int32_t imgid, void *user_data)
248{
249 _paste_action_ctx_t *ctx = (_paste_action_ctx_t *)user_data;
251 if(imgid <= 0) return FALSE;
252
254 imgid,
256 FALSE,
257 dt_conf_get_int("history/paste/mode"),
258 dt_conf_get_bool("history/paste/copy_iop_order"),
259 ctx ? &ctx->batch : NULL) == 0;
260 return pasted;
261}
262
263gboolean dt_history_paste_on_image(const int32_t imgid)
264{
265 return _history_paste_apply(imgid, NULL); // NULL batch → always show report popup
266}
267
268gboolean dt_history_paste_on_list(const GList *list)
269{
271 _paste_action_ctx_t ctx = { 0 };
273}
274
276{
278
279 // we launch the dialog
282
283 if(res != GTK_RESPONSE_OK)
284 {
285 return FALSE;
286 }
287
288 return TRUE;
289}
290
291static gboolean _history_paste_parts_apply(const int32_t imgid, void *user_data)
292{
293 _paste_action_ctx_t *ctx = (_paste_action_ctx_t *)user_data;
296 if(imgid <= 0) return FALSE;
297
299 imgid,
301 FALSE,
302 dt_conf_get_int("history/paste/mode"),
303 dt_conf_get_bool("history/paste/copy_iop_order"),
304 ctx ? &ctx->batch : NULL) == 0;
305 return pasted;
306}
307
308gboolean dt_history_paste_parts_on_image(const int32_t imgid)
309{
310 return _history_paste_parts_apply(imgid, NULL); // NULL batch → always show report popup
311}
312
313gboolean dt_history_paste_parts_on_list(const GList *list)
314{
317 return FALSE;
318 _paste_action_ctx_t ctx = { 0 };
320}
321
322static gboolean _history_compress_apply(const int32_t imgid, void *user_data)
323{
324 (void)user_data;
325 dt_print(DT_DEBUG_HISTORY, "[dt_history_compress_on_image] compressing history for image %i\n", imgid);
326 if(imgid <= 0) return FALSE;
327
328 dt_develop_t dev;
329 dt_dev_init(&dev, FALSE);
330 dt_dev_reload_history_items(&dev, imgid);
332 dt_dev_write_history_ext(&dev, imgid);
333 dt_dev_cleanup(&dev);
334 return TRUE;
335}
336
337void dt_history_compress_on_image(const int32_t imgid)
338{
339 _history_compress_apply(imgid, NULL);
340}
341
342int dt_history_compress_on_list(const GList *imgs)
343{
345 return 0;
346}
347
353
354static gboolean _history_load_and_apply_apply(const int32_t imgid, void *user_data)
355{
358 if(IS_NULL_PTR(img)) return FALSE;
359
361 hist->imgid = imgid;
363
364 if(dt_exif_xmp_read(img, params->filename, params->history_only))
365 {
367 // ugly but if not history_only => called from crawler - do not write the xmp
368 params->history_only ? DT_IMAGE_CACHE_SAFE : DT_IMAGE_CACHE_RELAXED);
369 return FALSE;
370 }
371
375
377 // ugly but if not history_only => called from crawler - do not write the xmp
378 params->history_only ? DT_IMAGE_CACHE_SAFE : DT_IMAGE_CACHE_RELAXED);
379
380 // Loading an XMP rewrites the image history straight into the DB and image cache, bypassing
381 // the darkroom/paste path that goes through dt_dev_history_notify_change(). Nothing invalidates
382 // the rendered thumbnail, so it keeps showing the pre-XMP development (issue #861).
383 // The cached metadata (history_items, ratings...) is refreshed by the DT_SIGNAL_IMAGE_INFO_CHANGED
384 // raised in _history_action_finalize_list(), but the pixels are not: reload metadata, invalidate
385 // the mipmap and refresh the thumbnail (lighttable context, so refresh the filmstrip too).
387
388 return TRUE;
389}
390
391int dt_history_load_and_apply(const int32_t imgid, gchar *filename, int history_only)
392{
393 dt_history_load_params_t params = { .filename = filename, .history_only = history_only };
394 return _history_load_and_apply_apply(imgid, &params) ? 0 : 1;
395}
396
397int dt_history_load_and_apply_on_image(int32_t imgid, gchar *filename, int history_only)
398{
399 return dt_history_load_and_apply(imgid, filename, history_only);
400}
401
402int dt_history_load_and_apply_on_list(gchar *filename, const GList *list)
403{
404 dt_history_load_params_t params = { .filename = filename, .history_only = 1 };
405 const gboolean changed = _history_action_on_list(list, _history_load_and_apply_apply, &params);
406 return changed ? 0 : 1;
407}
408
413
414static gboolean _history_delete_apply(const int32_t imgid, void *user_data)
415{
416 if(imgid <= 0) return FALSE;
417
418 const dt_history_delete_params_t *params = (dt_history_delete_params_t *)user_data;
419 const gboolean undo = params ? params->undo : TRUE;
420
421 dt_undo_lt_history_t *hist = NULL;
422 if(undo)
423 {
425 hist->imgid = imgid;
427 }
428
430
431 if(undo)
432 {
436 }
437
438 return TRUE;
439}
440
441gboolean dt_history_delete_on_list(const GList *list, gboolean undo)
442{
443 dt_history_delete_params_t params = { .undo = undo };
444 return _history_action_on_list_with_undo(list, _history_delete_apply, &params, undo);
445}
446
455
456static gboolean _history_style_apply(const int32_t imgid, void *user_data)
457{
459 if(IS_NULL_PTR(params) || params->style_id == 0 || IS_NULL_PTR(params->name) || !*params->name) return FALSE;
460
461 int32_t newimgid = imgid;
462 if(params->duplicate)
463 {
464 newimgid = dt_image_duplicate(imgid);
465 if(newimgid == UNKNOWN_IMAGE) return FALSE;
466
467 // Structural copy of original history into the duplicate; no merge report needed here.
468 const gboolean pasted = dt_history_copy_and_paste_on_image(imgid, newimgid, NULL, TRUE, params->mode,
469 dt_conf_get_bool("history/style/copy_iop_order"),
470 NULL) == 0;
471 return pasted;
472 }
473
475 hist->imgid = newimgid;
477
478 const int ret_val = dt_styles_apply_to_image_merge(params->name, params->style_id, newimgid, params->mode,
479 &params->batch);
480
484
485 const gboolean changed = (ret_val == 0);
486 return changed;
487}
488
489gboolean dt_history_style_on_image(const int32_t imgid, const char *name, const gboolean duplicate)
490{
491 if(IS_NULL_PTR(name) || !*name) return FALSE;
492
494 .name = name,
495 .style_id = dt_styles_get_id_by_name(name),
496 .duplicate = duplicate,
497 .mode = dt_conf_get_int("history/style/mode"),
498 .batch = { .decision = DT_HM_BATCH_UNDECIDED },
499 };
500 if(params.style_id == 0) return FALSE;
501
502 return _history_style_apply(imgid, &params);
503}
504
505gboolean dt_history_style_on_list(const GList *list, const char *name, const gboolean duplicate)
506{
507 if(IS_NULL_PTR(name) || !*name) return FALSE;
508
510 .name = name,
511 .style_id = dt_styles_get_id_by_name(name),
512 .duplicate = duplicate,
513 .mode = dt_conf_get_int("history/style/mode"),
514 .batch = { .decision = DT_HM_BATCH_UNDECIDED },
515 };
516 if(params.style_id == 0) return FALSE;
517
518 return _history_action_on_list(list, _history_style_apply, &params);
519}
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
void dt_history_delete_on_image_ext(int32_t imgid, gboolean undo)
int32_t dt_image_duplicate(const int32_t imgid)
void dt_image_history_changed(const int32_t imgid, const gboolean refresh_filmstrip)
char * name
int dt_conf_get_bool(const char *name)
int dt_conf_get_int(const char *name)
void dt_control_log(const char *msg,...)
Definition control.c:761
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
#define UNKNOWN_IMAGE
Definition darktable.h:182
@ DT_DEBUG_HISTORY
Definition darktable.h:740
@ DT_DEBUG_PARAMS
Definition darktable.h:736
#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
int dt_dev_replace_history_on_image(dt_develop_t *dev_src, const int32_t dest_imgid, const gboolean reload_defaults, const char *msg)
Replace an image history with the content of dev_src.
int dt_dev_merge_history_into_image(dt_develop_t *dev_src, int32_t dest_imgid, const GList *mod_list, gboolean merge_iop_order, const dt_history_merge_strategy_t mode, const gboolean paste_instances, const char *source_label, dt_hm_batch_state_t *batch)
Merge a list of modules into a destination image history via dt_history_merge().
void dt_dev_history_compress_or_truncate(dt_develop_t *dev)
Compress history if history_end is at top, otherwise truncate.
gboolean dt_history_module_skip_copy(const int flags)
Determine whether a module should be skipped during history copy.
void dt_dev_write_history_ext(dt_develop_t *dev, const int32_t imgid)
Write dev->history to DB and XMP for a given image id.
gboolean dt_dev_reload_history_items(dt_develop_t *dev, const int32_t imgid)
Reload history from DB and rebuild pipelines/GUI state.
dt_dev_history_item_t * dt_dev_history_get_first_item_by_module(GList *history_list, dt_iop_module_t *module)
Find the first history item referencing a module.
void dt_dev_cleanup(dt_develop_t *dev)
Definition develop.c:188
void dt_dev_init(dt_develop_t *dev, int32_t gui_attached)
Definition develop.c:128
int dt_exif_xmp_read(dt_image_t *img, const char *filename, const int history_only)
Definition exif.cc:3105
int dt_gui_hist_dialog_new(dt_history_copy_item_t *d, int32_t imgid, gboolean iscopy)
static gboolean _history_action_on_list_with_undo(const GList *list, dt_history_action_fn action, void *user_data, const gboolean use_undo)
gboolean dt_history_paste_on_list(const GList *list)
static gboolean _history_paste_apply(const int32_t imgid, void *user_data)
int dt_history_load_and_apply(const int32_t imgid, gchar *filename, int history_only)
gboolean dt_history_copy(int32_t imgid)
static gboolean _history_style_apply(const int32_t imgid, void *user_data)
gboolean dt_history_paste_parts_on_list(const GList *list)
static gboolean _history_paste_parts_apply(const int32_t imgid, void *user_data)
static GList * _get_user_mod_list(dt_develop_t *dev_src, GList *ops, gboolean copy_full)
Build a module list to copy based on selected history indices.
int dt_history_load_and_apply_on_image(int32_t imgid, gchar *filename, int history_only)
static gboolean _history_action_on_list(const GList *list, dt_history_action_fn action, void *user_data)
gboolean dt_history_delete_on_list(const GList *list, gboolean undo)
gboolean dt_history_paste_on_image(const int32_t imgid)
gboolean dt_history_style_on_image(const int32_t imgid, const char *name, const gboolean duplicate)
static gboolean _history_load_and_apply_apply(const int32_t imgid, void *user_data)
static int _history_copy_and_paste_on_image_merge(int32_t imgid, int32_t dest_imgid, GList *ops, const gboolean copy_full, const dt_history_merge_strategy_t mode, const gboolean copy_iop_order, dt_hm_batch_state_t *batch)
Copy/merge history between images using the merge pipeline.
gboolean dt_history_paste_parts_prepare(void)
static void _history_action_finalize_list(const GList *list, const gboolean changed)
int dt_history_load_and_apply_on_list(gchar *filename, const GList *list)
int dt_history_compress_on_list(const GList *imgs)
gboolean dt_history_copy_parts(int32_t imgid)
static gboolean _history_delete_apply(const int32_t imgid, void *user_data)
gboolean dt_history_paste_parts_on_image(const int32_t imgid)
gboolean dt_history_copy_and_paste_on_image(const int32_t imgid, const int32_t dest_imgid, GList *ops, const gboolean copy_full, const dt_history_merge_strategy_t mode, const gboolean copy_iop_order, dt_hm_batch_state_t *batch)
static gboolean _history_compress_apply(const int32_t imgid, void *user_data)
void dt_history_compress_on_image(const int32_t imgid)
gboolean dt_history_style_on_list(const GList *list, const char *name, const gboolean duplicate)
gboolean(* dt_history_action_fn)(const int32_t imgid, void *user_data)
@ DT_HM_BATCH_UNDECIDED
dt_history_merge_strategy_t
@ DT_HISTORY_MERGE_REPLACE
void dt_history_snapshot_undo_create(const int32_t imgid, int *snap_id, int *history_end)
void dt_history_snapshot_undo_lt_history_data_free(gpointer data)
dt_undo_lt_history_t * dt_history_snapshot_item_init(void)
void dt_history_snapshot_undo_pop(gpointer user_data, dt_undo_type_t type, dt_undo_data_t data, dt_undo_action_t action, GList **imgs)
dt_image_t * dt_image_cache_get(dt_image_cache_t *cache, const int32_t imgid, char mode)
void dt_image_cache_write_release(dt_image_cache_t *cache, dt_image_t *img, dt_image_cache_write_mode_t mode)
@ DT_IMAGE_CACHE_RELAXED
Definition image_cache.h:51
@ DT_IMAGE_CACHE_SAFE
Definition image_cache.h:49
gboolean dt_iop_is_hidden(dt_iop_module_t *module)
Definition imageop.c:1127
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:347
@ DT_SIGNAL_IMAGE_INFO_CHANGED
This signal is raised when any of image info has changed
Definition signal.h:144
@ DT_SIGNAL_TAG_CHANGED
This signal is raised when a tag is added/deleted/changed
Definition signal.h:130
int dt_styles_apply_to_image_merge(const char *name, const int style_id, const int32_t newimgid, const dt_history_merge_strategy_t mode, dt_hm_batch_state_t *batch)
int32_t dt_styles_get_id_by_name(const char *name)
dt_hm_batch_state_t batch
struct dt_undo_t * undo
Definition darktable.h:787
struct dt_control_signal_t * signals
Definition darktable.h:774
struct dt_image_cache_t * image_cache
Definition darktable.h:777
struct dt_view_manager_t * view_manager
Definition darktable.h:772
GList * iop
Definition develop.h:279
GList * history
Definition develop.h:275
dt_history_merge_strategy_t mode
dt_hm_batch_state_t batch
GModule *dt_dev_operation_t op
Definition imageop.h:256
dt_history_copy_item_t copy_paste
Definition view.h:207
A widget to manage and display image thumbnails in Ansel's lighttable and filmstrip views.
void dt_undo_end_group(dt_undo_t *self)
Definition undo.c:149
void dt_undo_start_group(dt_undo_t *self, dt_undo_type_t type)
Definition undo.c:134
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
@ DT_UNDO_LT_HISTORY
Definition undo.h:48
void * dt_undo_data_t
Definition undo.h:67