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 const gboolean recursive)
1197{
1198 char *escaped_text = sqlite3_mprintf("%q", text);
1199 const unsigned int escaped_length = strlen(escaped_text);
1200 gchar *query = NULL;
1201
1202 switch(property)
1203 {
1204 case DT_COLLECTION_PROP_QUERY: // raw user-provided SQL WHERE expression (advanced)
1205 // Intentionally NOT escaped: this is a power-user escape hatch that injects a raw
1206 // read-only WHERE clause against the local library. A malformed expression makes the
1207 // prepared statement fail gracefully (empty collection), it does not crash.
1208 if(text && *text)
1209 query = g_strdup_printf("(%s)", text);
1210 else
1211 query = g_strdup("1=1");
1212 break;
1213
1214 case DT_COLLECTION_PROP_FILMROLL: // film roll
1215 if(!(escaped_text && *escaped_text))
1216 // clang-format off
1217 query = g_strdup_printf("(film_id IN (SELECT id FROM main.film_rolls WHERE folder LIKE '%s%%'))",
1218 escaped_text);
1219 // clang-format on
1220 else
1221 // clang-format off
1222 query = g_strdup_printf("(film_id IN (SELECT id FROM main.film_rolls WHERE folder LIKE '%s'))",
1223 escaped_text);
1224 // clang-format on
1225 break;
1226
1227 case DT_COLLECTION_PROP_FOLDERS: // folders
1228 {
1229 // Recursion is normally the explicit `recursive` flag; a still-present trailing '*' is
1230 // only recognized as a fallback for collections/presets saved before that flag existed,
1231 // and for the Queries tab's raw rule editor, which has no checkbox and still relies on
1232 // typing '*' by hand -- so this is permanent, not a transitional shim.
1233 const gboolean has_star = (escaped_length > 0) && (escaped_text[escaped_length-1] == '*');
1234 if(recursive || has_star)
1235 {
1236 if(has_star) escaped_text[escaped_length-1] = '\0';
1237 // clang-format off
1238 query = g_strdup_printf("(film_id IN (SELECT id FROM main.film_rolls WHERE folder LIKE '%s' OR folder LIKE '%s"
1239 G_DIR_SEPARATOR_S "%%'))",
1240 escaped_text, escaped_text);
1241 // clang-format on
1242 }
1243 // replace |% at the end with /% to only show subfolders
1244 else if ((escaped_length > 1) && (strcmp(escaped_text+escaped_length-2, "|%") == 0 ))
1245 {
1246 escaped_text[escaped_length-2] = '\0';
1247 // clang-format off
1248 query = g_strdup_printf("(film_id IN (SELECT id FROM main.film_rolls WHERE folder LIKE '%s"
1249 G_DIR_SEPARATOR_S "%%'))",
1250 escaped_text);
1251 // clang-format on
1252 }
1253 else
1254 {
1255 // clang-format off
1256 query = g_strdup_printf("(film_id IN (SELECT id FROM main.film_rolls WHERE folder LIKE '%s'))",
1257 escaped_text);
1258 // clang-format on
1259 }
1260 }
1261 break;
1262
1263 case DT_COLLECTION_PROP_COLORLABEL: // colorlabel
1264 {
1265 if(!(escaped_text && *escaped_text) || strcmp(escaped_text, "%") == 0)
1266 // clang-format off
1267 query = g_strdup_printf("(id IN (SELECT imgid FROM main.color_labels WHERE color IS NOT NULL))");
1268 // clang-format on
1269 else
1270 {
1271 int color = 0;
1272 if(strcmp(escaped_text, _("red")) == 0)
1273 color = 0;
1274 else if(strcmp(escaped_text, _("yellow")) == 0)
1275 color = 1;
1276 else if(strcmp(escaped_text, _("green")) == 0)
1277 color = 2;
1278 else if(strcmp(escaped_text, _("blue")) == 0)
1279 color = 3;
1280 else if(strcmp(escaped_text, _("purple")) == 0)
1281 color = 4;
1282 // clang-format off
1283 query = g_strdup_printf("(id IN (SELECT imgid FROM main.color_labels WHERE color=%d))", color);
1284 // clang-format on
1285 }
1286 }
1287 break;
1288
1289 case DT_COLLECTION_PROP_HISTORY: // history
1290 {
1291 if(strcmp(escaped_text, _("altered")) == 0)
1292 {
1293 query = g_strdup("EXISTS (SELECT 1 FROM main.history h WHERE h.imgid = id)");
1294 }
1295 else if(strcmp(escaped_text, _("unaltered")) == 0)
1296 {
1297 query = g_strdup("NOT EXISTS (SELECT 1 FROM main.history h WHERE h.imgid = id)");
1298 }
1299 else
1300 {
1301 query = g_strdup("1");
1302 }
1303 }
1304 break;
1305
1306 case DT_COLLECTION_PROP_GEOTAGGING: // geotagging
1307 {
1308 const gboolean not_tagged = strcmp(escaped_text, _("not tagged")) == 0;
1309 const gboolean no_location = strcmp(escaped_text, _("tagged")) == 0;
1310 const gboolean all_tagged = strcmp(escaped_text, _("tagged*")) == 0;
1311 char *escaped_text2 = g_strstr_len(escaped_text, -1, "|");
1312 char *name_clause = g_strdup_printf("t.name LIKE \'%s\' || \'%s\'",
1313 dt_map_location_data_tag_root(), escaped_text2 ? escaped_text2 : "%");
1314
1315 if (escaped_text2 && (escaped_text2[strlen(escaped_text2)-1] == '*'))
1316 {
1317 escaped_text2[strlen(escaped_text2)-1] = '\0';
1318 name_clause = g_strdup_printf("(t.name LIKE \'%s\' || \'%s\' OR t.name LIKE \'%s\' || \'%s|%%\')",
1319 dt_map_location_data_tag_root(), escaped_text2 , dt_map_location_data_tag_root(), escaped_text2);
1320 }
1321
1322 if(not_tagged || all_tagged)
1323 // clang-format off
1324 query = g_strdup_printf("(id %s IN (SELECT id AS imgid FROM main.images "
1325 "WHERE (longitude IS NOT NULL AND latitude IS NOT NULL))) ",
1326 all_tagged ? "" : "not");
1327 // clang-format on
1328 else
1329 // clang-format off
1330 query = g_strdup_printf("(id IN (SELECT id AS imgid FROM main.images "
1331 "WHERE (longitude IS NOT NULL AND latitude IS NOT NULL))"
1332 "AND id %s IN (SELECT imgid FROM main.tagged_images AS ti"
1333 " JOIN data.tags AS t"
1334 " ON t.id = ti.tagid"
1335 " AND %s)) ",
1336 no_location ? "not" : "",
1337 name_clause);
1338 // clang-format on
1339 }
1340 break;
1341
1342 case DT_COLLECTION_PROP_LOCAL_COPY: // local copy
1343 // clang-format off
1344 query = g_strdup_printf("(id %s IN (SELECT id AS imgid FROM main.images WHERE (flags & %d))) ",
1345 (strcmp(escaped_text, _("not copied locally")) == 0) ? "not" : "",
1347 // clang-format on
1348 break;
1349
1350 case DT_COLLECTION_PROP_CAMERA: // camera
1351 // Start query with a false statement to avoid special casing the first condition
1352 query = g_strdup_printf("((1=0)");
1353 GList *lists = NULL;
1354 dt_collection_get_makermodels(text, NULL, &lists);
1355 for(GList *element = lists; element; element = g_list_next(element))
1356 {
1357 GList *tuple = element->data;
1358 char *clause = sqlite3_mprintf(" OR (maker = '%q' AND model = '%q')", tuple->data, tuple->next->data);
1359 query = dt_util_dstrcat(query, "%s", clause);
1360 sqlite3_free(clause);
1361 dt_free(tuple->data);
1362 dt_free(tuple->next->data);
1363 g_list_free(tuple);
1364 tuple = NULL;
1365 }
1366 g_list_free(lists);
1367 lists = NULL;
1368 query = dt_util_dstrcat(query, ")");
1369 break;
1370
1371 case DT_COLLECTION_PROP_TAG: // tag
1372 {
1373 if(!strcmp(escaped_text, _("not tagged")))
1374 {
1375 // clang-format off
1376 query = g_strdup_printf("(id NOT IN (SELECT DISTINCT imgid FROM main.tagged_images "
1377 "WHERE tagid NOT IN memory.darktable_tags))");
1378 // clang-format on
1379 }
1380 else
1381 {
1382 if ((escaped_length > 0) && (escaped_text[escaped_length-1] == '*'))
1383 {
1384 // shift-click adds an asterix * to include items in and under this hierarchy
1385 // without using a wildcard % which also would include similar named items
1386 escaped_text[escaped_length-1] = '\0';
1387 // clang-format off
1388 query = g_strdup_printf("(id IN (SELECT imgid FROM main.tagged_images WHERE tagid IN "
1389 "(SELECT id FROM data.tags "
1390 "WHERE LOWER(name) = LOWER('%s')"
1391 " OR SUBSTR(LOWER(name), 1, LENGTH('%s') + 1) = LOWER('%s|'))))",
1392 escaped_text, escaped_text, escaped_text);
1393 // clang-format on
1394 }
1395 else if ((escaped_length > 0) && (escaped_text[escaped_length-1] == '%'))
1396 {
1397 // ends with % or |%
1398 escaped_text[escaped_length-1] = '\0';
1399 // clang-format off
1400 query = g_strdup_printf("(id IN (SELECT imgid FROM main.tagged_images WHERE tagid IN "
1401 "(SELECT id FROM data.tags WHERE SUBSTR(LOWER(name), 1, LENGTH('%s')) = LOWER('%s'))))",
1402 escaped_text, escaped_text);
1403 // clang-format on
1404 }
1405 else
1406 {
1407 // default
1408 // clang-format off
1409 query = g_strdup_printf("(id IN (SELECT imgid FROM main.tagged_images WHERE tagid IN "
1410 "(SELECT id FROM data.tags WHERE LOWER(name) = LOWER('%s'))))",
1411 escaped_text);
1412 // clang-format on
1413 }
1414 }
1415 }
1416 break;
1417
1418 case DT_COLLECTION_PROP_LENS: // lens
1419 query = g_strdup_printf("(lens LIKE '%%%s%%')", escaped_text);
1420 break;
1421
1422 case DT_COLLECTION_PROP_FOCAL_LENGTH: // focal length
1423 {
1424 gchar *operator, *number1, *number2;
1425 dt_collection_split_operator_number(escaped_text, &number1, &number2, &operator);
1426
1427 if(operator && strcmp(operator, "[]") == 0)
1428 {
1429 if(number1 && number2)
1430 query = g_strdup_printf("((focal_length >= %s) AND (focal_length <= %s))", number1, number2);
1431 }
1432 else if(operator && number1)
1433 query = g_strdup_printf("(focal_length %s %s)", operator, number1);
1434 else if(number1)
1435 // clang-format off
1436 query = g_strdup_printf("(CAST(focal_length AS INTEGER) = CAST(%s AS INTEGER))", number1);
1437 // clang-format on
1438 else
1439 query = g_strdup_printf("(focal_length LIKE '%%%s%%')", escaped_text);
1440
1441 dt_free(operator);
1442 dt_free(number1);
1443 dt_free(number2);
1444 }
1445 break;
1446
1447 case DT_COLLECTION_PROP_ISO: // iso
1448 {
1449 gchar *operator, *number1, *number2;
1450 dt_collection_split_operator_number(escaped_text, &number1, &number2, &operator);
1451
1452 if(operator && strcmp(operator, "[]") == 0)
1453 {
1454 if(number1 && number2)
1455 query = g_strdup_printf("((iso >= %s) AND (iso <= %s))", number1, number2);
1456 }
1457 else if(operator && number1)
1458 query = g_strdup_printf("(iso %s %s)", operator, number1);
1459 else if(number1)
1460 query = g_strdup_printf("(iso = %s)", number1);
1461 else
1462 query = g_strdup_printf("(iso LIKE '%%%s%%')", escaped_text);
1463
1464 dt_free(operator);
1465 dt_free(number1);
1466 dt_free(number2);
1467 }
1468 break;
1469
1470 case DT_COLLECTION_PROP_APERTURE: // aperture
1471 {
1472 gchar *operator, *number1, *number2;
1473 dt_collection_split_operator_number(escaped_text, &number1, &number2, &operator);
1474
1475 if(operator && strcmp(operator, "[]") == 0)
1476 {
1477 if(number1 && number2)
1478 // clang-format off
1479 query = g_strdup_printf("((ROUND(aperture,1) >= %s) AND (ROUND(aperture,1) <= %s))", number1,
1480 number2);
1481 // clang-format on
1482 }
1483 else if(operator && number1)
1484 query = g_strdup_printf("(ROUND(aperture,1) %s %s)", operator, number1);
1485 else if(number1)
1486 query = g_strdup_printf("(ROUND(aperture,1) = %s)", number1);
1487 else
1488 query = g_strdup_printf("(ROUND(aperture,1) LIKE '%%%s%%')", escaped_text);
1489
1490 dt_free(operator);
1491 dt_free(number1);
1492 dt_free(number2);
1493 }
1494 break;
1495
1496 case DT_COLLECTION_PROP_EXPOSURE: // exposure
1497 {
1498 gchar *operator, *number1, *number2;
1499 dt_collection_split_operator_exposure(escaped_text, &number1, &number2, &operator);
1500
1501 if(operator && strcmp(operator, "[]") == 0)
1502 {
1503 if(number1 && number2)
1504 // clang-format off
1505 query = g_strdup_printf("((exposure >= %s - 1.0/100000) AND (exposure <= %s + 1.0/100000))", number1,
1506 number2);
1507 // clang-format on
1508 }
1509 else if(operator && number1)
1510 query = g_strdup_printf("(exposure %s %s)", operator, number1);
1511 else if(number1)
1512 // clang-format off
1513 query = g_strdup_printf("(CASE WHEN exposure < 0.4 THEN ((exposure >= %s - 1.0/100000) AND (exposure <= %s + 1.0/100000)) "
1514 "ELSE (ROUND(exposure,2) >= %s - 1.0/100000) AND (ROUND(exposure,2) <= %s + 1.0/100000) END)",
1515 number1, number1, number1, number1);
1516 // clang-format on
1517 else
1518 query = g_strdup_printf("(exposure LIKE '%%%s%%')", escaped_text);
1519
1520 dt_free(operator);
1521 dt_free(number1);
1522 dt_free(number2);
1523 }
1524 break;
1525
1526 case DT_COLLECTION_PROP_FILENAME: // filename
1527 {
1528 GList *list = dt_util_str_to_glist(",", escaped_text);
1529
1530 for (GList *l = list; l; l = g_list_next(l))
1531 {
1532 char *name = (char*)l->data; // remember the original content of this list node
1533 l->data = g_strdup_printf("(filename LIKE '%%%s%%')", name);
1534 dt_free(name); // free the original filename
1535 }
1536
1537 char *subquery = dt_util_glist_to_str(" OR ", list);
1538 query = g_strdup_printf("(%s)", subquery);
1539 dt_free(subquery);
1540 g_list_free_full(list, dt_free_gpointer); // free the SQL clauses as well as the list
1541 list = NULL;
1542
1543 break;
1544 }
1551 {
1552 const int local_property = property;
1553 char *colname = NULL;
1554
1555 switch(local_property)
1556 {
1557 case DT_COLLECTION_PROP_DAY: colname = "datetime_taken" ; break ;
1558 case DT_COLLECTION_PROP_TIME: colname = "datetime_taken" ; break ;
1559 case DT_COLLECTION_PROP_IMPORT_TIMESTAMP: colname = "import_timestamp" ; break ;
1560 case DT_COLLECTION_PROP_CHANGE_TIMESTAMP: colname = "change_timestamp" ; break ;
1561 case DT_COLLECTION_PROP_EXPORT_TIMESTAMP: colname = "export_timestamp" ; break ;
1562 case DT_COLLECTION_PROP_PRINT_TIMESTAMP: colname = "print_timestamp" ; break ;
1563 }
1564 gchar *operator, *number1, *number2;
1565 dt_collection_split_operator_datetime(escaped_text, &number1, &number2, &operator);
1566 if(number1 && number1[strlen(number1) - 1] == '%')
1567 number1[strlen(number1) - 1] = '\0';
1568 GTimeSpan nb1 = number1 ? dt_datetime_exif_to_gtimespan(number1) : 0;
1569 GTimeSpan nb2 = number2 ? dt_datetime_exif_to_gtimespan(number2) : 0;
1570
1571 if(strcmp(operator, "[]") == 0)
1572 {
1573 if(number1 && number2)
1574 query = g_strdup_printf("((%s >= %" G_GINT64_FORMAT ") AND (%s <= %" G_GINT64_FORMAT "))", colname, nb1, colname, nb2);
1575 }
1576 else if((strcmp(operator, "=") == 0 || strcmp(operator, "") == 0) && number1 && number2)
1577 query = g_strdup_printf("((%s >= %" G_GINT64_FORMAT ") AND (%s <= %" G_GINT64_FORMAT "))", colname, nb1, colname, nb2);
1578 else if(strcmp(operator, "<>") == 0 && number1 && number2)
1579 // a date/period spans the range [nb1;nb2]; "not equal" means anything OUTSIDE it
1580 // (before its start OR after its end). AND here would be unsatisfiable (nb1 < nb2).
1581 query = g_strdup_printf("((%s < %" G_GINT64_FORMAT ") OR (%s > %" G_GINT64_FORMAT "))", colname, nb1, colname, nb2);
1582 else if(number1)
1583 query = g_strdup_printf("(%s %s %" G_GINT64_FORMAT ")", colname, operator, nb1);
1584 else
1585 query = g_strdup("1 = 1");
1586
1587 dt_free(operator);
1588 dt_free(number1);
1589 dt_free(number2);
1590 break;
1591 }
1592
1593 case DT_COLLECTION_PROP_GROUPING: // grouping
1594 query = g_strdup_printf("(id %s group_id)", (strcmp(escaped_text, _("group leaders")) == 0) ? "=" : "!=");
1595 break;
1596
1597 case DT_COLLECTION_PROP_MODULE: // dev module
1598 {
1599 // clang-format off
1600 query = g_strdup_printf("(id IN (SELECT imgid AS id FROM main.history AS h "
1601 "JOIN memory.darktable_iop_names AS m ON m.operation = h.operation "
1602 "WHERE h.enabled = 1 AND m.name LIKE '%s'))", escaped_text);
1603 // clang-format on
1604 }
1605 break;
1606
1607 case DT_COLLECTION_PROP_ORDER: // module order
1608 {
1609 int i = 0;
1610 for(i = 0; i < DT_IOP_ORDER_LAST; i++)
1611 {
1612 if(strcmp(escaped_text, _(dt_iop_order_string(i))) == 0) break;
1613 }
1614 if(i < DT_IOP_ORDER_LAST)
1615 // clang-format off
1616 query = g_strdup_printf("(id IN (SELECT imgid FROM main.module_order WHERE version = %d))", i);
1617 // clang-format on
1618 else
1619 // clang-format off
1620 query = g_strdup_printf("(id NOT IN (SELECT imgid FROM main.module_order))");
1621 // clang-format on
1622 }
1623 break;
1624
1625 case DT_COLLECTION_PROP_RATING: // image rating
1626 {
1627 gchar *operator, *number1, *number2;
1628 dt_collection_split_operator_number(escaped_text, &number1, &number2, &operator);
1629
1630 if(operator && strcmp(operator, "[]") == 0)
1631 {
1632 if(number1 && number2)
1633 {
1634 if(atoi(number1) == -1)
1635 { // rejected + star rating
1636 // clang-format off
1637 query = g_strdup_printf("(flags & 7 >= %s AND flags & 7 <= %s)", number1, number2);
1638 // clang-format on
1639 }
1640 else
1641 { // non-rejected + star rating
1642 // clang-format off
1643 query = g_strdup_printf("((flags & 8 == 0) AND (flags & 7 >= %s AND flags & 7 <= %s))", number1, number2);
1644 // clang-format on
1645 }
1646 }
1647 }
1648 else if(operator && number1)
1649 {
1650 if(g_strcmp0(operator, "<=") == 0 || g_strcmp0(operator, "<") == 0)
1651 { // all below rating + rejected
1652 // clang-format off
1653 query = g_strdup_printf("(flags & 8 == 8 OR flags & 7 %s %s)", operator, number1);
1654 // clang-format on
1655 }
1656 else if(g_strcmp0(operator, ">=") == 0 || g_strcmp0(operator, ">") == 0)
1657 {
1658 if(atoi(number1) >= 0)
1659 { // non rejected above rating
1660 // clang-format off
1661 query = g_strdup_printf("(flags & 8 == 0 AND flags & 7 %s %s)", operator, number1);
1662 // clang-format on
1663 }
1664 // otherwise no filter (rejected + all ratings)
1665 }
1666 else
1667 { // <> exclusion operator
1668 if(atoi(number1) == -1)
1669 { // all except rejected
1670 query = g_strdup_printf("(flags & 8 == 0)");
1671 }
1672 else
1673 { // all except star rating (including rejected)
1674 query = g_strdup_printf("(flags & 8 == 8 OR flags & 7 %s %s)", operator, number1);
1675 }
1676 }
1677 }
1678 else if(number1)
1679 {
1680 if(atoi(number1) == -1)
1681 { // rejected only
1682 query = g_strdup_printf("(flags & 8 == 8)");
1683 }
1684 else
1685 { // non-rejected + star rating
1686 query = g_strdup_printf("(flags & 8 == 0 AND flags & 7 == %s)", number1);
1687 }
1688 }
1689
1690 dt_free(operator);
1691 dt_free(number1);
1692 dt_free(number2);
1693 }
1694 break;
1695
1696 default:
1697 {
1698 if(property >= DT_COLLECTION_PROP_METADATA
1700 {
1702 if(strcmp(escaped_text, _("not defined")) != 0)
1703 // clang-format off
1704 query = g_strdup_printf("(id IN (SELECT id FROM main.meta_data WHERE key = %d AND value "
1705 "LIKE '%%%s%%'))", keyid, escaped_text);
1706 // clang-format on
1707 else
1708 // clang-format off
1709 query = g_strdup_printf("(id NOT IN (SELECT id FROM main.meta_data WHERE key = %d))",
1710 keyid);
1711 // clang-format off
1712 }
1713 }
1714 break;
1715 }
1716 sqlite3_free(escaped_text);
1717
1718 if(IS_NULL_PTR(query)) // We've screwed up and not done a query string, send a placeholder
1719 query = g_strdup_printf("(1=1)");
1720
1721 return query;
1722}
1723
1725 gboolean recursive)
1726{
1727 // Build the same WHERE clause the collection would use for this single rule, then
1728 // enumerate the matching image ids. Independent of the currently active collection so it
1729 // can feed batch/background operations (remove, attach tag, pre-render thumbnails, ...).
1730 GList *result = NULL;
1731 gchar *where = get_query_string(property, text, recursive);
1732 if(IS_NULL_PTR(where)) return NULL;
1733
1734 gchar *query = g_strdup_printf("SELECT id FROM main.images WHERE %s", where);
1735 dt_free(where);
1736
1737 sqlite3_stmt *stmt = NULL;
1738 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
1739 if(stmt)
1740 {
1741 while(sqlite3_step(stmt) == SQLITE_ROW)
1742 result = g_list_prepend(result, GINT_TO_POINTER(sqlite3_column_int(stmt, 0)));
1743 sqlite3_finalize(stmt);
1744 }
1745 dt_free(query);
1746
1747 return g_list_reverse(result);
1748}
1749
1751{
1753 if(!v) return;
1754 g_free(v->name);
1755 g_free(v);
1756}
1757
1758static dt_collection_name_value_t *_name_value_new(char *name, int id, int count, int status)
1759{
1761 v->name = name;
1762 v->id = id;
1763 v->count = count;
1764 v->status = status;
1765 return v;
1766}
1767
1769{
1770 GList *out = NULL;
1771 gchar *where_ext = dt_collection_get_extended_where(darktable.collection, rule);
1772
1773 // Camera is special: it groups on two text columns and combines them into a display name.
1774 if(property == DT_COLLECTION_PROP_CAMERA)
1775 {
1776 gchar *q = g_strdup_printf("SELECT maker, model, COUNT(*) AS count FROM main.images AS mi"
1777 " WHERE %s GROUP BY maker, model", where_ext);
1778 g_free(where_ext);
1779 sqlite3_stmt *stmt = NULL;
1781 int index = 0;
1782 while(stmt && sqlite3_step(stmt) == SQLITE_ROW)
1783 {
1784 const char *maker = (const char *)sqlite3_column_text(stmt, 0);
1785 const char *model = (const char *)sqlite3_column_text(stmt, 1);
1787 out = g_list_prepend(out, _name_value_new(name, index++, sqlite3_column_int(stmt, 2), -1));
1788 }
1789 if(stmt) sqlite3_finalize(stmt);
1790 g_free(q);
1791 return g_list_reverse(out);
1792 }
1793
1794 const gboolean is_date = property == DT_COLLECTION_PROP_DAY || property == DT_COLLECTION_PROP_TIME
1799 const gboolean has_status
1800 = (property == DT_COLLECTION_PROP_FOLDERS || property == DT_COLLECTION_PROP_FILMROLL);
1801 gchar *query = NULL;
1802
1803 switch(property)
1804 {
1806 query = g_strdup_printf("SELECT folder, film_rolls_id, COUNT(*) AS count, status"
1807 " FROM main.images AS mi"
1808 " JOIN (SELECT fr.id AS film_rolls_id, folder, status"
1809 " FROM main.film_rolls AS fr"
1810 " JOIN memory.film_folder AS ff ON fr.id = ff.id)"
1811 " ON film_id = film_rolls_id"
1812 " WHERE %s GROUP BY folder, film_rolls_id", where_ext);
1813 break;
1814
1816 query = g_strdup_printf("SELECT name, 1 AS tagid, SUM(count) AS count"
1817 " FROM (SELECT tagid, COUNT(*) as count"
1818 " FROM main.images AS mi JOIN main.tagged_images ON id = imgid"
1819 " WHERE %s GROUP BY tagid)"
1820 " JOIN (SELECT name, id AS tag_id FROM data.tags)"
1821 " ON tagid = tag_id GROUP BY name", where_ext);
1822 query = dt_util_dstrcat(query, " UNION ALL "
1823 "SELECT '%s' AS name, 0 as id, COUNT(*) AS count "
1824 "FROM main.images AS mi WHERE mi.id NOT IN"
1825 " (SELECT DISTINCT imgid FROM main.tagged_images AS ti"
1826 " WHERE ti.tagid NOT IN memory.darktable_tags)",
1827 _("not tagged"));
1828 break;
1829
1831 query = g_strdup_printf("SELECT CASE WHEN mi.longitude IS NULL OR mi.latitude IS null THEN '%s'"
1832 " ELSE CASE WHEN ta.imgid IS NULL THEN '%s' ELSE '%s' || ta.tagname END"
1833 " END AS name, ta.tagid AS tag_id, COUNT(*) AS count"
1834 " FROM main.images AS mi"
1835 " LEFT JOIN (SELECT imgid, t.id AS tagid, SUBSTR(t.name, %d) AS tagname"
1836 " FROM main.tagged_images AS ti JOIN data.tags AS t ON ti.tagid = t.id"
1837 " JOIN data.locations AS l ON l.tagid = t.id) AS ta ON ta.imgid = mi.id"
1838 " WHERE %s GROUP BY name, tag_id",
1839 _("not tagged"), _("tagged"), _("tagged"),
1840 (int)strlen(dt_map_location_data_tag_root()) + 1, where_ext);
1841 break;
1842
1844 query = g_strdup_printf("SELECT (datetime_taken / 86400000000) * 86400000000 AS date, 1, COUNT(*) AS count"
1845 " FROM main.images AS mi"
1846 " WHERE datetime_taken IS NOT NULL AND datetime_taken <> 0 AND %s"
1847 " GROUP BY date", where_ext);
1848 break;
1849
1855 {
1856 char *colname = NULL;
1857 switch(property)
1858 {
1859 case DT_COLLECTION_PROP_TIME: colname = "datetime_taken"; break;
1860 case DT_COLLECTION_PROP_IMPORT_TIMESTAMP: colname = "import_timestamp"; break;
1861 case DT_COLLECTION_PROP_CHANGE_TIMESTAMP: colname = "change_timestamp"; break;
1862 case DT_COLLECTION_PROP_EXPORT_TIMESTAMP: colname = "export_timestamp"; break;
1863 case DT_COLLECTION_PROP_PRINT_TIMESTAMP: colname = "print_timestamp"; break;
1864 default: break; // unreachable: outer switch already restricts to the timestamp cases
1865 }
1866 query = g_strdup_printf("SELECT %s AS date, 1, COUNT(*) AS count FROM main.images AS mi"
1867 " WHERE %s IS NOT NULL AND %s <> 0 AND %s GROUP BY date",
1868 colname, colname, colname, where_ext);
1869 break;
1870 }
1871
1873 query = g_strdup_printf("SELECT CASE WHEN EXISTS (SELECT 1 FROM main.history h WHERE h.imgid = mi.id)"
1874 " THEN '%s' ELSE '%s' END as altered, 1, COUNT(*) AS count"
1875 " FROM main.images AS mi WHERE %s GROUP BY altered ORDER BY altered ASC",
1876 _("altered"), _("unaltered"), where_ext);
1877 break;
1878
1880 query = g_strdup_printf("SELECT CASE WHEN (flags & %d) THEN '%s' ELSE '%s' END as lcp, 1, COUNT(*) AS count"
1881 " FROM main.images AS mi WHERE %s GROUP BY lcp ORDER BY lcp ASC",
1882 DT_IMAGE_LOCAL_COPY, _("copied locally"), _("not copied locally"), where_ext);
1883 break;
1884
1886 query = g_strdup_printf("SELECT CASE color WHEN 0 THEN '%s' WHEN 1 THEN '%s' WHEN 2 THEN '%s'"
1887 " WHEN 3 THEN '%s' WHEN 4 THEN '%s' ELSE '' END, color, COUNT(*) AS count"
1888 " FROM main.images AS mi"
1889 " JOIN (SELECT imgid AS color_labels_id, color FROM main.color_labels)"
1890 " ON id = color_labels_id WHERE %s GROUP BY color ORDER BY color DESC",
1891 _("red"), _("yellow"), _("green"), _("blue"), _("purple"), where_ext);
1892 break;
1893
1895 query = g_strdup_printf("SELECT lens, 1, COUNT(*) AS count FROM main.images AS mi WHERE %s"
1896 " GROUP BY lens ORDER BY lens", where_ext);
1897 break;
1898
1900 query = g_strdup_printf("SELECT CAST(focal_length AS INTEGER) AS focal_length, 1, COUNT(*) AS count"
1901 " FROM main.images AS mi WHERE %s GROUP BY CAST(focal_length AS INTEGER)"
1902 " ORDER BY CAST(focal_length AS INTEGER)", where_ext);
1903 break;
1904
1906 query = g_strdup_printf("SELECT CAST(iso AS INTEGER) AS iso, 1, COUNT(*) AS count"
1907 " FROM main.images AS mi WHERE %s GROUP BY iso ORDER BY iso", where_ext);
1908 break;
1909
1911 query = g_strdup_printf("SELECT ROUND(aperture,1) AS aperture, 1, COUNT(*) AS count"
1912 " FROM main.images AS mi WHERE %s GROUP BY aperture ORDER BY aperture", where_ext);
1913 break;
1914
1916 query = g_strdup_printf("SELECT CASE WHEN (exposure < 0.4) THEN '1/' || CAST(1/exposure + 0.9 AS INTEGER)"
1917 " ELSE ROUND(exposure,2) || '\"' END as _exposure, 1, COUNT(*) AS count"
1918 " FROM main.images AS mi WHERE %s GROUP BY _exposure ORDER BY exposure", where_ext);
1919 break;
1920
1922 query = g_strdup_printf("SELECT filename, 1, COUNT(*) AS count FROM main.images AS mi WHERE %s"
1923 " GROUP BY filename ORDER BY filename", where_ext);
1924 break;
1925
1927 query = g_strdup_printf("SELECT CASE WHEN id = group_id THEN '%s' ELSE '%s' END as group_leader, 1,"
1928 " COUNT(*) AS count FROM main.images AS mi WHERE %s"
1929 " GROUP BY group_leader ORDER BY group_leader ASC",
1930 _("group leaders"), _("group followers"), where_ext);
1931 break;
1932
1934 query = g_strdup_printf("SELECT m.name AS module_name, 1, COUNT(*) AS count FROM main.images AS mi"
1935 " JOIN (SELECT DISTINCT imgid, operation FROM main.history WHERE enabled = 1) AS h"
1936 " ON h.imgid = mi.id JOIN memory.darktable_iop_names AS m"
1937 " ON m.operation = h.operation WHERE %s GROUP BY module_name ORDER BY module_name",
1938 where_ext);
1939 break;
1940
1942 {
1943 char *orders = NULL;
1944 for(int i = 0; i < DT_IOP_ORDER_LAST; i++)
1945 orders = dt_util_dstrcat(orders, "WHEN mo.version = %d THEN '%s' ", i, _(dt_iop_order_string(i)));
1946 orders = dt_util_dstrcat(orders, "ELSE '%s' ", _("none"));
1947 query = g_strdup_printf("SELECT CASE %s END as ver, 1, COUNT(*) AS count FROM main.images AS mi"
1948 " LEFT JOIN (SELECT imgid, version FROM main.module_order) mo ON mo.imgid = mi.id"
1949 " WHERE %s GROUP BY ver ORDER BY ver", orders, where_ext);
1950 g_free(orders);
1951 break;
1952 }
1953
1955 query = g_strdup_printf("SELECT CASE WHEN (flags & 8) == 8 THEN -1 ELSE (flags & 7) END AS rating, 1,"
1956 " COUNT(*) AS count FROM main.images AS mi WHERE %s GROUP BY rating ORDER BY rating",
1957 where_ext);
1958 break;
1959
1960 default:
1962 {
1964 const char *name = (const char *)dt_metadata_get_name(keyid);
1965 char *setting = g_strdup_printf("plugins/lighttable/metadata/%s_flag", name);
1966 const gboolean hidden = dt_conf_get_int(setting) & DT_METADATA_FLAG_HIDDEN;
1967 g_free(setting);
1968 if(!hidden)
1969 query = g_strdup_printf("SELECT CASE WHEN value IS NULL THEN '%s' ELSE value END AS value, 1,"
1970 " COUNT(*) AS count, CASE WHEN value IS NULL THEN 0 ELSE 1 END AS force_order"
1971 " FROM main.images AS mi"
1972 " LEFT JOIN (SELECT id AS meta_data_id, value FROM main.meta_data WHERE key = %d)"
1973 " ON id = meta_data_id WHERE %s GROUP BY value ORDER BY force_order, value",
1974 _("not defined"), keyid, where_ext);
1975 }
1976 else // film roll
1977 {
1978 gchar *order_by = NULL;
1979 const char *filmroll_sort = dt_conf_get_string_const("plugins/collect/filmroll_sort");
1980 if(strcmp(filmroll_sort, "id") == 0)
1981 order_by = g_strdup("film_rolls_id DESC");
1982 else
1983 order_by = dt_conf_get_bool("plugins/collect/descending") ? g_strdup("folder DESC") : g_strdup("folder");
1984 query = g_strdup_printf("SELECT folder, film_rolls_id, COUNT(*) AS count, status FROM main.images AS mi"
1985 " JOIN (SELECT fr.id AS film_rolls_id, folder, status FROM main.film_rolls AS fr"
1986 " JOIN memory.film_folder AS ff ON ff.id = fr.id) ON film_id = film_rolls_id"
1987 " WHERE %s GROUP BY folder ORDER BY %s", where_ext, order_by);
1988 g_free(order_by);
1989 }
1990 break;
1991 }
1992 g_free(where_ext);
1993 if(!query) return NULL;
1994
1995 sqlite3_stmt *stmt = NULL;
1996 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt, NULL);
1997 while(stmt && sqlite3_step(stmt) == SQLITE_ROW)
1998 {
1999 char *name;
2000 if(is_date)
2001 {
2002 char sdt[DT_DATETIME_EXIF_LENGTH] = { 0 };
2003 dt_datetime_gtimespan_to_exif(sdt, sizeof(sdt), sqlite3_column_int64(stmt, 0));
2004 if(property == DT_COLLECTION_PROP_DAY) sdt[10] = '\0';
2005 name = g_strdup(sdt);
2006 }
2007 else
2008 {
2009 const char *txt = (const char *)sqlite3_column_text(stmt, 0);
2010 name = txt ? g_strdup(txt) : g_strdup("");
2011 }
2012 const int id = sqlite3_column_int(stmt, 1);
2013 const int count = sqlite3_column_int(stmt, 2);
2014 const int status = has_status ? sqlite3_column_int(stmt, 3) : -1;
2015 out = g_list_prepend(out, _name_value_new(name, id, count, status));
2016 }
2017 if(stmt) sqlite3_finalize(stmt);
2018 g_free(query);
2019 return g_list_reverse(out);
2020}
2021
2022int dt_collection_serialize(char *buf, int bufsize)
2023{
2024 char confname[200];
2025 int c;
2026 const int num_rules = dt_conf_get_int("plugins/lighttable/collect/num_rules");
2027 c = snprintf(buf, bufsize, "%d:", num_rules);
2028 buf += c;
2029 bufsize -= c;
2030 for(int k = 0; k < num_rules; k++)
2031 {
2032 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/mode%1d", k);
2033 const int mode = dt_conf_get_int(confname);
2034 c = snprintf(buf, bufsize, "%d:", mode);
2035 buf += c;
2036 bufsize -= c;
2037 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/item%1d", k);
2038 const int item = dt_conf_get_int(confname);
2039 c = snprintf(buf, bufsize, "%d:", item);
2040 buf += c;
2041 bufsize -= c;
2042 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/string%1d", k);
2043 const char *str = dt_conf_get_string_const(confname);
2044 // Fold the recursive flag back into the trailing '*' this wire format has always used, so
2045 // dt_collection_deserialize() (and any consumer reading this string directly) sees exactly
2046 // what get_query_string() expects, with no separate field to carry through.
2047 gchar *str_recursive = NULL;
2048 if(item == DT_COLLECTION_PROP_FOLDERS && str && str[0] != '\0' && str[strlen(str) - 1] != '*')
2049 {
2050 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/recursive%1d", k);
2051 if(dt_conf_get_bool(confname)) str_recursive = g_strconcat(str, "*", NULL);
2052 }
2053 const char *emit = str_recursive ? str_recursive : str;
2054 if(emit && (emit[0] != '\0'))
2055 c = snprintf(buf, bufsize, "%s$", emit);
2056 else
2057 c = snprintf(buf, bufsize, "%%$");
2058 g_free(str_recursive);
2059 buf += c;
2060 bufsize -= c;
2061 }
2062 return 0;
2063}
2064
2065void dt_collection_deserialize(const char *buf)
2066{
2067 int num_rules = 0;
2068 sscanf(buf, "%d", &num_rules);
2069 if(num_rules == 0)
2070 {
2071 dt_conf_set_int("plugins/lighttable/collect/num_rules", 1);
2072 dt_conf_set_int("plugins/lighttable/collect/mode0", 0);
2073 dt_conf_set_int("plugins/lighttable/collect/item0", 0);
2074 dt_conf_set_string("plugins/lighttable/collect/string0", "%");
2075 dt_conf_set_bool("plugins/lighttable/collect/recursive0", FALSE);
2076 }
2077 else
2078 {
2079 int mode = 0, item = 0;
2080 dt_conf_set_int("plugins/lighttable/collect/num_rules", num_rules);
2081 while(buf[0] != '\0' && buf[0] != ':') buf++;
2082 if(buf[0] == ':') buf++;
2083 char str[400], confname[200];
2084 for(int k = 0; k < num_rules; k++)
2085 {
2086 const int n = sscanf(buf, "%d:%d:%399[^$]", &mode, &item, str);
2087 if(n == 3)
2088 {
2089 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/mode%1d", k);
2090 dt_conf_set_int(confname, mode);
2091 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/item%1d", k);
2092 dt_conf_set_int(confname, item);
2093 // A FOLDERS rule's trailing '*' is the recursion marker (see dt_collection_serialize):
2094 // pull it back out into its own flag here, setting AND clearing so a rule slot never
2095 // inherits a stale flag left over from whatever collection previously occupied it.
2096 if(item == DT_COLLECTION_PROP_FOLDERS)
2097 {
2098 const size_t len = strlen(str);
2099 const gboolean recursive = len > 0 && str[len - 1] == '*';
2100 if(recursive) str[len - 1] = '\0';
2101 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/recursive%1d", k);
2102 dt_conf_set_bool(confname, recursive);
2103 }
2104 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/string%1d", k);
2105 dt_conf_set_string(confname, str);
2106 }
2107 else if(num_rules == 1)
2108 {
2109 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/mode%1d", k);
2110 dt_conf_set_int(confname, 0);
2111 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/item%1d", k);
2112 dt_conf_set_int(confname, 0);
2113 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/string%1d", k);
2114 dt_conf_set_string(confname, "%");
2115 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/recursive%1d", k);
2116 dt_conf_set_bool(confname, FALSE);
2117 break;
2118 }
2119 else
2120 {
2121 dt_conf_set_int("plugins/lighttable/collect/num_rules", k);
2122 break;
2123 }
2124 while(buf[0] != '$' && buf[0] != '\0') buf++;
2125 if(buf[0] == '$') buf++;
2126 }
2127 }
2129}
2130
2131/* Store the n most recent collections in config for re-use in menu */
2133{
2134 if(IS_NULL_PTR(darktable.gui)) return;
2135 if(IS_NULL_PTR(darktable.gui->ui)) return;
2136
2137 // Serialize current request
2138 char confname[200] = { 0 };
2139 char buf[4096];
2140 dt_collection_serialize(buf, sizeof(buf));
2141
2142 int n = -1;
2143 gboolean found_duplicate = FALSE;
2144
2145 // Check if current request already exist in history
2146 int num_items = dt_conf_get_int("plugins/lighttable/recentcollect/num_items");
2147 for(int k = 0; k < num_items; k++)
2148 {
2149 snprintf(confname, sizeof(confname), "plugins/lighttable/recentcollect/line%1d", k);
2150 const char *line = dt_conf_get_string_const(confname);
2151 if(IS_NULL_PTR(line)) continue;
2152 if(!strcmp(line, buf))
2153 {
2154 n = k;
2155 found_duplicate = TRUE;
2156 break;
2157 }
2158 }
2159
2160 // Shift all history items one step behind. When the history is already full,
2161 // the last item has no destination slot and must be dropped before moving
2162 // the remaining entries down.
2163 const int max_items = CLAMP(dt_conf_get_int("plugins/lighttable/recentcollect/max_items"), 1,
2165 int shifted_index = MIN(num_items - (found_duplicate ? 1 : 0), max_items);
2166 for(int k = num_items - 1; k > -1; k--)
2167 {
2168 if(k == n) continue; // this is the duplicate of current collection we found, skip it
2169
2170 // Get old records
2171 snprintf(confname, sizeof(confname), "plugins/lighttable/recentcollect/line%1d", k);
2172 gchar *line1 = dt_conf_get_string(confname);
2173 snprintf(confname, sizeof(confname), "plugins/lighttable/recentcollect/pos%1d", k);
2174 uint32_t pos1 = dt_conf_get_int(confname);
2175
2176 // Write new records shifted by 1 slot
2177 if(IS_NULL_PTR(line1) || line1[0] == '\0')
2178 {
2179 dt_free(line1);
2180 continue;
2181 }
2182
2183 if(shifted_index >= 0 && shifted_index < max_items)
2184 {
2185 snprintf(confname, sizeof(confname), "plugins/lighttable/recentcollect/line%1d", shifted_index);
2186 dt_conf_set_string(confname, line1);
2187 snprintf(confname, sizeof(confname), "plugins/lighttable/recentcollect/pos%1d", shifted_index);
2188 dt_conf_set_int(confname, pos1);
2189 }
2190 shifted_index -= 1;
2191 dt_free(line1);
2192 }
2193
2194 // Prepend current collection on top of history
2195 dt_conf_set_string("plugins/lighttable/recentcollect/line0", buf);
2196
2197 // Increment items if we didn't find a duplicate
2198 num_items += found_duplicate ? 0 : 1;
2199 dt_conf_set_int("plugins/lighttable/recentcollect/num_items", CLAMP(num_items, 1, max_items));
2200}
2201
2202
2204 dt_collection_properties_t changed_property, GList *list)
2205{
2206 int next = -1;
2207 if(list)
2208 {
2209 // for changing offsets, thumbtable needs to know the first untouched imageid after the list
2210 // we do this here
2211
2212 // 1. create a string with all the imgids of the list to be used inside IN sql query
2213 gchar *txt = NULL;
2214 int i = 0;
2215 for(GList *l = list; l; l = g_list_next(l))
2216 {
2217 const int id = GPOINTER_TO_INT(l->data);
2218 if(i == 0)
2219 txt = dt_util_dstrcat(txt, "%d", id);
2220 else
2221 txt = dt_util_dstrcat(txt, ",%d", id);
2222 i++;
2223 }
2224 // 2. search the first imgid not in the list but AFTER the list (or in a gap inside the list)
2225 // we need to be carefull that some images in the list may not be present on screen (collapsed groups)
2226 // clang-format off
2227 gchar *query = g_strdup_printf("SELECT imgid"
2228 " FROM memory.collected_images"
2229 " WHERE imgid NOT IN (%s)"
2230 " AND rowid > (SELECT rowid"
2231 " FROM memory.collected_images"
2232 " WHERE imgid IN (%s)"
2233 " ORDER BY rowid LIMIT 1)"
2234 " ORDER BY rowid LIMIT 1",
2235 txt, txt);
2236 // clang-format on
2237 sqlite3_stmt *stmt2;
2238 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt2, NULL);
2239 if(sqlite3_step(stmt2) == SQLITE_ROW)
2240 {
2241 next = sqlite3_column_int(stmt2, 0);
2242 }
2243 sqlite3_finalize(stmt2);
2244 dt_free(query);
2245 // 3. if next is still unvalid, let's try to find the first untouched image BEFORE the list
2246 if(next < 0)
2247 {
2248 // clang-format off
2249 query = g_strdup_printf("SELECT imgid"
2250 " FROM memory.collected_images"
2251 " WHERE imgid NOT IN (%s)"
2252 " AND rowid < (SELECT rowid"
2253 " FROM memory.collected_images"
2254 " WHERE imgid IN (%s)"
2255 " ORDER BY rowid LIMIT 1)"
2256 " ORDER BY rowid DESC LIMIT 1",
2257 txt, txt);
2258 // clang-format on
2259 DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), query, -1, &stmt2, NULL);
2260 if(sqlite3_step(stmt2) == SQLITE_ROW)
2261 {
2262 next = sqlite3_column_int(stmt2, 0);
2263 }
2264 sqlite3_finalize(stmt2);
2265 dt_free(query);
2266 }
2267 dt_free(txt);
2268 }
2269
2270 char confname[200];
2271
2272 const int _n_r = dt_conf_get_int("plugins/lighttable/collect/num_rules");
2273 const int num_rules = CLAMP(_n_r, 1, 10);
2274 char *conj[] = { "AND", "OR", "AND NOT" };
2275
2276 gchar **query_parts = g_new (gchar*, num_rules + 1);
2277 query_parts[num_rules] = NULL;
2278
2279 for(int i = 0; i < num_rules; i++)
2280 {
2281 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/item%1d", i);
2282 const int property = dt_conf_get_int(confname);
2283 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/string%1d", i);
2284 gchar *text = dt_conf_get_string(confname);
2285 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/mode%1d", i);
2286 const int mode = dt_conf_get_int(confname);
2287 snprintf(confname, sizeof(confname), "plugins/lighttable/collect/recursive%1d", i);
2288 const gboolean recursive = dt_conf_get_bool(confname);
2289
2290 if(IS_NULL_PTR(text) || text[0] == '\0')
2291 {
2292 if (mode == 1) // for OR show all
2293 query_parts[i] = g_strdup(" OR 1=1");
2294 else
2295 query_parts[i] = g_strdup("");
2296 }
2297 else
2298 {
2299 gchar *query = get_query_string(property, text, recursive);
2300
2301 query_parts[i] = g_strdup_printf(" %s %s", conj[mode], query);
2302
2303 dt_free(query);
2304 }
2305 dt_free(text);
2306 }
2307
2308 /* set the extended where and the use of it in the query */
2309 dt_collection_set_extended_where(collection, query_parts);
2310 g_strfreev(query_parts);
2313
2314 /* update query and at last the visual */
2315 dt_collection_update(collection);
2316
2317 /* Update recent collections history before we raise the signal,
2318 * since some signal listeners will need it */
2320
2321 /* raise signal of collection change, only if this is an original */
2324 list, next);
2325}
2326
2328{
2329 // Restore previous collection
2330 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "DELETE FROM memory.collected_images", NULL, NULL, NULL);
2332 "INSERT INTO memory.collected_images"
2333 " SELECT * FROM memory.collected_backup",
2334 NULL, NULL, NULL);
2335}
2336
2338{
2339 // Backup current collection
2340 DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "DELETE FROM memory.collected_backup", NULL, NULL, NULL);
2342 "INSERT INTO memory.collected_backup"
2343 " SELECT * FROM memory.collected_images",
2344 NULL, NULL, NULL);
2345}
2346
2348{
2349 // Culling mode restricts the collection to the selection
2350
2351 // Remove non-selected from collected images, aka culling mode
2354 "DELETE FROM memory.collected_images"
2355 " WHERE imgid NOT IN "
2356 " (SELECT imgid FROM main.selected_images)",
2357 NULL, NULL, NULL);
2358
2359 // Backup and reset current selection
2362}
2363
2365{
2366 // Restore everything as before
2369}
2370
2371
2373{
2375 dt_free(message);
2376 return FALSE;
2377}
2378
2380{
2381 /* collection hinting */
2382 gchar *message;
2383
2384 const int c = dt_collection_get_count(collection);
2386
2387 if(cs == 1)
2388 {
2389 /* determine offset of the single selected image */
2390 GList *selected_imgids = dt_selection_get_list(darktable.selection);
2391 int selected = -1;
2392
2393 if(selected_imgids)
2394 {
2395 selected = GPOINTER_TO_INT(selected_imgids->data);
2396 selected = dt_collection_image_offset_with_collection(collection, selected);
2397 selected++;
2398 }
2399 g_list_free(selected_imgids);
2400 selected_imgids = NULL;
2401 message = g_strdup_printf(_("%d image of %d (#%d) in current collection is selected"), cs, c, selected);
2402 }
2403 else
2404 {
2405 message = g_strdup_printf(
2406 ngettext(
2407 "%d image of %d in current collection is selected",
2408 "%d images of %d in current collection are selected",
2409 cs),
2410 cs, c);
2411 }
2412
2413 g_idle_add(dt_collection_hint_message_internal, message);
2414}
2415
2416static int dt_collection_image_offset_with_collection(const dt_collection_t *collection, int32_t imgid)
2417{
2418 if(imgid == UNKNOWN_IMAGE) return 0;
2419 int offset = 0;
2421 {
2423 "SELECT imgid FROM memory.collected_images",
2425 }
2426 sqlite3_stmt *stmt = _collection_image_offset_stmt;
2427 sqlite3_reset(stmt);
2428 sqlite3_clear_bindings(stmt);
2429
2430 gboolean found = FALSE;
2431
2432 while(sqlite3_step(stmt) == SQLITE_ROW)
2433 {
2434 const int id = sqlite3_column_int(stmt, 0);
2435 if(imgid == id)
2436 {
2437 found = TRUE;
2438 break;
2439 }
2440 offset++;
2441 }
2442
2443 if(!found) offset = 0;
2444
2445 return offset;
2446}
2447
2448static inline void _dt_collection_change_view_after_import(const dt_view_t *current_view, gboolean open_single_image)
2449{
2450 // Studio Capture already shows every newly-imported image itself (see
2451 // _studio_image_imported_callback() in views/studio_capture.c), without leaving the atelier:
2452 // forcing a switch to darkroom/lighttable here on every auto-imported capture would fight that
2453 // and kick the user out of a live shooting session.
2454 if(!g_strcmp0(current_view->module_name, "studio_capture")) return;
2455
2456 if(open_single_image)
2457 {
2458 if(!g_strcmp0(current_view->module_name, "darkroom")) // if current view IS "darkroom".
2459 dt_ctl_reload_view("darkroom");
2460 else
2461 dt_ctl_switch_mode_to("darkroom");
2462 }
2463 else if(g_strcmp0(current_view->module_name, "lighttable")) // if current view IS NOT "lighttable".
2464 dt_ctl_switch_mode_to("lighttable");
2465}
2466
2467static inline gboolean _collection_can_switch_folder(const int32_t imgid, const dt_view_t *current_atelier)
2468{
2469 // Go out if the image is unknown.
2470 gboolean result = imgid == UNKNOWN_IMAGE;
2471
2472 // Go out if we are not in lighttable or Studio Capture: those are the only two ateliers whose
2473 // filmstrip/grid should follow newly-imported images into their folder. Studio Capture's own
2474 // filmstrip is driven by the same darktable.collection query as lighttable's grid, so without
2475 // this it never picks up an auto-imported capture that lands outside the currently browsed
2476 // folder.
2477 result |= current_atelier && g_strcmp0(current_atelier->module_name, "lighttable")
2478 && g_strcmp0(current_atelier->module_name, "studio_capture");
2479
2480 // Go out if the Collection module is not showing the "Folders" tab. Only applies to
2481 // lighttable, the only atelier with a Collect module UI exposing that tab: Studio Capture has
2482 // no such module, so this persisted, lighttable-specific tab selection must not gate it too.
2483 const gboolean is_lighttable = current_atelier && !g_strcmp0(current_atelier->module_name, "lighttable");
2484 if(is_lighttable)
2485 result |= dt_conf_get_int("plugins/lighttable/collect/tab") != 0;
2486
2487 return result;
2488}
2489
2490void dt_collection_load_filmroll(dt_collection_t *collection, const int32_t imgid, gboolean open_single_image)
2491{
2493
2494 // Go out if conditions are not reunited
2495 if(_collection_can_switch_folder(imgid, current_atelier))
2496 return;
2497
2498 gchar first_directory[PATH_MAX] = { 0 };
2499 dt_get_dirname_from_imgid(first_directory, imgid);
2500
2501 const gboolean copy = dt_conf_get_bool("ui_last/import_copy");
2502 const dt_collection_properties_t Collection_view = dt_conf_get_int("plugins/lighttable/collect/item0");
2503 gchar dir[PATH_MAX] = { 0 };
2504
2505 // - If user imports images in place and View mode is on "Tree":
2506 // - if the user selecter 1 folder in Import:
2507 // - the lighttable displays the contents of that folder.
2508 // - else, the lighttable displays the contents of the folder
2509 // showing in the file explorer in Import.
2510 //
2511 // - In all other cases, the lighttable displays the first
2512 // imported image's folder.
2513
2514 if (Collection_view == DT_COLLECTION_PROP_FOLDERS && !copy)
2515 {
2516 int nb = dt_conf_get_int("ui_last/import_selection_nb");
2517 const gchar *first_selection = dt_conf_get_string_const("ui_last/import_first_selected_str");
2518
2519 if(nb ==1 && dt_util_dir_exist(first_selection))
2520 {
2521 fprintf(stdout,"Collection: one folder.\n");
2522 g_strlcpy(dir, g_strdup(first_selection), sizeof(dir));
2523 }
2524 else
2525 {
2526 fprintf(stdout,"Collection: files and folders.\n");
2527 const gchar *import_last_dir = dt_conf_get_string("ui_last/import_last_directory");
2528 if(dt_util_dir_exist(import_last_dir))
2529 g_strlcpy(dir, g_strdup(import_last_dir), sizeof(dir));
2530 }
2531 }
2532 else // in List view or we copy
2533 {
2534 fprintf(stdout,"Collection: copy or in List view.\n");
2535
2536 gchar first_img_path[PATH_MAX] = { 0 };
2537 dt_get_dirname_from_imgid(first_img_path, imgid);
2538
2539 if(dt_util_dir_exist(first_img_path))
2540 {
2541 g_strlcpy(dir, first_img_path, sizeof(dir));
2542 fprintf(stdout,"Collection: ID %d, last img path %s.\n", imgid, first_img_path);
2543 }
2544 }
2545
2546 // Don't append "*": it's the legacy encoding for "recursive" and would silently
2547 // override the user's current recursive/sub-folders setting on every import.
2548 dt_conf_set_string("plugins/lighttable/collect/string0", dir);
2549 dt_conf_set_int("plugins/lighttable/collect/num_rules", 1);
2550
2551 // Reload the collection with the current filmroll
2553
2554 // Necessary to directly open in darkroom if we want to.
2556
2557 // To scroll the lighttable automatically to this image,
2558 // it needs to be selected.
2560
2561 // New images are untagged, that may need an update of the collection module for untagged count
2563
2564 if(current_atelier) _dt_collection_change_view_after_import(current_atelier, open_single_image);
2565}
2566
2567// clang-format off
2568// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2569// vim: shiftwidth=2 expandtab tabstop=2 cindent
2570// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2571// 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)
const gchar * dt_collection_get_query(const dt_collection_t *collection)
Definition collection.c:552
GList * dt_collection_get_images_for_rule(const dt_collection_properties_t property, const char *text, gboolean recursive)
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
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
static gchar * get_query_string(const dt_collection_properties_t property, const gchar *text, const gboolean recursive)
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:673
void dt_control_set_mouse_over_id(int32_t value)
Definition control.c:947
void dt_ctl_reload_view(const char *mode)
Definition control.c:692
void dt_control_hinter_message(const struct dt_control_t *s, const char *message)
Definition control.c:934
darktable_t darktable
Definition darktable.c:183
#define UNKNOWN_IMAGE
Definition darktable.h:194
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:485
#define dt_free(ptr)
Definition darktable.h:478
static const dt_aligned_pixel_simd_t value
Definition darktable.h:599
#define PATH_MAX
Definition darktable.h:1189
#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:293
sqlite3 * dt_database_get(const dt_database_t *db)
Definition database.c:3653
GTimeSpan dt_datetime_exif_to_gtimespan(const char *sdt)
Definition datetime.c:406
gboolean dt_datetime_gtimespan_to_exif(char *sdt, const size_t sdt_size, const GTimeSpan gts)
Definition datetime.c:391
gboolean dt_datetime_entry_to_exif_upper_bound(char *exif, const size_t exif_size, const char *entry)
Definition datetime.c:334
gboolean dt_datetime_entry_to_exif(char *exif, const size_t exif_size, const char *entry)
Definition datetime.c:319
#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:1356
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:366
@ 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:803
struct dt_collection_t * collection
Definition darktable.h:809
struct dt_selection_t * selection
Definition darktable.h:810
const struct dt_database_t * db
Definition darktable.h:807
struct dt_control_signal_t * signals
Definition darktable.h:802
struct dt_view_manager_t * view_manager
Definition darktable.h:800
struct dt_control_t * control
Definition darktable.h:801
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:165
gboolean culling_mode
Definition gtk.h:192
char module_name[64]
Definition view.h:154
#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:141
@ DT_VIEW_STAR_4
Definition view.h:173
@ DT_VIEW_STAR_5
Definition view.h:174
@ DT_VIEW_STAR_3
Definition view.h:172
@ DT_VIEW_STAR_1
Definition view.h:170
@ DT_VIEW_STAR_2
Definition view.h:171
@ DT_VIEW_DESERT
Definition view.h:169