Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
selection.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2011-2012 Henrik Andersson.
4 Copyright (C) 2012 James C. McPherson.
5 Copyright (C) 2012 johannes hanika.
6 Copyright (C) 2012 Richard Wonka.
7 Copyright (C) 2012-2014, 2016 Tobias Ellinghaus.
8 Copyright (C) 2013 Jérémy Rosen.
9 Copyright (C) 2013, 2018-2021 Pascal Obry.
10 Copyright (C) 2013 Simon Spannagel.
11 Copyright (C) 2014-2016 Roman Lebedev.
12 Copyright (C) 2018 Rick Yorgason.
13 Copyright (C) 2019 Edgardo Hoszowski.
14 Copyright (C) 2019 Rikard Öxler.
15 Copyright (C) 2020-2021 Aldric Renaudin.
16 Copyright (C) 2020 Hubert Kowalski.
17 Copyright (C) 2020, 2022 Philippe Weyland.
18 Copyright (C) 2021 Ralf Brown.
19 Copyright (C) 2021 solarer.
20 Copyright (C) 2022-2023, 2025-2026 Aurélien PIERRE.
21 Copyright (C) 2022 Martin Bařinka.
22 Copyright (C) 2023 Luca Zulberti.
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
38#include "common/collection.h"
39#include "common/selection.h"
40#include "common/darktable.h"
41#include "control/signal.h"
42#include "gui/gtk.h"
43#include "views/view.h"
44
45static sqlite3_stmt *_selection_database_to_glist_stmt = NULL;
46
47typedef struct dt_selection_t
48{
49 /* length of selection. 0 means no selection, -1 means it needs to be updated */
50 uint32_t length;
51
52 /* this stores the last single clicked image id indicating
53 the start of a selection range */
55
56 /* GList of ids of all images in selection */
57 GList *ids;
59
60
61// Signal the GUI that selection got changed and trigger a selected images counter update
67
68
69int32_t dt_selection_get_first_id(struct dt_selection_t *selection)
70{
71 return selection->last_single_id;
72}
73
74
75static void _reset_ids_list(dt_selection_t *selection)
76{
77 g_list_free(g_steal_pointer(&selection->ids));
78 selection->ids = NULL;
79 selection->length = 0;
80 selection->last_single_id = -1;
81}
82
83static void _update_last_ids(dt_selection_t *selection)
84{
85 GList *last = g_list_last(selection->ids);
86 if(last)
87 selection->last_single_id = GPOINTER_TO_INT(last->data);
88 else
89 selection->last_single_id = -1;
90}
91
92// Drop selected imgids that are not in the current collection
93// WARNING: that doesn't take care of visible/unvisible image group members in GUI
94static void _clean_missing_ids(dt_selection_t *selection)
95{
97 "DELETE FROM main.selected_images"
98 " WHERE imgid NOT IN"
99 " (SELECT imgid FROM memory.collected_images)", NULL, NULL, NULL);
100}
101
102// Unroll DB imgids to GList
104{
105 GList *list = NULL;
106
108 {
109 // clang-format off
111 "SELECT imgid FROM main.selected_images ORDER BY imgid DESC",
113 // clang-format on
114 }
115 sqlite3_stmt *stmt = _selection_database_to_glist_stmt;
116 sqlite3_reset(stmt);
117 sqlite3_clear_bindings(stmt);
118
119 while(sqlite3_step(stmt) == SQLITE_ROW)
120 {
121 const int32_t imgid = sqlite3_column_int(stmt, 0);
122 list = g_list_prepend(list, GINT_TO_POINTER(imgid));
123 }
124
125 // Don't reverse the GList since we ordered SQL rows by descending order
126 // and prepend to the GList.
127 return list;
128}
129
131{
132 _reset_ids_list(selection);
133 selection->ids = _selection_database_to_glist(selection);
134 selection->length = g_list_length(selection->ids);
135 _update_last_ids(selection);
136}
137
138/* On collection change events, ensure the selection is only a subset of the current collection,
139 * aka it doesn't contain dangling imgids that can't be found in current collection
140 */
141static void _selection_update_collection(gpointer instance, dt_collection_change_t query_change,
142 dt_collection_properties_t changed_property, gpointer imgs, uint32_t next,
143 dt_selection_t *selection)
144{
145 _clean_missing_ids(selection);
147 _update_gui();
148}
149
150
151static void _remove_id_link(dt_selection_t *selection, int32_t imgid)
152{
153 GList *link = g_list_find(selection->ids, GINT_TO_POINTER(imgid));
154 if(link)
155 {
156 selection->ids = g_list_delete_link(selection->ids, link);
157 --selection->length;
158 }
159 _update_last_ids(selection);
160}
161
162static void _add_id_link(dt_selection_t *selection, int32_t imgid)
163{
164 if(!g_list_find(selection->ids, GINT_TO_POINTER(imgid)))
165 {
166 selection->ids = g_list_append(selection->ids, GINT_TO_POINTER(imgid));
167 ++selection->length;
168 }
169 selection->last_single_id = imgid;
170}
171
172GList *dt_selection_get_list(struct dt_selection_t *selection)
173{
174 if(IS_NULL_PTR(selection->ids)) return NULL;
175
176 return g_list_copy(selection->ids);
177}
178
180{
181 if(IS_NULL_PTR(selection) || !selection->ids) return 0;
182
183 return selection->length;
184}
185
186static void _selection_select(dt_selection_t *selection, int32_t imgid)
187{
188 if(imgid < 0) return;
189
190 gchar *query = g_strdup_printf("INSERT OR IGNORE INTO main.selected_images VALUES (%d)", imgid);
191 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), query, NULL, NULL, NULL);
192 dt_free(query);
193}
194
195static void _selection_deselect(dt_selection_t *selection, int32_t imgid)
196{
197 if(imgid < 0) return;
198
199 gchar *query = g_strdup_printf("DELETE FROM main.selected_images WHERE imgid = %d", imgid);
200 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), query, NULL, NULL, NULL);
201 dt_free(query);
202}
203
205{
206 // Backup current selection
208 {
209 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "DELETE FROM memory.selected_backup", NULL, NULL, NULL);
210 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "INSERT INTO memory.selected_backup"
211 " SELECT * FROM main.selected_images", NULL, NULL, NULL);
213
214 // Commit from DB to GList of imgids
216 }
217
218 _update_gui();
219}
220
222{
223 // Restore current selection
225 {
226 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "DELETE FROM main.selected_images", NULL, NULL, NULL);
227 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "INSERT INTO main.selected_images"
228 " SELECT * FROM memory.selected_backup", NULL, NULL, NULL);
230
231 // Commit from DB to GList of imgids
233 }
234
235 _update_gui();
236}
237
239{
240 dt_selection_t *selection = g_malloc0(sizeof(dt_selection_t));
241
242 /* populate our local cache */
244
245 /* setup signal handler for collection update to sanitize selection imgids */
247 G_CALLBACK(_selection_update_collection), (gpointer)selection);
248
249 return selection;
250}
251
253{
255 (gpointer)selection);
256 g_list_free(selection->ids);
257 selection->ids = NULL;
259 {
260 sqlite3_finalize(_selection_database_to_glist_stmt);
262 }
263 dt_free(selection);
264}
265
267{
268 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "DELETE FROM main.selected_images", NULL, NULL, NULL);
269 _reset_ids_list(selection);
270 _update_gui();
271}
272
273void dt_selection_select(dt_selection_t *selection, int32_t imgid)
274{
275 if(imgid == UNKNOWN_IMAGE) return;
276 _selection_select(selection, imgid);
277 _add_id_link(selection, imgid);
278 _update_gui();
279}
280
281void dt_selection_deselect(dt_selection_t *selection, int32_t imgid)
282{
283 if(imgid == UNKNOWN_IMAGE) return;
284 _selection_deselect(selection, imgid);
285 _remove_id_link(selection, imgid);
286 _update_gui();
287}
288
289void dt_selection_select_single(dt_selection_t *selection, int32_t imgid)
290{
291 if(imgid == UNKNOWN_IMAGE) return;
292 dt_selection_clear(selection);
293 dt_selection_select(selection, imgid);
294}
295
296void dt_selection_toggle(dt_selection_t *selection, int32_t imgid)
297{
298 if(imgid == UNKNOWN_IMAGE) return;
299
300 if(g_list_find(selection->ids, GINT_TO_POINTER(imgid)))
301 dt_selection_deselect(selection, imgid);
302 else
303 dt_selection_select(selection, imgid);
304}
305
306static int32_t _list_iterate(struct dt_selection_t *selection, GList **list, int *count, const gboolean add)
307{
308 *count += 1;
309 int32_t imgid = GPOINTER_TO_INT((*list)->data);
310
311 if(add)
312 _add_id_link(selection, imgid);
313 else
314 _remove_id_link(selection, imgid);
315
316 *list = g_list_next(*list);
317 return imgid;
318}
319
320void dt_selection_select_list(struct dt_selection_t *selection, const GList *const l)
321{
322 if(IS_NULL_PTR(l)) return;
323 GList *list = (GList *)l;
324
325 // Send SQL queries by batches of 400 imgids for performance
326 while(list)
327 {
328 int count = 0;
329 gchar *ids = g_strdup("");
330 while(list && count < 400)
331 {
332 int32_t imgid = _list_iterate(selection, &list, &count, TRUE);
333 ids = dt_util_dstrcat(ids, (ids[0] != '\0') ? ", (%i)" : "(%i)", imgid);
334 }
335 gchar *query = g_strdup_printf("INSERT OR IGNORE INTO main.selected_images VALUES %s", ids);
336 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), query, NULL, NULL, NULL);
337 dt_free(query);
338 }
339
340 _update_gui();
341}
342
343void dt_selection_deselect_list(struct dt_selection_t *selection, const GList *const l)
344{
345 if(IS_NULL_PTR(l)) return;
346 GList *list = (GList *)l;
347
348 // Send SQL queries by batches of 400 imgids for performance
349 while(list)
350 {
351 int count = 0;
352 gchar *ids = g_strdup("");
353 while(list && count < 400)
354 {
355 int32_t imgid = _list_iterate(selection, &list, &count, FALSE);
356 ids = dt_util_dstrcat(ids, (ids[0] != '\0') ? ", %i" : "%i", imgid);
357 }
358 gchar *query = g_strdup_printf("DELETE FROM main.selected_images WHERE imgid IN (%s)", ids);
359 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), query, NULL, NULL, NULL);
360 dt_free(query);
361 dt_free(ids);
362 }
363
364 _update_gui();
365}
366
368{
369 // There is no selection even after init, abort
370 if(IS_NULL_PTR(selection->ids)) return NULL;
371
372 gchar **ids = g_malloc0_n(selection->length + 1, 9 * sizeof(char *));
373 uint32_t i = 0;
374
375 // Build the array of uint32_tegers as charaters
376 for(GList *id = g_list_first(selection->ids); id; id = g_list_next(id))
377 {
378 ids[i] = g_strdup_printf("%i", GPOINTER_TO_INT(id->data));
379 i++;
380 }
381
382 // ids needs to be null-terminated for strjoinv
383 ids[i] = NULL;
384
385 // Concatenate with blank comas within
386 gchar *result = g_strjoinv(",", ids);
387
388 g_strfreev(ids);
389
390 return result;
391}
392
393gboolean dt_selection_is_id_selected(struct dt_selection_t *selection, int32_t imgid)
394{
395 if(IS_NULL_PTR(selection) || !selection->ids) return FALSE;
396 return (g_list_find(selection->ids, GINT_TO_POINTER(imgid)) != NULL);
397}
398
399// clang-format off
400// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
401// vim: shiftwidth=2 expandtab tabstop=2 cindent
402// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
403// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_collection_hint_message(const dt_collection_t *collection)
dt_collection_properties_t
Definition collection.h:107
dt_collection_change_t
Definition collection.h:147
darktable_t darktable
Definition darktable.c:181
#define UNKNOWN_IMAGE
Definition darktable.h:182
#define dt_free(ptr)
Definition darktable.h:456
#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_EXEC(a, b, c, d, e)
Definition debug.h:99
#define DT_DEBUG_SQLITE3_PREPARE_V2(a, b, c, d, e)
Definition debug.h:107
static GList * _selection_database_to_glist(dt_selection_t *selection)
Definition selection.c:103
int32_t dt_selection_get_first_id(struct dt_selection_t *selection)
Definition selection.c:69
void dt_selection_deselect_list(struct dt_selection_t *selection, const GList *const l)
Definition selection.c:343
static sqlite3_stmt * _selection_database_to_glist_stmt
Definition selection.c:45
void dt_selection_reload_from_database_real(dt_selection_t *selection)
Definition selection.c:130
void dt_selection_push(dt_selection_t *selection)
Definition selection.c:204
static void _update_gui()
Definition selection.c:62
gchar * dt_selection_ids_to_string(struct dt_selection_t *selection)
Definition selection.c:367
static void _remove_id_link(dt_selection_t *selection, int32_t imgid)
Definition selection.c:151
static void _selection_update_collection(gpointer instance, dt_collection_change_t query_change, dt_collection_properties_t changed_property, gpointer imgs, uint32_t next, dt_selection_t *selection)
Definition selection.c:141
int dt_selection_get_length(struct dt_selection_t *selection)
Definition selection.c:179
static void _update_last_ids(dt_selection_t *selection)
Definition selection.c:83
static void _selection_deselect(dt_selection_t *selection, int32_t imgid)
Definition selection.c:195
void dt_selection_free(dt_selection_t *selection)
Definition selection.c:252
static int32_t _list_iterate(struct dt_selection_t *selection, GList **list, int *count, const gboolean add)
Definition selection.c:306
static void _selection_select(dt_selection_t *selection, int32_t imgid)
Definition selection.c:186
GList * dt_selection_get_list(struct dt_selection_t *selection)
Definition selection.c:172
gboolean dt_selection_is_id_selected(struct dt_selection_t *selection, int32_t imgid)
Definition selection.c:393
dt_selection_t * dt_selection_new()
Definition selection.c:238
void dt_selection_select_list(struct dt_selection_t *selection, const GList *const l)
Definition selection.c:320
static void _add_id_link(dt_selection_t *selection, int32_t imgid)
Definition selection.c:162
static void _clean_missing_ids(dt_selection_t *selection)
Definition selection.c:94
static void _reset_ids_list(dt_selection_t *selection)
Definition selection.c:75
void dt_selection_clear(dt_selection_t *selection)
Definition selection.c:266
void dt_selection_deselect(dt_selection_t *selection, int32_t imgid)
Definition selection.c:281
void dt_selection_select(dt_selection_t *selection, int32_t imgid)
Definition selection.c:273
void dt_selection_select_single(dt_selection_t *selection, int32_t imgid)
Definition selection.c:289
void dt_selection_pop(dt_selection_t *selection)
Definition selection.c:221
void dt_selection_toggle(dt_selection_t *selection, int32_t imgid)
Definition selection.c:296
#define dt_selection_reload_from_database(selection)
Definition selection.h:112
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:368
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:347
@ DT_SIGNAL_SELECTION_CHANGED
This signal is raised when the selection is changed no param, no returned value.
Definition signal.h:127
@ DT_SIGNAL_COLLECTION_CHANGED
This signal is raised when collection changed. To avoid leaking the list, dt_collection_t is connecte...
Definition signal.h:122
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:357
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_collection_t * collection
Definition darktable.h:781
const struct dt_database_t * db
Definition darktable.h:779
struct dt_control_signal_t * signals
Definition darktable.h:774
gboolean selection_stacked
Definition gtk.h:191
uint32_t length
Definition selection.c:50
int32_t last_single_id
Definition selection.c:54
GList * ids
Definition selection.c:57
gchar * dt_util_dstrcat(gchar *str, const gchar *format,...)
Definition utility.c:95