Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
collection.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010-2012 Henrik Andersson.
4 Copyright (C) 2010-2013 johannes hanika.
5 Copyright (C) 2010-2017 Tobias Ellinghaus.
6 Copyright (C) 2011-2012 José Carlos García Sogo.
7 Copyright (C) 2011 Robert Bieber.
8 Copyright (C) 2012, 2018-2022 Pascal Obry.
9 Copyright (C) 2012 Petr Styblo.
10 Copyright (C) 2012 Richard Wonka.
11 Copyright (C) 2012-2013 Simon Spannagel.
12 Copyright (C) 2013 Gaspard Jankowiak.
13 Copyright (C) 2013 hal.
14 Copyright (C) 2013 Ulrich Pegelow.
15 Copyright (C) 2014-2016 Roman Lebedev.
16 Copyright (C) 2015-2016 Jérémy Rosen.
17 Copyright (C) 2015 Pedro Côrte-Real.
18 Copyright (C) 2016, 2020-2022 Aldric Renaudin.
19 Copyright (C) 2016 itinerarium.
20 Copyright (C) 2016-2017 Peter Budai.
21 Copyright (C) 2016 Petr Synek.
22 Copyright (C) 2017 Dominik Markiewicz.
23 Copyright (C) 2017, 2019 Liran Vaknin.
24 Copyright (C) 2017, 2019 luzpaz.
25 Copyright (C) 2018 August Schwerdfeger.
26 Copyright (C) 2018 Mario Lueder.
27 Copyright (C) 2018 Rick Yorgason.
28 Copyright (C) 2018 Rikard Öxler.
29 Copyright (C) 2018, 2020 Sam Smith.
30 Copyright (C) 2018 Simon Legner.
31 Copyright (C) 2019 Bill Ferguson.
32 Copyright (C) 2019-2020 Heiko Bauke.
33 Copyright (C) 2019 Mark Feit.
34 Copyright (C) 2019 rrd1.
35 Copyright (C) 2020 codingdave@gmail.com.
36 Copyright (C) 2020 David-Tillmann Schaefer.
37 Copyright (C) 2020 Hanno Schwalm.
38 Copyright (C) 2020 Hubert Kowalski.
39 Copyright (C) 2020 JP Verrue.
40 Copyright (C) 2020 jpverrue.
41 Copyright (C) 2020-2022 Philippe Weyland.
42 Copyright (C) 2020 Tino Mettler.
43 Copyright (C) 2020 U-DESKTOP-HQME86J\marco.
44 Copyright (C) 2021 Arnaud TANGUY.
45 Copyright (C) 2021 Chris Elston.
46 Copyright (C) 2021 Daniel Vogelbacher.
47 Copyright (C) 2021 HansBull.
48 Copyright (C) 2021 Harald.
49 Copyright (C) 2021 quovadit.
50 Copyright (C) 2021 Ralf Brown.
51 Copyright (C) 2021 Stefan Boxleitner.
52 Copyright (C) 2022-2026 Aurélien PIERRE.
53 Copyright (C) 2022 Martin Bařinka.
54 Copyright (C) 2022 Miloš Komarčević.
55 Copyright (C) 2023 André Doherty.
56 Copyright (C) 2024-2025 Guillaume Stutin.
57
58 darktable is free software: you can redistribute it and/or modify
59 it under the terms of the GNU General Public License as published by
60 the Free Software Foundation, either version 3 of the License, or
61 (at your option) any later version.
62
63 darktable is distributed in the hope that it will be useful,
64 but WITHOUT ANY WARRANTY; without even the implied warranty of
65 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
66 GNU General Public License for more details.
67
68 You should have received a copy of the GNU General Public License
69 along with darktable. If not, see <http://www.gnu.org/licenses/>.
70*/
71
72#include "common/darktable.h"
73#include "common/collection.h"
74#include "common/debug.h"
75#include "common/colorlabels.h"
76#include "common/image.h"
78#include "common/metadata.h"
79#include "common/utility.h"
81#include "common/datetime.h"
82#include "common/selection.h"
83#include "control/conf.h"
84#include "control/control.h"
85
86#include <assert.h>
87#include <glib.h>
88#include <memory.h>
89#include <stdio.h>
90#include <stdlib.h>
91#include <unistd.h>
92
93
94#ifdef _WIN32
95//MSVCRT does not have strptime implemented
96#include "win/strptime.h"
97#endif
98
99
100#define SELECT_QUERY "SELECT DISTINCT * FROM %s"
101#define LIMIT_QUERY "LIMIT ?1, ?2"
102
103static sqlite3_stmt *_collection_count_stmt = NULL;
104static sqlite3_stmt *_collection_get_stmt = NULL;
105static sqlite3_stmt *_collection_get_limit_stmt = NULL;
106static sqlite3_stmt *_collection_get_makermodels_stmt = NULL;
107static sqlite3_stmt *_collection_image_offset_stmt = NULL;
108
109/* Stores the collection query, returns 1 if changed.. */
110static int _dt_collection_store(const dt_collection_t *collection, gchar *query);
111/* Counts the number of images in the current collection */
112static uint32_t _dt_collection_compute_count(dt_collection_t *collection);
113
114/* determine image offset of specified imgid for the given collection */
115static int dt_collection_image_offset_with_collection(const dt_collection_t *collection, int32_t imgid);
116
118{
119 dt_collection_t *collection = g_malloc0(sizeof(dt_collection_t));
120 dt_collection_reset(collection);
121 return collection;
122}
123
124void dt_collection_free(const dt_collection_t *collection)
125{
126 dt_free(collection->query);
127 dt_free(collection->params.text_filter);
128 g_strfreev(collection->where_ext);
130 {
131 sqlite3_finalize(_collection_count_stmt);
133 }
135 {
136 sqlite3_finalize(_collection_get_stmt);
138 }
140 {
141 sqlite3_finalize(_collection_get_limit_stmt);
143 }
145 {
146 sqlite3_finalize(_collection_get_makermodels_stmt);
148 }
150 {
151 sqlite3_finalize(_collection_image_offset_stmt);
153 }
154 dt_free(collection);
155}
156
158{
159 return &collection->params;
160}
161
162
163// Return a pointer to a static string for an "AND" operator if the
164// number of terms processed so far requires it. The variable used
165// for term should be an int initialized to and_operator_initial()
166// before use.
167#define and_operator_initial() (0)
168static char * and_operator(int *term)
169{
170 assert(!IS_NULL_PTR(term));
171 if(*term == 0)
172 {
173 *term = 1;
174 return "";
175 }
176 else
177 {
178 return " AND ";
179 }
180
181 assert(0); // Not reached.
182}
183
184#define or_operator_initial() (0)
185static char * or_operator(int *term)
186{
187 assert(!IS_NULL_PTR(term));
188 if(*term == 0)
189 {
190 *term = 1;
191 return "";
192 }
193 else
194 {
195 return " OR ";
196 }
197
198 assert(0); // Not reached.
199}
200
202{
204 sqlite3_stmt *stmt;
205
206 /* check if we can get a query from collection */
207 gchar *query = g_strdup(dt_collection_get_query(darktable.collection));
208 if(IS_NULL_PTR(query)) return;
209
210 // Handle culling mode across re-queryings : re-restrict collection to selection
213
214 // 1. drop previous data
215
216 // clang-format off
218 "DELETE FROM memory.collected_images",
219 NULL, NULL, NULL);
220 // reset autoincrement. need in star_key_accel_callback
222 "DELETE FROM memory.sqlite_sequence"
223 " WHERE name='collected_images'",
224 NULL, NULL, NULL);
225 // clang-format on
226
227 // 2. insert collected images into the temporary table
228 gchar *ins_query = g_strdup_printf("INSERT INTO memory.collected_images (imgid) %s", query);
229
230 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), ins_query, -1, &stmt, NULL);
231 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, 0);
232 DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, -1);
233 sqlite3_step(stmt);
234 sqlite3_finalize(stmt);
235
236 dt_free(query);
237 dt_free(ins_query);
238
239 // Handle culling mode across re-queryings : re-restrict collection to selection
242
245}
246
247static void _dt_collection_set_selq_pre_sort(const dt_collection_t *collection, char **selq_pre)
248{
249 const uint32_t tagid = collection->tagid;
250 char tag[16] = { 0 };
251 snprintf(tag, sizeof(tag), "%u", tagid);
252
253 // clang-format off
254 *selq_pre = dt_util_dstrcat(*selq_pre,
255 "SELECT DISTINCT mi.id FROM (SELECT"
256 " id, group_id, film_id, filename, datetime_taken, "
257 " flags, version, aspect_ratio,"
258 " maker, model, lens, aperture, exposure, focal_length,"
259 " iso, import_timestamp, change_timestamp,"
260 " export_timestamp, print_timestamp"
261 " FROM main.images AS mi %s%s WHERE ",
262 tagid ? " LEFT JOIN main.tagged_images AS ti"
263 " ON ti.imgid = mi.id AND ti.tagid = " : "",
264 tagid ? tag : "");
265 // clang-format on
266}
267
269{
270 uint32_t result;
271 gchar *wq, *sq, *selq_pre, *selq_post, *query;
272 wq = sq = selq_pre = selq_post = query = NULL;
273
274 /* build where part */
275 gchar *where_ext = dt_collection_get_extended_where(collection, -1);
277 {
278 wq = g_strdup(where_ext);
279 }
280 else if(collection->params.filter_flags > COLLECTION_FILTER_NONE)
281 {
282 char *rejected_check = g_strdup_printf("((flags & %d) = %d)", DT_IMAGE_REJECTED, DT_IMAGE_REJECTED);
283 int and_term = 1; // that effectively makes the use of and_operator() useless
284
285 // DON'T SELECT IMAGES MARKED TO BE DELETED.
286 wq = g_strdup_printf(" ((flags & %d) != %d) ", DT_IMAGE_REMOVE, DT_IMAGE_REMOVE);
287
288 /* From there, the other arguments are OR so we need parentheses if any rating filter is used */
289 gboolean got_rating_filter
290 = collection->params.filter_flags
294
295 if(got_rating_filter)
296 wq = dt_util_dstrcat(wq, " %s (", and_operator(&and_term));
297
298 int or_term = or_operator_initial();
299 /* Rejected was a mutually-exclusive rating in initial design, but got converted to
300 a toggle state circa 2019, aka images can now have a rating AND be rejected.
301 Which sucks because users will not expect rejected images to show when they target n stars ratings.
302 Aka we collect images that are rejected OR (have rating == n AND are not rejected).
303 Also, because rating flags are bitmasks but not octal, we can't build a single bitmask to
304 turn into a single SQL request
305 */
307 wq = dt_util_dstrcat(wq, " %s %s ", or_operator(&or_term), rejected_check);
308
310 wq = dt_util_dstrcat(wq, " %s ((flags & 7) = %i AND NOT %s) ", or_operator(&or_term),
311 DT_VIEW_DESERT, rejected_check);
312
314 wq = dt_util_dstrcat(wq, " %s ((flags & 7) = %i AND NOT %s) ", or_operator(&or_term),
315 DT_VIEW_STAR_1, rejected_check);
316
318 wq = dt_util_dstrcat(wq, " %s ((flags & 7) = %i AND NOT %s) ", or_operator(&or_term),
319 DT_VIEW_STAR_2, rejected_check);
320
322 wq = dt_util_dstrcat(wq, " %s ((flags & 7) = %i AND NOT %s) ", or_operator(&or_term),
323 DT_VIEW_STAR_3, rejected_check);
324
326 wq = dt_util_dstrcat(wq, " %s ((flags & 7) = %i AND NOT %s) ", or_operator(&or_term),
327 DT_VIEW_STAR_4, rejected_check);
328
330 wq = dt_util_dstrcat(wq, " %s ((flags & 7) = %i AND NOT %s) ", or_operator(&or_term),
331 DT_VIEW_STAR_5, rejected_check);
332
333 /* Closing the OR parentheses */
334 if(got_rating_filter)
335 wq = dt_util_dstrcat(wq, ") ");
336
337 gboolean got_altered_filter
339
340 if(got_altered_filter)
341 wq = dt_util_dstrcat(wq, " %s (", and_operator(&and_term));
342
343 or_term = or_operator_initial();
345 // clang-format off
346 wq = dt_util_dstrcat(wq, " %s id IN (SELECT imgid FROM main.history)",
347 or_operator(&or_term));
348 // clang-format on
349
351 // clang-format off
352 wq = dt_util_dstrcat(wq, " %s id NOT IN (SELECT imgid FROM main.history) ",
353 or_operator(&or_term));
354 // clang-format on
355
356 if(got_altered_filter)
357 wq = dt_util_dstrcat(wq, ") ");
358
359 /* add text filter if any */
360 if(collection->params.text_filter && collection->params.text_filter[0])
361 {
362 // clang-format off
363 wq = dt_util_dstrcat(wq, " %s id IN (SELECT id FROM main.meta_data WHERE value LIKE '%s'"
364 " UNION SELECT imgid AS id FROM main.tagged_images AS ti, data.tags AS t"
365 " WHERE t.id=ti.tagid AND (t.name LIKE '%s' OR t.synonyms LIKE '%s')"
366 " UNION SELECT id FROM main.images"
367 " WHERE filename LIKE '%s'"
368 " UNION SELECT i.id FROM main.images AS i, main.film_rolls AS fr"
369 " WHERE fr.id=i.film_id AND fr.folder LIKE '%s')",
370 and_operator(&and_term), collection->params.text_filter,
371 collection->params.text_filter,
372 collection->params.text_filter,
373 collection->params.text_filter,
374 collection->params.text_filter);
375 // clang-format on
376 }
377
378 /* add colorlabel filter if any */
379 gboolean got_color_filter = collection->params.filter_flags
382
383 if(got_color_filter)
384 {
385 int color_mask = 0;
387 color_mask |= 1 << DT_COLORLABELS_RED;
389 color_mask |= 1 << DT_COLORLABELS_YELLOW;
391 color_mask |= 1 << DT_COLORLABELS_GREEN;
393 color_mask |= 1 << DT_COLORLABELS_BLUE;
395 color_mask |= 1 << DT_COLORLABELS_PURPLE;
396
397 // color_mask = 31 when all flags are on
398 wq = dt_util_dstrcat(wq, " %s (", and_operator(&and_term));
399
400 or_term = or_operator_initial();
401
402 // clang-format off
403 if(color_mask > 0)
404 wq = dt_util_dstrcat(wq, " %s id IN (SELECT id FROM"
405 " (SELECT imgid AS id, SUM(1 << color) AS mask FROM main.color_labels GROUP BY imgid)"
406 " WHERE ((mask & %i) > 0))",
407 or_operator(&or_term), color_mask);
408
410 wq = dt_util_dstrcat(wq, " %s id NOT IN (SELECT id FROM"
411 " (SELECT imgid AS id, SUM(1 << color) AS mask FROM main.color_labels GROUP BY imgid)"
412 " WHERE ((mask & 31) > 0))",
413 or_operator(&or_term));
414
415 // clang-format on
416 wq = dt_util_dstrcat(wq, ")");
417 }
418
419 /* add where ext if wanted */
421 wq = dt_util_dstrcat(wq, " %s %s", and_operator(&and_term), where_ext);
422
423 dt_free(rejected_check);
424 }
425 else
426 {
427 // No filter set: no collection, because filters are toggle in.
428 // Just setup some bullshit condition impossible to match.
429 wq = g_strdup(" id=0");
430 }
431
432 dt_free(where_ext);
433
434 /* build select part includes where */
435 /* only COLOR */
436 if((collection->params.sort == DT_COLLECTION_SORT_COLOR)
438 {
439 _dt_collection_set_selq_pre_sort(collection, &selq_pre);
440 // clang-format off
441 selq_post = dt_util_dstrcat(selq_post, ") AS mi LEFT OUTER JOIN main.color_labels AS b ON mi.id = b.imgid");
442 // clang-format on
443 }
444 /* only PATH */
445 else if((collection->params.sort == DT_COLLECTION_SORT_PATH)
447 {
448 _dt_collection_set_selq_pre_sort(collection, &selq_pre);
449 // clang-format off
450 selq_post = dt_util_dstrcat
451 (selq_post,
452 ") AS mi JOIN (SELECT id AS film_rolls_id, folder FROM main.film_rolls) ON film_id = film_rolls_id");
453 // clang-format on
454 }
455 /* only TITLE */
456 else if((collection->params.sort == DT_COLLECTION_SORT_TITLE)
458 {
459 _dt_collection_set_selq_pre_sort(collection, &selq_pre);
460 // clang-format off
461 selq_post = dt_util_dstrcat(selq_post, ") AS mi LEFT OUTER JOIN main.meta_data AS m ON mi.id = m.id AND m.key = %d ",
463 // clang-format on
464 }
466 {
467 const uint32_t tagid = collection->tagid;
468 char tag[16] = { 0 };
469 snprintf(tag, sizeof(tag), "%u", tagid);
470 // clang-format off
471 selq_pre = dt_util_dstrcat(selq_pre,
472 "SELECT DISTINCT mi.id FROM (SELECT"
473 " id, group_id, film_id, filename, datetime_taken, "
474 " flags, version, %s position, aspect_ratio,"
475 " maker, model, lens, aperture, exposure, focal_length,"
476 " iso, import_timestamp, change_timestamp,"
477 " export_timestamp, print_timestamp"
478 " FROM main.images AS mi %s%s ) AS mi ",
479 tagid ? "CASE WHEN ti.position IS NULL THEN 0 ELSE ti.position END AS" : "",
480 tagid ? " LEFT JOIN main.tagged_images AS ti"
481 " ON ti.imgid = mi.id AND ti.tagid = " : "",
482 tagid ? tag : "");
483 // clang-format on
484 }
485 else
486 {
487 const uint32_t tagid = collection->tagid;
488 char tag[16] = { 0 };
489 snprintf(tag, sizeof(tag), "%u", tagid);
490 // clang-format off
491 selq_pre = dt_util_dstrcat(selq_pre,
492 "SELECT DISTINCT mi.id FROM (SELECT"
493 " id, group_id, film_id, filename, datetime_taken, "
494 " flags, version, %s position, aspect_ratio,"
495 " maker, model, lens, aperture, exposure, focal_length,"
496 " iso, import_timestamp, change_timestamp,"
497 " export_timestamp, print_timestamp"
498 " FROM main.images AS mi %s%s ) AS mi WHERE ",
499 tagid ? "CASE WHEN ti.position IS NULL THEN 0 ELSE ti.position END AS" : "",
500 tagid ? " LEFT JOIN main.tagged_images AS ti"
501 " ON ti.imgid = mi.id AND ti.tagid = " : "",
502 tagid ? tag : "");
503 // clang-format on
504 }
505
506
507 /* build sort order part */
510 {
511 sq = dt_collection_get_sort_query(collection);
512 }
513
514 /* store the new query */
515 query
516 = dt_util_dstrcat(query, "%s%s%s %s%s", selq_pre, wq, selq_post ? selq_post : "", sq ? sq : "",
517 (collection->params.query_flags & COLLECTION_QUERY_USE_LIMIT) ? " " LIMIT_QUERY : "");
518
519 result = _dt_collection_store(collection, query);
520
521 /* free memory used */
522 dt_free(sq);
523 dt_free(wq);
524 dt_free(selq_pre);
525 dt_free(selq_post);
526 dt_free(query);
527
528 return result;
529}
530
532{
533 dt_collection_params_t *params = (dt_collection_params_t *)&collection->params;
534
535 /* setup defaults */
536 params->query_flags = COLLECTION_QUERY_FULL;
537
538 // enable all filters, aka filter in everything
539 params->filter_flags = COLLECTION_FILTER_ALL;
540
541 /* apply stored query parameters from previous darktable session */
542 int flags = dt_conf_get_int("plugins/collection/filter_flags");
543 params->filter_flags = (flags < 0) ? COLLECTION_FILTER_ALL : flags;
544
545 dt_free(params->text_filter);
546 params->text_filter = dt_conf_get_string("plugins/collection/text_filter");
547 params->sort = dt_conf_get_int("plugins/collection/sort");
548 params->descending = dt_conf_get_bool("plugins/collection/descending");
550}
551
552const gchar *dt_collection_get_query(const dt_collection_t *collection)
553{
554 /* ensure there is a query string for collection */
555 if(IS_NULL_PTR(collection->query)) dt_collection_update(collection);
556
557 return collection->query;
558}
559
564
566{
567 dt_collection_params_t *params = (dt_collection_params_t *)&collection->params;
568 params->filter_flags = flags;
569}
570
572{
573 return collection->params.text_filter;
574}
575
576void dt_collection_set_text_filter(const dt_collection_t *collection, char *text_filter)
577{
578 dt_collection_params_t *params = (dt_collection_params_t *)&collection->params;
579 dt_free(params->text_filter);
580 params->text_filter = text_filter;
581}
582
587
589{
590 dt_collection_params_t *params = (dt_collection_params_t *)&collection->params;
591 params->query_flags = flags;
592}
593
594gchar *dt_collection_get_extended_where(const dt_collection_t *collection, int exclude)
595{
596 gchar *complete_string = NULL;
597
598 if (exclude >= 0)
599 {
600 complete_string = g_strdup("");
601 char confname[200];
602 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/mode%1d", exclude);
603 const int mode = dt_conf_get_int(confname);
604 if (mode != 1) // don't limit the collection for OR
605 {
606 for(int i = 0; !IS_NULL_PTR(collection->where_ext[i]); i++)
607 {
608 // exclude the one rule from extended where
609 if (i != exclude)
610 complete_string = dt_util_dstrcat(complete_string, "%s", collection->where_ext[i]);
611 }
612 }
613 }
614 else
615 complete_string = g_strjoinv(complete_string, ((dt_collection_t *)collection)->where_ext);
616
617 gchar *where_ext = g_strdup_printf("(1=1%s)", complete_string);
618 dt_free(complete_string);
619
620 return where_ext;
621}
622
623void dt_collection_set_extended_where(const dt_collection_t *collection, gchar **extended_where)
624{
625 /* free extended where if already exists */
626 g_strfreev(collection->where_ext);
627
628 /* set new from parameter */
629 ((dt_collection_t *)collection)->where_ext = g_strdupv(extended_where);
630}
631
632void dt_collection_set_tag_id(dt_collection_t *collection, const uint32_t tagid)
633{
634 collection->tagid = tagid;
635}
636
637void dt_collection_set_sort(const dt_collection_t *collection, dt_collection_sort_t sort, gboolean reverse)
638{
639 dt_collection_params_t *params = (dt_collection_params_t *)&collection->params;
640
641 if(sort != DT_COLLECTION_SORT_NONE)
642 params->sort = sort;
643
644 if(reverse != -1) params->descending = reverse;
645}
646
648{
649 return collection->params.sort;
650}
651
653{
654 return collection->params.descending;
655}
656
658{
659 char *col_name = NULL;
660 switch(prop)
661 {
662 case DT_COLLECTION_PROP_FILMROLL: return _("film roll");
663 case DT_COLLECTION_PROP_FOLDERS: return _("folder");
664 case DT_COLLECTION_PROP_CAMERA: return _("camera");
665 case DT_COLLECTION_PROP_TAG: return _("tag");
666 case DT_COLLECTION_PROP_DAY: return _("date taken");
667 case DT_COLLECTION_PROP_TIME: return _("date-time taken");
668 case DT_COLLECTION_PROP_IMPORT_TIMESTAMP: return _("import timestamp");
669 case DT_COLLECTION_PROP_CHANGE_TIMESTAMP: return _("change timestamp");
670 case DT_COLLECTION_PROP_EXPORT_TIMESTAMP: return _("export timestamp");
671 case DT_COLLECTION_PROP_PRINT_TIMESTAMP: return _("print timestamp");
672 case DT_COLLECTION_PROP_HISTORY: return _("history");
673 case DT_COLLECTION_PROP_COLORLABEL: return _("color label");
674 case DT_COLLECTION_PROP_LENS: return _("lens");
675 case DT_COLLECTION_PROP_FOCAL_LENGTH: return _("focal length");
676 case DT_COLLECTION_PROP_ISO: return _("ISO");
677 case DT_COLLECTION_PROP_APERTURE: return _("aperture");
678 case DT_COLLECTION_PROP_EXPOSURE: return _("exposure");
679 case DT_COLLECTION_PROP_FILENAME: return _("filename");
680 case DT_COLLECTION_PROP_GEOTAGGING: return _("geotagging");
681 case DT_COLLECTION_PROP_GROUPING: return _("grouping");
682 case DT_COLLECTION_PROP_LOCAL_COPY: return _("local copy");
683 case DT_COLLECTION_PROP_MODULE: return _("module");
684 case DT_COLLECTION_PROP_ORDER: return _("module order");
685 case DT_COLLECTION_PROP_RATING: return _("rating");
686 case DT_COLLECTION_PROP_QUERY: return _("custom query");
687 case DT_COLLECTION_PROP_LAST: return NULL;
688 default:
689 {
692 {
693 const int i = prop - DT_COLLECTION_PROP_METADATA;
696 {
697 const char *name = (gchar *)dt_metadata_get_name_by_display_order(i);
698 char *setting = g_strdup_printf("plugins/lighttable/metadata/%s_flag", name);
699 const gboolean hidden = dt_conf_get_int(setting) & DT_METADATA_FLAG_HIDDEN;
700 dt_free(setting);
701 if(!hidden) col_name = _(name);
702 }
703 }
704 }
705 }
706 return col_name;
707}
708
710{
711 gchar *sq = NULL;
712 const gchar *order = (collection->params.descending) ? "DESC" : "ASC";
713
714 switch(collection->params.sort)
715 {
721 {
722 const int local_order = collection->params.sort;
723 char *colname;
724
725 switch(local_order)
726 {
727 case DT_COLLECTION_SORT_DATETIME: colname = "datetime_taken" ; break ;
728 case DT_COLLECTION_SORT_IMPORT_TIMESTAMP: colname = "import_timestamp" ; break ;
729 case DT_COLLECTION_SORT_CHANGE_TIMESTAMP: colname = "change_timestamp" ; break ;
730 case DT_COLLECTION_SORT_EXPORT_TIMESTAMP: colname = "export_timestamp" ; break ;
731 case DT_COLLECTION_SORT_PRINT_TIMESTAMP: colname = "print_timestamp" ; break ;
732 default: colname = "";
733 }
734 // clang-format off
735 sq = g_strdup_printf("ORDER BY %s %s", colname, order);
736 // clang-format on
737 break;
738 }
739
741 // clang-format off
742 sq = g_strdup_printf("ORDER BY CASE WHEN flags & 8 = 8 THEN -1 ELSE flags & 7 END %s", order);
743 // clang-format on
744 break;
745
747 // clang-format off
748 sq = g_strdup_printf("ORDER BY filename %s", order);
749 // clang-format on
750 break;
751
753 // clang-format off
754 sq = g_strdup_printf("ORDER BY mi.id %s", order);
755 // clang-format on
756 break;
757
759 // clang-format off
760 sq = g_strdup_printf("ORDER BY color %s", order);
761 // clang-format on
762 break;
763
765 // clang-format off
766 sq = g_strdup_printf("ORDER BY group_id %s, mi.id-group_id != 0", order);
767 // clang-format on
768 break;
769
771 // clang-format off
772 sq = g_strdup_printf("ORDER BY folder %s", order);
773 // clang-format on
774 break;
775
777 // clang-format off
778 sq = g_strdup_printf("ORDER BY m.value %s", order);
779 // clang-format on
780 break;
781
783 default:/*fall through for default*/
784 // shouldn't happen
785 // clang-format off
786 sq = g_strdup_printf("ORDER BY mi.id %s", order);
787 // clang-format on
788 break;
789 }
790
791 // Finish with unique IDs in case we have aliasing
792 // try to keep grouped images next to each other, then similar files
793 sq = dt_util_dstrcat(sq, ", group_id ASC, mi.id-group_id != 0, filename ASC, version ASC, mi.id ASC");
794
795 return sq;
796}
797
798
799static int _dt_collection_store(const dt_collection_t *collection, gchar *query)
800{
801 /* store flags to conf */
802 if(collection == darktable.collection)
803 {
804 dt_conf_set_int("plugins/collection/query_flags", collection->params.query_flags);
805 dt_conf_set_int("plugins/collection/filter_flags", collection->params.filter_flags);
806 dt_conf_set_string("plugins/collection/text_filter", collection->params.text_filter ? collection->params.text_filter : "");
807 dt_conf_set_int("plugins/collection/sort", collection->params.sort);
808 dt_conf_set_bool("plugins/collection/descending", collection->params.descending);
809 }
810
811 /* store query in context */
812 dt_free(collection->query);
813
814 ((dt_collection_t *)collection)->query = g_strdup(query);
815
816 return 1;
817}
818
820{
821 uint32_t count = 1;
823 {
825 "SELECT COUNT(DISTINCT imgid) from memory.collected_images",
826 -1, &_collection_count_stmt, NULL);
827 }
828 sqlite3_stmt *stmt = _collection_count_stmt;
829 sqlite3_reset(stmt);
830 sqlite3_clear_bindings(stmt);
831 if(sqlite3_step(stmt) == SQLITE_ROW) count = sqlite3_column_int(stmt, 0);
832 collection->count = count;
833 return count;
834}
835
836uint32_t dt_collection_get_count(const dt_collection_t *collection)
837{
838 return collection->count;
839}
840
841GList *dt_collection_get(const dt_collection_t *collection, int limit)
842{
843 GList *list = NULL;
844 const gchar *query = dt_collection_get_query(collection);
845 if(query)
846 {
848 {
850 {
852 "SELECT imgid FROM memory.collected_images LIMIT -1, ?1",
853 -1, &_collection_get_limit_stmt, NULL);
854 }
855 sqlite3_stmt *stmt = _collection_get_limit_stmt;
856 sqlite3_reset(stmt);
857 sqlite3_clear_bindings(stmt);
858 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, limit);
859
860 while(sqlite3_step(stmt) == SQLITE_ROW)
861 {
862 const int32_t imgid = sqlite3_column_int(stmt, 0);
863 list = g_list_prepend(list, GINT_TO_POINTER(imgid));
864 }
865 }
866 else
867 {
869 {
871 "SELECT imgid FROM memory.collected_images",
872 -1, &_collection_get_stmt, NULL);
873 }
874 sqlite3_stmt *stmt = _collection_get_stmt;
875 sqlite3_reset(stmt);
876 sqlite3_clear_bindings(stmt);
877
878 while(sqlite3_step(stmt) == SQLITE_ROW)
879 {
880 const int32_t imgid = sqlite3_column_int(stmt, 0);
881 list = g_list_prepend(list, GINT_TO_POINTER(imgid));
882 }
883 }
884 }
885
886 return g_list_reverse(list); // list built in reverse order, so un-reverse it
887}
888
889GList *dt_collection_get_all(const dt_collection_t *collection, int limit)
890{
891 return dt_collection_get(collection, limit);
892}
893
894int dt_collection_get_nth(const dt_collection_t *collection, int nth)
895{
896 if(nth < 0 || nth >= dt_collection_get_count(collection))
897 return -1;
898 const gchar *query = dt_collection_get_query(collection);
899 sqlite3_stmt *stmt = NULL;
901 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, nth);
902 DT_DEBUG_SQLITE3_BIND_INT(stmt, 2, 1);
903
904 int result = -1;
905 if(sqlite3_step(stmt) == SQLITE_ROW)
906 {
907 result = sqlite3_column_int(stmt, 0);
908 }
909
910 sqlite3_finalize(stmt);
911
912 return result;
913
914}
915
916/* splits an input string into a number part and an optional operator part.
917 number can be a decimal integer or rational numerical item.
918 operator can be any of "=", "<", ">", "<=", ">=" and "<>".
919 range notation [x;y] can also be used
920
921 number and operator are returned as pointers to null terminated strings in g_mallocated
922 memory (to be g_free'd after use) - or NULL if no match is found.
923*/
924void dt_collection_split_operator_number(const gchar *input, char **number1, char **number2, char **operator)
925{
926 GRegex *regex;
927 GMatchInfo *match_info;
928
929 *number1 = *number2 = *operator= NULL;
930
931 // we test the range expression first
932 regex = g_regex_new("^\\s*\\[\\s*([-+]?[0-9]+\\.?[0-9]*)\\s*;\\s*([-+]?[0-9]+\\.?[0-9]*)\\s*\\]\\s*$", 0, 0, NULL);
933 g_regex_match_full(regex, input, -1, 0, 0, &match_info, NULL);
934 int match_count = g_match_info_get_match_count(match_info);
935
936 if(match_count == 3)
937 {
938 *number1 = g_match_info_fetch(match_info, 1);
939 *number2 = g_match_info_fetch(match_info, 2);
940 *operator= g_strdup("[]");
941 g_match_info_free(match_info);
942 g_regex_unref(regex);
943 return;
944 }
945
946 g_match_info_free(match_info);
947 g_regex_unref(regex);
948
949 // and we test the classic comparison operators
950 regex = g_regex_new("^\\s*(=|<|>|<=|>=|<>)?\\s*([-+]?[0-9]+\\.?[0-9]*)\\s*$", 0, 0, NULL);
951 g_regex_match_full(regex, input, -1, 0, 0, &match_info, NULL);
952 match_count = g_match_info_get_match_count(match_info);
953
954 if(match_count == 3)
955 {
956 *operator= g_match_info_fetch(match_info, 1);
957 *number1 = g_match_info_fetch(match_info, 2);
958
959 if(*operator && strcmp(*operator, "") == 0)
960 {
961 dt_free(*operator);
962 }
963 }
964
965 g_match_info_free(match_info);
966 g_regex_unref(regex);
967}
968
969static char *_dt_collection_compute_datetime(const char *operator, const char *input)
970{
971 if(strlen(input) < 4) return NULL;
972
973 char bound[DT_DATETIME_LENGTH];
974 gboolean res;
975 if(strcmp(operator, ">") == 0 || strcmp(operator, "<=") == 0)
976 res = dt_datetime_entry_to_exif_upper_bound(bound, sizeof(bound), input);
977 else
978 res = dt_datetime_entry_to_exif(bound, sizeof(bound), input);
979 if(res)
980 return g_strdup(bound);
981 else return NULL;
982}
983/* splits an input string into a date-time part and an optional operator part.
984 operator can be any of "=", "<", ">", "<=", ">=" and "<>".
985 range notation [x;y] can also be used
986 datetime values should follow the pattern YYYY:MM:DD hh:mm:ss.sss
987 but only year part is mandatory
988
989 datetime and operator are returned as pointers to null terminated strings in g_mallocated
990 memory (to be g_free'd after use) - or NULL if no match is found.
991*/
992void dt_collection_split_operator_datetime(const gchar *input, char **number1, char **number2, char **operator)
993{
994 GRegex *regex;
995 GMatchInfo *match_info;
996
997 *number1 = *number2 = *operator= NULL;
998
999 // we test the range expression first
1000 // 2 elements : date-time1 and date-time2
1001 regex = g_regex_new("^\\s*\\[\\s*(\\d{4}[:.\\d\\s]*)\\s*;\\s*(\\d{4}[:.\\d\\s]*)\\s*\\]\\s*$", 0, 0, NULL);
1002 g_regex_match_full(regex, input, -1, 0, 0, &match_info, NULL);
1003 int match_count = g_match_info_get_match_count(match_info);
1004
1005 if(match_count == 3)
1006 {
1007 gchar *txt = g_match_info_fetch(match_info, 1);
1008 gchar *txt2 = g_match_info_fetch(match_info, 2);
1009
1010 *number1 = _dt_collection_compute_datetime(">=", txt);
1011 *number2 = _dt_collection_compute_datetime("<=", txt2);
1012 *operator= g_strdup("[]");
1013
1014 dt_free(txt);
1015 dt_free(txt2);
1016 g_match_info_free(match_info);
1017 g_regex_unref(regex);
1018 return;
1019 }
1020
1021 g_match_info_free(match_info);
1022 g_regex_unref(regex);
1023
1024 // and we test the classic comparison operators
1025 // 2 elements : operator and date-time
1026 regex = g_regex_new("^\\s*(=|<|>|<=|>=|<>)?\\s*(\\d{4}[:.\\d\\s]*)?\\s*%?\\s*$", 0, 0, NULL);
1027 g_regex_match_full(regex, input, -1, 0, 0, &match_info, NULL);
1028 match_count = g_match_info_get_match_count(match_info);
1029
1030 if(match_count == 3)
1031 {
1032 *operator= g_match_info_fetch(match_info, 1);
1033 gchar *txt = g_match_info_fetch(match_info, 2);
1034
1035 if(strcmp(*operator, "") == 0 || strcmp(*operator, "=") == 0 || strcmp(*operator, "<>") == 0)
1036 {
1037 *number1 = dt_util_dstrcat(*number1, "%s%%", txt);
1038 *number2 = _dt_collection_compute_datetime(">", txt);
1039 }
1040 else
1041 *number1 = _dt_collection_compute_datetime(*operator, txt);
1042
1043 dt_free(txt);
1044 }
1045
1046 // ensure operator is not null
1047 if(IS_NULL_PTR(*operator)) *operator= g_strdup("");
1048
1049 g_match_info_free(match_info);
1050 g_regex_unref(regex);
1051}
1052
1053void dt_collection_split_operator_exposure(const gchar *input, char **number1, char **number2, char **operator)
1054{
1055 GRegex *regex;
1056 GMatchInfo *match_info;
1057
1058 *number1 = *number2 = *operator= NULL;
1059
1060 // we test the range expression first
1061 regex = g_regex_new("^\\s*\\[\\s*(1/)?([0-9]+\\.?[0-9]*)(\")?\\s*;\\s*(1/)?([0-9]+\\.?[0-9]*)(\")?\\s*\\]\\s*$", 0, 0, NULL);
1062 g_regex_match_full(regex, input, -1, 0, 0, &match_info, NULL);
1063 int match_count = g_match_info_get_match_count(match_info);
1064
1065 if(match_count == 6 || match_count == 7)
1066 {
1067 gchar *n1 = g_match_info_fetch(match_info, 2);
1068
1069 if(strstr(g_match_info_fetch(match_info, 1), "1/") != NULL)
1070 *number1 = g_strdup_printf("1.0/%s", n1);
1071 else
1072 *number1 = n1;
1073
1074 gchar *n2 = g_match_info_fetch(match_info, 5);
1075
1076 if(strstr(g_match_info_fetch(match_info, 4), "1/") != NULL)
1077 *number2 = g_strdup_printf("1.0/%s", n2);
1078 else
1079 *number2 = n2;
1080
1081 *operator= g_strdup("[]");
1082 g_match_info_free(match_info);
1083 g_regex_unref(regex);
1084 return;
1085 }
1086
1087 g_match_info_free(match_info);
1088 g_regex_unref(regex);
1089
1090 // and we test the classic comparison operators
1091 regex = g_regex_new("^\\s*(=|<|>|<=|>=|<>)?\\s*(1/)?([0-9]+\\.?[0-9]*)(\")?\\s*$", 0, 0, NULL);
1092 g_regex_match_full(regex, input, -1, 0, 0, &match_info, NULL);
1093 match_count = g_match_info_get_match_count(match_info);
1094 if(match_count == 4 || match_count == 5)
1095 {
1096 *operator= g_match_info_fetch(match_info, 1);
1097
1098 gchar *n1 = g_match_info_fetch(match_info, 3);
1099
1100 if(strstr(g_match_info_fetch(match_info, 2), "1/") != NULL)
1101 *number1 = g_strdup_printf("1.0/%s", n1);
1102 else
1103 *number1 = n1;
1104
1105 if(*operator && strcmp(*operator, "") == 0)
1106 {
1107 dt_free(*operator);
1108 }
1109 }
1110
1111 g_match_info_free(match_info);
1112 g_regex_unref(regex);
1113}
1114
1115void dt_collection_get_makermodels(const gchar *filter, GList **sanitized, GList **exif)
1116{
1117 gchar *needle = NULL;
1118 gboolean wildcard = FALSE;
1119
1120 GHashTable *names = NULL;
1121 if (sanitized)
1122 names = g_hash_table_new(g_str_hash, g_str_equal);
1123
1124 if (filter && filter[0] != '\0')
1125 {
1126 needle = g_utf8_strdown(filter, -1);
1127 wildcard = (needle && needle[strlen(needle) - 1] == '%') ? TRUE : FALSE;
1128 if(wildcard)
1129 needle[strlen(needle) - 1] = '\0';
1130 }
1131
1133 {
1135 "SELECT maker, model FROM main.images GROUP BY maker, model",
1137 }
1138 sqlite3_stmt *stmt = _collection_get_makermodels_stmt;
1139 sqlite3_reset(stmt);
1140 sqlite3_clear_bindings(stmt);
1141 while(sqlite3_step(stmt) == SQLITE_ROW)
1142 {
1143 const char *exif_maker = (char *)sqlite3_column_text(stmt, 0);
1144 const char *exif_model = (char *)sqlite3_column_text(stmt, 1);
1145
1146 gchar *makermodel = dt_collection_get_makermodel(exif_maker, exif_model);
1147
1148 gchar *haystack = g_utf8_strdown(makermodel, -1);
1149 if (IS_NULL_PTR(needle) || (wildcard && g_strrstr(haystack, needle) != NULL)
1150 || (!wildcard && !g_strcmp0(haystack, needle)))
1151 {
1152 if (exif)
1153 {
1154 // Append a two element list with maker and model
1155 GList *inner_list = NULL;
1156 inner_list = g_list_append(inner_list, g_strdup(exif_maker));
1157 inner_list = g_list_append(inner_list, g_strdup(exif_model));
1158 *exif = g_list_append(*exif, inner_list);
1159 }
1160
1161 if (sanitized)
1162 {
1163 gchar *key = g_strdup(makermodel);
1164 g_hash_table_add(names, key);
1165 }
1166 }
1167 dt_free(haystack);
1168 dt_free(makermodel);
1169 }
1170 dt_free(needle);
1171
1172 if(sanitized)
1173 {
1174 *sanitized = g_list_sort(g_hash_table_get_keys(names), (GCompareFunc) strcmp);
1175 g_hash_table_destroy(names);
1176 }
1177}
1178
1179gchar *dt_collection_get_makermodel(const char *exif_maker, const char *exif_model)
1180{
1181 char maker[64];
1182 char model[64];
1183 char alias[64];
1184 maker[0] = model[0] = alias[0] = '\0';
1185 dt_imageio_lookup_makermodel(exif_maker, exif_model,
1186 maker, sizeof(maker),
1187 model, sizeof(model),
1188 alias, sizeof(alias));
1189
1190 // Create the makermodel by concatenation
1191 gchar *makermodel = g_strdup_printf("%s %s", maker, model);
1192 return makermodel;
1193}
1194
1195static gchar *get_query_string(const dt_collection_properties_t property, const gchar *text)
1196{
1197 char *escaped_text = sqlite3_mprintf("%q", text);
1198 const unsigned int escaped_length = strlen(escaped_text);
1199 gchar *query = NULL;
1200
1201 switch(property)
1202 {
1203 case DT_COLLECTION_PROP_QUERY: // raw user-provided SQL WHERE expression (advanced)
1204 // Intentionally NOT escaped: this is a power-user escape hatch that injects a raw
1205 // read-only WHERE clause against the local library. A malformed expression makes the
1206 // prepared statement fail gracefully (empty collection), it does not crash.
1207 if(text && *text)
1208 query = g_strdup_printf("(%s)", text);
1209 else
1210 query = g_strdup("1=1");
1211 break;
1212
1213 case DT_COLLECTION_PROP_FILMROLL: // film roll
1214 if(!(escaped_text && *escaped_text))
1215 // clang-format off
1216 query = g_strdup_printf("(film_id IN (SELECT id FROM main.film_rolls WHERE folder LIKE '%s%%'))",
1217 escaped_text);
1218 // clang-format on
1219 else
1220 // clang-format off
1221 query = g_strdup_printf("(film_id IN (SELECT id FROM main.film_rolls WHERE folder LIKE '%s'))",
1222 escaped_text);
1223 // clang-format on
1224 break;
1225
1226 case DT_COLLECTION_PROP_FOLDERS: // folders
1227 {
1228 // replace * at the end with OR-clause to include subfolders
1229 if ((escaped_length > 0) && (escaped_text[escaped_length-1] == '*'))
1230 {
1231 escaped_text[escaped_length-1] = '\0';
1232 // clang-format off
1233 query = g_strdup_printf("(film_id IN (SELECT id FROM main.film_rolls WHERE folder LIKE '%s' OR folder LIKE '%s"
1234 G_DIR_SEPARATOR_S "%%'))",
1235 escaped_text, escaped_text);
1236 // clang-format on
1237 }
1238 // replace |% at the end with /% to only show subfolders
1239 else if ((escaped_length > 1) && (strcmp(escaped_text+escaped_length-2, "|%") == 0 ))
1240 {
1241 escaped_text[escaped_length-2] = '\0';
1242 // clang-format off
1243 query = g_strdup_printf("(film_id IN (SELECT id FROM main.film_rolls WHERE folder LIKE '%s"
1244 G_DIR_SEPARATOR_S "%%'))",
1245 escaped_text);
1246 // clang-format on
1247 }
1248 else
1249 {
1250 // clang-format off
1251 query = g_strdup_printf("(film_id IN (SELECT id FROM main.film_rolls WHERE folder LIKE '%s'))",
1252 escaped_text);
1253 // clang-format on
1254 }
1255 }
1256 break;
1257
1258 case DT_COLLECTION_PROP_COLORLABEL: // colorlabel
1259 {
1260 if(!(escaped_text && *escaped_text) || strcmp(escaped_text, "%") == 0)
1261 // clang-format off
1262 query = g_strdup_printf("(id IN (SELECT imgid FROM main.color_labels WHERE color IS NOT NULL))");
1263 // clang-format on
1264 else
1265 {
1266 int color = 0;
1267 if(strcmp(escaped_text, _("red")) == 0)
1268 color = 0;
1269 else if(strcmp(escaped_text, _("yellow")) == 0)
1270 color = 1;
1271 else if(strcmp(escaped_text, _("green")) == 0)
1272 color = 2;
1273 else if(strcmp(escaped_text, _("blue")) == 0)
1274 color = 3;
1275 else if(strcmp(escaped_text, _("purple")) == 0)
1276 color = 4;
1277 // clang-format off
1278 query = g_strdup_printf("(id IN (SELECT imgid FROM main.color_labels WHERE color=%d))", color);
1279 // clang-format on
1280 }
1281 }
1282 break;
1283
1284 case DT_COLLECTION_PROP_HISTORY: // history
1285 {
1286 if(strcmp(escaped_text, _("altered")) == 0)
1287 {
1288 query = g_strdup("EXISTS (SELECT 1 FROM main.history h WHERE h.imgid = id)");
1289 }
1290 else if(strcmp(escaped_text, _("unaltered")) == 0)
1291 {
1292 query = g_strdup("NOT EXISTS (SELECT 1 FROM main.history h WHERE h.imgid = id)");
1293 }
1294 else
1295 {
1296 query = g_strdup("1");
1297 }
1298 }
1299 break;
1300
1301 case DT_COLLECTION_PROP_GEOTAGGING: // geotagging
1302 {
1303 const gboolean not_tagged = strcmp(escaped_text, _("not tagged")) == 0;
1304 const gboolean no_location = strcmp(escaped_text, _("tagged")) == 0;
1305 const gboolean all_tagged = strcmp(escaped_text, _("tagged*")) == 0;
1306 char *escaped_text2 = g_strstr_len(escaped_text, -1, "|");
1307 char *name_clause = g_strdup_printf("t.name LIKE \'%s\' || \'%s\'",
1308 dt_map_location_data_tag_root(), escaped_text2 ? escaped_text2 : "%");
1309
1310 if (escaped_text2 && (escaped_text2[strlen(escaped_text2)-1] == '*'))
1311 {
1312 escaped_text2[strlen(escaped_text2)-1] = '\0';
1313 name_clause = g_strdup_printf("(t.name LIKE \'%s\' || \'%s\' OR t.name LIKE \'%s\' || \'%s|%%\')",
1314 dt_map_location_data_tag_root(), escaped_text2 , dt_map_location_data_tag_root(), escaped_text2);
1315 }
1316
1317 if(not_tagged || all_tagged)
1318 // clang-format off
1319 query = g_strdup_printf("(id %s IN (SELECT id AS imgid FROM main.images "
1320 "WHERE (longitude IS NOT NULL AND latitude IS NOT NULL))) ",
1321 all_tagged ? "" : "not");
1322 // clang-format on
1323 else
1324 // clang-format off
1325 query = g_strdup_printf("(id IN (SELECT id AS imgid FROM main.images "
1326 "WHERE (longitude IS NOT NULL AND latitude IS NOT NULL))"
1327 "AND id %s IN (SELECT imgid FROM main.tagged_images AS ti"
1328 " JOIN data.tags AS t"
1329 " ON t.id = ti.tagid"
1330 " AND %s)) ",
1331 no_location ? "not" : "",
1332 name_clause);
1333 // clang-format on
1334 }
1335 break;
1336
1337 case DT_COLLECTION_PROP_LOCAL_COPY: // local copy
1338 // clang-format off
1339 query = g_strdup_printf("(id %s IN (SELECT id AS imgid FROM main.images WHERE (flags & %d))) ",
1340 (strcmp(escaped_text, _("not copied locally")) == 0) ? "not" : "",
1342 // clang-format on
1343 break;
1344
1345 case DT_COLLECTION_PROP_CAMERA: // camera
1346 // Start query with a false statement to avoid special casing the first condition
1347 query = g_strdup_printf("((1=0)");
1348 GList *lists = NULL;
1349 dt_collection_get_makermodels(text, NULL, &lists);
1350 for(GList *element = lists; element; element = g_list_next(element))
1351 {
1352 GList *tuple = element->data;
1353 char *clause = sqlite3_mprintf(" OR (maker = '%q' AND model = '%q')", tuple->data, tuple->next->data);
1354 query = dt_util_dstrcat(query, "%s", clause);
1355 sqlite3_free(clause);
1356 dt_free(tuple->data);
1357 dt_free(tuple->next->data);
1358 g_list_free(tuple);
1359 tuple = NULL;
1360 }
1361 g_list_free(lists);
1362 lists = NULL;
1363 query = dt_util_dstrcat(query, ")");
1364 break;
1365
1366 case DT_COLLECTION_PROP_TAG: // tag
1367 {
1368 if(!strcmp(escaped_text, _("not tagged")))
1369 {
1370 // clang-format off
1371 query = g_strdup_printf("(id NOT IN (SELECT DISTINCT imgid FROM main.tagged_images "
1372 "WHERE tagid NOT IN memory.darktable_tags))");
1373 // clang-format on
1374 }
1375 else
1376 {
1377 if ((escaped_length > 0) && (escaped_text[escaped_length-1] == '*'))
1378 {
1379 // shift-click adds an asterix * to include items in and under this hierarchy
1380 // without using a wildcard % which also would include similar named items
1381 escaped_text[escaped_length-1] = '\0';
1382 // clang-format off
1383 query = g_strdup_printf("(id IN (SELECT imgid FROM main.tagged_images WHERE tagid IN "
1384 "(SELECT id FROM data.tags "
1385 "WHERE LOWER(name) = LOWER('%s')"
1386 " OR SUBSTR(LOWER(name), 1, LENGTH('%s') + 1) = LOWER('%s|'))))",
1387 escaped_text, escaped_text, escaped_text);
1388 // clang-format on
1389 }
1390 else if ((escaped_length > 0) && (escaped_text[escaped_length-1] == '%'))
1391 {
1392 // ends with % or |%
1393 escaped_text[escaped_length-1] = '\0';
1394 // clang-format off
1395 query = g_strdup_printf("(id IN (SELECT imgid FROM main.tagged_images WHERE tagid IN "
1396 "(SELECT id FROM data.tags WHERE SUBSTR(LOWER(name), 1, LENGTH('%s')) = LOWER('%s'))))",
1397 escaped_text, escaped_text);
1398 // clang-format on
1399 }
1400 else
1401 {
1402 // default
1403 // clang-format off
1404 query = g_strdup_printf("(id IN (SELECT imgid FROM main.tagged_images WHERE tagid IN "
1405 "(SELECT id FROM data.tags WHERE LOWER(name) = LOWER('%s'))))",
1406 escaped_text);
1407 // clang-format on
1408 }
1409 }
1410 }
1411 break;
1412
1413 case DT_COLLECTION_PROP_LENS: // lens
1414 query = g_strdup_printf("(lens LIKE '%%%s%%')", escaped_text);
1415 break;
1416
1417 case DT_COLLECTION_PROP_FOCAL_LENGTH: // focal length
1418 {
1419 gchar *operator, *number1, *number2;
1420 dt_collection_split_operator_number(escaped_text, &number1, &number2, &operator);
1421
1422 if(operator && strcmp(operator, "[]") == 0)
1423 {
1424 if(number1 && number2)
1425 query = g_strdup_printf("((focal_length >= %s) AND (focal_length <= %s))", number1, number2);
1426 }
1427 else if(operator && number1)
1428 query = g_strdup_printf("(focal_length %s %s)", operator, number1);
1429 else if(number1)
1430 // clang-format off
1431 query = g_strdup_printf("(CAST(focal_length AS INTEGER) = CAST(%s AS INTEGER))", number1);
1432 // clang-format on
1433 else
1434 query = g_strdup_printf("(focal_length LIKE '%%%s%%')", escaped_text);
1435
1436 dt_free(operator);
1437 dt_free(number1);
1438 dt_free(number2);
1439 }
1440 break;
1441
1442 case DT_COLLECTION_PROP_ISO: // iso
1443 {
1444 gchar *operator, *number1, *number2;
1445 dt_collection_split_operator_number(escaped_text, &number1, &number2, &operator);
1446
1447 if(operator && strcmp(operator, "[]") == 0)
1448 {
1449 if(number1 && number2)
1450 query = g_strdup_printf("((iso >= %s) AND (iso <= %s))", number1, number2);
1451 }
1452 else if(operator && number1)
1453 query = g_strdup_printf("(iso %s %s)", operator, number1);
1454 else if(number1)
1455 query = g_strdup_printf("(iso = %s)", number1);
1456 else
1457 query = g_strdup_printf("(iso LIKE '%%%s%%')", escaped_text);
1458
1459 dt_free(operator);
1460 dt_free(number1);
1461 dt_free(number2);
1462 }
1463 break;
1464
1465 case DT_COLLECTION_PROP_APERTURE: // aperture
1466 {
1467 gchar *operator, *number1, *number2;
1468 dt_collection_split_operator_number(escaped_text, &number1, &number2, &operator);
1469
1470 if(operator && strcmp(operator, "[]") == 0)
1471 {
1472 if(number1 && number2)
1473 // clang-format off
1474 query = g_strdup_printf("((ROUND(aperture,1) >= %s) AND (ROUND(aperture,1) <= %s))", number1,
1475 number2);
1476 // clang-format on
1477 }
1478 else if(operator && number1)
1479 query = g_strdup_printf("(ROUND(aperture,1) %s %s)", operator, number1);
1480 else if(number1)
1481 query = g_strdup_printf("(ROUND(aperture,1) = %s)", number1);
1482 else
1483 query = g_strdup_printf("(ROUND(aperture,1) LIKE '%%%s%%')", escaped_text);
1484
1485 dt_free(operator);
1486 dt_free(number1);
1487 dt_free(number2);
1488 }
1489 break;
1490
1491 case DT_COLLECTION_PROP_EXPOSURE: // exposure
1492 {
1493 gchar *operator, *number1, *number2;
1494 dt_collection_split_operator_exposure(escaped_text, &number1, &number2, &operator);
1495
1496 if(operator && strcmp(operator, "[]") == 0)
1497 {
1498 if(number1 && number2)
1499 // clang-format off
1500 query = g_strdup_printf("((exposure >= %s - 1.0/100000) AND (exposure <= %s + 1.0/100000))", number1,
1501 number2);
1502 // clang-format on
1503 }
1504 else if(operator && number1)
1505 query = g_strdup_printf("(exposure %s %s)", operator, number1);
1506 else if(number1)
1507 // clang-format off
1508 query = g_strdup_printf("(CASE WHEN exposure < 0.4 THEN ((exposure >= %s - 1.0/100000) AND (exposure <= %s + 1.0/100000)) "
1509 "ELSE (ROUND(exposure,2) >= %s - 1.0/100000) AND (ROUND(exposure,2) <= %s + 1.0/100000) END)",
1510 number1, number1, number1, number1);
1511 // clang-format on
1512 else
1513 query = g_strdup_printf("(exposure LIKE '%%%s%%')", escaped_text);
1514
1515 dt_free(operator);
1516 dt_free(number1);
1517 dt_free(number2);
1518 }
1519 break;
1520
1521 case DT_COLLECTION_PROP_FILENAME: // filename
1522 {
1523 GList *list = dt_util_str_to_glist(",", escaped_text);
1524
1525 for (GList *l = list; l; l = g_list_next(l))
1526 {
1527 char *name = (char*)l->data; // remember the original content of this list node
1528 l->data = g_strdup_printf("(filename LIKE '%%%s%%')", name);
1529 dt_free(name); // free the original filename
1530 }
1531
1532 char *subquery = dt_util_glist_to_str(" OR ", list);
1533 query = g_strdup_printf("(%s)", subquery);
1534 dt_free(subquery);
1535 g_list_free_full(list, dt_free_gpointer); // free the SQL clauses as well as the list
1536 list = NULL;
1537
1538 break;
1539 }
1546 {
1547 const int local_property = property;
1548 char *colname = NULL;
1549
1550 switch(local_property)
1551 {
1552 case DT_COLLECTION_PROP_DAY: colname = "datetime_taken" ; break ;
1553 case DT_COLLECTION_PROP_TIME: colname = "datetime_taken" ; break ;
1554 case DT_COLLECTION_PROP_IMPORT_TIMESTAMP: colname = "import_timestamp" ; break ;
1555 case DT_COLLECTION_PROP_CHANGE_TIMESTAMP: colname = "change_timestamp" ; break ;
1556 case DT_COLLECTION_PROP_EXPORT_TIMESTAMP: colname = "export_timestamp" ; break ;
1557 case DT_COLLECTION_PROP_PRINT_TIMESTAMP: colname = "print_timestamp" ; break ;
1558 }
1559 gchar *operator, *number1, *number2;
1560 dt_collection_split_operator_datetime(escaped_text, &number1, &number2, &operator);
1561 if(number1 && number1[strlen(number1) - 1] == '%')
1562 number1[strlen(number1) - 1] = '\0';
1563 GTimeSpan nb1 = number1 ? dt_datetime_exif_to_gtimespan(number1) : 0;
1564 GTimeSpan nb2 = number2 ? dt_datetime_exif_to_gtimespan(number2) : 0;
1565
1566 if(strcmp(operator, "[]") == 0)
1567 {
1568 if(number1 && number2)
1569 query = g_strdup_printf("((%s >= %" G_GINT64_FORMAT ") AND (%s <= %" G_GINT64_FORMAT "))", colname, nb1, colname, nb2);
1570 }
1571 else if((strcmp(operator, "=") == 0 || strcmp(operator, "") == 0) && number1 && number2)
1572 query = g_strdup_printf("((%s >= %" G_GINT64_FORMAT ") AND (%s <= %" G_GINT64_FORMAT "))", colname, nb1, colname, nb2);
1573 else if(strcmp(operator, "<>") == 0 && number1 && number2)
1574 query = g_strdup_printf("((%s < %" G_GINT64_FORMAT ") AND (%s > %" G_GINT64_FORMAT "))", colname, nb1, colname, nb2);
1575 else if(number1)
1576 query = g_strdup_printf("(%s %s %" G_GINT64_FORMAT ")", colname, operator, nb1);
1577 else
1578 query = g_strdup("1 = 1");
1579
1580 dt_free(operator);
1581 dt_free(number1);
1582 dt_free(number2);
1583 break;
1584 }
1585
1586 case DT_COLLECTION_PROP_GROUPING: // grouping
1587 query = g_strdup_printf("(id %s group_id)", (strcmp(escaped_text, _("group leaders")) == 0) ? "=" : "!=");
1588 break;
1589
1590 case DT_COLLECTION_PROP_MODULE: // dev module
1591 {
1592 // clang-format off
1593 query = g_strdup_printf("(id IN (SELECT imgid AS id FROM main.history AS h "
1594 "JOIN memory.darktable_iop_names AS m ON m.operation = h.operation "
1595 "WHERE h.enabled = 1 AND m.name LIKE '%s'))", escaped_text);
1596 // clang-format on
1597 }
1598 break;
1599
1600 case DT_COLLECTION_PROP_ORDER: // module order
1601 {
1602 int i = 0;
1603 for(i = 0; i < DT_IOP_ORDER_LAST; i++)
1604 {
1605 if(strcmp(escaped_text, _(dt_iop_order_string(i))) == 0) break;
1606 }
1607 if(i < DT_IOP_ORDER_LAST)
1608 // clang-format off
1609 query = g_strdup_printf("(id IN (SELECT imgid FROM main.module_order WHERE version = %d))", i);
1610 // clang-format on
1611 else
1612 // clang-format off
1613 query = g_strdup_printf("(id NOT IN (SELECT imgid FROM main.module_order))");
1614 // clang-format on
1615 }
1616 break;
1617
1618 case DT_COLLECTION_PROP_RATING: // image rating
1619 {
1620 gchar *operator, *number1, *number2;
1621 dt_collection_split_operator_number(escaped_text, &number1, &number2, &operator);
1622
1623 if(operator && strcmp(operator, "[]") == 0)
1624 {
1625 if(number1 && number2)
1626 {
1627 if(atoi(number1) == -1)
1628 { // rejected + star rating
1629 // clang-format off
1630 query = g_strdup_printf("(flags & 7 >= %s AND flags & 7 <= %s)", number1, number2);
1631 // clang-format on
1632 }
1633 else
1634 { // non-rejected + star rating
1635 // clang-format off
1636 query = g_strdup_printf("((flags & 8 == 0) AND (flags & 7 >= %s AND flags & 7 <= %s))", number1, number2);
1637 // clang-format on
1638 }
1639 }
1640 }
1641 else if(operator && number1)
1642 {
1643 if(g_strcmp0(operator, "<=") == 0 || g_strcmp0(operator, "<") == 0)
1644 { // all below rating + rejected
1645 // clang-format off
1646 query = g_strdup_printf("(flags & 8 == 8 OR flags & 7 %s %s)", operator, number1);
1647 // clang-format on
1648 }
1649 else if(g_strcmp0(operator, ">=") == 0 || g_strcmp0(operator, ">") == 0)
1650 {
1651 if(atoi(number1) >= 0)
1652 { // non rejected above rating
1653 // clang-format off
1654 query = g_strdup_printf("(flags & 8 == 0 AND flags & 7 %s %s)", operator, number1);
1655 // clang-format on
1656 }
1657 // otherwise no filter (rejected + all ratings)
1658 }
1659 else
1660 { // <> exclusion operator
1661 if(atoi(number1) == -1)
1662 { // all except rejected
1663 query = g_strdup_printf("(flags & 8 == 0)");
1664 }
1665 else
1666 { // all except star rating (including rejected)
1667 query = g_strdup_printf("(flags & 8 == 8 OR flags & 7 %s %s)", operator, number1);
1668 }
1669 }
1670 }
1671 else if(number1)
1672 {
1673 if(atoi(number1) == -1)
1674 { // rejected only
1675 query = g_strdup_printf("(flags & 8 == 8)");
1676 }
1677 else
1678 { // non-rejected + star rating
1679 query = g_strdup_printf("(flags & 8 == 0 AND flags & 7 == %s)", number1);
1680 }
1681 }
1682
1683 dt_free(operator);
1684 dt_free(number1);
1685 dt_free(number2);
1686 }
1687 break;
1688
1689 default:
1690 {
1691 if(property >= DT_COLLECTION_PROP_METADATA
1693 {
1695 if(strcmp(escaped_text, _("not defined")) != 0)
1696 // clang-format off
1697 query = g_strdup_printf("(id IN (SELECT id FROM main.meta_data WHERE key = %d AND value "
1698 "LIKE '%%%s%%'))", keyid, escaped_text);
1699 // clang-format on
1700 else
1701 // clang-format off
1702 query = g_strdup_printf("(id NOT IN (SELECT id FROM main.meta_data WHERE key = %d))",
1703 keyid);
1704 // clang-format off
1705 }
1706 }
1707 break;
1708 }
1709 sqlite3_free(escaped_text);
1710
1711 if(IS_NULL_PTR(query)) // We've screwed up and not done a query string, send a placeholder
1712 query = g_strdup_printf("(1=1)");
1713
1714 return query;
1715}
1716
1718{
1719 // Build the same WHERE clause the collection would use for this single rule, then
1720 // enumerate the matching image ids. Independent of the currently active collection so it
1721 // can feed batch/background operations (remove, attach tag, pre-render thumbnails, ...).
1722 GList *result = NULL;
1723 gchar *where = get_query_string(property, text);
1724 if(IS_NULL_PTR(where)) return NULL;
1725
1726 gchar *query = g_strdup_printf("SELECT id FROM main.images WHERE %s", where);
1727 dt_free(where);
1728
1729 sqlite3_stmt *stmt = NULL;
1730 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
1731 if(stmt)
1732 {
1733 while(sqlite3_step(stmt) == SQLITE_ROW)
1734 result = g_list_prepend(result, GINT_TO_POINTER(sqlite3_column_int(stmt, 0)));
1735 sqlite3_finalize(stmt);
1736 }
1737 dt_free(query);
1738
1739 return g_list_reverse(result);
1740}
1741
1743{
1745 if(!v) return;
1746 g_free(v->name);
1747 g_free(v);
1748}
1749
1750static dt_collection_name_value_t *_name_value_new(char *name, int id, int count, int status)
1751{
1753 v->name = name;
1754 v->id = id;
1755 v->count = count;
1756 v->status = status;
1757 return v;
1758}
1759
1761{
1762 GList *out = NULL;
1763 gchar *where_ext = dt_collection_get_extended_where(darktable.collection, rule);
1764
1765 // Camera is special: it groups on two text columns and combines them into a display name.
1766 if(property == DT_COLLECTION_PROP_CAMERA)
1767 {
1768 gchar *q = g_strdup_printf("SELECT maker, model, COUNT(*) AS count FROM main.images AS mi"
1769 " WHERE %s GROUP BY maker, model", where_ext);
1770 g_free(where_ext);
1771 sqlite3_stmt *stmt = NULL;
1773 int index = 0;
1774 while(stmt && sqlite3_step(stmt) == SQLITE_ROW)
1775 {
1776 const char *maker = (const char *)sqlite3_column_text(stmt, 0);
1777 const char *model = (const char *)sqlite3_column_text(stmt, 1);
1779 out = g_list_prepend(out, _name_value_new(name, index++, sqlite3_column_int(stmt, 2), -1));
1780 }
1781 if(stmt) sqlite3_finalize(stmt);
1782 g_free(q);
1783 return g_list_reverse(out);
1784 }
1785
1786 const gboolean is_date = property == DT_COLLECTION_PROP_DAY || property == DT_COLLECTION_PROP_TIME
1791 const gboolean has_status
1792 = (property == DT_COLLECTION_PROP_FOLDERS || property == DT_COLLECTION_PROP_FILMROLL);
1793 gchar *query = NULL;
1794
1795 switch(property)
1796 {
1798 query = g_strdup_printf("SELECT folder, film_rolls_id, COUNT(*) AS count, status"
1799 " FROM main.images AS mi"
1800 " JOIN (SELECT fr.id AS film_rolls_id, folder, status"
1801 " FROM main.film_rolls AS fr"
1802 " JOIN memory.film_folder AS ff ON fr.id = ff.id)"
1803 " ON film_id = film_rolls_id"
1804 " WHERE %s GROUP BY folder, film_rolls_id", where_ext);
1805 break;
1806
1808 query = g_strdup_printf("SELECT name, 1 AS tagid, SUM(count) AS count"
1809 " FROM (SELECT tagid, COUNT(*) as count"
1810 " FROM main.images AS mi JOIN main.tagged_images ON id = imgid"
1811 " WHERE %s GROUP BY tagid)"
1812 " JOIN (SELECT LOWER(name) AS name, id AS tag_id FROM data.tags)"
1813 " ON tagid = tag_id GROUP BY name", where_ext);
1814 query = dt_util_dstrcat(query, " UNION ALL "
1815 "SELECT '%s' AS name, 0 as id, COUNT(*) AS count "
1816 "FROM main.images AS mi WHERE mi.id NOT IN"
1817 " (SELECT DISTINCT imgid FROM main.tagged_images AS ti"
1818 " WHERE ti.tagid NOT IN memory.darktable_tags)",
1819 _("not tagged"));
1820 break;
1821
1823 query = g_strdup_printf("SELECT CASE WHEN mi.longitude IS NULL OR mi.latitude IS null THEN '%s'"
1824 " ELSE CASE WHEN ta.imgid IS NULL THEN '%s' ELSE '%s' || ta.tagname END"
1825 " END AS name, ta.tagid AS tag_id, COUNT(*) AS count"
1826 " FROM main.images AS mi"
1827 " LEFT JOIN (SELECT imgid, t.id AS tagid, SUBSTR(t.name, %d) AS tagname"
1828 " FROM main.tagged_images AS ti JOIN data.tags AS t ON ti.tagid = t.id"
1829 " JOIN data.locations AS l ON l.tagid = t.id) AS ta ON ta.imgid = mi.id"
1830 " WHERE %s GROUP BY name, tag_id",
1831 _("not tagged"), _("tagged"), _("tagged"),
1832 (int)strlen(dt_map_location_data_tag_root()) + 1, where_ext);
1833 break;
1834
1836 query = g_strdup_printf("SELECT (datetime_taken / 86400000000) * 86400000000 AS date, 1, COUNT(*) AS count"
1837 " FROM main.images AS mi"
1838 " WHERE datetime_taken IS NOT NULL AND datetime_taken <> 0 AND %s"
1839 " GROUP BY date", where_ext);
1840 break;
1841
1847 {
1848 char *colname = NULL;
1849 switch(property)
1850 {
1851 case DT_COLLECTION_PROP_TIME: colname = "datetime_taken"; break;
1852 case DT_COLLECTION_PROP_IMPORT_TIMESTAMP: colname = "import_timestamp"; break;
1853 case DT_COLLECTION_PROP_CHANGE_TIMESTAMP: colname = "change_timestamp"; break;
1854 case DT_COLLECTION_PROP_EXPORT_TIMESTAMP: colname = "export_timestamp"; break;
1855 case DT_COLLECTION_PROP_PRINT_TIMESTAMP: colname = "print_timestamp"; break;
1856 default: break; // unreachable: outer switch already restricts to the timestamp cases
1857 }
1858 query = g_strdup_printf("SELECT %s AS date, 1, COUNT(*) AS count FROM main.images AS mi"
1859 " WHERE %s IS NOT NULL AND %s <> 0 AND %s GROUP BY date",
1860 colname, colname, colname, where_ext);
1861 break;
1862 }
1863
1865 query = g_strdup_printf("SELECT CASE WHEN EXISTS (SELECT 1 FROM main.history h WHERE h.imgid = mi.id)"
1866 " THEN '%s' ELSE '%s' END as altered, 1, COUNT(*) AS count"
1867 " FROM main.images AS mi WHERE %s GROUP BY altered ORDER BY altered ASC",
1868 _("altered"), _("unaltered"), where_ext);
1869 break;
1870
1872 query = g_strdup_printf("SELECT CASE WHEN (flags & %d) THEN '%s' ELSE '%s' END as lcp, 1, COUNT(*) AS count"
1873 " FROM main.images AS mi WHERE %s GROUP BY lcp ORDER BY lcp ASC",
1874 DT_IMAGE_LOCAL_COPY, _("copied locally"), _("not copied locally"), where_ext);
1875 break;
1876
1878 query = g_strdup_printf("SELECT CASE color WHEN 0 THEN '%s' WHEN 1 THEN '%s' WHEN 2 THEN '%s'"
1879 " WHEN 3 THEN '%s' WHEN 4 THEN '%s' ELSE '' END, color, COUNT(*) AS count"
1880 " FROM main.images AS mi"
1881 " JOIN (SELECT imgid AS color_labels_id, color FROM main.color_labels)"
1882 " ON id = color_labels_id WHERE %s GROUP BY color ORDER BY color DESC",
1883 _("red"), _("yellow"), _("green"), _("blue"), _("purple"), where_ext);
1884 break;
1885
1887 query = g_strdup_printf("SELECT lens, 1, COUNT(*) AS count FROM main.images AS mi WHERE %s"
1888 " GROUP BY lens ORDER BY lens", where_ext);
1889 break;
1890
1892 query = g_strdup_printf("SELECT CAST(focal_length AS INTEGER) AS focal_length, 1, COUNT(*) AS count"
1893 " FROM main.images AS mi WHERE %s GROUP BY CAST(focal_length AS INTEGER)"
1894 " ORDER BY CAST(focal_length AS INTEGER)", where_ext);
1895 break;
1896
1898 query = g_strdup_printf("SELECT CAST(iso AS INTEGER) AS iso, 1, COUNT(*) AS count"
1899 " FROM main.images AS mi WHERE %s GROUP BY iso ORDER BY iso", where_ext);
1900 break;
1901
1903 query = g_strdup_printf("SELECT ROUND(aperture,1) AS aperture, 1, COUNT(*) AS count"
1904 " FROM main.images AS mi WHERE %s GROUP BY aperture ORDER BY aperture", where_ext);
1905 break;
1906
1908 query = g_strdup_printf("SELECT CASE WHEN (exposure < 0.4) THEN '1/' || CAST(1/exposure + 0.9 AS INTEGER)"
1909 " ELSE ROUND(exposure,2) || '\"' END as _exposure, 1, COUNT(*) AS count"
1910 " FROM main.images AS mi WHERE %s GROUP BY _exposure ORDER BY exposure", where_ext);
1911 break;
1912
1914 query = g_strdup_printf("SELECT filename, 1, COUNT(*) AS count FROM main.images AS mi WHERE %s"
1915 " GROUP BY filename ORDER BY filename", where_ext);
1916 break;
1917
1919 query = g_strdup_printf("SELECT CASE WHEN id = group_id THEN '%s' ELSE '%s' END as group_leader, 1,"
1920 " COUNT(*) AS count FROM main.images AS mi WHERE %s"
1921 " GROUP BY group_leader ORDER BY group_leader ASC",
1922 _("group leaders"), _("group followers"), where_ext);
1923 break;
1924
1926 query = g_strdup_printf("SELECT m.name AS module_name, 1, COUNT(*) AS count FROM main.images AS mi"
1927 " JOIN (SELECT DISTINCT imgid, operation FROM main.history WHERE enabled = 1) AS h"
1928 " ON h.imgid = mi.id JOIN memory.darktable_iop_names AS m"
1929 " ON m.operation = h.operation WHERE %s GROUP BY module_name ORDER BY module_name",
1930 where_ext);
1931 break;
1932
1934 {
1935 char *orders = NULL;
1936 for(int i = 0; i < DT_IOP_ORDER_LAST; i++)
1937 orders = dt_util_dstrcat(orders, "WHEN mo.version = %d THEN '%s' ", i, _(dt_iop_order_string(i)));
1938 orders = dt_util_dstrcat(orders, "ELSE '%s' ", _("none"));
1939 query = g_strdup_printf("SELECT CASE %s END as ver, 1, COUNT(*) AS count FROM main.images AS mi"
1940 " LEFT JOIN (SELECT imgid, version FROM main.module_order) mo ON mo.imgid = mi.id"
1941 " WHERE %s GROUP BY ver ORDER BY ver", orders, where_ext);
1942 g_free(orders);
1943 break;
1944 }
1945
1947 query = g_strdup_printf("SELECT CASE WHEN (flags & 8) == 8 THEN -1 ELSE (flags & 7) END AS rating, 1,"
1948 " COUNT(*) AS count FROM main.images AS mi WHERE %s GROUP BY rating ORDER BY rating",
1949 where_ext);
1950 break;
1951
1952 default:
1954 {
1956 const char *name = (const char *)dt_metadata_get_name(keyid);
1957 char *setting = g_strdup_printf("plugins/lighttable/metadata/%s_flag", name);
1958 const gboolean hidden = dt_conf_get_int(setting) & DT_METADATA_FLAG_HIDDEN;
1959 g_free(setting);
1960 if(!hidden)
1961 query = g_strdup_printf("SELECT CASE WHEN value IS NULL THEN '%s' ELSE value END AS value, 1,"
1962 " COUNT(*) AS count, CASE WHEN value IS NULL THEN 0 ELSE 1 END AS force_order"
1963 " FROM main.images AS mi"
1964 " LEFT JOIN (SELECT id AS meta_data_id, value FROM main.meta_data WHERE key = %d)"
1965 " ON id = meta_data_id WHERE %s GROUP BY value ORDER BY force_order, value",
1966 _("not defined"), keyid, where_ext);
1967 }
1968 else // film roll
1969 {
1970 gchar *order_by = NULL;
1971 const char *filmroll_sort = dt_conf_get_string_const("plugins/collect/filmroll_sort");
1972 if(strcmp(filmroll_sort, "id") == 0)
1973 order_by = g_strdup("film_rolls_id DESC");
1974 else
1975 order_by = dt_conf_get_bool("plugins/collect/descending") ? g_strdup("folder DESC") : g_strdup("folder");
1976 query = g_strdup_printf("SELECT folder, film_rolls_id, COUNT(*) AS count, status FROM main.images AS mi"
1977 " JOIN (SELECT fr.id AS film_rolls_id, folder, status FROM main.film_rolls AS fr"
1978 " JOIN memory.film_folder AS ff ON ff.id = fr.id) ON film_id = film_rolls_id"
1979 " WHERE %s GROUP BY folder ORDER BY %s", where_ext, order_by);
1980 g_free(order_by);
1981 }
1982 break;
1983 }
1984 g_free(where_ext);
1985 if(!query) return NULL;
1986
1987 sqlite3_stmt *stmt = NULL;
1988 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
1989 while(stmt && sqlite3_step(stmt) == SQLITE_ROW)
1990 {
1991 char *name;
1992 if(is_date)
1993 {
1994 char sdt[DT_DATETIME_EXIF_LENGTH] = { 0 };
1995 dt_datetime_gtimespan_to_exif(sdt, sizeof(sdt), sqlite3_column_int64(stmt, 0));
1996 if(property == DT_COLLECTION_PROP_DAY) sdt[10] = '\0';
1997 name = g_strdup(sdt);
1998 }
1999 else
2000 {
2001 const char *txt = (const char *)sqlite3_column_text(stmt, 0);
2002 name = txt ? g_strdup(txt) : g_strdup("");
2003 }
2004 const int id = sqlite3_column_int(stmt, 1);
2005 const int count = sqlite3_column_int(stmt, 2);
2006 const int status = has_status ? sqlite3_column_int(stmt, 3) : -1;
2007 out = g_list_prepend(out, _name_value_new(name, id, count, status));
2008 }
2009 if(stmt) sqlite3_finalize(stmt);
2010 g_free(query);
2011 return g_list_reverse(out);
2012}
2013
2014int dt_collection_serialize(char *buf, int bufsize)
2015{
2016 char confname[200];
2017 int c;
2018 const int num_rules = dt_conf_get_int("plugins/lighttable/collect/num_rules");
2019 c = snprintf(buf, bufsize, "%d:", num_rules);
2020 buf += c;
2021 bufsize -= c;
2022 for(int k = 0; k < num_rules; k++)
2023 {
2024 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/mode%1d", k);
2025 const int mode = dt_conf_get_int(confname);
2026 c = snprintf(buf, bufsize, "%d:", mode);
2027 buf += c;
2028 bufsize -= c;
2029 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/item%1d", k);
2030 const int item = dt_conf_get_int(confname);
2031 c = snprintf(buf, bufsize, "%d:", item);
2032 buf += c;
2033 bufsize -= c;
2034 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/string%1d", k);
2035 const char *str = dt_conf_get_string_const(confname);
2036 if(str && (str[0] != '\0'))
2037 c = snprintf(buf, bufsize, "%s$", str);
2038 else
2039 c = snprintf(buf, bufsize, "%%$");
2040 buf += c;
2041 bufsize -= c;
2042 }
2043 return 0;
2044}
2045
2046void dt_collection_deserialize(const char *buf)
2047{
2048 int num_rules = 0;
2049 sscanf(buf, "%d", &num_rules);
2050 if(num_rules == 0)
2051 {
2052 dt_conf_set_int("plugins/lighttable/collect/num_rules", 1);
2053 dt_conf_set_int("plugins/lighttable/collect/mode0", 0);
2054 dt_conf_set_int("plugins/lighttable/collect/item0", 0);
2055 dt_conf_set_string("plugins/lighttable/collect/string0", "%");
2056 }
2057 else
2058 {
2059 int mode = 0, item = 0;
2060 dt_conf_set_int("plugins/lighttable/collect/num_rules", num_rules);
2061 while(buf[0] != '\0' && buf[0] != ':') buf++;
2062 if(buf[0] == ':') buf++;
2063 char str[400], confname[200];
2064 for(int k = 0; k < num_rules; k++)
2065 {
2066 const int n = sscanf(buf, "%d:%d:%399[^$]", &mode, &item, str);
2067 if(n == 3)
2068 {
2069 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/mode%1d", k);
2070 dt_conf_set_int(confname, mode);
2071 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/item%1d", k);
2072 dt_conf_set_int(confname, item);
2073 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/string%1d", k);
2074 dt_conf_set_string(confname, str);
2075 }
2076 else if(num_rules == 1)
2077 {
2078 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/mode%1d", k);
2079 dt_conf_set_int(confname, 0);
2080 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/item%1d", k);
2081 dt_conf_set_int(confname, 0);
2082 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/string%1d", k);
2083 dt_conf_set_string(confname, "%");
2084 break;
2085 }
2086 else
2087 {
2088 dt_conf_set_int("plugins/lighttable/collect/num_rules", k);
2089 break;
2090 }
2091 while(buf[0] != '$' && buf[0] != '\0') buf++;
2092 if(buf[0] == '$') buf++;
2093 }
2094 }
2096}
2097
2098/* Store the n most recent collections in config for re-use in menu */
2100{
2101 if(IS_NULL_PTR(darktable.gui)) return;
2102 if(IS_NULL_PTR(darktable.gui->ui)) return;
2103
2104 // Serialize current request
2105 char confname[200] = { 0 };
2106 char buf[4096];
2107 dt_collection_serialize(buf, sizeof(buf));
2108
2109 int n = -1;
2110 gboolean found_duplicate = FALSE;
2111
2112 // Check if current request already exist in history
2113 int num_items = dt_conf_get_int("plugins/lighttable/recentcollect/num_items");
2114 for(int k = 0; k < num_items; k++)
2115 {
2116 snprintf(confname, sizeof(confname), "plugins/lighttable/recentcollect/line%1d", k);
2117 const char *line = dt_conf_get_string_const(confname);
2118 if(IS_NULL_PTR(line)) continue;
2119 if(!strcmp(line, buf))
2120 {
2121 n = k;
2122 found_duplicate = TRUE;
2123 break;
2124 }
2125 }
2126
2127 // Shift all history items one step behind. When the history is already full,
2128 // the last item has no destination slot and must be dropped before moving
2129 // the remaining entries down.
2130 const int max_items = CLAMP(dt_conf_get_int("plugins/lighttable/recentcollect/max_items"), 1,
2132 int shifted_index = MIN(num_items - (found_duplicate ? 1 : 0), max_items);
2133 for(int k = num_items - 1; k > -1; k--)
2134 {
2135 if(k == n) continue; // this is the duplicate of current collection we found, skip it
2136
2137 // Get old records
2138 snprintf(confname, sizeof(confname), "plugins/lighttable/recentcollect/line%1d", k);
2139 gchar *line1 = dt_conf_get_string(confname);
2140 snprintf(confname, sizeof(confname), "plugins/lighttable/recentcollect/pos%1d", k);
2141 uint32_t pos1 = dt_conf_get_int(confname);
2142
2143 // Write new records shifted by 1 slot
2144 if(IS_NULL_PTR(line1) || line1[0] == '\0')
2145 {
2146 dt_free(line1);
2147 continue;
2148 }
2149
2150 if(shifted_index >= 0 && shifted_index < max_items)
2151 {
2152 snprintf(confname, sizeof(confname), "plugins/lighttable/recentcollect/line%1d", shifted_index);
2153 dt_conf_set_string(confname, line1);
2154 snprintf(confname, sizeof(confname), "plugins/lighttable/recentcollect/pos%1d", shifted_index);
2155 dt_conf_set_int(confname, pos1);
2156 }
2157 shifted_index -= 1;
2158 dt_free(line1);
2159 }
2160
2161 // Prepend current collection on top of history
2162 dt_conf_set_string("plugins/lighttable/recentcollect/line0", buf);
2163
2164 // Increment items if we didn't find a duplicate
2165 num_items += found_duplicate ? 0 : 1;
2166 dt_conf_set_int("plugins/lighttable/recentcollect/num_items", CLAMP(num_items, 1, max_items));
2167}
2168
2169
2171 dt_collection_properties_t changed_property, GList *list)
2172{
2173 int next = -1;
2174 if(list)
2175 {
2176 // for changing offsets, thumbtable needs to know the first untouched imageid after the list
2177 // we do this here
2178
2179 // 1. create a string with all the imgids of the list to be used inside IN sql query
2180 gchar *txt = NULL;
2181 int i = 0;
2182 for(GList *l = list; l; l = g_list_next(l))
2183 {
2184 const int id = GPOINTER_TO_INT(l->data);
2185 if(i == 0)
2186 txt = dt_util_dstrcat(txt, "%d", id);
2187 else
2188 txt = dt_util_dstrcat(txt, ",%d", id);
2189 i++;
2190 }
2191 // 2. search the first imgid not in the list but AFTER the list (or in a gap inside the list)
2192 // we need to be carefull that some images in the list may not be present on screen (collapsed groups)
2193 // clang-format off
2194 gchar *query = g_strdup_printf("SELECT imgid"
2195 " FROM memory.collected_images"
2196 " WHERE imgid NOT IN (%s)"
2197 " AND rowid > (SELECT rowid"
2198 " FROM memory.collected_images"
2199 " WHERE imgid IN (%s)"
2200 " ORDER BY rowid LIMIT 1)"
2201 " ORDER BY rowid LIMIT 1",
2202 txt, txt);
2203 // clang-format on
2204 sqlite3_stmt *stmt2;
2205 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt2, NULL);
2206 if(sqlite3_step(stmt2) == SQLITE_ROW)
2207 {
2208 next = sqlite3_column_int(stmt2, 0);
2209 }
2210 sqlite3_finalize(stmt2);
2211 dt_free(query);
2212 // 3. if next is still unvalid, let's try to find the first untouched image BEFORE the list
2213 if(next < 0)
2214 {
2215 // clang-format off
2216 query = g_strdup_printf("SELECT imgid"
2217 " FROM memory.collected_images"
2218 " WHERE imgid NOT IN (%s)"
2219 " AND rowid < (SELECT rowid"
2220 " FROM memory.collected_images"
2221 " WHERE imgid IN (%s)"
2222 " ORDER BY rowid LIMIT 1)"
2223 " ORDER BY rowid DESC LIMIT 1",
2224 txt, txt);
2225 // clang-format on
2226 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt2, NULL);
2227 if(sqlite3_step(stmt2) == SQLITE_ROW)
2228 {
2229 next = sqlite3_column_int(stmt2, 0);
2230 }
2231 sqlite3_finalize(stmt2);
2232 dt_free(query);
2233 }
2234 dt_free(txt);
2235 }
2236
2237 char confname[200];
2238
2239 const int _n_r = dt_conf_get_int("plugins/lighttable/collect/num_rules");
2240 const int num_rules = CLAMP(_n_r, 1, 10);
2241 char *conj[] = { "AND", "OR", "AND NOT" };
2242
2243 gchar **query_parts = g_new (gchar*, num_rules + 1);
2244 query_parts[num_rules] = NULL;
2245
2246 for(int i = 0; i < num_rules; i++)
2247 {
2248 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/item%1d", i);
2249 const int property = dt_conf_get_int(confname);
2250 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/string%1d", i);
2251 gchar *text = dt_conf_get_string(confname);
2252 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/mode%1d", i);
2253 const int mode = dt_conf_get_int(confname);
2254
2255 if(IS_NULL_PTR(text) || text[0] == '\0')
2256 {
2257 if (mode == 1) // for OR show all
2258 query_parts[i] = g_strdup(" OR 1=1");
2259 else
2260 query_parts[i] = g_strdup("");
2261 }
2262 else
2263 {
2264 gchar *query = get_query_string(property, text);
2265
2266 query_parts[i] = g_strdup_printf(" %s %s", conj[mode], query);
2267
2268 dt_free(query);
2269 }
2270 dt_free(text);
2271 }
2272
2273 /* set the extended where and the use of it in the query */
2274 dt_collection_set_extended_where(collection, query_parts);
2275 g_strfreev(query_parts);
2278
2279 /* update query and at last the visual */
2280 dt_collection_update(collection);
2281
2282 /* Update recent collections history before we raise the signal,
2283 * since some signal listeners will need it */
2285
2286 /* raise signal of collection change, only if this is an original */
2289 list, next);
2290}
2291
2293{
2294 // Restore previous collection
2295 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "DELETE FROM memory.collected_images", NULL, NULL, NULL);
2297 "INSERT INTO memory.collected_images"
2298 " SELECT * FROM memory.collected_backup",
2299 NULL, NULL, NULL);
2300}
2301
2303{
2304 // Backup current collection
2305 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "DELETE FROM memory.collected_backup", NULL, NULL, NULL);
2307 "INSERT INTO memory.collected_backup"
2308 " SELECT * FROM memory.collected_images",
2309 NULL, NULL, NULL);
2310}
2311
2313{
2314 // Culling mode restricts the collection to the selection
2315
2316 // Remove non-selected from collected images, aka culling mode
2319 "DELETE FROM memory.collected_images"
2320 " WHERE imgid NOT IN "
2321 " (SELECT imgid FROM main.selected_images)",
2322 NULL, NULL, NULL);
2323
2324 // Backup and reset current selection
2327}
2328
2330{
2331 // Restore everything as before
2334}
2335
2336
2338{
2340 dt_free(message);
2341 return FALSE;
2342}
2343
2345{
2346 /* collection hinting */
2347 gchar *message;
2348
2349 const int c = dt_collection_get_count(collection);
2351
2352 if(cs == 1)
2353 {
2354 /* determine offset of the single selected image */
2355 GList *selected_imgids = dt_selection_get_list(darktable.selection);
2356 int selected = -1;
2357
2358 if(selected_imgids)
2359 {
2360 selected = GPOINTER_TO_INT(selected_imgids->data);
2361 selected = dt_collection_image_offset_with_collection(collection, selected);
2362 selected++;
2363 }
2364 g_list_free(selected_imgids);
2365 selected_imgids = NULL;
2366 message = g_strdup_printf(_("%d image of %d (#%d) in current collection is selected"), cs, c, selected);
2367 }
2368 else
2369 {
2370 message = g_strdup_printf(
2371 ngettext(
2372 "%d image of %d in current collection is selected",
2373 "%d images of %d in current collection are selected",
2374 cs),
2375 cs, c);
2376 }
2377
2378 g_idle_add(dt_collection_hint_message_internal, message);
2379}
2380
2381static int dt_collection_image_offset_with_collection(const dt_collection_t *collection, int32_t imgid)
2382{
2383 if(imgid == UNKNOWN_IMAGE) return 0;
2384 int offset = 0;
2386 {
2388 "SELECT imgid FROM memory.collected_images",
2390 }
2391 sqlite3_stmt *stmt = _collection_image_offset_stmt;
2392 sqlite3_reset(stmt);
2393 sqlite3_clear_bindings(stmt);
2394
2395 gboolean found = FALSE;
2396
2397 while(sqlite3_step(stmt) == SQLITE_ROW)
2398 {
2399 const int id = sqlite3_column_int(stmt, 0);
2400 if(imgid == id)
2401 {
2402 found = TRUE;
2403 break;
2404 }
2405 offset++;
2406 }
2407
2408 if(!found) offset = 0;
2409
2410 return offset;
2411}
2412
2413static inline void _dt_collection_change_view_after_import(const dt_view_t *current_view, gboolean open_single_image)
2414{
2415 if(open_single_image)
2416 {
2417 if(!g_strcmp0(current_view->module_name, "darkroom")) // if current view IS "darkroom".
2418 dt_ctl_reload_view("darkroom");
2419 else
2420 dt_ctl_switch_mode_to("darkroom");
2421 }
2422 else if(g_strcmp0(current_view->module_name, "lighttable")) // if current view IS NOT "lighttable".
2423 dt_ctl_switch_mode_to("lighttable");
2424}
2425
2426static inline gboolean _collection_can_switch_folder(const int32_t imgid, const dt_view_t *current_atelier)
2427{
2428 // Go out if the image is unknown.
2429 gboolean result = imgid == UNKNOWN_IMAGE;
2430
2431 // Go out if we are not in lighttable.
2432 result |= current_atelier && g_strcmp0(current_atelier->module_name, "lighttable"); // current atelier IS NOT "lighttabke".
2433
2434 // Go out if the Collection module is not showing the "Folders" tab
2435 // (should it switch to this tab instead ?)
2436 result |= dt_conf_get_int("plugins/lighttable/collect/tab") != 0;
2437 return result;
2438}
2439
2440void dt_collection_load_filmroll(dt_collection_t *collection, const int32_t imgid, gboolean open_single_image)
2441{
2443
2444 // Go out if conditions are not reunited
2445 if(_collection_can_switch_folder(imgid, current_atelier))
2446 return;
2447
2448 gchar first_directory[PATH_MAX] = { 0 };
2449 dt_get_dirname_from_imgid(first_directory, imgid);
2450
2451 const gboolean copy = dt_conf_get_bool("ui_last/import_copy");
2452 const dt_collection_properties_t Collection_view = dt_conf_get_int("plugins/lighttable/collect/item0");
2453 gchar dir[PATH_MAX] = { 0 };
2454
2455 // - If user imports images in place and View mode is on "Tree":
2456 // - if the user selecter 1 folder in Import:
2457 // - the lighttable displays the contents of that folder.
2458 // - else, the lighttable displays the contents of the folder
2459 // showing in the file explorer in Import.
2460 //
2461 // - In all other cases, the lighttable displays the first
2462 // imported image's folder.
2463
2464 if (Collection_view == DT_COLLECTION_PROP_FOLDERS && !copy)
2465 {
2466 int nb = dt_conf_get_int("ui_last/import_selection_nb");
2467 const gchar *first_selection = dt_conf_get_string_const("ui_last/import_first_selected_str");
2468
2469 if(nb ==1 && dt_util_dir_exist(first_selection))
2470 {
2471 fprintf(stdout,"Collection: one folder.\n");
2472 g_strlcpy(dir, g_strdup(first_selection), sizeof(dir));
2473 }
2474 else
2475 {
2476 fprintf(stdout,"Collection: files and folders.\n");
2477 const gchar *import_last_dir = dt_conf_get_string("ui_last/import_last_directory");
2478 if(dt_util_dir_exist(import_last_dir))
2479 g_strlcpy(dir, g_strdup(import_last_dir), sizeof(dir));
2480 }
2481 }
2482 else // in List view or we copy
2483 {
2484 fprintf(stdout,"Collection: copy or in List view.\n");
2485
2486 gchar first_img_path[PATH_MAX] = { 0 };
2487 dt_get_dirname_from_imgid(first_img_path, imgid);
2488
2489 if(dt_util_dir_exist(first_img_path))
2490 {
2491 g_strlcpy(dir, first_img_path, sizeof(dir));
2492 fprintf(stdout,"Collection: ID %d, last img path %s.\n", imgid, first_img_path);
2493 }
2494 }
2495
2496 fprintf(stdout,"Collection: view = %s\n", Collection_view ? "Tree" : "List");
2497 const char *path = g_strdup_printf("%s%s", dir, Collection_view ? "*" : "");
2498 fprintf(stdout,"Collection: path = %s\n", path);
2499
2500 dt_conf_set_string("plugins/lighttable/collect/string0", g_strdup_printf("%s*", dir));
2501 dt_conf_set_int("plugins/lighttable/collect/num_rules", 1);
2502
2503 // Reload the collection with the current filmroll
2505
2506 // Necessary to directly open in darkroom if we want to.
2508
2509 // To scroll the lighttable automatically to this image,
2510 // it needs to be selected.
2512
2513 // New images are untagged, that may need an update of the collection module for untagged count
2515
2516 if(current_atelier) _dt_collection_change_view_after_import(current_atelier, open_single_image);
2517}
2518
2519// clang-format off
2520// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2521// vim: shiftwidth=2 expandtab tabstop=2 cindent
2522// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2523// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
const char * dt_collection_name(dt_collection_properties_t prop)
Definition collection.c:657
void dt_collection_load_filmroll(dt_collection_t *collection, const int32_t imgid, gboolean open_single_image)
void dt_collection_update_query(const dt_collection_t *collection, dt_collection_change_t query_change, dt_collection_properties_t changed_property, GList *list)
void dt_pop_collection()
void dt_culling_mode_to_selection()
void dt_collection_reset(const dt_collection_t *collection)
Definition collection.c:531
void dt_collection_set_query_flags(const dt_collection_t *collection, dt_collection_query_flags_t flags)
Definition collection.c:588
void dt_collection_deserialize(const char *buf)
const dt_collection_params_t * dt_collection_params(const dt_collection_t *collection)
Definition collection.c:157
static char * _dt_collection_compute_datetime(const char *operator, const char *input)
Definition collection.c:969
void dt_collection_split_operator_exposure(const gchar *input, char **number1, char **number2, char **operator)
void dt_collection_set_text_filter(const dt_collection_t *collection, char *text_filter)
Definition collection.c:576
static int dt_collection_image_offset_with_collection(const dt_collection_t *collection, int32_t imgid)
char * dt_collection_get_text_filter(const dt_collection_t *collection)
Definition collection.c:571
gboolean dt_collection_get_sort_descending(const dt_collection_t *collection)
Definition collection.c:652
gchar * dt_collection_get_extended_where(const dt_collection_t *collection, int exclude)
Definition collection.c:594
void dt_selection_to_culling_mode()
static sqlite3_stmt * _collection_count_stmt
Definition collection.c:103
static dt_collection_name_value_t * _name_value_new(char *name, int id, int count, int status)
GList * dt_collection_get(const dt_collection_t *collection, int limit)
Definition collection.c:841
void dt_collection_set_filter_flags(const dt_collection_t *collection, dt_collection_filter_flag_t flags)
Definition collection.c:565
void dt_collection_memory_update()
Definition collection.c:201
static char * or_operator(int *term)
Definition collection.c:185
void dt_collection_split_operator_number(const gchar *input, char **number1, char **number2, char **operator)
Definition collection.c:924
dt_collection_filter_flag_t dt_collection_get_filter_flags(const dt_collection_t *collection)
Definition collection.c:560
void dt_collection_get_makermodels(const gchar *filter, GList **sanitized, GList **exif)
GList * dt_collection_get_images_for_rule(const dt_collection_properties_t property, const char *text)
const gchar * dt_collection_get_query(const dt_collection_t *collection)
Definition collection.c:552
static sqlite3_stmt * _collection_get_makermodels_stmt
Definition collection.c:106
#define LIMIT_QUERY
Definition collection.c:101
static void _dt_collection_set_selq_pre_sort(const dt_collection_t *collection, char **selq_pre)
Definition collection.c:247
dt_collection_query_flags_t dt_collection_get_query_flags(const dt_collection_t *collection)
Definition collection.c:583
static gchar * get_query_string(const dt_collection_properties_t property, const gchar *text)
dt_collection_sort_t dt_collection_get_sort_field(const dt_collection_t *collection)
Definition collection.c:647
void dt_collection_set_tag_id(dt_collection_t *collection, const uint32_t tagid)
Definition collection.c:632
void dt_collection_name_value_free(gpointer value)
static void _update_recentcollections()
int dt_collection_get_nth(const dt_collection_t *collection, int nth)
Definition collection.c:894
static int _dt_collection_store(const dt_collection_t *collection, gchar *query)
Definition collection.c:799
gchar * dt_collection_get_makermodel(const char *exif_maker, const char *exif_model)
gboolean dt_collection_hint_message_internal(void *message)
void dt_push_collection()
static gboolean _collection_can_switch_folder(const int32_t imgid, const dt_view_t *current_atelier)
#define or_operator_initial()
Definition collection.c:184
void dt_collection_split_operator_datetime(const gchar *input, char **number1, char **number2, char **operator)
Definition collection.c:992
void dt_collection_hint_message(const dt_collection_t *collection)
int dt_collection_serialize(char *buf, int bufsize)
static sqlite3_stmt * _collection_get_limit_stmt
Definition collection.c:105
static char * and_operator(int *term)
Definition collection.c:168
void dt_collection_set_sort(const dt_collection_t *collection, dt_collection_sort_t sort, gboolean reverse)
Definition collection.c:637
static void _dt_collection_change_view_after_import(const dt_view_t *current_view, gboolean open_single_image)
static uint32_t _dt_collection_compute_count(dt_collection_t *collection)
Definition collection.c:819
uint32_t dt_collection_get_count(const dt_collection_t *collection)
Definition collection.c:836
int dt_collection_update(const dt_collection_t *collection)
Definition collection.c:268
static sqlite3_stmt * _collection_get_stmt
Definition collection.c:104
dt_collection_t * dt_collection_new()
Definition collection.c:117
static sqlite3_stmt * _collection_image_offset_stmt
Definition collection.c:107
void dt_collection_set_extended_where(const dt_collection_t *collection, gchar **extended_where)
Definition collection.c:623
GList * dt_collection_get_property_values(const dt_collection_properties_t property, const int rule)
GList * dt_collection_get_all(const dt_collection_t *collection, int limit)
Definition collection.c:889
gchar * dt_collection_get_sort_query(const dt_collection_t *collection)
Definition collection.c:709
void dt_collection_free(const dt_collection_t *collection)
Definition collection.c:124
dt_collection_properties_t
Definition collection.h:107
@ DT_COLLECTION_PROP_EXPOSURE
Definition collection.h:115
@ DT_COLLECTION_PROP_MODULE
Definition collection.h:134
@ DT_COLLECTION_PROP_RATING
Definition collection.h:136
@ DT_COLLECTION_PROP_QUERY
Definition collection.h:138
@ DT_COLLECTION_PROP_TIME
Definition collection.h:120
@ DT_COLLECTION_PROP_METADATA
Definition collection.h:129
@ DT_COLLECTION_PROP_GROUPING
Definition collection.h:130
@ DT_COLLECTION_PROP_TAG
Definition collection.h:127
@ DT_COLLECTION_PROP_FILMROLL
Definition collection.h:108
@ DT_COLLECTION_PROP_LENS
Definition collection.h:113
@ DT_COLLECTION_PROP_CAMERA
Definition collection.h:112
@ DT_COLLECTION_PROP_LAST
Definition collection.h:140
@ DT_COLLECTION_PROP_LOCAL_COPY
Definition collection.h:131
@ DT_COLLECTION_PROP_GEOTAGGING
Definition collection.h:126
@ DT_COLLECTION_PROP_FILENAME
Definition collection.h:110
@ DT_COLLECTION_PROP_ISO
Definition collection.h:117
@ DT_COLLECTION_PROP_COLORLABEL
Definition collection.h:128
@ DT_COLLECTION_PROP_UNDEF
Definition collection.h:142
@ DT_COLLECTION_PROP_DAY
Definition collection.h:119
@ DT_COLLECTION_PROP_ORDER
Definition collection.h:135
@ DT_COLLECTION_PROP_FOLDERS
Definition collection.h:109
@ DT_COLLECTION_PROP_APERTURE
Definition collection.h:114
@ DT_COLLECTION_PROP_IMPORT_TIMESTAMP
Definition collection.h:121
@ DT_COLLECTION_PROP_FOCAL_LENGTH
Definition collection.h:116
@ DT_COLLECTION_PROP_EXPORT_TIMESTAMP
Definition collection.h:123
@ DT_COLLECTION_PROP_CHANGE_TIMESTAMP
Definition collection.h:122
@ DT_COLLECTION_PROP_HISTORY
Definition collection.h:133
@ DT_COLLECTION_PROP_PRINT_TIMESTAMP
Definition collection.h:124
dt_collection_query_flags_t
Definition collection.h:56
@ COLLECTION_QUERY_USE_ONLY_WHERE_EXT
Definition collection.h:61
@ COLLECTION_QUERY_USE_WHERE_EXT
Definition collection.h:60
@ COLLECTION_QUERY_USE_SORT
Definition collection.h:58
@ COLLECTION_QUERY_USE_LIMIT
Definition collection.h:59
#define COLLECTION_QUERY_FULL
Definition collection.h:63
dt_collection_filter_flag_t
Definition collection.h:66
@ COLLECTION_FILTER_ALTERED
Definition collection.h:68
@ COLLECTION_FILTER_UNALTERED
Definition collection.h:69
@ COLLECTION_FILTER_GREEN
Definition collection.h:79
@ COLLECTION_FILTER_NONE
Definition collection.h:67
@ COLLECTION_FILTER_2_STAR
Definition collection.h:73
@ COLLECTION_FILTER_MAGENTA
Definition collection.h:81
@ COLLECTION_FILTER_0_STAR
Definition collection.h:71
@ COLLECTION_FILTER_1_STAR
Definition collection.h:72
@ COLLECTION_FILTER_BLUE
Definition collection.h:80
@ COLLECTION_FILTER_5_STAR
Definition collection.h:76
@ COLLECTION_FILTER_YELLOW
Definition collection.h:78
@ COLLECTION_FILTER_ALL
Definition collection.h:83
@ COLLECTION_FILTER_REJECTED
Definition collection.h:70
@ COLLECTION_FILTER_4_STAR
Definition collection.h:75
@ COLLECTION_FILTER_3_STAR
Definition collection.h:74
@ COLLECTION_FILTER_RED
Definition collection.h:77
@ COLLECTION_FILTER_WHITE
Definition collection.h:82
dt_collection_change_t
Definition collection.h:147
@ DT_COLLECTION_CHANGE_NEW_QUERY
Definition collection.h:149
#define NUM_LAST_COLLECTIONS
Definition collection.h:53
dt_collection_sort_t
Definition collection.h:87
@ DT_COLLECTION_SORT_EXPORT_TIMESTAMP
Definition collection.h:93
@ DT_COLLECTION_SORT_IMPORT_TIMESTAMP
Definition collection.h:91
@ DT_COLLECTION_SORT_DATETIME
Definition collection.h:90
@ DT_COLLECTION_SORT_GROUP
Definition collection.h:98
@ DT_COLLECTION_SORT_FILENAME
Definition collection.h:89
@ DT_COLLECTION_SORT_RATING
Definition collection.h:95
@ DT_COLLECTION_SORT_PATH
Definition collection.h:99
@ DT_COLLECTION_SORT_TITLE
Definition collection.h:100
@ DT_COLLECTION_SORT_CHANGE_TIMESTAMP
Definition collection.h:92
@ DT_COLLECTION_SORT_NONE
Definition collection.h:88
@ DT_COLLECTION_SORT_PRINT_TIMESTAMP
Definition collection.h:94
@ DT_COLLECTION_SORT_ID
Definition collection.h:96
@ DT_COLLECTION_SORT_COLOR
Definition collection.h:97
@ DT_COLORLABELS_PURPLE
Definition colorlabels.h:45
@ DT_COLORLABELS_GREEN
Definition colorlabels.h:43
@ DT_COLORLABELS_YELLOW
Definition colorlabels.h:42
@ DT_COLORLABELS_BLUE
Definition colorlabels.h:44
@ DT_COLORLABELS_RED
Definition colorlabels.h:41
const dt_colormatrix_t dt_aligned_pixel_t out
void dt_get_dirname_from_imgid(gchar *dir, const int32_t imgid)
const char * dt_map_location_data_tag_root()
char * key
const char * dt_metadata_get_name_by_display_order(const uint32_t order)
const char * dt_metadata_get_name(const uint32_t keyid)
int type
int dt_metadata_get_type_by_display_order(const uint32_t order)
dt_metadata_t dt_metadata_get_keyid_by_display_order(const uint32_t order)
char * name
void dt_conf_set_bool(const char *name, int val)
int dt_conf_get_bool(const char *name)
gchar * dt_conf_get_string(const char *name)
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
void dt_conf_set_string(const char *name, const char *val)
const char * dt_conf_get_string_const(const char *name)
void dt_ctl_switch_mode_to(const char *mode)
Definition control.c:657
void dt_control_set_mouse_over_id(int32_t value)
Definition control.c:931
void dt_ctl_reload_view(const char *mode)
Definition control.c:676
void dt_control_hinter_message(const struct dt_control_t *s, const char *message)
Definition control.c:918
darktable_t darktable
Definition darktable.c:181
#define UNKNOWN_IMAGE
Definition darktable.h:182
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:463
#define dt_free(ptr)
Definition darktable.h:456
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
#define PATH_MAX
Definition darktable.h:1062
#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
GTimeSpan dt_datetime_exif_to_gtimespan(const char *sdt)
Definition datetime.c:399
gboolean dt_datetime_gtimespan_to_exif(char *sdt, const size_t sdt_size, const GTimeSpan gts)
Definition datetime.c:384
gboolean dt_datetime_entry_to_exif_upper_bound(char *exif, const size_t exif_size, const char *entry)
Definition datetime.c:327
gboolean dt_datetime_entry_to_exif(char *exif, const size_t exif_size, const char *entry)
Definition datetime.c:312
#define DT_DATETIME_LENGTH
Definition datetime.h:37
#define DT_DATETIME_EXIF_LENGTH
Definition datetime.h:38
#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
#define DT_DEBUG_SQLITE3_BIND_INT(a, b, c)
Definition debug.h:115
@ DT_IMAGE_REMOVE
Definition image.h:115
@ DT_IMAGE_LOCAL_COPY
Definition image.h:121
@ DT_IMAGE_REJECTED
Definition image.h:100
gboolean dt_imageio_lookup_makermodel(const char *maker, const char *model, char *mk, int mk_len, char *md, int md_len, char *al, int al_len)
Definition imageio.c:1312
const char * maker
const char * model
static const dt_iop_order_entry_t * orders[5]
Definition iop_order.c:903
const char * dt_iop_order_string(const dt_iop_order_t order)
Return the human-readable name for an IOP order enum value.
Definition iop_order.c:80
@ DT_IOP_ORDER_LAST
Definition iop_order.h:150
const float v
float *const restrict const size_t k
@ DT_METADATA_FLAG_HIDDEN
Definition metadata.h:74
@ DT_METADATA_NUMBER
Definition metadata.h:52
@ DT_METADATA_XMP_DC_TITLE
Definition metadata.h:46
@ DT_METADATA_TYPE_INTERNAL
Definition metadata.h:60
dt_mipmap_buffer_dsc_flags flags
Definition mipmap_cache.c:4
void copy(double *dest, double *source, size_t num_el)
Copy a flat buffer.
void dt_selection_push(dt_selection_t *selection)
Definition selection.c:204
int dt_selection_get_length(struct dt_selection_t *selection)
Definition selection.c:179
GList * dt_selection_get_list(struct dt_selection_t *selection)
Definition selection.c:172
void dt_selection_clear(dt_selection_t *selection)
Definition selection.c:266
void dt_selection_select(dt_selection_t *selection, int32_t imgid)
Definition selection.c:273
void dt_selection_pop(dt_selection_t *selection)
Definition selection.c:221
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:347
@ DT_SIGNAL_TAG_CHANGED
This signal is raised when a tag is added/deleted/changed
Definition signal.h:130
@ 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
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_collection_t * collection
Definition darktable.h:781
struct dt_selection_t * selection
Definition darktable.h:782
const struct dt_database_t * db
Definition darktable.h:779
struct dt_control_signal_t * signals
Definition darktable.h:774
struct dt_view_manager_t * view_manager
Definition darktable.h:772
struct dt_control_t * control
Definition darktable.h:773
dt_collection_query_flags_t query_flags
Definition collection.h:157
dt_collection_sort_t sort
Definition collection.h:166
dt_collection_filter_flag_t filter_flags
Definition collection.h:160
unsigned int count
Definition collection.h:175
dt_collection_params_t params
Definition collection.h:177
unsigned int tagid
Definition collection.h:176
gchar ** where_ext
Definition collection.h:174
dt_ui_t * ui
Definition gtk.h:164
gboolean culling_mode
Definition gtk.h:187
char module_name[64]
Definition view.h:153
#define MIN(a, b)
Definition thinplate.c:32
gboolean dt_util_dir_exist(const char *dir)
Definition utility.c:367
GList * dt_util_str_to_glist(const gchar *separator, const gchar *text)
Definition utility.c:830
gchar * dt_util_glist_to_str(const gchar *separator, GList *items)
Definition utility.c:166
gchar * dt_util_dstrcat(gchar *str, const gchar *format,...)
Definition utility.c:95
const dt_view_t * dt_view_manager_get_current_view(dt_view_manager_t *vm)
Definition view.c:140
@ DT_VIEW_STAR_4
Definition view.h:172
@ DT_VIEW_STAR_5
Definition view.h:173
@ DT_VIEW_STAR_3
Definition view.h:171
@ DT_VIEW_STAR_1
Definition view.h:169
@ DT_VIEW_STAR_2
Definition view.h:170
@ DT_VIEW_DESERT
Definition view.h:168