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 dt_control_import(data);
999 }
1000 else
1001 dt_control_log(_("No files to import. Check your selection."));
1002
1004 gui_cleanup(d);
1005 _cleanup(d);
1006
1007 // Re-allocate focus to center widget
1009}
1010
1011void _file_chooser_response(GtkDialog *dialog, gint response_id, dt_lib_import_t *d)
1012{
1013 // Stop capturing the filelist changes for the in-popup label file counter.
1015
1016 switch(response_id)
1017 {
1018 case GTK_RESPONSE_ACCEPT:
1019 {
1020 if(d->selection_scan_timeout_id > 0)
1021 {
1022 g_source_remove(d->selection_scan_timeout_id);
1023 d->selection_scan_timeout_id = 0;
1024 }
1025
1026 // The next file list change will now only fire the importer job
1028
1029 // It would be swell if we could just re-use the file list computed on "select" callback.
1030 // However, it depends on the file filter used, and we can't refresh the list when
1031 // filter is changed (no callback to connect to).
1032 // To be safe, we need to start again here, from scratch.
1034
1035 // TODO: print "pending" message on modal window
1036 break;
1037 }
1038 case GTK_RESPONSE_CANCEL:
1039 default:
1040 gui_cleanup(d);
1041 _cleanup(d);
1042 break;
1043 }
1044}
1045
1046
1048{
1050
1051 d->dialog = gtk_dialog_new_with_buttons
1052 ( _("Ansel - Open pictures"), NULL, GTK_DIALOG_DESTROY_WITH_PARENT,
1053 _("Cancel"), GTK_RESPONSE_CANCEL,
1054 _("Import"), GTK_RESPONSE_ACCEPT,
1055 NULL);
1056 dt_gui_add_class(d->dialog, "dt_import_dialog");
1057
1058#ifdef GDK_WINDOWING_QUARTZ
1059// TODO: On MacOS (at least on version 13) the dialog windows doesn't behave as expected. The dialog
1060// needs to have a parent window. "set_parent_window" wasn't working, so set_transient_for is
1061// the way to go. Still the window manager isn't dealing with the dialog properly, when the dialog
1062// is shifted outside its parent. The dialog isn't visible any longer but still listed as a window
1063// of the app.
1065 gtk_window_set_position(GTK_WINDOW(d->dialog), GTK_WIN_POS_CENTER_ON_PARENT);
1066#endif
1067
1068 gtk_window_set_default_size(GTK_WINDOW(d->dialog),
1069 dt_conf_get_int("ui_last/import_dialog_width"),
1070 dt_conf_get_int("ui_last/import_dialog_height"));
1071 gtk_window_set_modal(GTK_WINDOW(d->dialog), FALSE);
1072 gtk_window_set_transient_for(GTK_WINDOW(d->dialog), GTK_WINDOW(dt_ui_main_window(darktable.gui->ui)));
1073 g_signal_connect(d->dialog, "response", G_CALLBACK(_file_chooser_response), d);
1074
1075 GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(d->dialog));
1076 g_signal_connect(d->dialog, "check-resize", G_CALLBACK(_resize_dialog), NULL);
1077
1078 /* Grid of options for copy/duplicate */
1079 d->grid = gtk_grid_new();
1080 GtkGrid *grid = GTK_GRID(d->grid);
1081 gtk_grid_set_column_spacing(grid, DT_GUI_BOX_SPACING / 2.);
1082 gtk_grid_set_row_spacing(grid, DT_GUI_BOX_SPACING / 2.);
1083 gtk_grid_set_column_homogeneous(grid, FALSE);
1084 gtk_grid_set_row_homogeneous(grid, FALSE);
1085
1086 /* BOTTOM PANEL */
1087 GtkWidget *rbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1088 gtk_box_pack_start(GTK_BOX(content), rbox, TRUE, TRUE, 0);
1089
1090 // File browser
1091 d->file_chooser = gtk_file_chooser_widget_new(GTK_FILE_CHOOSER_ACTION_OPEN);
1092 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(d->file_chooser), TRUE);
1093 gtk_file_chooser_set_use_preview_label(GTK_FILE_CHOOSER(d->file_chooser), FALSE);
1094 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(d->file_chooser),
1095 dt_conf_get_string_const("ui_last/import_last_directory"));
1096 gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(d->file_chooser), FALSE);
1097 gtk_box_pack_start(GTK_BOX(rbox), d->file_chooser, TRUE, TRUE, 0);
1098 g_signal_connect(G_OBJECT(d->file_chooser), "current-folder-changed", G_CALLBACK(_update_directory), NULL);
1099 g_signal_connect(G_OBJECT(d->file_chooser), "file-activated", G_CALLBACK(_file_activated), GTK_DIALOG(d->dialog));
1100 g_signal_connect(G_OBJECT(d->file_chooser), "selection-changed", G_CALLBACK(_selection_changed), d);
1101 g_signal_connect(G_OBJECT(d->file_chooser), "update-preview", G_CALLBACK(update_preview_cb), d);
1102
1103 // file extension filters
1104 _file_filters(d->file_chooser);
1105
1106 // File browser toolbox (extra widgets)
1107 GtkWidget *toolbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1108 gtk_widget_set_halign(toolbox, GTK_ALIGN_END);
1109
1110 GtkWidget *select_all = gtk_button_new_with_label(_("Select all"));
1111 gtk_box_pack_start(GTK_BOX(toolbox), select_all, FALSE, FALSE, 0);
1112 g_signal_connect(select_all, "clicked", G_CALLBACK(_do_select_all_clicked), d);
1113
1114 GtkWidget *select_none = gtk_button_new_with_label(_("Select none"));
1115 gtk_box_pack_start(GTK_BOX(toolbox), select_none, FALSE, FALSE, 0);
1116 g_signal_connect(select_none, "clicked", G_CALLBACK(_do_select_none_clicked), d);
1117
1118 GtkWidget *select_new = gtk_button_new_with_label(_("Select new"));
1119 gtk_box_pack_start(GTK_BOX(toolbox), select_new, FALSE, FALSE, 0);
1120 g_signal_connect(select_new, "clicked", G_CALLBACK(_do_select_new_clicked), d);
1121 gtk_widget_set_tooltip_text(select_new,
1122 _("Selecting new files targets pictures that have never been added to the library. "
1123 "The lookup is done by searching for the original filename and date/time. "
1124 "It can detect files existing at another path, under a different name. "
1125 "False-positive can arise if two pictures have been taken at the same time with the same name."));
1126
1127 d->selected_files = gtk_label_new("");
1128 gtk_box_pack_start(GTK_BOX(toolbox), d->selected_files, FALSE, FALSE, 0);
1129
1130 gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(d->file_chooser), toolbox);
1131
1132 /* RIGHT PANEL */
1133 // File browser preview box
1134 // 1. Thumbnail
1135 GtkWidget *preview_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1136 d->preview = gtk_image_new();
1137 gtk_widget_set_size_request(d->preview, DT_PIXEL_APPLY_DPI(240), DT_PIXEL_APPLY_DPI(240));
1138 gtk_box_pack_start(GTK_BOX(preview_box), d->preview, TRUE, FALSE, 0);
1139
1140 // 2. Exif metadata
1141 d->exif = gtk_grid_new();
1142 gtk_grid_set_column_spacing(GTK_GRID(d->exif), DT_GUI_BOX_SPACING);
1143 _attach_aligned_grid_item(d->exif, 0, 0, _("Shot:"), GTK_ALIGN_END, FALSE, FALSE);
1144 _attach_grid_separator( d->exif, 1, 2);
1145 _attach_aligned_grid_item(d->exif, 2, 0, _("Camera:"), GTK_ALIGN_END, FALSE, FALSE);
1146 _attach_aligned_grid_item(d->exif, 3, 0, _("Brand:"), GTK_ALIGN_END, FALSE, FALSE);
1147 _attach_aligned_grid_item(d->exif, 4, 0, _("Lens:"), GTK_ALIGN_END, FALSE, FALSE);
1148 _attach_aligned_grid_item(d->exif, 5, 0, _("Focal:"), GTK_ALIGN_END, FALSE, FALSE);
1149 _attach_grid_separator( d->exif, 6, 2);
1150 // exposure trifecta
1151 _attach_grid_separator( d->exif, 8, 2);
1152
1153 GtkWidget *imported_label = gtk_label_new(_("Imported:"));
1154 GtkBox *help_box_inlib = attach_help_popover(
1155 imported_label,
1156 _("Images already in the library will not be imported again, selected or not. "
1157 "Remove them from the library first, or use the menu "
1158 "`Run \342\206\222 Resynchronize library and XMP` to update the local database from distant XMP.\n\n"
1159 "Ansel indexes images by their filename and parent folder (full path), "
1160 "not by their content. Therefore, renaming or moving images on the filesystem, "
1161 "or changing the mounting point of their external drive will make them "
1162 "look like new (unknown) images.\n\n"
1163 "If an XMP file is present alongside images, it will be imported as well, "
1164 "including the metadata and settings stored in it. If it is not what you want, "
1165 "you can reset metadata in the lighttable."));
1166 gtk_widget_set_halign(imported_label, GTK_ALIGN_END);
1167 gtk_grid_attach(GTK_GRID(d->exif), GTK_WIDGET(help_box_inlib), 0, EXIF_INLIB_FIELD, 1, 1);
1168 //_attach_aligned_grid_item(d->exif, 9, 0, _("Imported :"), GTK_ALIGN_END, FALSE, FALSE);
1169
1170 d->exif_info[EXIF_DATETIME_FIELD] = _attach_aligned_grid_item(d->exif, 0, 1, "", GTK_ALIGN_START, TRUE, FALSE);
1171 d->exif_info[EXIF_MODEL_FIELD] = _attach_aligned_grid_item(d->exif, 2, 1, "", GTK_ALIGN_START, TRUE, FALSE);
1172 d->exif_info[EXIF_MAKER_FIELD] = _attach_aligned_grid_item(d->exif, 3, 1, "", GTK_ALIGN_START, TRUE, FALSE);
1173 d->exif_info[EXIF_LENS_FIELD] = _attach_aligned_grid_item(d->exif, 4, 1, "", GTK_ALIGN_START, TRUE, FALSE);
1174 d->exif_info[EXIF_FOCAL_LENS_FIELD] = _attach_aligned_grid_item(d->exif, 5, 1, "", GTK_ALIGN_START, TRUE, FALSE);
1175 d->exif_info[EXIF_EXPOSURE_FIELD] = _attach_aligned_grid_item(d->exif, 7, 0, "", GTK_ALIGN_CENTER, TRUE, TRUE);
1176 d->exif_info[EXIF_INLIB_FIELD] = _attach_aligned_grid_item(d->exif, 9, 1, "", GTK_ALIGN_START, FALSE, TRUE);
1177 d->exif_info[EXIF_PATH_FIELD] = _attach_aligned_grid_item(d->exif, 10, 0, "", GTK_ALIGN_START, FALSE, TRUE);
1178 gtk_label_set_ellipsize(GTK_LABEL(d->exif_info[EXIF_PATH_FIELD]), PANGO_ELLIPSIZE_MIDDLE);
1179
1180 gtk_box_pack_start(GTK_BOX(preview_box), d->exif, TRUE, TRUE, 0);
1181 gtk_widget_show_all(d->exif);
1182
1183 gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(d->file_chooser), preview_box);
1184 /* BOTTOM PANEL */
1185
1186 GtkWidget *files = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1187 GtkWidget *file_handling = gtk_label_new("");
1188 gtk_label_set_markup(GTK_LABEL(file_handling), _("<b>File handling</b>"));
1189 gtk_box_pack_start(GTK_BOX(files), GTK_WIDGET(file_handling), FALSE, FALSE, 0);
1190
1191 GtkWidget *copy = gtk_combo_box_text_new();
1192 gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(copy), NULL, _("Add to library"));
1193 gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(copy), NULL, _("Copy to disk"));
1194 gtk_combo_box_set_active(GTK_COMBO_BOX(copy), dt_conf_get_bool("ui_last/import_copy"));
1195 gtk_box_pack_start(GTK_BOX(files), GTK_WIDGET(copy), FALSE, FALSE, 0);
1196 g_signal_connect(G_OBJECT(copy), "changed", G_CALLBACK(_copy_toggled_callback), (gpointer)d);
1197
1198 d->help_string = gtk_label_new("");
1199 _set_help_string(d, dt_conf_get_bool("ui_last/import_copy"));
1200 gtk_box_pack_start(GTK_BOX(files), GTK_WIDGET(d->help_string), FALSE, FALSE, 0);
1201
1202 gtk_box_pack_start(GTK_BOX(rbox), GTK_WIDGET(files), FALSE, FALSE, 0);
1203
1204 // Project date
1205 GtkWidget *calendar_label = gtk_label_new(_("Project date"));
1206 gtk_widget_set_halign(calendar_label, GTK_ALIGN_START);
1207 d->datetime = gtk_entry_new();
1209 gtk_entry_set_width_chars(GTK_ENTRY(d->datetime), 20);
1210 g_signal_connect(G_OBJECT(d->datetime), "changed", G_CALLBACK(_datetime_changed_callback), d);
1211
1212 // Date is inited as today by default
1213 GDateTime *now = g_date_time_new_now_local();
1214 gchar *now_string = g_date_time_format(now, "%F");
1215 gtk_entry_set_text(GTK_ENTRY(d->datetime), now_string);
1216 dt_free(now_string);
1217
1218 // Date chooser
1219 GtkWidget *calendar = gtk_calendar_new();
1220 // GtkCalendar uses monthes in [0:11]. Glib GDateTime returns monthes in [1:12]. Stupid.
1221 gtk_calendar_select_month(GTK_CALENDAR(calendar), g_date_time_get_month(now) - 1, g_date_time_get_year(now));
1222 const guint day = g_date_time_get_day_of_month(now);
1223 gtk_calendar_select_day(GTK_CALENDAR(calendar), day);
1224 gtk_calendar_mark_day(GTK_CALENDAR(calendar), day);
1225 GtkBox *box_calendar = attach_popover(d->datetime, "appointment-new", calendar);
1226 g_signal_connect(G_OBJECT(calendar), "day-selected", G_CALLBACK(_update_date), d->datetime);
1227
1228 // free date
1229 g_date_time_unref(now);
1230
1231 // Base directory of projects
1232 GtkWidget *jobcode = gtk_entry_new();
1234 gtk_entry_set_text(GTK_ENTRY(jobcode), dt_conf_get_string_const("ui_last/import_jobcode"));
1235 gtk_widget_set_hexpand(jobcode, TRUE);
1236 g_signal_connect(G_OBJECT(jobcode), "changed", G_CALLBACK(_jobcode_changed), d);
1237
1238 GtkWidget *jobcode_label = gtk_label_new(_("Jobcode"));
1239 gtk_widget_set_halign(jobcode_label, GTK_ALIGN_START);
1240
1241 GtkWidget *base_label = gtk_label_new(_("Base directory of all projects"));
1242 gtk_widget_set_halign(base_label, GTK_ALIGN_START);
1243
1244 GtkWidget *dir_label = gtk_label_new(_("Project directory naming pattern"));
1245 gtk_widget_set_halign(dir_label, GTK_ALIGN_START);
1246
1247 GtkWidget *file_label = gtk_label_new(_("File naming pattern"));
1248 gtk_widget_set_halign(file_label, GTK_ALIGN_START);
1249
1250 GtkWidget *base_dir
1251 = gtk_file_chooser_button_new(_("Select a base directory"), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
1252 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(base_dir),
1253 dt_conf_get_string_const("session/base_directory_pattern"));
1254 g_signal_connect(G_OBJECT(base_dir), "file-set", G_CALLBACK(_base_dir_changed), d);
1255 gtk_widget_set_hexpand(base_dir, TRUE);
1256
1257 GtkWidget *sep1 = gtk_label_new(G_DIR_SEPARATOR_S);
1258 GtkWidget *sep2 = gtk_label_new(G_DIR_SEPARATOR_S);
1259
1260 GtkWidget *project_dir = gtk_entry_new();
1262 gtk_entry_set_text(GTK_ENTRY(project_dir), dt_conf_get_string_const("session/sub_directory_pattern"));
1263 gtk_widget_set_hexpand(project_dir, TRUE);
1265 gtk_widget_set_tooltip_text(project_dir, _("Start typing `$(` to see available variables through auto-completion"));
1266 g_signal_connect(G_OBJECT(project_dir), "changed", G_CALLBACK(_project_dir_changed), d);
1267
1268 GtkWidget *file = gtk_entry_new();
1270 gtk_entry_set_text(GTK_ENTRY(file), dt_conf_get_string_const("session/filename_pattern"));
1271 gtk_widget_set_hexpand(file, TRUE);
1273 g_signal_connect(G_OBJECT(file), "changed", G_CALLBACK(_filename_changed), d);
1274
1275 GtkWidget *pattern_label = gtk_label_new(_("Pattern result"));
1276 gtk_widget_set_halign(pattern_label, GTK_ALIGN_START);
1277
1278 d->test_path = gtk_label_new(_("Choose a file to see the result..."));
1279 gtk_widget_set_halign(d->test_path, GTK_ALIGN_START);
1280 gtk_label_set_line_wrap(GTK_LABEL(d->test_path), TRUE);
1281 gtk_label_set_max_width_chars(GTK_LABEL(d->test_path), 60);
1282 _set_test_path(d, NULL);
1283
1284 /* Create the grid of import params when using duplication */
1285 int row = 0;
1286 _attach_grid_separator(GTK_WIDGET(grid), row, 5);
1287 row++;
1288
1289 // Row 0: labels for text entries
1290 gtk_grid_attach(grid, calendar_label, 0, row, 1, 1);
1291 gtk_grid_attach(grid, jobcode_label, 2, row, 1, 1);
1292 gtk_grid_attach(grid, pattern_label, 4, row, 1, 1);
1293 row++;
1294
1295 // Row 1: text entries
1296 gtk_grid_attach(grid, GTK_WIDGET(box_calendar), 0, row, 1, 1);
1297 gtk_grid_attach(grid, jobcode, 2, row, 1, 1);
1298 gtk_grid_attach(grid, d->test_path, 4, row, 1, 1);
1299 row++;
1300
1301 // Row 2: separator
1302 _attach_grid_separator(GTK_WIDGET(grid), row, 5);
1303 row++;
1304
1305 // Row 3: labels for text entries
1306 gtk_grid_attach(grid, base_label, 0, row, 1, 1);
1307 gtk_grid_attach(grid, dir_label, 2, row, 1, 1);
1308 gtk_grid_attach(grid, file_label, 4, row, 1, 1);
1309 row++;
1310
1311 // Row 4: text entries
1312 gtk_grid_attach(grid, base_dir, 0, row, 1, 1);
1313 gtk_grid_attach(grid, sep1, 1, row, 1, 1);
1314 gtk_grid_attach(grid, project_dir, 2, row, 1, 1);
1315 gtk_grid_attach(grid, sep2, 3, row, 1, 1);
1316 gtk_grid_attach(grid, file, 4, row, 1, 1);
1317 row++;
1318
1319 gtk_box_pack_start(GTK_BOX(rbox), GTK_WIDGET(grid), FALSE, FALSE, 0);
1320
1321 gtk_widget_show_all(d->dialog);
1322
1323 // Duplication parameters visible only if the option is set
1324 gtk_widget_set_visible(GTK_WIDGET(grid), dt_conf_get_bool("ui_last/import_copy"));
1325
1326 // Update the number of selected files string because Gtk forces a default selection at opening time
1328 G_CALLBACK(_filelist_changed_callback), d);
1329}
1330
1332{
1333 gtk_file_chooser_unselect_all(GTK_FILE_CHOOSER(d->file_chooser));
1334}
1335
1337{
1338 gtk_file_chooser_select_all(GTK_FILE_CHOOSER(d->file_chooser));
1339}
1340
1342{
1343 // Twisted Gtk doesn't let us select multiple files.
1344 // We need to select all then unselect what we don't want.
1346
1347 GtkFileChooser *chooser = GTK_FILE_CHOOSER(d->file_chooser);
1348 gchar *folder = gtk_file_chooser_get_current_folder(chooser);
1349 if(IS_NULL_PTR(folder)) return;
1350
1351 GFile *folder_file = g_file_new_for_path(folder);
1352 GFileEnumerator *files = NULL;
1353 if(IS_NULL_PTR(folder_file))
1354 goto end;
1355
1356 files = g_file_enumerate_children(
1357 folder_file, G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE,
1358 G_FILE_QUERY_INFO_NONE, NULL, NULL);
1359 g_object_unref(folder_file);
1360 if(IS_NULL_PTR(files))
1361 goto end;
1362
1363 // Get the file filter in use
1364 GtkFileFilter *filter = gtk_file_chooser_get_filter(chooser);
1365 if(IS_NULL_PTR(filter))
1366 {
1367 goto end;
1368 }
1369 const GtkFileFilterFlags filter_needed = gtk_file_filter_get_needed(filter);
1370
1371 GFile *file = NULL;
1372 while(g_file_enumerator_iterate(files, NULL, &file, NULL, NULL))
1373 {
1374 // g_file_enumerator_iterate returns FALSE only on errors, not on end of enumeration.
1375 // We need an ugly break here else infinite loop.
1376 if(IS_NULL_PTR(file)) break;
1377
1378 gchar *parse_name = g_file_get_parse_name(file);
1379 gchar *uri = g_file_get_uri(file);
1380 gchar *basename = g_file_get_basename(file);
1381 gchar *filepath = g_file_get_path(file);
1382 GtkFileFilterInfo filter_info = { filter_needed,
1383 parse_name,
1384 uri,
1385 parse_name, NULL };
1386
1387 const gboolean is_regular = !IS_NULL_PTR(filepath) && g_file_test(filepath, G_FILE_TEST_IS_REGULAR);
1388 const int is_path_in_lib = !IS_NULL_PTR(basename) ? _is_in_library_by_path(folder, basename) : -1;
1389 const int is_metadata_in_lib = _is_in_library_by_metadata(file);
1390 const gboolean is_in_lib = (is_path_in_lib > -1) || (is_metadata_in_lib > -1);
1391
1392 // We need to act only on files passing the file filter, aka being currently displayed on screen.
1393 // Unselecting files not displayed in the current list freezes the UI and introduces oddities.
1394 if(gtk_file_filter_filter(filter, &filter_info)
1395 && !(is_regular && !is_in_lib))
1396 {
1397 gtk_file_chooser_unselect_file(chooser, file);
1398 }
1399
1400 dt_free(parse_name);
1401 dt_free(uri);
1402 dt_free(basename);
1403 dt_free(filepath);
1404 // g_file_enumerator_iterate() returns transfer-none children owned by the enumerator.
1405 // Unref happens when the enumerator advances or is destroyed.
1406 file = NULL;
1407 }
1408
1409 end:
1410 if(!IS_NULL_PTR(files)) g_object_unref(files);
1411 dt_free(folder);
1412}
1413
1415{
1416 d->closing = TRUE;
1417
1418 if(d->selection_scan_timeout_id > 0)
1419 {
1420 g_source_remove(d->selection_scan_timeout_id);
1421 d->selection_scan_timeout_id = 0;
1422 }
1423
1424 // Disconnect callbacks that may enqueue async work while widgets are being destroyed.
1425 if(!IS_NULL_PTR(d->file_chooser))
1426 {
1427 g_signal_handlers_disconnect_by_func(G_OBJECT(d->file_chooser), G_CALLBACK(_selection_changed), d);
1428 g_signal_handlers_disconnect_by_func(G_OBJECT(d->file_chooser), G_CALLBACK(update_preview_cb), d);
1429 g_signal_handlers_disconnect_by_func(G_OBJECT(d->file_chooser), G_CALLBACK(_file_activated), GTK_DIALOG(d->dialog));
1430 g_signal_handlers_disconnect_by_func(G_OBJECT(d->file_chooser), G_CALLBACK(_update_directory), NULL);
1431 }
1432
1433 // Ensure the background recursive folder detection is finished before destroying widgets.
1434 // Reason is, if a job is still running, it might send its signal upon completion,
1435 // and then the widgets supposed to be updated in callback will be undefined (but not NULL... WTF Gtk ?)
1436 dt_pthread_mutex_lock(&d->lock);
1437 gtk_widget_destroy(d->dialog);
1438 d->dialog = NULL;
1439 d->file_chooser = NULL;
1440 d->preview = NULL;
1441 d->exif = NULL;
1442 d->grid = NULL;
1443 d->jobcode = NULL;
1444 d->help_string = NULL;
1445 d->test_path = NULL;
1446 d->selected_files = NULL;
1447 for(int k = 0; k < EXIF_LAST_FIELD; k++) d->exif_info[k] = NULL;
1448 dt_pthread_mutex_unlock(&d->lock);
1449}
1450
1452{
1453 dt_lib_import_t *d = malloc(sizeof(dt_lib_import_t));
1454 d->closing = FALSE;
1455 d->selection_scan_timeout_id = 0;
1456 dt_pthread_mutex_init(&d->lock, NULL);
1457 d->path_file = NULL;
1458 d->scan_state = calloc(1, sizeof(dt_import_scan_state_t));
1459 dt_pthread_mutex_init(&d->scan_state->lock, NULL);
1460 d->scan_state->generation = 0;
1461 d->scan_state->refcount = 1;
1462 d->scan_state->closing = FALSE;
1463
1464 return d;
1465}
1466
1468{
1469 // Teardown can be entered from multiple control paths. Ensure no pending global signal
1470 // callback can still target this module state after memory is released.
1473
1474 if(!IS_NULL_PTR(d->scan_state))
1475 {
1476 gboolean release = FALSE;
1477 dt_pthread_mutex_lock(&d->scan_state->lock);
1478 d->scan_state->closing = TRUE;
1479 d->scan_state->generation++;
1480 if(d->scan_state->refcount > 0) d->scan_state->refcount--;
1481 release = (d->scan_state->refcount == 0);
1482 dt_pthread_mutex_unlock(&d->scan_state->lock);
1483 if(release)
1484 {
1485 dt_pthread_mutex_destroy(&d->scan_state->lock);
1486 dt_free(d->scan_state);
1487 }
1488 d->scan_state = NULL;
1489 }
1490
1492 dt_free(d->path_file);
1493 dt_free(d);
1494}
1495
1497{
1498 dt_lib_import_t *d = _init();
1499 gui_init(d);
1500}
1501
1502static dt_import_t * dt_import_init(dt_lib_import_t *d, const uint32_t generation)
1503{
1504 dt_import_t *import = g_malloc(sizeof(dt_import_t));
1505 import->generation = generation;
1506 import->files = NULL;
1507 import->elements = 0;
1508 dt_pthread_mutex_init(&import->lock, NULL);
1509 import->scan_state = d->scan_state;
1510 dt_pthread_mutex_lock(&import->scan_state->lock);
1511 import->scan_state->refcount++;
1512 dt_pthread_mutex_unlock(&import->scan_state->lock);
1513
1514 dt_pthread_mutex_lock(&import->lock);
1515
1516 // selection is owned here and will need to be freed.
1517 import->selection = gtk_file_chooser_get_uris(GTK_FILE_CHOOSER(d->file_chooser));
1518
1519 dt_pthread_mutex_unlock(&import->lock);
1520
1521 return import;
1522}
1523
1524static void dt_import_cleanup(void *data)
1525{
1526 // dt_import_t owns the recursive selection list for the whole detection job lifetime.
1527 // Signal receivers may inspect it, but must not release it.
1528 dt_import_t *import = (dt_import_t *)data;
1529 g_list_free_full(import->files, dt_free_gpointer);
1530 import->files = NULL;
1531 g_slist_free_full(import->selection, dt_free_gpointer);
1532 import->selection = NULL;
1533 if(!IS_NULL_PTR(import->scan_state))
1534 {
1535 gboolean release = FALSE;
1536 dt_pthread_mutex_lock(&import->scan_state->lock);
1537 if(import->scan_state->refcount > 0) import->scan_state->refcount--;
1538 release = (import->scan_state->refcount == 0);
1539 dt_pthread_mutex_unlock(&import->scan_state->lock);
1540 if(release)
1541 {
1542 dt_pthread_mutex_destroy(&import->scan_state->lock);
1543 dt_free(import->scan_state);
1544 }
1545 import->scan_state = NULL;
1546 }
1547 dt_pthread_mutex_destroy(&import->lock);
1548 dt_free(import);
1549}
1550
1551// clang-format off
1552// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1553// vim: shiftwidth=2 expandtab tabstop=2 cindent
1554// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1555// 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:761
darktable_t darktable
Definition darktable.c:181
gboolean dt_supported_image(const gchar *filename)
check if file is a supported image
Definition darktable.c:312
#define dt_free_align(ptr)
Definition darktable.h:481
#define UNKNOWN_IMAGE
Definition darktable.h:182
#define dt_pixelpipe_cache_alloc_align_cache(size, id)
Definition darktable.h:433
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:463
#define dt_free(ptr)
Definition darktable.h:456
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:453
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
#define PATH_MAX
Definition darktable.h:1062
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
GDateTime * dt_string_to_datetime(const char *string)
Definition datetime.c:300
gboolean dt_datetime_img_to_local(char *local, const size_t local_size, const dt_image_t *img, const gboolean msec)
Definition datetime.c:172
gboolean dt_datetime_unix_to_exif(char *exif, const size_t exif_size, const time_t *unix)
Definition datetime.c:191
gboolean dt_datetime_entry_to_exif(char *exif, const size_t exif_size, const char *entry)
Definition datetime.c:312
#define DT_DATETIME_LENGTH
Definition datetime.h:37
#define DT_DATETIME_EXIF_LENGTH
Definition datetime.h:38
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:374
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Definition dtpthread.h:359
static int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:379
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:364
int dt_exif_read(dt_image_t *img, const char *path)
Definition exif.cc:1753
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:3194
void dt_gui_refocus_center()
Definition gtk.c:3234
GtkBox * attach_popover(GtkWidget *widget, const char *icon, GtkWidget *content)
Definition gtk.c:3164
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:3225
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:1235
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:180
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:208
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:1524
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:1414
static dt_import_t * dt_import_init(dt_lib_import_t *d, const uint32_t generation)
Definition import.c:1502
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:1496
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:1341
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:1467
static dt_lib_import_t * _init()
Definition import.c:1451
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:1047
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:1336
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:1011
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:1331
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:74
void 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:368
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:347
@ DT_SIGNAL_FILELIST_CHANGED
Raised when the recursive file crawler returns. no params, return :
Definition signal.h:316
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:357
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:775
struct dt_control_signal_t * signals
Definition darktable.h:774
struct dt_image_cache_t * image_cache
Definition darktable.h:777
struct dt_control_t * control
Definition darktable.h:773
void * data
char * target_subfolder_pattern
Definition import_jobs.h:43
dt_ui_t * ui
Definition gtk.h:164
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