Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
colorlabels.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010 Alexandre Prokoudine.
4 Copyright (C) 2010-2011 Henrik Andersson.
5 Copyright (C) 2010-2011 johannes hanika.
6 Copyright (C) 2011 Robert Bieber.
7 Copyright (C) 2011-2014, 2016 Tobias Ellinghaus.
8 Copyright (C) 2012 Richard Wonka.
9 Copyright (C) 2013 José Carlos García Sogo.
10 Copyright (C) 2013 Jérémy Rosen.
11 Copyright (C) 2014 Ulrich Pegelow.
12 Copyright (C) 2016 Roman Lebedev.
13 Copyright (C) 2019 Heiko Bauke.
14 Copyright (C) 2019-2022 Pascal Obry.
15 Copyright (C) 2019-2020 Philippe Weyland.
16 Copyright (C) 2020-2021 Aldric Renaudin.
17 Copyright (C) 2020 Hubert Kowalski.
18 Copyright (C) 2021 Diederik Ter Rahe.
19 Copyright (C) 2021 Ralf Brown.
20 Copyright (C) 2021 solarer.
21 Copyright (C) 2022-2023, 2025-2026 Aurélien PIERRE.
22 Copyright (C) 2022 Martin Bařinka.
23
24 darktable is free software: you can redistribute it and/or modify
25 it under the terms of the GNU General Public License as published by
26 the Free Software Foundation, either version 3 of the License, or
27 (at your option) any later version.
28
29 darktable is distributed in the hope that it will be useful,
30 but WITHOUT ANY WARRANTY; without even the implied warranty of
31 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32 GNU General Public License for more details.
33
34 You should have received a copy of the GNU General Public License
35 along with darktable. If not, see <http://www.gnu.org/licenses/>.
36*/
37#include "common/colorlabels.h"
38#include "common/collection.h"
39#include "common/darktable.h"
40#include "common/debug.h"
41#include "common/image_cache.h"
42#include "common/undo.h"
43#include "common/grouping.h"
44#include "control/conf.h"
45#include "control/control.h"
46#include "gui/gtk.h"
47
48#include <gdk/gdkkeysyms.h>
49#include "bauhaus/bauhaus.h"
50
51const char *dt_colorlabels_name[] = {
52 "red", "yellow", "green", "blue", "purple",
53 NULL // termination
54};
55
56char * dt_colorlabels_get_name(const int label)
57{
58 switch(label)
59 {
60 case 0:
61 return _("red");
62 case 1:
63 return _("yellow");
64 case 2:
65 return _("green");
66 case 3:
67 return _("blue");
68 case 4:
69 return _("purple");
70 case 5:
71 return _("empty");
72 default:
73 return _("unknown/invalid");
74 }
75}
76
83
84static sqlite3_stmt *_colorlabels_get_labels_stmt = NULL;
85static sqlite3_stmt *_colorlabels_remove_labels_stmt = NULL;
86static sqlite3_stmt *_colorlabels_set_label_stmt = NULL;
87static sqlite3_stmt *_colorlabels_remove_label_stmt = NULL;
88
89int dt_colorlabels_get_labels(const int32_t imgid)
90{
92 {
93 // clang-format off
95 "SELECT color FROM main.color_labels WHERE imgid = ?1",
97 // clang-format on
98 }
99 sqlite3_stmt *stmt = _colorlabels_get_labels_stmt;
100 sqlite3_reset(stmt);
101 sqlite3_clear_bindings(stmt);
102 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
103 int colors = 0;
104
105 // Colors are int between 0 and 5, turn them into octal bitmask
106 while(sqlite3_step(stmt) == SQLITE_ROW)
107 colors |= (1 << sqlite3_column_int(stmt, 0));
108 return colors;
109}
110
111void dt_colorlabels_set_labels(const int32_t imgid, const int colors)
112{
113 for(int color = 0; color < 5; color++)
114 {
115 if(colors & (1 << color))
116 dt_colorlabels_set_label(imgid, color);
117 else
118 dt_colorlabels_remove_label(imgid, color);
119 }
120}
121
122static void _pop_undo_execute(const int32_t imgid, const int before, const int after)
123{
125 if(IS_NULL_PTR(image)) return;
126
127 // Write to image
128 for(int color = 0; color < 5; color++)
129 {
130 if(after & (1 << color))
131 {
132 if (!(before & (1 << color)))
133 image->color_labels |= (1 << color);
134 }
135 else if (before & (1 << color))
136 image->color_labels &= ~(1 << color);
137 }
138
139 // Update image cache object and write to DB in _write_release
142}
143
144static void _pop_undo(gpointer user_data, dt_undo_type_t type, dt_undo_data_t data, dt_undo_action_t action, GList **imgs)
145{
147 {
148 for(GList *list = (GList *)data; list; list = g_list_next(list))
149 {
150 dt_undo_colorlabels_t *undocolorlabels = (dt_undo_colorlabels_t *)list->data;
151
152 const int before = (action == DT_ACTION_UNDO) ? undocolorlabels->after : undocolorlabels->before;
153 const int after = (action == DT_ACTION_UNDO) ? undocolorlabels->before : undocolorlabels->after;
154 _pop_undo_execute(undocolorlabels->imgid, before, after);
155 *imgs = g_list_prepend(*imgs, GINT_TO_POINTER(undocolorlabels->imgid));
156 }
158 }
159}
160
161static void _colorlabels_undo_data_free(gpointer data)
162{
163 GList *l = (GList *)data;
164 g_list_free(l);
165 l = NULL;
166}
167
168void dt_colorlabels_remove_labels(const int32_t imgid)
169{
171 {
173 "DELETE FROM main.color_labels WHERE imgid=?1",
175 }
176 sqlite3_stmt *stmt = _colorlabels_remove_labels_stmt;
177 sqlite3_reset(stmt);
178 sqlite3_clear_bindings(stmt);
179 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
180 sqlite3_step(stmt);
181}
182
183void dt_colorlabels_set_label(const int32_t imgid, const int color)
184{
186 {
187 // clang-format off
189 "INSERT OR IGNORE INTO main.color_labels (imgid, color) VALUES (?1, ?2)",
190 -1, &_colorlabels_set_label_stmt, NULL);
191 // clang-format on
192 }
193 sqlite3_stmt *stmt = _colorlabels_set_label_stmt;
194 sqlite3_reset(stmt);
195 sqlite3_clear_bindings(stmt);
196 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
197 DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, color);
198 sqlite3_step(stmt);
199}
200
201void dt_colorlabels_remove_label(const int32_t imgid, const int color)
202{
204 {
205 // clang-format off
207 "DELETE FROM main.color_labels WHERE imgid=?1 AND color=?2",
209 // clang-format on
210 }
211 sqlite3_stmt *stmt = _colorlabels_remove_label_stmt;
212 sqlite3_reset(stmt);
213 sqlite3_clear_bindings(stmt);
214 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
215 DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, color);
216 sqlite3_step(stmt);
217}
218
220{
222 {
223 sqlite3_finalize(_colorlabels_get_labels_stmt);
225 }
227 {
228 sqlite3_finalize(_colorlabels_remove_labels_stmt);
230 }
232 {
233 sqlite3_finalize(_colorlabels_set_label_stmt);
235 }
237 {
238 sqlite3_finalize(_colorlabels_remove_label_stmt);
240 }
241}
242
249
250
251static void _colorlabels_execute(GList *imgs, const int labels, GList **undo, const gboolean undo_on, int action)
252{
253 if(action == DT_CA_TOGGLE)
254 {
255 // if we are supposed to toggle color labels, first check if all images have that label
256 for(const GList *image = g_list_first(imgs); image; image = g_list_next((GList *)image))
257 {
258 const int32_t image_id = GPOINTER_TO_INT(image->data);
259
260 dt_image_t *img = dt_image_cache_get(darktable.image_cache, image_id, 'r');
261 if(IS_NULL_PTR(img)) continue;
262
263 const int before = img->color_labels;
265
266 // as long as a single image does not have the label we do not toggle the label for all images
267 // but add the label to all unlabeled images first
268 if(!(before & labels))
269 {
270 action = DT_CA_ADD;
271 break;
272 }
273 }
274 }
275
276 for(GList *image = g_list_first(imgs); image; image = g_list_next((GList *)image))
277 {
278 const int32_t image_id = GPOINTER_TO_INT(image->data);
279
280 dt_image_t *img = dt_image_cache_get(darktable.image_cache, image_id, 'w');
281 if(IS_NULL_PTR(img)) continue;
282
283 const int before = img->color_labels;
284 int after = 0;
285 switch(action)
286 {
287 case DT_CA_SET:
288 after = labels;
289 break;
290 case DT_CA_ADD:
291 after = before | labels;
292 break;
293 case DT_CA_TOGGLE:
294 after = (before & labels) ? before & (~labels) : before | labels;
295 break;
296 default:
297 after = before;
298 break;
299 }
300
301 img->color_labels = after;
303
304 if(undo_on)
305 {
306 dt_undo_colorlabels_t *undocolorlabels = (dt_undo_colorlabels_t *)malloc(sizeof(dt_undo_colorlabels_t));
307 undocolorlabels->imgid = image_id;
308 undocolorlabels->before = before;
309 undocolorlabels->after = after;
310 *undo = g_list_append(*undo, undocolorlabels);
311 }
312 }
313}
314
315void dt_colorlabels_toggle_label_on_list(GList *list, const int color, const gboolean undo_on)
316{
317 const int label = 1<<color;
318 GList *undo = NULL;
320
321 if(color == 5)
322 {
323 _colorlabels_execute(list, 0, &undo, undo_on, DT_CA_SET);
324 }
325 else
326 {
327 _colorlabels_execute(list, label, &undo, undo_on, DT_CA_TOGGLE);
328 }
329
330 if(undo_on)
331 {
334 }
336 dt_toast_log(_("Color label set to %s for %i image(s)"), dt_colorlabels_get_name(color), g_list_length(list));
337}
338
339int dt_colorlabels_check_label(const int32_t imgid, const int color)
340{
341 if(imgid <= 0) return 0;
342 sqlite3_stmt *stmt;
343 // clang-format off
345 "SELECT * FROM main.color_labels WHERE imgid=?1 AND color=?2 LIMIT 1",
346 -1, &stmt, NULL);
347 // clang-format on
348 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
349 DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, color);
350 if(sqlite3_step(stmt) == SQLITE_ROW)
351 {
352 sqlite3_finalize(stmt);
353 return 1;
354 }
355 else
356 {
357 sqlite3_finalize(stmt);
358 return 0;
359 }
360}
361
362// FIXME: XMP uses Red, Green, ... while we use red, green, ... What should this function return?
363const char *dt_colorlabels_to_string(int label)
364{
365 if(label < 0 || label >= DT_COLORLABELS_LAST) return ""; // shouldn't happen
366 return dt_colorlabels_name[label];
367}
368
369// clang-format off
370// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
371// vim: shiftwidth=2 expandtab tabstop=2 cindent
372// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
373// clang-format on
void dt_collection_hint_message(const dt_collection_t *collection)
dt_colorlabels_actions_t
@ DT_CA_TOGGLE
@ DT_CA_ADD
@ DT_CA_SET
static void _pop_undo(gpointer user_data, dt_undo_type_t type, dt_undo_data_t data, dt_undo_action_t action, GList **imgs)
void dt_colorlabels_remove_label(const int32_t imgid, const int color)
static void _colorlabels_execute(GList *imgs, const int labels, GList **undo, const gboolean undo_on, int action)
void dt_colorlabels_remove_labels(const int32_t imgid)
static void _colorlabels_undo_data_free(gpointer data)
static sqlite3_stmt * _colorlabels_remove_label_stmt
Definition colorlabels.c:87
void dt_colorlabels_cleanup(void)
static sqlite3_stmt * _colorlabels_get_labels_stmt
Definition colorlabels.c:84
int dt_colorlabels_get_labels(const int32_t imgid)
Definition colorlabels.c:89
int dt_colorlabels_check_label(const int32_t imgid, const int color)
static void _pop_undo_execute(const int32_t imgid, const int before, const int after)
static sqlite3_stmt * _colorlabels_set_label_stmt
Definition colorlabels.c:86
void dt_colorlabels_set_label(const int32_t imgid, const int color)
static sqlite3_stmt * _colorlabels_remove_labels_stmt
Definition colorlabels.c:85
void dt_colorlabels_toggle_label_on_list(GList *list, const int color, const gboolean undo_on)
const char * dt_colorlabels_to_string(int label)
char * dt_colorlabels_get_name(const int label)
Definition colorlabels.c:56
const char * dt_colorlabels_name[]
Definition colorlabels.c:51
void dt_colorlabels_set_labels(const int32_t imgid, const int colors)
@ DT_COLORLABELS_LAST
Definition colorlabels.h:46
int type
void dt_toast_log(const char *msg,...)
Definition control.c:808
darktable_t darktable
Definition darktable.c:181
#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
sqlite3 * dt_database_get(const dt_database_t *db)
Definition database.c:3646
#define DT_DEBUG_SQLITE3_PREPARE_V2(a, b, c, d, e)
Definition debug.h:107
#define DT_DEBUG_SQLITE3_BIND_INT(a, b, c)
Definition debug.h:115
const dt_collection_filter_flag_t colors[6]
Definition filter.c:295
void dt_image_cache_read_release(dt_image_cache_t *cache, const dt_image_t *img)
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_SAFE
Definition image_cache.h:49
struct dt_undo_t * undo
Definition darktable.h:787
struct dt_collection_t * collection
Definition darktable.h:781
const struct dt_database_t * db
Definition darktable.h:779
struct dt_image_cache_t * image_cache
Definition darktable.h:777
int color_labels
Definition image.h:371
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_type_t
Definition undo.h:39
@ DT_UNDO_COLORLABELS
Definition undo.h:45
void * dt_undo_data_t
Definition undo.h:67
dt_undo_action_t
Definition undo.h:62
@ DT_ACTION_UNDO
Definition undo.h:63