Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
import.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2023-2024 Alynx Zhou.
4 Copyright (C) 2023-2026 Aurélien PIERRE.
5 Copyright (C) 2023-2025 Guillaume Stutin.
6 Copyright (C) 2023 lologor.
7 Copyright (C) 2023 Luca Zulberti.
8
9 Ansel is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 Ansel is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
21*/
22
23#include "bauhaus/bauhaus.h"
24#include "common/atomic.h"
25#include "common/cache.h"
26#include "common/collection.h"
27#include "common/darktable.h"
29#include "common/debug.h"
30#include "common/exif.h"
31#include "common/import.h"
32#include "common/image.h"
33#include "common/image_cache.h"
34#include "common/imageio.h"
35#include "common/metadata.h"
36#include "common/datetime.h"
37#include "common/selection.h"
38#include "control/conf.h"
39#include "control/control.h"
40#include "control/signal.h"
42#include "dtgtk/button.h"
43
44#include "gui/draw.h"
45#include "gui/preferences.h"
46#include "gui/gtkentry.h"
47
48#include <gio/gio.h>
49
50#ifdef GDK_WINDOWING_QUARTZ
51#include "osx/osx.h"
52#endif
53#ifdef _WIN32
54//MSVCRT does not have strptime implemented
55#include "win/strptime.h"
56#endif
57#include <strings.h>
58#include <librsvg/rsvg.h>
59// ugh, ugly hack. why do people break stuff all the time?
60#ifndef RSVG_CAIRO_H
61#include <librsvg/rsvg-cairo.h>
62#endif
63
64
66{
67 dt_pthread_mutex_t lock;
68 uint32_t generation;
69 uint32_t refcount;
70 gboolean closing;
72
73typedef struct dt_import_t {
74 // User-selected folders and files from the Gtk file chooser,
75 // referenced by basename.
76 GSList *selection;
77
78 // List of GFiles to import, built recursively by traversing the user selection
79 GList *files;
80
81 // Generation snapshot captured when this job starts.
82 uint32_t generation;
83
84 // Number of elements in the list
85 uint32_t elements;
86
87 // Job-local lock. Do not alias dialog state because the dialog can be destroyed
88 // while background jobs are still running.
89 dt_pthread_mutex_t lock;
90
92
94
109
110
137
138static dt_import_t *dt_import_init(dt_lib_import_t *d, const uint32_t generation);
139static void dt_import_cleanup(void *import);
140
141static dt_lib_import_t * _init();
142static void _cleanup(dt_lib_import_t *d);
143
144static void gui_init(dt_lib_import_t *d);
145static void gui_cleanup(dt_lib_import_t *d);
146
147static void _set_test_path(dt_lib_import_t *d, dt_image_t *img);
148
149static void _do_select_all(dt_lib_import_t *d);
150static void _do_select_none(dt_lib_import_t *d);
151static void _do_select_new(dt_lib_import_t *d);
152static gboolean _selection_changed_scan_trigger(gpointer user_data);
153
154static void _recurse_folder(GVfs *vfs, GFile *folder, dt_import_t *const import);
155
156static gboolean _scan_still_valid(dt_import_t *const import)
157{
158 gboolean valid = FALSE;
159 dt_pthread_mutex_lock(&import->scan_state->lock);
160 valid = !import->scan_state->closing && import->scan_state->generation == import->generation;
161 dt_pthread_mutex_unlock(&import->scan_state->lock);
162 return valid;
163}
164
165// one-liner to set GtkLabel text from non-constant text and free it straight away
166static void _gtk_label_set_and_free(GtkWidget *widget, gchar *label)
167{
168 gtk_label_set_text(GTK_LABEL(widget), label);
169 dt_free(label);
170}
171
172static void _filter_document(GVfs *vfs, GFile *document, dt_import_t *import)
173{
174 if(!_scan_still_valid(import)) return;
175
176 gchar *pathname = g_file_get_path(document);
177
178 // Check that document is a real file (not directory) and it passes the type check defined by user in GUI filters.
179 // gtk_file_chooser_get_files() applies the filters on the first level of recursivity,
180 // so this test is only useful for the next levels if folders are selected at the first level.
181 // We must not call GtkFileFilter from worker threads because Gtk objects are not thread-safe.
182 if(pathname && g_file_test(pathname, G_FILE_TEST_IS_REGULAR) && dt_supported_image(pathname))
183 {
184 import->files = g_list_prepend(import->files, pathname);
185 // prepend is more efficient than append. Import control reorders alphabetically anyway.
186 pathname = NULL;
187 }
188 else if(pathname && g_file_test(pathname, G_FILE_TEST_IS_DIR))
189 {
190 _recurse_folder(vfs, document, import);
191 }
192
193 dt_free(pathname);
194}
195
196static void _recurse_folder(GVfs *vfs, GFile *folder, dt_import_t *const import)
197{
198 // Get subfolders and files from current folder
199 if(!_scan_still_valid(import)) return;
200
201 GFileEnumerator *files
202 = g_file_enumerate_children(folder, G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE,
203 G_FILE_QUERY_INFO_NONE, NULL, NULL);
204 if(IS_NULL_PTR(files)) return;
205
206 GFile *file = NULL;
207 while(g_file_enumerator_iterate(files, NULL, &file, NULL, NULL))
208 {
209 // g_file_enumerator_iterate returns FALSE only on errors, not on end of enumeration.
210 // We need an ugly break here else infinite loop.
211 if(IS_NULL_PTR(file)) break;
212
213 // Shutdown ASAP
214 if(!_scan_still_valid(import))
215 {
216 g_object_unref(files);
217 return;
218 }
219
220 _filter_document(vfs, file, import);
221 // g_file_enumerator_iterate() returns transfer-none children owned by the enumerator.
222 // Unref happens when the enumerator advances or is destroyed.
223 file = NULL;
224 }
225
226 g_object_unref(files);
227}
228
229static void _recurse_selection(GSList *selection, dt_import_t *const import)
230{
231 // Entry point of the file recursion : process user selection.
232 // GtkFileChooser gives us a GSList for selection, so we can't directly recurse from here
233 // since the import job expects a GList.
234
235 if(!_scan_still_valid(import) || IS_NULL_PTR(selection)) return;
236
237 GVfs *vfs = g_vfs_get_default();
238 for(GSList *uri = selection; uri; uri = g_slist_next(uri))
239 {
240 GFile *file = g_vfs_get_file_for_uri(vfs, (const char *)uri->data);
241 _filter_document(vfs, file, import);
242 g_object_unref(file);
243 }
244
245 // get the unsorted filtered path of the first selected element in file explorer.
246 GFile *filepath = g_vfs_get_file_for_uri(vfs, (const char *)selection->data);
247 gchar *first_element = g_file_get_path(filepath);
248 g_object_unref(filepath);
249
250 if(first_element) dt_conf_set_string("ui_last/import_first_selected_str", first_element);
251 dt_free(first_element);
252
253 // get the number of selected elements
254 dt_conf_set_int("ui_last/import_selection_nb", g_slist_length(selection));
255
256 import->files = g_list_sort(import->files, (GCompareFunc) g_strcmp0);
257}
258
259static int32_t dt_get_selected_files(dt_import_t *import)
260{
261 // Recurse through subfolders if any selected.
262 // Can be called directly from GUI thread without using a job,
263 // but that might freeze the GUI on large directories.
264
265 dt_pthread_mutex_lock(&import->lock);
266
267 // Get the new list
268 _recurse_selection(import->selection, import);
269 import->elements = (import->files) ? g_list_length(import->files) : 0;
270 gboolean valid = _scan_still_valid(import);
271
272 // If shutdown was triggered, we may already have no Gtk label widget to update through the callback.
273 // In that case, it will segfault. So don't raise the signal at all if shutdown was set.
274 if(valid && import->files)
275 {
276 dt_pthread_mutex_unlock(&import->lock);
277
279 // Signal receivers only observe this list. Ownership stays in dt_import_t and is released in dt_import_cleanup.
280 }
281 else if(import->files)
282 {
283 g_list_free_full(g_steal_pointer(&import->files), dt_free_gpointer);
284 import->files = NULL;
285 // no callback will be triggered. Free here.
286
287 dt_pthread_mutex_unlock(&import->lock);
288 }
289 else
290 {
291 dt_pthread_mutex_unlock(&import->lock);
292 }
293
294 return valid; // TRUE if completed without interruption
295}
296
301
302void dt_control_get_selected_files(dt_lib_import_t *d, gboolean destroy_window)
303{
304 if(d->closing || IS_NULL_PTR(d->scan_state)) return;
305
306 uint32_t generation = 0;
307 dt_pthread_mutex_lock(&d->scan_state->lock);
308 if(d->scan_state->closing)
309 {
310 dt_pthread_mutex_unlock(&d->scan_state->lock);
311 return;
312 }
313 d->scan_state->generation++;
314 generation = d->scan_state->generation;
315 dt_pthread_mutex_unlock(&d->scan_state->lock);
316
317 dt_job_t *job = dt_control_job_create(&_get_selected_files_job, "recursively detect files to import");
318 if(job)
319 {
320 dt_import_t *import = dt_import_init(d, generation);
321 if(IS_NULL_PTR(import))
322 {
324 return;
325 }
327 // Note : we don't free import->files. It's returned with the signal.
329 }
330}
331
332static GdkPixbuf *_import_get_thumbnail(const gchar *filename, const int width, const int height,
333 const gboolean valid_exif, dt_image_t *img)
334{
335 if(!filename || !g_file_test(filename, G_FILE_TEST_IS_REGULAR)) return NULL;
336
337 GdkPixbuf *pixbuf = NULL;
338 uint8_t *buffer = NULL;
339 int th_width;
340 int th_height;
341 char *mime_type = NULL;
342 const char *const extension = g_strrstr(filename, ".");
345 if(!dt_image_is_hdr(img)
346 && !dt_imageio_large_thumbnail(filename, &buffer, &th_width, &th_height, &color_space, width, height))
347 {
348 const float ratio = ((float)th_height) / ((float)th_width);
349
350 // Convert RGBa to RGB because GdkPixbuf doesn't do RGBa
352 th_width * th_height * 3 * sizeof(uint8_t),
353 0);
354 if(rgb)
355 {
357 for(size_t k = 0; k < th_width * th_height; k++)
358 {
359 const float alpha = buffer[k * 4 + 3] > 0 ? buffer[k * 4 + 3] / 255.0f : 1.0f;
360 rgb[k * 3] = CLAMP((int)roundf((buffer[k * 4] / 255.0f * alpha + (1.0f - alpha)) * 255.0f), 0, 255);
361 rgb[k * 3 + 1] = CLAMP((int)roundf((buffer[k * 4 + 1] / 255.0f * alpha + (1.0f - alpha)) * 255.0f), 0, 255);
362 rgb[k * 3 + 2] = CLAMP((int)roundf((buffer[k * 4 + 2] / 255.0f * alpha + (1.0f - alpha)) * 255.0f), 0, 255);
363 }
364
365 // Build the actual pixbuf object
366 GdkPixbuf *tmp = gdk_pixbuf_new_from_data(rgb, 0, FALSE, 8, th_width, th_height,
367 th_width * 3 * sizeof(uint8_t), NULL, NULL);
368 if(tmp)
369 {
370 pixbuf = gdk_pixbuf_scale_simple(tmp, roundf((float)width / ratio), height, GDK_INTERP_HYPER);
371 g_object_unref(tmp);
372 }
373 }
374
377 dt_free(mime_type);
378 }
379
380 if(IS_NULL_PTR(pixbuf))
381 {
382 const gboolean use_internal_loader = !(file_type & DT_IMAGE_RAW);
383
384 if(use_internal_loader)
385 {
386 dt_cache_entry_t cache_entry = { 0 };
387 dt_mipmap_buffer_t mipbuf = { 0 };
388 mipbuf.size = DT_MIPMAP_FULL;
389 mipbuf.cache_entry = &cache_entry;
390
391 /* If embedded preview extraction failed, non-RAW files should still get a preview by
392 * decoding the real image through Ansel instead of relying on the desktop pixbuf stack.
393 * RAWs stay excluded here because the import dialog only wants a lightweight fallback. */
394 if(dt_imageio_open(img, filename, &mipbuf) == DT_IMAGEIO_OK
395 && !IS_NULL_PTR(mipbuf.buf) && mipbuf.width > 0 && mipbuf.height > 0)
396 {
397 const size_t pixels = (size_t)mipbuf.width * mipbuf.height;
398 uint8_t *rgb = dt_pixelpipe_cache_alloc_align_cache(pixels * 3 * sizeof(uint8_t), 0);
399 if(!IS_NULL_PTR(rgb))
400 {
401 const float *const in = (const float *const)mipbuf.buf;
403 for(size_t k = 0; k < pixels; k++)
404 {
405 const float alpha = in[k * 4 + 3] > 0.0f ? CLAMPF(in[k * 4 + 3], 0.0f, 1.0f) : 1.0f;
406 rgb[k * 3] = CLAMP((int)roundf((CLAMPF(in[k * 4], 0.0f, 1.0f) * alpha + (1.0f - alpha)) * 255.0f), 0, 255);
407 rgb[k * 3 + 1] = CLAMP((int)roundf((CLAMPF(in[k * 4 + 1], 0.0f, 1.0f) * alpha + (1.0f - alpha)) * 255.0f), 0, 255);
408 rgb[k * 3 + 2] = CLAMP((int)roundf((CLAMPF(in[k * 4 + 2], 0.0f, 1.0f) * alpha + (1.0f - alpha)) * 255.0f), 0, 255);
409 }
410
411 GdkPixbuf *tmp = gdk_pixbuf_new_from_data(rgb, 0, FALSE, 8, mipbuf.width, mipbuf.height,
412 mipbuf.width * 3 * sizeof(uint8_t), NULL, NULL);
413 if(!IS_NULL_PTR(tmp))
414 {
415 const float ratio = (float)mipbuf.height / (float)mipbuf.width;
416 pixbuf = gdk_pixbuf_scale_simple(tmp, roundf((float)width / ratio), height, GDK_INTERP_HYPER);
417 g_object_unref(tmp);
418 }
419
421 }
422 }
423
424 dt_free_align(cache_entry.data);
425 cache_entry.data = NULL;
426 }
427 }
428
429 // Fallback to whatever Gtk found in the file
430 if(IS_NULL_PTR(pixbuf))
431 pixbuf = gdk_pixbuf_new_from_file_at_size(filename, width, height, NULL);
432
433 if(IS_NULL_PTR(pixbuf)) return NULL;
434
435 // Rotate the image to the correct orientation
436 GdkPixbuf *tmp = pixbuf;
438 tmp = gdk_pixbuf_rotate_simple(pixbuf, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
440 tmp = gdk_pixbuf_rotate_simple(pixbuf, GDK_PIXBUF_ROTATE_CLOCKWISE);
442 tmp = gdk_pixbuf_rotate_simple(pixbuf, GDK_PIXBUF_ROTATE_UPSIDEDOWN);
443
444 if(pixbuf != tmp) g_object_unref(pixbuf);
445
446 return tmp;
447}
448
450{
451 gchar basedir[PATH_MAX] = { 0 };
452 g_strlcpy(basedir, dt_conf_get_string_const("session/base_directory_pattern"), sizeof(basedir));
453
454 if(*basedir == 0 && dt_get_user_pictures_dir(dt_loc_get_home_dir(NULL), basedir, sizeof(basedir)))
455 {
456 // Basedir is empty
457 dt_conf_set_string("session/base_directory_pattern", basedir);
458 }
459 else if(strstr(basedir, "$(") != NULL)
460 {
461 // Basedir contains a pattern to expand - remnant of Darktable's defaults
462 dt_variables_params_t *params;
464
465 gchar *file_expand = dt_variables_expand(params, basedir, FALSE);
466 dt_conf_set_string("session/base_directory_pattern", file_expand);
467
468 dt_free(file_expand);
470 }
471}
472
474{
476}
477
482
484{
486}
487
488
489static void _resize_dialog(GtkWidget *widget)
490{
491 GtkAllocation allocation;
492 gtk_widget_get_allocation(widget, &allocation);
493 dt_conf_set_int("ui_last/import_dialog_width", allocation.width);
494 dt_conf_set_int("ui_last/import_dialog_height", allocation.height);
495}
496
497static void _build_filter(GtkFileFilter *filter, const gchar *extension)
498{
499 gchar *text = g_strdup_printf("*.%s", extension);
500 gchar *TEXT = g_utf8_strup(text, -1); // uppercase variant
501 gtk_file_filter_add_pattern(filter, text);
502 gtk_file_filter_add_pattern(filter, TEXT);
503 dt_free(text);
504 dt_free(TEXT);
505}
506
507/* Add file extension patterns for file chooser filters
508* Bloody GTK doesn't support regex patterns so we need to unroll
509* every combination separately, for lowercase and uppercase.
510*/
511static void _file_filters(GtkWidget *file_chooser)
512{
513 GtkFileFilter *filter;
514
515 const char *raster[] = {
516 "jpg", "jpeg", "j2c", "jp2", "tif", "tiff", "png", "exr",
517 "bmp", "dng", "heif", "heic", "avi", "avif", "webp", NULL };
518
519 const char *raw[] = {
520 "3fr", "ari", "arw", "bay", "bmq", "cap", "cine", "cr2",
521 "cr3", "crw", "cs1", "dc2", "dcr", "dng", "gpr", "erf",
522 "fff", "hdr", "ia", "iiq", "k25", "kc2", "kdc", "mdc",
523 "mef", "mos", "mrw", "nef", "nrw", "orf", "ori", "pef",
524 "pfm", "pnm", "pxn", "qtk", "raf", "raw", "rdc", "rw2",
525 "rwl", "sr2", "srf", "srw", "sti", "x3f", NULL };
526
527 /* ALL IMAGES */
528 filter = gtk_file_filter_new();
529 gtk_file_filter_set_name(filter, _("All image files"));
530 //TODO: use dt_supported_extensions list ?
531 for(int i = 0; i < 46; i++) _build_filter(filter, raw[i]);
532 for(int i = 0; i < 14; i++) _build_filter(filter, raster[i]);
533
534 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(file_chooser), filter);
535
536 // Set ALL IMAGES as default
537 gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(file_chooser), filter);
538
539 /* RAW ONLY */
540 filter = gtk_file_filter_new();
541 gtk_file_filter_set_name(filter, _("Raw image files"));
542 for(int i = 0; i < 46; i++) _build_filter(filter, raw[i]);
543 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(file_chooser), filter);
544
545 /* RASTER ONLY */
546 filter = gtk_file_filter_new();
547 gtk_file_filter_set_name(filter, _("Raster image files"));
548 for(int i = 0; i < 14; i++) _build_filter(filter, raster[i]);
549
550 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(file_chooser), filter);
551}
552
553static GtkWidget * _attach_aligned_grid_item(GtkWidget *grid, const int row, const int column,
554 const char *label, const GtkAlign align, const gboolean fixed_width,
555 const gboolean full_width)
556{
557 GtkWidget *w = gtk_label_new(label);
558 if(fixed_width)
559 gtk_label_set_max_width_chars(GTK_LABEL(w), 25);
560
561 gtk_label_set_ellipsize(GTK_LABEL(w), PANGO_ELLIPSIZE_END);
562 gtk_grid_attach(GTK_GRID(grid), w, column, row, full_width ? 2 : 1, 1);
563 gtk_label_set_xalign(GTK_LABEL(w), align);
564 gtk_widget_set_halign(w, align);
565 gtk_label_set_line_wrap(GTK_LABEL(w), TRUE);
566 return w;
567}
568
569static GtkWidget * _attach_grid_separator(GtkWidget *grid, const int row, const int length)
570{
571 GtkWidget *w = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
572 gtk_grid_attach(GTK_GRID(grid), w, 0, row, length, 1);
573 dt_gui_add_class(w, "grid-separator");
574 return w;
575}
576
577static int _is_in_library_by_path(const gchar *folder, const char *filename)
578{
579 int32_t filmroll_id = dt_film_get_id(folder);
580 int32_t image_id = dt_image_get_id(filmroll_id, filename);
581 return image_id;
582}
583
584static int _is_in_library_by_metadata(GFile *file)
585{
586 GError *error = NULL;
587 GFileInfo *info = g_file_query_info(file,
588 G_FILE_ATTRIBUTE_STANDARD_NAME ","
589 G_FILE_ATTRIBUTE_TIME_MODIFIED,
590 G_FILE_QUERY_INFO_NONE, NULL, &error);
591 if(IS_NULL_PTR(info))
592 {
593 if(error) g_error_free(error);
594 return 0;
595 }
596
597 const guint64 datetime = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
598 char dtid[DT_DATETIME_EXIF_LENGTH];
599 dt_datetime_unix_to_exif(dtid, sizeof(dtid), (const time_t *)&datetime);
600 const int res = dt_metadata_already_imported(g_file_info_get_name(info), dtid);
601 g_object_unref(info);
602 return res;
603}
604
605static void _exif_text_set_and_free(dt_lib_import_t *d, exif_fields_t field, gchar *label)
606{
607 _gtk_label_set_and_free(d->exif_info[field], label);
608}
609
610static void update_preview_cb(GtkFileChooser *file_chooser, gpointer userdata)
611{
612 dt_lib_import_t *d = (dt_lib_import_t *)userdata;
613 if(d->closing) return;
614 gchar *uri = gtk_file_chooser_get_preview_uri(file_chooser);
615 if(IS_NULL_PTR(uri))
616 {
617 gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
618 return; // nothing to do, nothing to free.
619 }
620
621 GVfs *vfs = g_vfs_get_default();
622 GFile *in = g_vfs_get_file_for_uri(vfs, (const char *)uri);
623 char *filename = g_file_get_path(in);
624
625 gboolean have_file = (!IS_NULL_PTR(filename)) && g_file_test(filename, G_FILE_TEST_IS_REGULAR);
626 gtk_file_chooser_set_preview_widget_active(file_chooser, have_file);
627
628 dt_image_t *img = NULL;
629 int valid_exif = 0;
630 if(have_file)
631 {
632 const char *const extension = g_strrstr(filename, ".");
634
635 dt_free(d->path_file);
636 d->path_file = g_strdup(filename);
637
638 img = malloc(sizeof(dt_image_t));
639 dt_image_init(img);
640 if(!(file_type & DT_IMAGE_HDR))
641 valid_exif = dt_exif_read(img, filename);
642 else
643 valid_exif = 1;
644 _set_test_path(d, img);
645 }
646 else
647 {
648 g_object_unref(in);
649 dt_free(filename);
650 dt_free(uri);
651 return;
652 }
653
654 /* Get the thumbnail */
655 // 160x120 px seems a reasonably generic size for small thumbs from RAW files
656 if(!dt_conf_get_bool("import/disable_thumbnail"))
657 {
658 GdkPixbuf *pixbuf = _import_get_thumbnail(filename, (int) DT_PIXEL_APPLY_DPI(180), (int) DT_PIXEL_APPLY_DPI(180), valid_exif, img);
659 gtk_image_set_from_pixbuf(GTK_IMAGE(d->preview), pixbuf);
660 if(pixbuf) g_object_unref(pixbuf);
661 }
662
663 gtk_widget_show_all(d->preview);
664
665
666 // Reset everything
667 gtk_label_set_text(GTK_LABEL(d->exif_info[EXIF_DATETIME_FIELD]), "");
668 gtk_label_set_text(GTK_LABEL(d->exif_info[EXIF_MODEL_FIELD]), "");
669 gtk_label_set_text(GTK_LABEL(d->exif_info[EXIF_MAKER_FIELD]), "");
670 gtk_label_set_text(GTK_LABEL(d->exif_info[EXIF_LENS_FIELD]), "");
671 gtk_label_set_text(GTK_LABEL(d->exif_info[EXIF_FOCAL_LENS_FIELD]), "");
672 gtk_label_set_text(GTK_LABEL(d->exif_info[EXIF_EXPOSURE_FIELD]), "");
673 gtk_label_set_text(GTK_LABEL(d->exif_info[EXIF_INLIB_FIELD]), _("No"));
674 gtk_label_set_text(GTK_LABEL(d->exif_info[EXIF_PATH_FIELD]), "");
675
676 /* Do we already have this picture in library ? */
677 gchar *folder = dt_util_path_get_dirname(filename);
678 gchar *basename = g_file_get_basename(in);
679 const int is_path_in_lib = _is_in_library_by_path(folder, basename);
680 const int is_metadata_in_lib = _is_in_library_by_metadata(in);
681 g_object_unref(in);
682 const gboolean is_in_lib = (is_path_in_lib > -1) || (is_metadata_in_lib > -1);
683 dt_free(folder);
684 dt_free(basename);
685
686 /* If alread imported, find out where */
687 int32_t imgid = UNKNOWN_IMAGE;
688 if(is_path_in_lib > -1)
689 imgid = is_path_in_lib;
690 else if(is_metadata_in_lib > -1)
691 imgid = is_metadata_in_lib;
692
693 char path[512] = { 0 };
694 if(imgid > UNKNOWN_IMAGE)
695 {
696 dt_image_t *lib_img = dt_image_cache_get(darktable.image_cache, imgid, 'r');
697 if(lib_img)
698 {
699 dt_image_film_roll_directory(lib_img, path, sizeof(path));
701 }
702 }
703
704 /* Get EXIF info */
705 if(!valid_exif)
706 {
707 char datetime[200];
708 const gboolean valid = dt_datetime_img_to_local(datetime, sizeof(datetime), img, FALSE);
709 gchar *exposure = dt_util_format_exposure(img->exif_exposure);
710 gchar *exposure_field = g_strdup_printf("%.0f ISO - f/%.1f - %s", img->exif_iso, img->exif_aperture, exposure);
711 dt_free(exposure);
712 _exif_text_set_and_free(d, EXIF_DATETIME_FIELD, g_strdup_printf(" %s", valid ? datetime : "-"));
713 _exif_text_set_and_free(d, EXIF_MODEL_FIELD, g_strdup_printf(" %s", (img->exif_model[0] != '\0') ? img->exif_model : "-"));
714 _exif_text_set_and_free(d, EXIF_MAKER_FIELD, g_strdup_printf(" %s", (img->exif_maker[0] != '\0') ? img->exif_maker : "-"));
715 _exif_text_set_and_free(d, EXIF_LENS_FIELD, g_strdup_printf(" %s", (img->exif_lens[0] != '\0') ? img->exif_lens : "-"));
716 _exif_text_set_and_free(d, EXIF_FOCAL_LENS_FIELD, g_strdup_printf(" %0.f mm", img->exif_focal_length));
718 _exif_text_set_and_free(d, EXIF_INLIB_FIELD, (is_in_lib) ? g_strdup_printf(_(" Yes (ID %i), in"), imgid) : g_strdup_printf(_(" No")));
719
720 if(is_in_lib && path[0] != '\0') _exif_text_set_and_free(d, EXIF_PATH_FIELD, g_strdup_printf(_("%s"), path));
721 }
722
723 dt_free(filename);
724 dt_free(uri);
725 dt_free(img);
726}
727
728static void _update_directory(GtkWidget *file_chooser, dt_lib_import_t *d)
729{
730 gchar *path = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(file_chooser));
731 dt_conf_set_string("ui_last/import_last_directory", path);
732 dt_free(path);
733}
734
735static void _set_help_string(dt_lib_import_t *d, gboolean copy)
736{
737 if(copy)
738 gtk_label_set_markup(
739 GTK_LABEL(d->help_string),
740 _("<i>The files will be copied to the selected destination. You can rename them in batch below:</i>"));
741 else
742 gtk_label_set_markup(
743 GTK_LABEL(d->help_string),
744 _("<i>The files will stay at their original location</i>"));
745}
746
748{
749 if(IS_NULL_PTR(d->path_file) || IS_NULL_PTR(d->path_file))
750 return;
751
752 const gboolean duplicate = dt_conf_get_bool("ui_last/import_copy");
753 if(!duplicate)
754 {
755 gtk_label_set_text(GTK_LABEL(d->test_path), _("No copy."));
756 return;
757 }
758
759 char datetime_override[DT_DATETIME_LENGTH] = { 0 };
760 const char *date = gtk_entry_get_text(GTK_ENTRY(d->datetime));
761 GList *file = g_list_prepend(NULL, g_strdup(d->path_file));
762
763 if(date[0] && !dt_datetime_entry_to_exif(datetime_override, sizeof(datetime_override), date))
764 {
765 dt_control_log(_("invalid date/time format for import"));
766 return;
767 }
768
769 if(IS_NULL_PTR(file->data) || !dt_supported_image(file->data))
770 {
771 gtk_label_set_text(GTK_LABEL(d->test_path), _("Choose a file to see the result..."));
772 return;
773 }
774 else
775 {
776 gchar *basedir = dt_conf_get_string("session/base_directory_pattern");
777 dt_control_import_t data = {.imgs = file,
778 .datetime = dt_string_to_datetime(date),
779 .copy = 1,
780 .jobcode = dt_conf_get_string("ui_last/import_jobcode"),
781 .base_folder = basedir,
782 .target_subfolder_pattern = dt_conf_get_string("session/sub_directory_pattern"),
783 .target_file_pattern = dt_conf_get_string("session/filename_pattern"),
784 .target_dir = NULL,
785 .elements = 1,
786 .discarded = NULL,
787 };
788
789 gboolean free_after = FALSE;
790 if(IS_NULL_PTR(img))
791 {
792 img = malloc(sizeof(dt_image_t));
793 dt_image_init(img);
794
795 // Generate file I/O only if the pattern is using EXIF variables.
796 // Otherwise, discard it since it's really expensive if the file is on external/remote storage.
797 // This is mandatory BEFORE expanding variables in pattern
798 if(strstr(data.target_file_pattern, "$(EXIF") != NULL
799 || strstr(data.target_subfolder_pattern, "$(EXIF") != NULL )
800 dt_exif_read(img, (const char*)file->data);
801
802 free_after = TRUE;
803 }
804
805 gchar *_path = dt_build_filename_from_pattern((const char *const)file->data, 1, img, &data);
806 gchar * cut = g_strdup(g_strrstr(basedir, G_DIR_SEPARATOR_S));
807 gchar *fake_path = g_strdup(g_strrstr(_path, cut));
808
809 if(free_after)
810 {
811 dt_free(img);
812 }
813
814 if(fake_path && fake_path[0] != 0)
815 _gtk_label_set_and_free(d->test_path, g_strdup_printf(_("...%s"), fake_path));
816 else
817 gtk_label_set_text(GTK_LABEL(d->test_path), _("Can't build a valid path."));
818
819 dt_free(cut);
820 dt_free(_path);
821 dt_free(fake_path);
823 }
824}
825
826static void _filelist_changed_callback(gpointer instance, GList *files, guint elements, guint finished, gpointer user_data)
827{
828 dt_lib_import_t *d = (dt_lib_import_t *)user_data;
829 if(IS_NULL_PTR(d) || d->closing || IS_NULL_PTR(d->selected_files)) return;
830
831 if(finished)
832 {
833 // Lock the thread to ensure we have the correct final number
834 dt_pthread_mutex_lock(&d->lock);
835 _gtk_label_set_and_free(d->selected_files, g_strdup_printf(_("%i files selected"), elements));
837 }
838 else
839 {
840 // We don't care for correctness, we just want to show user that we are still at it
841 _gtk_label_set_and_free(d->selected_files, g_strdup_printf(_("Detection in progress... (%i files found so far)"), elements));
842 }
843}
844
846{
847 if(d->closing) return;
848 gtk_label_set_text(GTK_LABEL(d->selected_files), _("Detecting candidate files for import..."));
849
850 // Coalesce bursts of Gtk "selection-changed" signals while navigating file lists.
851 // A short delay avoids queueing redundant recursive scans for transient selections.
852 if(d->selection_scan_timeout_id > 0) g_source_remove(d->selection_scan_timeout_id);
853 d->selection_scan_timeout_id = g_timeout_add(120, _selection_changed_scan_trigger, d);
854}
855
867static gboolean _selection_changed_scan_trigger(gpointer user_data)
868{
869 dt_lib_import_t *d = (dt_lib_import_t *)user_data;
871 if(d->closing) return G_SOURCE_REMOVE;
873 return G_SOURCE_REMOVE;
874}
875
877{
878 gboolean state = gtk_combo_box_get_active(GTK_COMBO_BOX(combobox));
879 dt_conf_set_bool("ui_last/import_copy", state);
880 gtk_widget_set_visible(GTK_WIDGET(d->grid), state);
881 gtk_widget_set_visible(GTK_WIDGET(d->test_path), state);
883 _set_test_path(d, NULL);
884}
885
886static void _jobcode_changed(GtkFileChooserButton* widget, dt_lib_import_t *d)
887{
888 dt_conf_set_string("ui_last/import_jobcode", gtk_entry_get_text(GTK_ENTRY(widget)));
889 _set_test_path(d, NULL);
890}
891
892static void _base_dir_changed(GtkFileChooserButton* self, dt_lib_import_t *d)
893{
894 dt_conf_set_string("session/base_directory_pattern", gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(self)));
895 _set_test_path(d, NULL);
896}
897
899{
900 dt_conf_set_string("session/sub_directory_pattern", gtk_entry_get_text(GTK_ENTRY(widget)));
901 _set_test_path(d, NULL);
902}
903
905{
906 dt_conf_set_string("session/filename_pattern", gtk_entry_get_text(GTK_ENTRY(widget)));
907 _set_test_path(d, NULL);
908}
909
910static void _update_date(GtkCalendar *calendar, GtkWidget *entry)
911{
912 guint year, month, day;
913 gtk_calendar_get_date(calendar, &year, &month, &day);
914 GTimeZone *tz = g_time_zone_new_local();
915
916 // Again, GDateTime counts months from 1 but GtkCalendar from 0. Stupid.
917 GDateTime *datetime = g_date_time_new(tz, year, month + 1, day, 0, 0, 0.);
918 g_time_zone_unref(tz);
919 gchar *date = g_date_time_format(datetime, "%F");
920 gtk_entry_set_text(GTK_ENTRY(entry), date);
921 dt_free(date);
922 g_date_time_unref(datetime);
923}
924
925/* Validate user input, aka check if date format respects ISO 8601*/
926static void _datetime_changed_callback(GtkEntry *entry, dt_lib_import_t *d)
927{
928 const char *date = gtk_entry_get_text(entry);
929 if(date[0])
930 {
931 char filtered[DT_DATETIME_LENGTH] = { 0 };
932 gboolean valid = dt_datetime_entry_to_exif(filtered, sizeof(filtered), date);
933 if(!valid)
934 {
935 gtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_SECONDARY, "dialog-error");
936 gtk_entry_set_icon_tooltip_text(entry, GTK_ENTRY_ICON_SECONDARY,
937 _("Date should follow the ISO 8601 format, like :\n"
938 "YYYY-MM-DD\n"
939 "YYYY-MM-DD HH:mm\n"
940 "YYYY-MM-DD HH:mm:ss\n"
941 "YYYY-MM-DDTHH:mm:ss"));
942 return;
943 }
944 else
945 {
946 gtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_SECONDARY, "");
947 gtk_entry_set_icon_tooltip_text(entry, GTK_ENTRY_ICON_SECONDARY, "");
948 }
949 }
950 gtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_PRIMARY, NULL);
951 _set_test_path(d, NULL);
952}
953
954static void _file_activated(GtkFileChooser *chooser, GtkDialog *dialog)
955{
956 // If we double-click on image and we are not asking to duplicate files, let the filechooser
957 // behave as a replacement of lighttable and directly open the image in darkroom.
958 if(g_file_test(gtk_file_chooser_get_filename(chooser), G_FILE_TEST_IS_REGULAR)
959 && !dt_conf_get_bool("ui_last/import_copy"))
960 {
961 gtk_dialog_response(dialog, GTK_RESPONSE_ACCEPT);
962 }
963}
964
965
975static void _process_file_list(gpointer instance, GList *files, int elements, gboolean finished, gpointer user_data)
976{
977 if(!finished) return; // Should be fired only when we are done detecting stuff
978
979 dt_lib_import_t *d = (dt_lib_import_t *)user_data;
980 if(IS_NULL_PTR(d) || d->closing) return;
981
982 if(elements > 0)
983 {
984 // Deep-copy the source list so import job owns an independent set of file path strings.
985 dt_control_import_t data = {.imgs = g_list_copy_deep(files, (GCopyFunc)g_strdup, NULL),
986 .datetime = dt_string_to_datetime(gtk_entry_get_text(GTK_ENTRY(d->datetime))),
987 .copy = dt_conf_get_bool("ui_last/import_copy"),
988 .jobcode = dt_conf_get_string("ui_last/import_jobcode"),
989 .base_folder = dt_conf_get_string("session/base_directory_pattern"),
990 .target_subfolder_pattern = dt_conf_get_string("session/sub_directory_pattern"),
991 .target_file_pattern = dt_conf_get_string("session/filename_pattern"),
992 .target_dir = NULL,
993 .elements = elements,
994 .discarded = NULL
995 };
996
997 // Prepare to catch the end of import signal
998 if(dt_control_import(data))
999 dt_control_log(_("Could not start the import job."));
1000 }
1001 else
1002 dt_control_log(_("No files to import. Check your selection."));
1003
1005 gui_cleanup(d);
1006 _cleanup(d);
1007
1008 // Re-allocate focus to center widget
1010}
1011
1012void _file_chooser_response(GtkDialog *dialog, gint response_id, dt_lib_import_t *d)
1013{
1014 // Stop capturing the filelist changes for the in-popup label file counter.
1016
1017 switch(response_id)
1018 {
1019 case GTK_RESPONSE_ACCEPT:
1020 {
1021 if(d->selection_scan_timeout_id > 0)
1022 {
1023 g_source_remove(d->selection_scan_timeout_id);
1024 d->selection_scan_timeout_id = 0;
1025 }
1026
1027 // The next file list change will now only fire the importer job
1029
1030 // It would be swell if we could just re-use the file list computed on "select" callback.
1031 // However, it depends on the file filter used, and we can't refresh the list when
1032 // filter is changed (no callback to connect to).
1033 // To be safe, we need to start again here, from scratch.
1035
1036 // TODO: print "pending" message on modal window
1037 break;
1038 }
1039 case GTK_RESPONSE_CANCEL:
1040 default:
1041 gui_cleanup(d);
1042 _cleanup(d);
1043 break;
1044 }
1045}
1046
1047
1049{
1051
1052 d->dialog = gtk_dialog_new_with_buttons
1053 ( _("Ansel - Open pictures"), NULL, GTK_DIALOG_DESTROY_WITH_PARENT,
1054 _("Cancel"), GTK_RESPONSE_CANCEL,
1055 _("Import"), GTK_RESPONSE_ACCEPT,
1056 NULL);
1057 dt_gui_add_class(d->dialog, "dt_import_dialog");
1058
1059#ifdef GDK_WINDOWING_QUARTZ
1060// TODO: On MacOS (at least on version 13) the dialog windows doesn't behave as expected. The dialog
1061// needs to have a parent window. "set_parent_window" wasn't working, so set_transient_for is
1062// the way to go. Still the window manager isn't dealing with the dialog properly, when the dialog
1063// is shifted outside its parent. The dialog isn't visible any longer but still listed as a window
1064// of the app.
1066 gtk_window_set_position(GTK_WINDOW(d->dialog), GTK_WIN_POS_CENTER_ON_PARENT);
1067#endif
1068
1069 gtk_window_set_default_size(GTK_WINDOW(d->dialog),
1070 dt_conf_get_int("ui_last/import_dialog_width"),
1071 dt_conf_get_int("ui_last/import_dialog_height"));
1072 gtk_window_set_modal(GTK_WINDOW(d->dialog), FALSE);
1073 gtk_window_set_transient_for(GTK_WINDOW(d->dialog), GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)));
1074 g_signal_connect(d->dialog, "response", G_CALLBACK(_file_chooser_response), d);
1075
1076 GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(d->dialog));
1077 g_signal_connect(d->dialog, "check-resize", G_CALLBACK(_resize_dialog), NULL);
1078
1079 /* Grid of options for copy/duplicate */
1080 d->grid = gtk_grid_new();
1081 GtkGrid *grid = GTK_GRID(d->grid);
1082 gtk_grid_set_column_spacing(grid, DT_GUI_BOX_SPACING / 2.);
1083 gtk_grid_set_row_spacing(grid, DT_GUI_BOX_SPACING / 2.);
1084 gtk_grid_set_column_homogeneous(grid, FALSE);
1085 gtk_grid_set_row_homogeneous(grid, FALSE);
1086
1087 /* BOTTOM PANEL */
1088 GtkWidget *rbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1089 gtk_box_pack_start(GTK_BOX(content), rbox, TRUE, TRUE, 0);
1090
1091 // File browser
1092 d->file_chooser = gtk_file_chooser_widget_new(GTK_FILE_CHOOSER_ACTION_OPEN);
1093 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(d->file_chooser), TRUE);
1094 gtk_file_chooser_set_use_preview_label(GTK_FILE_CHOOSER(d->file_chooser), FALSE);
1095 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(d->file_chooser),
1096 dt_conf_get_string_const("ui_last/import_last_directory"));
1097 gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(d->file_chooser), FALSE);
1098 gtk_box_pack_start(GTK_BOX(rbox), d->file_chooser, TRUE, TRUE, 0);
1099 g_signal_connect(G_OBJECT(d->file_chooser), "current-folder-changed", G_CALLBACK(_update_directory), NULL);
1100 g_signal_connect(G_OBJECT(d->file_chooser), "file-activated", G_CALLBACK(_file_activated), GTK_DIALOG(d->dialog));
1101 g_signal_connect(G_OBJECT(d->file_chooser), "selection-changed", G_CALLBACK(_selection_changed), d);
1102 g_signal_connect(G_OBJECT(d->file_chooser), "update-preview", G_CALLBACK(update_preview_cb), d);
1103
1104 // file extension filters
1105 _file_filters(d->file_chooser);
1106
1107 // File browser toolbox (extra widgets)
1108 GtkWidget *toolbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1109 gtk_widget_set_halign(toolbox, GTK_ALIGN_END);
1110
1111 GtkWidget *select_all = gtk_button_new_with_label(_("Select all"));
1112 gtk_box_pack_start(GTK_BOX(toolbox), select_all, FALSE, FALSE, 0);
1113 g_signal_connect(select_all, "clicked", G_CALLBACK(_do_select_all_clicked), d);
1114
1115 GtkWidget *select_none = gtk_button_new_with_label(_("Select none"));
1116 gtk_box_pack_start(GTK_BOX(toolbox), select_none, FALSE, FALSE, 0);
1117 g_signal_connect(select_none, "clicked", G_CALLBACK(_do_select_none_clicked), d);
1118
1119 GtkWidget *select_new = gtk_button_new_with_label(_("Select new"));
1120 gtk_box_pack_start(GTK_BOX(toolbox), select_new, FALSE, FALSE, 0);
1121 g_signal_connect(select_new, "clicked", G_CALLBACK(_do_select_new_clicked), d);
1122 gtk_widget_set_tooltip_text(select_new,
1123 _("Selecting new files targets pictures that have never been added to the library. "
1124 "The lookup is done by searching for the original filename and date/time. "
1125 "It can detect files existing at another path, under a different name. "
1126 "False-positive can arise if two pictures have been taken at the same time with the same name."));
1127
1128 d->selected_files = gtk_label_new("");
1129 gtk_box_pack_start(GTK_BOX(toolbox), d->selected_files, FALSE, FALSE, 0);
1130
1131 gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(d->file_chooser), toolbox);
1132
1133 /* RIGHT PANEL */
1134 // File browser preview box
1135 // 1. Thumbnail
1136 GtkWidget *preview_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1137 d->preview = gtk_image_new();
1138 gtk_widget_set_size_request(d->preview, DT_PIXEL_APPLY_DPI(240), DT_PIXEL_APPLY_DPI(240));
1139 gtk_box_pack_start(GTK_BOX(preview_box), d->preview, TRUE, FALSE, 0);
1140
1141 // 2. Exif metadata
1142 d->exif = gtk_grid_new();
1143 gtk_grid_set_column_spacing(GTK_GRID(d->exif), DT_GUI_BOX_SPACING);
1144 _attach_aligned_grid_item(d->exif, 0, 0, _("Shot:"), GTK_ALIGN_END, FALSE, FALSE);
1145 _attach_grid_separator( d->exif, 1, 2);
1146 _attach_aligned_grid_item(d->exif, 2, 0, _("Camera:"), GTK_ALIGN_END, FALSE, FALSE);
1147 _attach_aligned_grid_item(d->exif, 3, 0, _("Brand:"), GTK_ALIGN_END, FALSE, FALSE);
1148 _attach_aligned_grid_item(d->exif, 4, 0, _("Lens:"), GTK_ALIGN_END, FALSE, FALSE);
1149 _attach_aligned_grid_item(d->exif, 5, 0, _("Focal:"), GTK_ALIGN_END, FALSE, FALSE);
1150 _attach_grid_separator( d->exif, 6, 2);
1151 // exposure trifecta
1152 _attach_grid_separator( d->exif, 8, 2);
1153
1154 GtkWidget *imported_label = gtk_label_new(_("Imported:"));
1155 GtkBox *help_box_inlib = attach_help_popover(
1156 imported_label,
1157 _("Images already in the library will not be imported again, selected or not. "
1158 "Remove them from the library first, or use the menu "
1159 "`Run \342\206\222 Resynchronize library and XMP` to update the local database from distant XMP.\n\n"
1160 "Ansel indexes images by their filename and parent folder (full path), "
1161 "not by their content. Therefore, renaming or moving images on the filesystem, "
1162 "or changing the mounting point of their external drive will make them "
1163 "look like new (unknown) images.\n\n"
1164 "If an XMP file is present alongside images, it will be imported as well, "
1165 "including the metadata and settings stored in it. If it is not what you want, "
1166 "you can reset metadata in the lighttable."));
1167 gtk_widget_set_halign(imported_label, GTK_ALIGN_END);
1168 gtk_grid_attach(GTK_GRID(d->exif), GTK_WIDGET(help_box_inlib), 0, EXIF_INLIB_FIELD, 1, 1);
1169 //_attach_aligned_grid_item(d->exif, 9, 0, _("Imported :"), GTK_ALIGN_END, FALSE, FALSE);
1170
1171 d->exif_info[EXIF_DATETIME_FIELD] = _attach_aligned_grid_item(d->exif, 0, 1, "", GTK_ALIGN_START, TRUE, FALSE);
1172 d->exif_info[EXIF_MODEL_FIELD] = _attach_aligned_grid_item(d->exif, 2, 1, "", GTK_ALIGN_START, TRUE, FALSE);
1173 d->exif_info[EXIF_MAKER_FIELD] = _attach_aligned_grid_item(d->exif, 3, 1, "", GTK_ALIGN_START, TRUE, FALSE);
1174 d->exif_info[EXIF_LENS_FIELD] = _attach_aligned_grid_item(d->exif, 4, 1, "", GTK_ALIGN_START, TRUE, FALSE);
1175 d->exif_info[EXIF_FOCAL_LENS_FIELD] = _attach_aligned_grid_item(d->exif, 5, 1, "", GTK_ALIGN_START, TRUE, FALSE);
1176 d->exif_info[EXIF_EXPOSURE_FIELD] = _attach_aligned_grid_item(d->exif, 7, 0, "", GTK_ALIGN_CENTER, TRUE, TRUE);
1177 d->exif_info[EXIF_INLIB_FIELD] = _attach_aligned_grid_item(d->exif, 9, 1, "", GTK_ALIGN_START, FALSE, TRUE);
1178 d->exif_info[EXIF_PATH_FIELD] = _attach_aligned_grid_item(d->exif, 10, 0, "", GTK_ALIGN_START, FALSE, TRUE);
1179 gtk_label_set_ellipsize(GTK_LABEL(d->exif_info[EXIF_PATH_FIELD]), PANGO_ELLIPSIZE_MIDDLE);
1180
1181 gtk_box_pack_start(GTK_BOX(preview_box), d->exif, TRUE, TRUE, 0);
1182 gtk_widget_show_all(d->exif);
1183
1184 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(d->file_chooser), preview_box);
1185 /* BOTTOM PANEL */
1186
1187 GtkWidget *files = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1188 GtkWidget *file_handling = gtk_label_new("");
1189 gtk_label_set_markup(GTK_LABEL(file_handling), _("<b>File handling</b>"));
1190 gtk_box_pack_start(GTK_BOX(files), GTK_WIDGET(file_handling), FALSE, FALSE, 0);
1191
1192 GtkWidget *copy = gtk_combo_box_text_new();
1193 gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(copy), NULL, _("Add to library"));
1194 gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(copy), NULL, _("Copy to disk"));
1195 gtk_combo_box_set_active(GTK_COMBO_BOX(copy), dt_conf_get_bool("ui_last/import_copy"));
1196 gtk_box_pack_start(GTK_BOX(files), GTK_WIDGET(copy), FALSE, FALSE, 0);
1197 g_signal_connect(G_OBJECT(copy), "changed", G_CALLBACK(_copy_toggled_callback), (gpointer)d);
1198
1199 d->help_string = gtk_label_new("");
1200 _set_help_string(d, dt_conf_get_bool("ui_last/import_copy"));
1201 gtk_box_pack_start(GTK_BOX(files), GTK_WIDGET(d->help_string), FALSE, FALSE, 0);
1202
1203 gtk_box_pack_start(GTK_BOX(rbox), GTK_WIDGET(files), FALSE, FALSE, 0);
1204
1205 // Project date
1206 GtkWidget *calendar_label = gtk_label_new(_("Project date"));
1207 gtk_widget_set_halign(calendar_label, GTK_ALIGN_START);
1208 d->datetime = gtk_entry_new();
1210 gtk_entry_set_width_chars(GTK_ENTRY(d->datetime), 20);
1211 g_signal_connect(G_OBJECT(d->datetime), "changed", G_CALLBACK(_datetime_changed_callback), d);
1212
1213 // Date is inited as today by default
1214 GDateTime *now = g_date_time_new_now_local();
1215 gchar *now_string = g_date_time_format(now, "%F");
1216 gtk_entry_set_text(GTK_ENTRY(d->datetime), now_string);
1217 dt_free(now_string);
1218
1219 // Date chooser
1220 GtkWidget *calendar = gtk_calendar_new();
1221 // GtkCalendar uses monthes in [0:11]. Glib GDateTime returns monthes in [1:12]. Stupid.
1222 gtk_calendar_select_month(GTK_CALENDAR(calendar), g_date_time_get_month(now) - 1, g_date_time_get_year(now));
1223 const guint day = g_date_time_get_day_of_month(now);
1224 gtk_calendar_select_day(GTK_CALENDAR(calendar), day);
1225 gtk_calendar_mark_day(GTK_CALENDAR(calendar), day);
1226 GtkBox *box_calendar = attach_popover(d->datetime, "appointment-new-symbolic", calendar);
1227 g_signal_connect(G_OBJECT(calendar), "day-selected", G_CALLBACK(_update_date), d->datetime);
1228
1229 // free date
1230 g_date_time_unref(now);
1231
1232 // Base directory of projects
1233 GtkWidget *jobcode = gtk_entry_new();
1235 gtk_entry_set_text(GTK_ENTRY(jobcode), dt_conf_get_string_const("ui_last/import_jobcode"));
1236 gtk_widget_set_hexpand(jobcode, TRUE);
1237 g_signal_connect(G_OBJECT(jobcode), "changed", G_CALLBACK(_jobcode_changed), d);
1238
1239 GtkWidget *jobcode_label = gtk_label_new(_("Jobcode"));
1240 gtk_widget_set_halign(jobcode_label, GTK_ALIGN_START);
1241
1242 GtkWidget *base_label = gtk_label_new(_("Base directory of all projects"));
1243 gtk_widget_set_halign(base_label, GTK_ALIGN_START);
1244
1245 GtkWidget *dir_label = gtk_label_new(_("Project directory naming pattern"));
1246 gtk_widget_set_halign(dir_label, GTK_ALIGN_START);
1247
1248 GtkWidget *file_label = gtk_label_new(_("File naming pattern"));
1249 gtk_widget_set_halign(file_label, GTK_ALIGN_START);
1250
1251 GtkWidget *base_dir
1252 = gtk_file_chooser_button_new(_("Select a base directory"), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
1253 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(base_dir),
1254 dt_conf_get_string_const("session/base_directory_pattern"));
1255 g_signal_connect(G_OBJECT(base_dir), "file-set", G_CALLBACK(_base_dir_changed), d);
1256 gtk_widget_set_hexpand(base_dir, TRUE);
1257
1258 GtkWidget *sep1 = gtk_label_new(G_DIR_SEPARATOR_S);
1259 GtkWidget *sep2 = gtk_label_new(G_DIR_SEPARATOR_S);
1260
1261 GtkWidget *project_dir = gtk_entry_new();
1263 gtk_entry_set_text(GTK_ENTRY(project_dir), dt_conf_get_string_const("session/sub_directory_pattern"));
1264 gtk_widget_set_hexpand(project_dir, TRUE);
1266 gtk_widget_set_tooltip_text(project_dir, _("Start typing `$(` to see available variables through auto-completion"));
1267 g_signal_connect(G_OBJECT(project_dir), "changed", G_CALLBACK(_project_dir_changed), d);
1268
1269 GtkWidget *file = gtk_entry_new();
1271 gtk_entry_set_text(GTK_ENTRY(file), dt_conf_get_string_const("session/filename_pattern"));
1272 gtk_widget_set_hexpand(file, TRUE);
1274 g_signal_connect(G_OBJECT(file), "changed", G_CALLBACK(_filename_changed), d);
1275
1276 GtkWidget *pattern_label = gtk_label_new(_("Pattern result"));
1277 gtk_widget_set_halign(pattern_label, GTK_ALIGN_START);
1278
1279 d->test_path = gtk_label_new(_("Choose a file to see the result..."));
1280 gtk_widget_set_halign(d->test_path, GTK_ALIGN_START);
1281 gtk_label_set_line_wrap(GTK_LABEL(d->test_path), TRUE);
1282 gtk_label_set_max_width_chars(GTK_LABEL(d->test_path), 60);
1283 _set_test_path(d, NULL);
1284
1285 /* Create the grid of import params when using duplication */
1286 int row = 0;
1287 _attach_grid_separator(GTK_WIDGET(grid), row, 5);
1288 row++;
1289
1290 // Row 0: labels for text entries
1291 gtk_grid_attach(grid, calendar_label, 0, row, 1, 1);
1292 gtk_grid_attach(grid, jobcode_label, 2, row, 1, 1);
1293 gtk_grid_attach(grid, pattern_label, 4, row, 1, 1);
1294 row++;
1295
1296 // Row 1: text entries
1297 gtk_grid_attach(grid, GTK_WIDGET(box_calendar), 0, row, 1, 1);
1298 gtk_grid_attach(grid, jobcode, 2, row, 1, 1);
1299 gtk_grid_attach(grid, d->test_path, 4, row, 1, 1);
1300 row++;
1301
1302 // Row 2: separator
1303 _attach_grid_separator(GTK_WIDGET(grid), row, 5);
1304 row++;
1305
1306 // Row 3: labels for text entries
1307 gtk_grid_attach(grid, base_label, 0, row, 1, 1);
1308 gtk_grid_attach(grid, dir_label, 2, row, 1, 1);
1309 gtk_grid_attach(grid, file_label, 4, row, 1, 1);
1310 row++;
1311
1312 // Row 4: text entries
1313 gtk_grid_attach(grid, base_dir, 0, row, 1, 1);
1314 gtk_grid_attach(grid, sep1, 1, row, 1, 1);
1315 gtk_grid_attach(grid, project_dir, 2, row, 1, 1);
1316 gtk_grid_attach(grid, sep2, 3, row, 1, 1);
1317 gtk_grid_attach(grid, file, 4, row, 1, 1);
1318 row++;
1319
1320 gtk_box_pack_start(GTK_BOX(rbox), GTK_WIDGET(grid), FALSE, FALSE, 0);
1321
1322 gtk_widget_show_all(d->dialog);
1323
1324 // Duplication parameters visible only if the option is set
1325 gtk_widget_set_visible(GTK_WIDGET(grid), dt_conf_get_bool("ui_last/import_copy"));
1326
1327 // Update the number of selected files string because Gtk forces a default selection at opening time
1329 G_CALLBACK(_filelist_changed_callback), d);
1330}
1331
1333{
1334 gtk_file_chooser_unselect_all(GTK_FILE_CHOOSER(d->file_chooser));
1335}
1336
1338{
1339 gtk_file_chooser_select_all(GTK_FILE_CHOOSER(d->file_chooser));
1340}
1341
1343{
1344 // Twisted Gtk doesn't let us select multiple files.
1345 // We need to select all then unselect what we don't want.
1347
1348 GtkFileChooser *chooser = GTK_FILE_CHOOSER(d->file_chooser);
1349 gchar *folder = gtk_file_chooser_get_current_folder(chooser);
1350 if(IS_NULL_PTR(folder)) return;
1351
1352 GFile *folder_file = g_file_new_for_path(folder);
1353 GFileEnumerator *files = NULL;
1354 if(IS_NULL_PTR(folder_file))
1355 goto end;
1356
1357 files = g_file_enumerate_children(
1358 folder_file, G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE,
1359 G_FILE_QUERY_INFO_NONE, NULL, NULL);
1360 g_object_unref(folder_file);
1361 if(IS_NULL_PTR(files))
1362 goto end;
1363
1364 // Get the file filter in use
1365 GtkFileFilter *filter = gtk_file_chooser_get_filter(chooser);
1366 if(IS_NULL_PTR(filter))
1367 {
1368 goto end;
1369 }
1370 const GtkFileFilterFlags filter_needed = gtk_file_filter_get_needed(filter);
1371
1372 GFile *file = NULL;
1373 while(g_file_enumerator_iterate(files, NULL, &file, NULL, NULL))
1374 {
1375 // g_file_enumerator_iterate returns FALSE only on errors, not on end of enumeration.
1376 // We need an ugly break here else infinite loop.
1377 if(IS_NULL_PTR(file)) break;
1378
1379 gchar *parse_name = g_file_get_parse_name(file);
1380 gchar *uri = g_file_get_uri(file);
1381 gchar *basename = g_file_get_basename(file);
1382 gchar *filepath = g_file_get_path(file);
1383 GtkFileFilterInfo filter_info = { filter_needed,
1384 parse_name,
1385 uri,
1386 parse_name, NULL };
1387
1388 const gboolean is_regular = !IS_NULL_PTR(filepath) && g_file_test(filepath, G_FILE_TEST_IS_REGULAR);
1389 const int is_path_in_lib = !IS_NULL_PTR(basename) ? _is_in_library_by_path(folder, basename) : -1;
1390 const int is_metadata_in_lib = _is_in_library_by_metadata(file);
1391 const gboolean is_in_lib = (is_path_in_lib > -1) || (is_metadata_in_lib > -1);
1392
1393 // We need to act only on files passing the file filter, aka being currently displayed on screen.
1394 // Unselecting files not displayed in the current list freezes the UI and introduces oddities.
1395 if(gtk_file_filter_filter(filter, &filter_info)
1396 && !(is_regular && !is_in_lib))
1397 {
1398 gtk_file_chooser_unselect_file(chooser, file);
1399 }
1400
1401 dt_free(parse_name);
1402 dt_free(uri);
1403 dt_free(basename);
1404 dt_free(filepath);
1405 // g_file_enumerator_iterate() returns transfer-none children owned by the enumerator.
1406 // Unref happens when the enumerator advances or is destroyed.
1407 file = NULL;
1408 }
1409
1410 end:
1411 if(!IS_NULL_PTR(files)) g_object_unref(files);
1412 dt_free(folder);
1413}
1414
1416{
1417 d->closing = TRUE;
1418
1419 if(d->selection_scan_timeout_id > 0)
1420 {
1421 g_source_remove(d->selection_scan_timeout_id);
1422 d->selection_scan_timeout_id = 0;
1423 }
1424
1425 // Disconnect callbacks that may enqueue async work while widgets are being destroyed.
1426 if(!IS_NULL_PTR(d->file_chooser))
1427 {
1428 g_signal_handlers_disconnect_by_func(G_OBJECT(d->file_chooser), G_CALLBACK(_selection_changed), d);
1429 g_signal_handlers_disconnect_by_func(G_OBJECT(d->file_chooser), G_CALLBACK(update_preview_cb), d);
1430 g_signal_handlers_disconnect_by_func(G_OBJECT(d->file_chooser), G_CALLBACK(_file_activated), GTK_DIALOG(d->dialog));
1431 g_signal_handlers_disconnect_by_func(G_OBJECT(d->file_chooser), G_CALLBACK(_update_directory), NULL);
1432 }
1433
1434 // Ensure the background recursive folder detection is finished before destroying widgets.
1435 // Reason is, if a job is still running, it might send its signal upon completion,
1436 // and then the widgets supposed to be updated in callback will be undefined (but not NULL... WTF Gtk ?)
1437 dt_pthread_mutex_lock(&d->lock);
1438 gtk_widget_destroy(d->dialog);
1439 d->dialog = NULL;
1440 d->file_chooser = NULL;
1441 d->preview = NULL;
1442 d->exif = NULL;
1443 d->grid = NULL;
1444 d->jobcode = NULL;
1445 d->help_string = NULL;
1446 d->test_path = NULL;
1447 d->selected_files = NULL;
1448 for(int k = 0; k < EXIF_LAST_FIELD; k++) d->exif_info[k] = NULL;
1449 dt_pthread_mutex_unlock(&d->lock);
1450}
1451
1453{
1454 dt_lib_import_t *d = malloc(sizeof(dt_lib_import_t));
1455 d->closing = FALSE;
1456 d->selection_scan_timeout_id = 0;
1457 dt_pthread_mutex_init(&d->lock, NULL);
1458 d->path_file = NULL;
1459 d->scan_state = calloc(1, sizeof(dt_import_scan_state_t));
1460 dt_pthread_mutex_init(&d->scan_state->lock, NULL);
1461 d->scan_state->generation = 0;
1462 d->scan_state->refcount = 1;
1463 d->scan_state->closing = FALSE;
1464
1465 return d;
1466}
1467
1469{
1470 // Teardown can be entered from multiple control paths. Ensure no pending global signal
1471 // callback can still target this module state after memory is released.
1474
1475 if(!IS_NULL_PTR(d->scan_state))
1476 {
1477 gboolean release = FALSE;
1478 dt_pthread_mutex_lock(&d->scan_state->lock);
1479 d->scan_state->closing = TRUE;
1480 d->scan_state->generation++;
1481 if(d->scan_state->refcount > 0) d->scan_state->refcount--;
1482 release = (d->scan_state->refcount == 0);
1483 dt_pthread_mutex_unlock(&d->scan_state->lock);
1484 if(release)
1485 {
1486 dt_pthread_mutex_destroy(&d->scan_state->lock);
1487 dt_free(d->scan_state);
1488 }
1489 d->scan_state = NULL;
1490 }
1491
1493 dt_free(d->path_file);
1494 dt_free(d);
1495}
1496
1498{
1499 dt_lib_import_t *d = _init();
1500 gui_init(d);
1501}
1502
1503static dt_import_t * dt_import_init(dt_lib_import_t *d, const uint32_t generation)
1504{
1505 dt_import_t *import = g_malloc(sizeof(dt_import_t));
1506 import->generation = generation;
1507 import->files = NULL;
1508 import->elements = 0;
1509 dt_pthread_mutex_init(&import->lock, NULL);
1510 import->scan_state = d->scan_state;
1511 dt_pthread_mutex_lock(&import->scan_state->lock);
1512 import->scan_state->refcount++;
1513 dt_pthread_mutex_unlock(&import->scan_state->lock);
1514
1515 dt_pthread_mutex_lock(&import->lock);
1516
1517 // selection is owned here and will need to be freed.
1518 import->selection = gtk_file_chooser_get_uris(GTK_FILE_CHOOSER(d->file_chooser));
1519
1520 dt_pthread_mutex_unlock(&import->lock);
1521
1522 return import;
1523}
1524
1525static void dt_import_cleanup(void *data)
1526{
1527 // dt_import_t owns the recursive selection list for the whole detection job lifetime.
1528 // Signal receivers may inspect it, but must not release it.
1529 dt_import_t *import = (dt_import_t *)data;
1530 g_list_free_full(import->files, dt_free_gpointer);
1531 import->files = NULL;
1532 g_slist_free_full(import->selection, dt_free_gpointer);
1533 import->selection = NULL;
1534 if(!IS_NULL_PTR(import->scan_state))
1535 {
1536 gboolean release = FALSE;
1537 dt_pthread_mutex_lock(&import->scan_state->lock);
1538 if(import->scan_state->refcount > 0) import->scan_state->refcount--;
1539 release = (import->scan_state->refcount == 0);
1540 dt_pthread_mutex_unlock(&import->scan_state->lock);
1541 if(release)
1542 {
1543 dt_pthread_mutex_destroy(&import->scan_state->lock);
1544 dt_free(import->scan_state);
1545 }
1546 import->scan_state = NULL;
1547 }
1548 dt_pthread_mutex_destroy(&import->lock);
1549 dt_free(import);
1550}
1551
1552// clang-format off
1553// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1554// vim: shiftwidth=2 expandtab tabstop=2 cindent
1555// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1556// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
const char * extension(dt_imageio_module_data_t *data)
Definition avif.c:645
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
dt_colorspaces_color_profile_type_t
Definition colorspaces.h:81
static dt_aligned_pixel_t rgb
static const int row
int32_t dt_image_get_id(int32_t film_id, const gchar *filename)
gboolean dt_image_is_hdr(const dt_image_t *img)
void dt_image_init(dt_image_t *img)
void dt_image_film_roll_directory(const dt_image_t *img, char *pathname, size_t pathname_len)
int dt_metadata_already_imported(const char *filename, const char *datetime)
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_control_log(const char *msg,...)
Definition control.c:777
darktable_t darktable
Definition darktable.c:183
gboolean dt_supported_image(const gchar *filename)
check if file is a supported image
Definition darktable.c:350
#define dt_free_align(ptr)
Definition darktable.h:503
#define UNKNOWN_IMAGE
Definition darktable.h:194
#define dt_pixelpipe_cache_alloc_align_cache(size, id)
Definition darktable.h:445
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:485
#define dt_free(ptr)
Definition darktable.h:478
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:475
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:270
#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
GDateTime * dt_string_to_datetime(const char *string)
Definition datetime.c:307
gboolean dt_datetime_img_to_local(char *local, const size_t local_size, const dt_image_t *img, const gboolean msec)
Definition datetime.c:179
gboolean dt_datetime_unix_to_exif(char *exif, const size_t exif_size, const time_t *unix)
Definition datetime.c:198
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
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:384
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Definition dtpthread.h:369
static int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:389
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:374
int dt_exif_read(dt_image_t *img, const char *path)
Definition exif.cc:1752
gchar * dt_loc_get_home_dir(const gchar *user)
int32_t dt_film_get_id(const char *folder)
Definition film.c:111
GtkBox * attach_help_popover(GtkWidget *widget, const char *label)
Definition gtk.c:3442
void dt_gui_refocus_center()
Definition gtk.c:3482
GtkBox * attach_popover(GtkWidget *widget, const char *icon, GtkWidget *content)
Definition gtk.c:3411
void dt_accels_disconnect_on_text_input(GtkWidget *widget)
Disconnects accels when a text or search entry gets the focus, and reconnects them when it looses it....
Definition gtk.c:3473
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:133
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
void dt_gtkentry_setup_completion(GtkEntry *entry, const dt_gtkentry_completion_spec *compl_list, const char *trigger_char)
Definition gtkentry.c:173
const dt_gtkentry_completion_spec * dt_gtkentry_get_default_path_compl_list()
Definition gtkentry.c:197
@ ORIENTATION_ROTATE_CCW_90_DEG
Definition image.h:215
@ ORIENTATION_ROTATE_CW_90_DEG
Definition image.h:216
@ ORIENTATION_ROTATE_180_DEG
Definition image.h:213
@ DT_IMAGEIO_OK
Definition image.h:79
dt_image_flags_t
Definition image.h:91
@ DT_IMAGE_RAW
Definition image.h:111
@ DT_IMAGE_HDR
Definition image.h:113
void dt_image_cache_read_release(dt_image_cache_t *cache, const dt_image_t *img)
dt_image_t * dt_image_cache_get(dt_image_cache_t *cache, const int32_t imgid, char mode)
dt_imageio_retval_t dt_imageio_open(dt_image_t *img, const char *filename, dt_mipmap_buffer_t *buf)
Definition imageio.c:1279
dt_image_flags_t dt_imageio_get_type_from_extension(const char *extension)
Map Exiv2 preview MIME types to decoder format identifiers.
Definition imageio.c:181
int dt_imageio_large_thumbnail(const char *filename, uint8_t **buffer, int32_t *th_width, int32_t *th_height, dt_colorspaces_color_profile_type_t *color_space, const int width, const int height)
Load the thumbnail embedded into a RAW file having at least the size MAX(width, height) x MAX(width,...
Definition imageio.c:209
static void _resize_dialog(GtkWidget *widget)
Definition import.c:489
static void _process_file_list(gpointer instance, GList *files, int elements, gboolean finished, gpointer user_data)
Import a list of file by copying them or not, and adding them to database.
Definition import.c:975
static void dt_import_cleanup(void *import)
Definition import.c:1525
static void _datetime_changed_callback(GtkEntry *entry, dt_lib_import_t *d)
Definition import.c:926
static void _do_select_all_clicked(GtkWidget *widget, dt_lib_import_t *d)
Definition import.c:473
static int32_t _get_selected_files_job(dt_job_t *job)
Definition import.c:297
void _dt_check_basedir()
Definition import.c:449
static void _update_date(GtkCalendar *calendar, GtkWidget *entry)
Definition import.c:910
void dt_control_get_selected_files(dt_lib_import_t *d, gboolean destroy_window)
Definition import.c:302
static void _filename_changed(GtkWidget *widget, dt_lib_import_t *d)
Definition import.c:904
static void _selection_changed(GtkWidget *filechooser, dt_lib_import_t *d)
Definition import.c:845
static int _is_in_library_by_path(const gchar *folder, const char *filename)
Definition import.c:577
static void gui_cleanup(dt_lib_import_t *d)
Definition import.c:1415
static dt_import_t * dt_import_init(dt_lib_import_t *d, const uint32_t generation)
Definition import.c:1503
static void _filelist_changed_callback(gpointer instance, GList *files, guint elements, guint finished, gpointer user_data)
Definition import.c:826
static void _gtk_label_set_and_free(GtkWidget *widget, gchar *label)
Definition import.c:166
static void _jobcode_changed(GtkFileChooserButton *widget, dt_lib_import_t *d)
Definition import.c:886
static void _copy_toggled_callback(GtkWidget *combobox, dt_lib_import_t *d)
Definition import.c:876
static gboolean _scan_still_valid(dt_import_t *const import)
Definition import.c:156
static void _file_activated(GtkFileChooser *chooser, GtkDialog *dialog)
Definition import.c:954
static void _set_test_path(dt_lib_import_t *d, dt_image_t *img)
Definition import.c:747
static void _do_select_none_clicked(GtkWidget *widget, dt_lib_import_t *d)
Definition import.c:478
static void _file_filters(GtkWidget *file_chooser)
Definition import.c:511
void dt_images_import()
Definition import.c:1497
static void _do_select_new_clicked(GtkWidget *widget, dt_lib_import_t *d)
Definition import.c:483
static GdkPixbuf * _import_get_thumbnail(const gchar *filename, const int width, const int height, const gboolean valid_exif, dt_image_t *img)
Definition import.c:332
static void _do_select_new(dt_lib_import_t *d)
Definition import.c:1342
static void _base_dir_changed(GtkFileChooserButton *self, dt_lib_import_t *d)
Definition import.c:892
static void _cleanup(dt_lib_import_t *d)
Definition import.c:1468
static dt_lib_import_t * _init()
Definition import.c:1452
static GtkWidget * _attach_aligned_grid_item(GtkWidget *grid, const int row, const int column, const char *label, const GtkAlign align, const gboolean fixed_width, const gboolean full_width)
Definition import.c:553
static int _is_in_library_by_metadata(GFile *file)
Definition import.c:584
static void gui_init(dt_lib_import_t *d)
Definition import.c:1048
static void _project_dir_changed(GtkWidget *widget, dt_lib_import_t *d)
Definition import.c:898
exif_fields_t
Definition import.c:95
@ EXIF_EXPOSURE_FIELD
Definition import.c:103
@ EXIF_INLIB_FIELD
Definition import.c:105
@ EXIF_LENS_FIELD
Definition import.c:100
@ EXIF_DATETIME_FIELD
Definition import.c:96
@ EXIF_LAST_FIELD
Definition import.c:107
@ EXIF_SEPARATOR2_FIELD
Definition import.c:102
@ EXIF_MAKER_FIELD
Definition import.c:99
@ EXIF_SEPARATOR1_FIELD
Definition import.c:97
@ EXIF_FOCAL_LENS_FIELD
Definition import.c:101
@ EXIF_MODEL_FIELD
Definition import.c:98
@ EXIF_PATH_FIELD
Definition import.c:106
@ EXIF_SEPARATOR3_FIELD
Definition import.c:104
static void _build_filter(GtkFileFilter *filter, const gchar *extension)
Definition import.c:497
static int32_t dt_get_selected_files(dt_import_t *import)
Definition import.c:259
static void _do_select_all(dt_lib_import_t *d)
Definition import.c:1337
static void _filter_document(GVfs *vfs, GFile *document, dt_import_t *import)
Definition import.c:172
void _file_chooser_response(GtkDialog *dialog, gint response_id, dt_lib_import_t *d)
Definition import.c:1012
static void _update_directory(GtkWidget *file_chooser, dt_lib_import_t *d)
Definition import.c:728
static gboolean _selection_changed_scan_trigger(gpointer user_data)
Trigger recursive import candidate detection after selection settle time.
Definition import.c:867
static void update_preview_cb(GtkFileChooser *file_chooser, gpointer userdata)
Definition import.c:610
static void _exif_text_set_and_free(dt_lib_import_t *d, exif_fields_t field, gchar *label)
Definition import.c:605
static void _recurse_selection(GSList *selection, dt_import_t *const import)
Definition import.c:229
static void _do_select_none(dt_lib_import_t *d)
Definition import.c:1332
static void _recurse_folder(GVfs *vfs, GFile *folder, dt_import_t *const import)
Definition import.c:196
static GtkWidget * _attach_grid_separator(GtkWidget *grid, const int row, const int length)
Definition import.c:569
static void _set_help_string(dt_lib_import_t *d, gboolean copy)
Definition import.c:735
gchar * dt_build_filename_from_pattern(const char *const filename, const int index, dt_image_t *img, dt_control_import_t *data)
Build a full path for a given image file, given a pattern.
Definition import_jobs.c:76
int dt_control_import(dt_control_import_t data)
Process a list of images to import with or without copying the files on an arbitrary hard-drive.
void dt_control_import_data_free(dt_control_import_t *data)
dt_job_t * dt_control_job_create(dt_job_execute_callback execute, const char *msg,...)
Definition jobs.c:135
int dt_control_add_job(dt_control_t *control, dt_job_queue_t queue_id, _dt_job_t *job)
Definition jobs.c:405
void * dt_control_job_get_params(const _dt_job_t *job)
Definition jobs.c:129
void dt_control_job_set_params(_dt_job_t *job, void *params, dt_job_destroy_callback callback)
Definition jobs.c:112
void dt_control_job_dispose(_dt_job_t *job)
Definition jobs.c:153
@ DT_JOB_QUEUE_USER_BG
Definition jobs.h:55
float *const restrict const size_t k
#define CLAMPF(a, mn, mx)
Definition math.h:89
dt_colorspaces_color_profile_type_t color_space
Definition mipmap_cache.c:5
@ DT_MIPMAP_FULL
void dt_osx_disallow_fullscreen(GtkWidget *widget)
Definition osx.mm:104
void copy(double *dest, double *source, size_t num_el)
Copy a flat buffer.
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:387
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:366
@ DT_SIGNAL_FILELIST_CHANGED
Raised when the recursive file crawler returns. no params, return :
Definition signal.h:325
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:376
struct _GtkWidget GtkWidget
Definition splash.h:29
char * dt_variables_expand(dt_variables_params_t *params, gchar *source, gboolean iterate)
void dt_variables_params_destroy(dt_variables_params_t *params)
gboolean dt_get_user_pictures_dir(const gchar *homedir, gchar *picdir, size_t picdir_size)
Gets the path to the current OS pictures directory.
void dt_variables_params_init(dt_variables_params_t **params)
const float uint32_t state[4]
static const char *const day[7]
Definition strptime.c:97
struct dt_gui_gtk_t * gui
Definition darktable.h:803
struct dt_control_signal_t * signals
Definition darktable.h:802
struct dt_image_cache_t * image_cache
Definition darktable.h:805
struct dt_control_t * control
Definition darktable.h:801
void * data
char * target_subfolder_pattern
Definition import_jobs.h:65
dt_ui_t * ui
Definition gtk.h:165
float exif_exposure
Definition image.h:285
float exif_iso
Definition image.h:288
float exif_aperture
Definition image.h:287
dt_image_orientation_t orientation
Definition image.h:284
float exif_focal_length
Definition image.h:289
char exif_maker[64]
Definition image.h:292
char exif_lens[128]
Definition image.h:294
char exif_model[64]
Definition image.h:293
uint32_t generation
Definition import.c:68
dt_pthread_mutex_t lock
Definition import.c:67
uint32_t elements
Definition import.c:85
GSList * selection
Definition import.c:76
uint32_t generation
Definition import.c:82
GList * files
Definition import.c:79
dt_pthread_mutex_t lock
Definition import.c:89
dt_import_scan_state_t * scan_state
Definition import.c:91
GtkWidget * jobcode
Definition import.c:121
GtkWidget * selected_files
Definition import.c:125
GtkWidget * grid
Definition import.c:120
char * path_file
Definition import.c:132
GtkWidget * file_chooser
Definition import.c:113
dt_import_scan_state_t * scan_state
Definition import.c:134
gboolean closing
Definition import.c:128
GtkWidget * dialog
Definition import.c:119
GtkWidget * datetime
Definition import.c:118
GtkWidget * preview
Definition import.c:114
guint selection_scan_timeout_id
Definition import.c:126
GtkWidget * test_path
Definition import.c:124
GtkWidget * help_string
Definition import.c:123
GtkWidget * exif
Definition import.c:115
dt_pthread_mutex_t lock
Definition import.c:130
GtkWidget * exif_info[EXIF_LAST_FIELD]
Definition import.c:117
dt_cache_entry_t * cache_entry
dt_mipmap_size_t size
gchar * dt_util_path_get_dirname(const gchar *filename)
Definition utility.c:774
char * dt_util_format_exposure(const float exposuretime)
Definition utility.c:865