Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
geotagging.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2012 Pascal de Bruijn.
4 Copyright (C) 2012-2018 Tobias Ellinghaus.
5 Copyright (C) 2013 Henrik Andersson.
6 Copyright (C) 2013 johannes hanika.
7 Copyright (C) 2013-2016, 2021 Roman Lebedev.
8 Copyright (C) 2013 Thomas Pryds.
9 Copyright (C) 2014, 2019-2021 Pascal Obry.
10 Copyright (C) 2017, 2020 parafin.
11 Copyright (C) 2018 Maurizio Paglia.
12 Copyright (C) 2018 rawfiner.
13 Copyright (C) 2019, 2022-2023, 2025 Aurélien PIERRE.
14 Copyright (C) 2019 jakubfi.
15 Copyright (C) 2020-2022 Diederik Ter Rahe.
16 Copyright (C) 2020 Hubert Kowalski.
17 Copyright (C) 2020 Marco.
18 Copyright (C) 2021-2022 Aldric Renaudin.
19 Copyright (C) 2021 Harald.
20 Copyright (C) 2021 lhietal.
21 Copyright (C) 2021 luzpaz.
22 Copyright (C) 2021 Marco Carrarini.
23 Copyright (C) 2021-2022 Philippe Weyland.
24 Copyright (C) 2021 Ralf Brown.
25 Copyright (C) 2021-2022 Victor Forsiuk.
26 Copyright (C) 2022 Martin Bařinka.
27
28 darktable is free software: you can redistribute it and/or modify
29 it under the terms of the GNU General Public License as published by
30 the Free Software Foundation, either version 3 of the License, or
31 (at your option) any later version.
32
33 darktable is distributed in the hope that it will be useful,
34 but WITHOUT ANY WARRANTY; without even the implied warranty of
35 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36 GNU General Public License for more details.
37
38 You should have received a copy of the GNU General Public License
39 along with darktable. If not, see <http://www.gnu.org/licenses/>.
40*/
41#include "control/control.h"
42#include "common/paths.h" // DT_PATH_MAX
44#include "common/act_on.h"
45#include "control/settings.h"
48#include "libs/lib.h"
49#include "widgets/gdkkeys.h"
50#include "system/macros.h"
53#include "caches/image_cache.h"
54#include "common/selection.h"
55#include "metadata/gpx.h"
56#include "metadata/geo.h"
57#include "common/datetime.h"
58#include "common/conf.h"
59#include "widgets/button.h"
60#include "control/jobs.h"
61
62#include "libs/lib_api.h"
63#ifdef HAVE_MAP
64#include "views/view.h"
65#endif
66#ifdef GDK_WINDOWING_QUARTZ
67#include "osx/osx.h"
68#endif
69
70#include <gdk/gdkkeysyms.h>
71#include <glib/gstdio.h>
72#include <sqlite3.h>
73#include "gui/application.h"
74#include "gui/window_manager.h"
76#include "widgets/label.h"
77#include "widgets/scroll_wrap.h"
78#include "widgets/container.h"
79
80DT_MODULE(1)
81
82typedef struct tz_tuple_t
83{
84 char *name, *display;
86
87#define DT_GEOTAG_PARTS_NB 7
88
94
95#ifdef HAVE_MAP
101
106
108{
109 DT_GEO_TRACKS_ACTIVE = 0, // active / deactivated track
110 DT_GEO_TRACKS_DATETIME, // displayed start datetime
113 DT_GEO_TRACKS_SEGID, // id track segment
114 DT_GEO_TRACKS_TOOLTIP, // datetime details
117
118#endif
119
155
156typedef struct dt_sel_img_t
157{
158 int32_t imgid;
159 uint32_t segid;
161 gboolean counted;
163 GObject *image;
165
166static void _datetime_entry_changed(GtkWidget *entry, dt_lib_module_t *self);
168
169static void free_tz_tuple(gpointer data)
170{
171 tz_tuple_t *tz_tuple = (tz_tuple_t *)data;
172 dt_free(tz_tuple->display);
173#ifdef _WIN32
174 dt_free(tz_tuple->name); // on non-Windows both point to the same string
175#endif
176 dt_free(tz_tuple);
177}
178
179const char *name(struct dt_lib_module_t *self)
180{
181 return _("Datetime and GPS");
182}
183
184const char **views(dt_lib_module_t *self)
185{
186#ifdef HAVE_MAP
187 static const char *v[] = {"lighttable", "map", "studio_capture", NULL};
188#else
189 static const char *v[] = {"lighttable", "studio_capture", NULL};
190#endif
191 return v;
192}
193
195{
197}
198
200{
201 return 2;
202}
203
204// modify the datetime_taken field in the db/cache of selected images
206{
208 if(d->offset)
209 dt_control_datetime(d->offset, NULL, NULL);
210}
211
212// modify the datetime_taken field in the db/cache of selected images
214{
216 if(d->datetime)
217 {
218 char dt[DT_DATETIME_LENGTH];
219 dt_datetime_gdatetime_to_exif(dt, sizeof(dt), d->datetime);
220 dt_control_datetime(0, dt, NULL);
221 }
222}
223
224static gboolean _lib_geotagging_filter_gpx(const GtkFileFilterInfo *filter_info, gpointer data)
225{
226 if(!g_ascii_strcasecmp(filter_info->mime_type, "application/gpx+xml")) return TRUE;
227
228 const gchar *filename = filter_info->filename;
229 const char *cc = filename + strlen(filename);
230 for(; *cc != '.' && cc > filename; cc--)
231 ;
232
233 if(!g_ascii_strcasecmp(cc, ".gpx")) return TRUE;
234
235 return FALSE;
236}
237
238static GtkWidget *_set_up_label(const char *name, const int align, GtkWidget *grid,
239 const int col, const int line, const int ellipsize)
240{
241 GtkWidget *label = gtk_label_new(name);
242 gtk_label_set_ellipsize(GTK_LABEL(label), ellipsize);
243 if(ellipsize != PANGO_ELLIPSIZE_NONE)
244 gtk_widget_set_visible(label, TRUE);
245 gtk_widget_set_halign(label, align);
246 gtk_widget_set_hexpand(label, TRUE);
247 gtk_grid_attach(GTK_GRID(grid), label, col, line, 1, 1);
248 return label;
249}
250
251static gchar *_utc_timeval_to_localtime_text(GDateTime *utc_dt, GTimeZone *tz_camera,
252 const gboolean full)
253{
254 GDateTime *local_dt = g_date_time_to_timezone(utc_dt, tz_camera);
255 gchar *dts = g_date_time_format(local_dt, full ? "%Y:%m:%d %H:%M:%S" : "%H:%M:%S");
256 g_date_time_unref(local_dt);
257 return dts;
258}
259
260static GDateTime *_localtime_text_to_utc_timeval(const char *date_time,
261 GTimeZone *tz_camera, GTimeZone *tz_utc,
262 GTimeSpan offset)
263{
264 GDateTime *exif_time = dt_datetime_exif_to_gdatetime(date_time, tz_camera);
265 GDateTime *dt_offset = g_date_time_add(exif_time, offset);
266 GDateTime *utc_time = g_date_time_to_timezone(dt_offset, tz_utc);
267
268 g_date_time_unref(exif_time);
269 g_date_time_unref(dt_offset);
270 return utc_time;
271}
272
274 dt_lib_module_t *self)
275{
277
278 int nb_imgs = 0;
279 for(GList *i = d->imgs; i; i = g_list_next(i))
280 {
281 dt_sel_img_t *im = (dt_sel_img_t *)i->data;
282 if(im->segid == -1)
283 {
284 GDateTime *dt = _localtime_text_to_utc_timeval(im->dt, d->tz_camera, dt_datetime_utc_tz(), d->offset);
285 if((g_date_time_compare(dt, t->start_dt) >= 0
286 && g_date_time_compare(dt, t->end_dt) <= 0)
287 || (n && g_date_time_compare(dt, t->end_dt) >= 0
288 && g_date_time_compare(dt, n->start_dt) <= 0))
289 {
290 nb_imgs++;
291 im->segid = t->id;
292 }
293 g_date_time_unref(dt);
294 }
295 }
296 return nb_imgs;
297}
298
299#ifdef HAVE_MAP
300static gchar *_utc_timeval_to_utc_text(GDateTime *utc_dt, const gboolean full)
301{
302 gchar *dts = g_date_time_format(utc_dt, full ? "%Y:%m:%d %H:%M:%S" : "%H:%M:%S");
303 return dts;
304}
305
306static gchar *_datetime_tooltip(GDateTime *start, GDateTime *end, GTimeZone *tz)
307{
308 gchar *dtsl = _utc_timeval_to_localtime_text(start, tz, FALSE);
309 gchar *dtel = _utc_timeval_to_localtime_text(end, tz, FALSE);
310 gchar *dtsu = _utc_timeval_to_utc_text(start, FALSE);
311 gchar *dteu = _utc_timeval_to_utc_text(end, FALSE);
312 gchar *res = g_strdup_printf("%s -> %s LT\n%s -> %s UTC", dtsl, dtel, dtsu, dteu);
313 dt_free(dtsl);
314 dt_free(dtel);
315 dt_free(dtsu);
316 dt_free(dteu);
317 return res;
318}
319
321{
323 for(GList *i = d->imgs; i; i = g_list_next(i))
324 {
325 dt_sel_img_t *im = (dt_sel_img_t *)i->data;
326 if(im->image)
327 {
329 im->image = NULL;
330 }
331 }
332}
333
334static void _refresh_images_displayed_on_track(const int segid, const gboolean active, dt_lib_module_t *self)
335{
337 for(GList *i = d->imgs; i; i = g_list_next(i))
338 {
339 dt_sel_img_t *im = (dt_sel_img_t *)i->data;
340 if(im->segid == segid && active)
341 {
342 GDateTime *dt = _localtime_text_to_utc_timeval(im->dt, d->tz_camera, dt_datetime_utc_tz(), d->offset);
343 if(!dt_gpx_get_location(d->map.gpx, dt, &im->gl))
344 im->gl.latitude = NAN;
345 g_date_time_unref(dt);
346 }
347 else if(im->segid == segid && !active && im->image)
348 {
350 im->image = NULL;
351 im->gl.latitude = NAN;
352 }
353 }
354 int count = 0;
355 for(GList *i = d->imgs; active && i; i = g_list_next(i))
356 {
357 dt_sel_img_t *im = (dt_sel_img_t *)i->data;
358 if(im->segid == segid && !isnan(im->gl.latitude))
359 {
360 count++;
361 dt_sel_img_t *next = i->next ? (dt_sel_img_t *)i->next->data
362 : NULL;
363 if(IS_NULL_PTR(im->image) && (IS_NULL_PTR(next)
364 || !((next->gl.latitude == im->gl.latitude)
365 && (next->gl.longitude == im->gl.longitude))))
366 {
367 struct {int32_t imgid; float latitude; float longitude; int count;} p;
368 p.imgid = im->imgid;
369 p.latitude = im->gl.latitude;
370 p.longitude = im->gl.longitude;
371 p.count = count == 1 ? 0 : count;
372 GList *img = g_list_prepend(NULL, &p);
374 g_list_free(img);
375 img = NULL;
376 count = 0;
377 }
378 }
379 }
380}
381
383{
385 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->map.gpx_view));
386 GtkTreeIter iter;
387 gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
388 int nb_imgs = 0;
389 for(int segid = 0; valid && segid < d->map.nb_tracks; segid++)
390 {
391 gboolean active;
392 int nb;
393 gtk_tree_model_get(model, &iter, DT_GEO_TRACKS_ACTIVE, &active,
394 DT_GEO_TRACKS_IMAGES, &nb, -1);
395 if(active)
396 nb_imgs += nb;
397 valid = gtk_tree_model_iter_next(model, &iter);
398 }
399 d->map.nb_imgs = nb_imgs;
400 gchar *nb = g_strdup_printf("%d/%d", nb_imgs, d->nb_imgs);
401 gtk_label_set_text(GTK_LABEL(d->map.nb_imgs_label), nb);
402 dt_free(nb);
403}
404
406{
408 gtk_widget_set_sensitive(d->map.preview_button, d->map.nb_tracks);
409 GtkWidget *label = gtk_bin_get_child(GTK_BIN(d->map.apply_gpx_button));
410 gtk_label_set_text(GTK_LABEL(label), d->offset ? _("apply offset and geo-location")
411 : _("apply geo-location"));
412 gtk_widget_set_tooltip_text(d->map.apply_gpx_button,
413 d->offset ? _("apply offset and geo-location to matching images"
414 "\ndouble operation: two ctrl-Z to undo")
415 : _("apply geo-location to matching images"));
416 gtk_widget_set_sensitive(d->map.apply_gpx_button, d->map.nb_imgs);
417 gtk_widget_set_sensitive(d->map.select_button,
418 d->map.nb_imgs && d->map.nb_imgs != d->nb_imgs);
419}
420
422{
424
425 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->map.gpx_view));
426 GtkTreeIter iter;
427 int segid = 0;
428 GList *imgs = NULL;
429 gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
430 while(valid)
431 {
432 gboolean active;
433 gtk_tree_model_get(model, &iter, DT_GEO_TRACKS_ACTIVE, &active, -1);
434 if(active)
435 {
436 for(GList *i = d->imgs; i; i = g_list_next(i))
437 {
438 dt_sel_img_t *im = (dt_sel_img_t *)i->data;
439 if(im->segid == segid)
440 imgs = g_list_prepend(imgs, GINT_TO_POINTER(im->imgid));
441 }
442 }
443 valid = gtk_tree_model_iter_next(model, &iter);
444 segid++;
445 }
446 return imgs;
447}
448
450{
452 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->map.gpx_view));
453 GtkTreeIter iter;
454 const gboolean preview = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->map.preview_button));
455 gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
456 for(int segid = 0; valid && segid < d->map.nb_tracks; segid++)
457 {
458 gboolean active;
459 gtk_tree_model_get(model, &iter, DT_GEO_TRACKS_ACTIVE, &active, -1);
460 _refresh_images_displayed_on_track(segid, active && preview, self);
461 valid = gtk_tree_model_iter_next(model, &iter);
462 }
463}
464
465static gboolean _update_map_box(const guint segid, GList *pts, dt_lib_module_t *self)
466{
468 // box for this track
469 if(pts)
470 {
471 d->map.tracks->td[segid].map_box.lon1 = 180.0;
472 d->map.tracks->td[segid].map_box.lon2 = -180.0;
473 d->map.tracks->td[segid].map_box.lat1 = -90.0;
474 d->map.tracks->td[segid].map_box.lat2 = 90.0;
475 for(GList *pt = pts; pt; pt = g_list_next(pt))
476 {
477 dt_geo_map_display_point_t *p = pt->data;
478 if(p->lon < d->map.tracks->td[segid].map_box.lon1)
479 d->map.tracks->td[segid].map_box.lon1 = MAX(-180.0, p->lon);
480 if(p->lon > d->map.tracks->td[segid].map_box.lon2)
481 d->map.tracks->td[segid].map_box.lon2 = MIN(180.0, p->lon);
482 if(p->lat > d->map.tracks->td[segid].map_box.lat1)
483 d->map.tracks->td[segid].map_box.lat1 = MIN(90.0, p->lat);
484 if(p->lat < d->map.tracks->td[segid].map_box.lat2)
485 d->map.tracks->td[segid].map_box.lat2 = MAX(-90.0, p->lat);
486 }
487 }
488
489 // box for all tracks
490 float lon1 = 180.0;
491 float lon2 = -180.0;
492 float lat1 = -90.0;
493 float lat2 = 90.0;
494 for(int i = 0; i < d->map.nb_tracks; i++)
495 {
496 if(d->map.tracks->td[i].track)
497 {
498 if(d->map.tracks->td[i].map_box.lon1 < lon1)
499 lon1 = d->map.tracks->td[i].map_box.lon1;
500 if(d->map.tracks->td[i].map_box.lon2 > lon2)
501 lon2 = d->map.tracks->td[i].map_box.lon2;
502 if(d->map.tracks->td[i].map_box.lat1 > lat1)
503 lat1 = d->map.tracks->td[i].map_box.lat1;
504 if(d->map.tracks->td[i].map_box.lat2 < lat2)
505 lat2 = d->map.tracks->td[i].map_box.lat2;
506 }
507 }
508 const gboolean grow = lon1 < d->map.map_box.lon1 || lon2 > d->map.map_box.lon1 ||
509 lat1 > d->map.map_box.lat1 || lat2 < d->map.map_box.lat2;
510 d->map.map_box.lon1 = lon1;
511 d->map.map_box.lon2 = lon2;
512 d->map.map_box.lat1 = lat1;
513 d->map.map_box.lat2 = lat2;
514
515 return grow;
516}
517
519{
521 if(d->map.tracks)
522 {
523 for(int i = 0; i < d->map.nb_tracks; i++)
524 {
525 if(d->map.tracks->td[i].track)
526 {
528 d->map.tracks->td[i].track);
529 d->map.tracks->td[i].track = NULL;
530 }
531 }
532 dt_free(d->map.tracks);
533 }
534 if(d->map.gpx)
535 {
536 dt_gpx_destroy(d->map.gpx);
537 d->map.gpx = NULL;
538 }
539}
540
541GdkRGBA color[] = {(GdkRGBA){.red = 1.0, .green = 0.0, .blue = 0.0, .alpha = 0.5 },
542 (GdkRGBA){.red = 0.0, .green = 1.0, .blue = 1.0, .alpha = 0.5 },
543 (GdkRGBA){.red = 0.0, .green = 0.0, .blue = 1.0, .alpha = 0.5 },
544 (GdkRGBA){.red = 1.0, .green = 1.0, .blue = 0.0, .alpha = 0.5 },
545 (GdkRGBA){.red = 0.0, .green = 1.0, .blue = 0.0, .alpha = 0.5 },
546 (GdkRGBA){.red = 1.0, .green = 0.0, .blue = 1.0, .alpha = 0.5 }};
547
548static gboolean _refresh_display_track(const gboolean active, const int segid, dt_lib_module_t *self)
549{
551 gboolean grow = FALSE;
552 if(active)
553 {
554 GList *pts = dt_gpx_get_trkpts(d->map.gpx, segid);
555 if(!d->map.tracks->td[segid].track)
556 d->map.tracks->td[segid].track = dt_view_map_add_marker(dt_view_manager_get_global(),
557 MAP_DISPLAY_TRACK, pts);
558 osm_gps_map_track_set_color((OsmGpsMapTrack *)d->map.tracks->td[segid].track, &color[segid % 6]);
559 grow = _update_map_box(segid, pts, self);
560 g_list_free_full(pts, dt_free_gpointer);
561 pts = NULL;
562 }
563 else
564 {
565 if(d->map.tracks->td[segid].track != NULL)
567 d->map.tracks->td[segid].track);
568 d->map.tracks->td[segid].track = NULL;
569 _update_map_box(segid, NULL, self);
570 }
571 return grow;
572}
573
575{
577 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->map.gpx_view));
578 GtkTreeIter iter;
579 gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
580 gboolean grow = FALSE;
581 for(int segid = 0; valid && segid < d->map.nb_tracks; segid++)
582 {
583 gboolean active;
584 gtk_tree_model_get(model, &iter, DT_GEO_TRACKS_ACTIVE, &active, -1);
585 grow = _refresh_display_track(active, segid, self) ? TRUE : grow;
586 valid = gtk_tree_model_iter_next(model, &iter);
587 }
588
589 if(grow)
590 {
591 dt_view_map_center_on_bbox(dt_view_manager_get_global(), d->map.map_box.lon1, d->map.map_box.lat1,
592 d->map.map_box.lon2, d->map.map_box.lat2);
593 }
595}
596
597static void _track_seg_toggled(GtkCellRendererToggle *cell_renderer, gchar *path_str, dt_lib_module_t *self)
598{
600 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->map.gpx_view));
601 GtkTreeIter iter;
602 GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
603 gboolean active;
604 uint32_t segid;
605
606 gtk_tree_model_get_iter(model, &iter, path);
607 gtk_tree_model_get(model, &iter, DT_GEO_TRACKS_ACTIVE, &active,
608 DT_GEO_TRACKS_SEGID, &segid, -1);
609 gtk_list_store_set(GTK_LIST_STORE(model), &iter, DT_GEO_TRACKS_ACTIVE, !active, -1);
610 gtk_tree_path_free(path);
611
612 active = !active;
613 if(_refresh_display_track(active, segid, self))
614 {
615 dt_view_map_center_on_bbox(dt_view_manager_get_global(), d->map.map_box.lon1, d->map.map_box.lat1,
616 d->map.map_box.lon2, d->map.map_box.lat2);
617 }
618
619 const gboolean preview = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->map.preview_button));
620 _refresh_images_displayed_on_track(segid, active && preview, self);
621 _update_nb_images(self);
622 _update_buttons(self);
623}
624
625static void _all_tracks_toggled(GtkTreeViewColumn *column, dt_lib_module_t *self)
626{
628 GtkWidget *toggle = gtk_tree_view_column_get_widget(column);
629 gboolean active = !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle));
630 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), active);
631
632 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->map.gpx_view));
633 GtkTreeIter iter;
634 gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
635 gboolean grow = FALSE;
636 for(int segid = 0; valid && segid < d->map.nb_tracks; segid++)
637 {
638 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
639 DT_GEO_TRACKS_ACTIVE, active,-1);
640 grow = _refresh_display_track(active, segid, self) ? TRUE : grow;
641 valid = gtk_tree_model_iter_next(model, &iter);
642 }
643 if(active && grow)
644 {
645 dt_view_map_center_on_bbox(dt_view_manager_get_global(), d->map.map_box.lon1, d->map.map_box.lat1,
646 d->map.map_box.lon2, d->map.map_box.lat2);
647 }
649 _update_nb_images(self);
650 _update_buttons(self);
651}
652
653static void _select_images(GtkWidget *widget, dt_lib_module_t *self)
654{
655 GList *imgs = _get_images_on_active_tracks(self);
658 g_list_free(imgs);
659 imgs = NULL;
660}
661
662static void _images_preview_toggled(GtkToggleButton *button, dt_lib_module_t *self)
663{
665}
666
668{
670 if(IS_NULL_PTR(d->map.gpx)) return;
671
672 GList *trkseg = dt_gpx_get_trkseg(d->map.gpx);
674 for(GList *i = d->imgs; i; i = g_list_next(i))
675 ((dt_sel_img_t *)i->data)->segid = -1;
676
677 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->map.gpx_view));
678 GtkTreeIter iter;
679 gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
680 for(GList *ts = trkseg; ts && valid; ts = g_list_next(ts))
681 {
683 gchar *dts = _utc_timeval_to_localtime_text(t->start_dt, d->tz_camera, TRUE);
684 const int nb_imgs = _count_images_per_track(t, ts->next ? ts->next->data : NULL, self);
685 gboolean active;
686 gtk_tree_model_get(model, &iter, DT_GEO_TRACKS_ACTIVE, &active, -1);
687 gchar *tooltip = _datetime_tooltip(t->start_dt, t->end_dt, d->tz_camera);
688 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
690 DT_GEO_TRACKS_POINTS, t->nb_trkpt,
691 DT_GEO_TRACKS_IMAGES, nb_imgs,
693 -1);
694 dt_free(dts);
696 valid = gtk_tree_model_iter_next(model, &iter);
697 }
698 _update_nb_images(self);
700 _update_buttons(self);
701}
702
704{
707 d->map.gpx = dt_gpx_new(gtk_label_get_text(GTK_LABEL(d->map.gpx_file)));
708 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(d->map.gpx_view));
709 g_object_ref(model);
710 gtk_tree_view_set_model(GTK_TREE_VIEW(d->map.gpx_view), NULL);
711 gtk_list_store_clear(GTK_LIST_STORE(model));
712
713 GList *trkseg = dt_gpx_get_trkseg(d->map.gpx);
714 d->map.nb_tracks = g_list_length(trkseg);
715 d->map.tracks = g_malloc0(sizeof(dt_lib_tracks_data_t) * d->map.nb_tracks);
716
718 for(GList *i = d->imgs; i; i = g_list_next(i))
719 ((dt_sel_img_t *)i->data)->segid = -1;
720
721 int segid = 0;
722 const gboolean active = gtk_toggle_button_get_active(
723 GTK_TOGGLE_BUTTON(gtk_tree_view_column_get_widget(d->map.sel_tracks)));
724 GtkTreeIter iter;
725 for(GList *ts = trkseg; ts; ts = g_list_next(ts))
726 {
728 gchar *dts = _utc_timeval_to_localtime_text(t->start_dt, d->tz_camera, TRUE);
729 gchar *tooltip = _datetime_tooltip(t->start_dt, t->end_dt, d->tz_camera);
730 const int nb_imgs = _count_images_per_track(t, ts->next ? ts->next->data : NULL, self);
731 gtk_list_store_append(GTK_LIST_STORE(model), &iter);
732 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
733 DT_GEO_TRACKS_ACTIVE, active,
735 DT_GEO_TRACKS_POINTS, t->nb_trkpt,
736 DT_GEO_TRACKS_IMAGES, nb_imgs,
737 DT_GEO_TRACKS_SEGID, segid,
739 -1);
740 segid++;
741 dt_free(dts);
743 }
744 gtk_tree_view_set_model(GTK_TREE_VIEW(d->map.gpx_view), model);
745 g_object_unref(model);
746
747 gtk_tree_view_column_set_clickable(d->map.sel_tracks, TRUE);
748 _update_nb_images(self);
749 _update_buttons(self);
751}
752
753static void _apply_gpx(GtkWidget *widget, dt_lib_module_t *self)
754{
756 gchar *tz = dt_conf_get_string("plugins/lighttable/geotagging/tz");
757 GList *imgs = _get_images_on_active_tracks(self);
758 if(imgs)
759 {
760 if(d->offset)
761 {
762 GList *imgs2 = g_list_copy(imgs);
763 dt_control_datetime(d->offset, NULL, imgs2);
764 }
765 dt_control_gpx_apply(gtk_label_get_text(GTK_LABEL(d->map.gpx_file)), -1, tz, imgs);
766 }
767 dt_free(tz);
768 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->map.preview_button), FALSE);
769}
770
772{
774 gtk_widget_set_visible(d->gpx_button, !d->map.view);
775 gtk_widget_set_visible(d->map.gpx_section, d->map.view);
776}
777
778static void _view_changed(gpointer instance, dt_view_t *old_view,
779 dt_view_t *new_view, dt_lib_module_t *self)
780{
782 if(old_view != new_view)
783 {
784 d->map.view = !g_strcmp0(new_view->module_name, "map");
785 if(d->map.view)
786 {
789 }
790 _update_layout(self);
791 }
792}
793
794static void _geotag_changed(gpointer instance, GList *imgs, const int locid, dt_lib_module_t *self)
795{
797 if(d->map.view && !locid)
798 {
800 _update_nb_images(self);
801 _update_buttons(self);
802 }
803}
804
806{
808 for(GList *i = d->imgs; i; i = g_list_next(i))
809 {
810 dt_sel_img_t *img = i->data;
811 const dt_image_t *cimg = dt_image_cache_get(img->imgid, 'r');
812 if(IS_NULL_PTR(cimg)) continue;
813 dt_datetime_img_to_exif(img->dt, sizeof(img->dt), cimg);
815 }
816}
817
818static gboolean _row_tooltip_setup(GtkWidget *view, gint x, gint y, gboolean kb_mode,
819 GtkTooltip* tooltip, dt_lib_module_t *self)
820{
821 gboolean res = FALSE;
822 GtkTreePath *path = NULL;
823 GtkTreeViewColumn *column = NULL;
824 // Get view path mouse position
825 gint tx, ty;
826 gtk_tree_view_convert_widget_to_bin_window_coords(GTK_TREE_VIEW(view), x, y, &tx, &ty);
827 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(view), tx, ty, &path, &column, NULL, NULL))
828 {
829 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
830 GtkTreeIter iter;
831 if(gtk_tree_model_get_iter(model, &iter, path))
832 {
833 char *text = NULL;
834 gtk_tree_model_get(model, &iter, DT_GEO_TRACKS_TOOLTIP, &text, -1);
835 if(text && text[0] != '\0')
836 {
837 gtk_tooltip_set_text(tooltip, text);
838 res = TRUE;
839 }
840 dt_free(text);
841 }
842 }
843 gtk_tree_path_free(path);
844 return res;
845}
846#endif // HAVE_MAP
847
848static void _preview_gpx_file(GtkWidget *widget, dt_lib_module_t *self)
849{
852 GtkWidget *dialog = gtk_dialog_new_with_buttons(
853 _("GPX file track segments"), GTK_WINDOW(win), GTK_DIALOG_DESTROY_WITH_PARENT,
854 _("done"), GTK_RESPONSE_CANCEL, NULL);
855
856 gchar *filedir = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
857 struct dt_gpx_t *gpx = dt_gpx_new(filedir);
858 dt_free(filedir);
859
860 GtkWidget *area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
861 GtkWidget *w = gtk_scrolled_window_new(NULL, NULL);
862 gtk_widget_set_size_request(w, -1, DT_PIXEL_APPLY_DPI(100));
863 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
864 gtk_box_pack_start(GTK_BOX(area), w, TRUE, TRUE, 0);
865
866 GtkWidget *grid = gtk_grid_new();
867 gtk_grid_set_column_spacing(GTK_GRID(grid), DT_GUI_BOX_SPACING);
868 int line = 0;
869
870 GList *trkseg = dt_gpx_get_trkseg(gpx);
871
872 _set_up_label(_("name"), GTK_ALIGN_START, grid, 0, line, PANGO_ELLIPSIZE_NONE);
873 _set_up_label(_("start time"), GTK_ALIGN_START, grid, 1, line, PANGO_ELLIPSIZE_NONE);
874 _set_up_label(_("end time"), GTK_ALIGN_START, grid, 2, line, PANGO_ELLIPSIZE_NONE);
875 _set_up_label(_("points"), GTK_ALIGN_CENTER, grid, 3, line, PANGO_ELLIPSIZE_NONE);
876 _set_up_label(_("images"), GTK_ALIGN_CENTER, grid, 4, line, PANGO_ELLIPSIZE_NONE);
877
878 for(GList *i = d->imgs; i; i = g_list_next(i))
879 ((dt_sel_img_t *)i->data)->segid = -1;
880 int total_imgs = 0;
881 int total_pts = 0;
882 line++;
883 for(GList *ts = trkseg; ts; ts = g_list_next(ts))
884 {
886 gchar *dts = _utc_timeval_to_localtime_text(t->start_dt, d->tz_camera, TRUE);
887 gchar *dte = _utc_timeval_to_localtime_text(t->end_dt, d->tz_camera, TRUE);
888
889 const int nb_imgs = _count_images_per_track(t, ts->next ? ts->next->data : NULL,self);
890 total_imgs += nb_imgs;
891
892 _set_up_label(t->name, GTK_ALIGN_START, grid, 0, line, PANGO_ELLIPSIZE_NONE);
893 _set_up_label(dts, GTK_ALIGN_START, grid, 1, line, PANGO_ELLIPSIZE_NONE);
894 _set_up_label(dte, GTK_ALIGN_START, grid, 2, line, PANGO_ELLIPSIZE_NONE);
895 char *nb = g_strdup_printf("%d", t->nb_trkpt);
896 _set_up_label(nb, GTK_ALIGN_CENTER, grid, 3, line, PANGO_ELLIPSIZE_NONE);
897 dt_free(nb);
898 nb = g_strdup_printf("%d", nb_imgs);
899 _set_up_label(nb, GTK_ALIGN_CENTER, grid, 4, line, PANGO_ELLIPSIZE_NONE);
900 dt_free(nb);
901 line++;
902 total_pts += t->nb_trkpt;
903 dt_free(dts);
904 dt_free(dte);
905 }
906
907 char *nb = g_strdup_printf("%d", total_pts);
908 _set_up_label(nb, GTK_ALIGN_CENTER, grid, 3, line, PANGO_ELLIPSIZE_NONE);
909 dt_free(nb);
910 nb = g_strdup_printf("%d / %d", total_imgs, d->nb_imgs);
911 _set_up_label(nb, GTK_ALIGN_CENTER, grid, 4, line, PANGO_ELLIPSIZE_NONE);
912 dt_free(nb);
913
914 dt_gpx_destroy(gpx);
915
916 gtk_container_add(GTK_CONTAINER(w), grid);
917
918#ifdef GDK_WINDOWING_QUARTZ
920#endif
921 gtk_widget_show_all(dialog);
922 gtk_dialog_run(GTK_DIALOG(dialog));
923
924 gtk_widget_destroy(dialog);
925}
926
927static gint _cmp_imgid_asc(gconstpointer a, gconstpointer b)
928{
929 return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
930}
931
933{
935 if(d->imgs)
936 {
937#ifdef HAVE_MAP
938 if(dt_conf_get_bool("/views/map/enable"))
940#endif
941 g_list_free_full(d->imgs, dt_free_gpointer);
942 d->imgs = NULL;
943 }
944 d->imgs = NULL;
945 d->nb_imgs = 0;
946
947 // selection.h reserves the raw "SELECT imgid FROM main.selected_images" for JOINs that fetch
948 // metadata in the same query; this loop reads its metadata from the image cache, so it is the
949 // other case and must iterate the selection.
950 //
951 // Sorted ascending, and that is load-bearing: the in-memory selection comes back in
952 // pick order, but the SQL scan this replaced yielded ascending imgid (the table's
953 // INTEGER PRIMARY KEY), and _refresh_images_displayed_on_track() collapses same-location
954 // images into one map marker by comparing each entry with its list NEIGHBOUR -- adjacency
955 // the deterministic order provided and click order does not.
957 for(GList *l = selected; l; l = g_list_next(l))
958 {
959 const int32_t imgid = GPOINTER_TO_INT(l->data);
960 const dt_image_t *cimg = dt_image_cache_get(imgid, 'r');
961 char dt[DT_DATETIME_LENGTH];
962 if(IS_NULL_PTR(cimg)) continue;
963 dt_datetime_img_to_exif(dt, sizeof(dt), cimg);
965
966 dt_sel_img_t *img = g_malloc0(sizeof(dt_sel_img_t));
967 if(IS_NULL_PTR(img)) continue;
968 memcpy(img->dt, dt, DT_DATETIME_LENGTH);
969 img->imgid = imgid;
970 d->imgs = g_list_prepend(d->imgs, img);
971 d->nb_imgs++;
972 }
973 g_list_free(selected); // shallow: the elements are the selection's own ints
974}
975
977{
978 // bring a filechooser to select the gpx file to apply to selection
981 GtkWidget *filechooser = gtk_file_chooser_dialog_new(
982 _("open GPX file"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_OPEN,
983 _("preview"), GTK_RESPONSE_ACCEPT,
984 _("_cancel"), GTK_RESPONSE_CANCEL,
985 _("_open"), GTK_RESPONSE_OK, (char *)NULL);
986#ifdef GDK_WINDOWING_QUARTZ
987 dt_osx_disallow_fullscreen(filechooser);
988#endif
989
990 dt_conf_get_folder_to_file_chooser("ui_last/gpx_last_directory", GTK_FILE_CHOOSER(filechooser));
991
992 GtkFileFilter *filter;
993 filter = GTK_FILE_FILTER(gtk_file_filter_new());
994 gtk_file_filter_add_custom(filter, GTK_FILE_FILTER_FILENAME | GTK_FILE_FILTER_MIME_TYPE,
995 _lib_geotagging_filter_gpx, NULL, NULL);
996 gtk_file_filter_set_name(filter, _("GPS data exchange format"));
997 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(filechooser), filter);
998
999 filter = GTK_FILE_FILTER(gtk_file_filter_new());
1000 gtk_file_filter_add_pattern(filter, "*");
1001 gtk_file_filter_set_name(filter, _("all files"));
1002 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(filechooser), filter);
1003
1004 if(IS_NULL_PTR(d->imgs))
1006
1007 int res = gtk_dialog_run(GTK_DIALOG(filechooser));
1008 while(res == GTK_RESPONSE_ACCEPT)
1009 {
1010 _preview_gpx_file(filechooser, self);
1011 res = gtk_dialog_run(GTK_DIALOG(filechooser));
1012 }
1013 if(res == GTK_RESPONSE_OK)
1014 {
1015 dt_conf_set_folder_from_file_chooser("ui_last/gpx_last_directory", GTK_FILE_CHOOSER(filechooser));
1016
1017 gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
1018
1019#ifdef HAVE_MAP
1020 if(dt_conf_get_bool("/views/map/enable") && d->map.view)
1021 {
1022 gtk_label_set_text(GTK_LABEL(d->map.gpx_file), filename);
1023 _show_gpx_tracks(self);
1024 gtk_widget_set_visible(d->map.gpx_view, d->map.view);
1025 }
1026 else
1027#endif
1028 {
1029 gchar *tz = dt_conf_get_string("plugins/lighttable/geotagging/tz");
1030 dt_control_gpx_apply(filename, -1, tz, NULL);
1031 dt_free(tz);
1032 g_list_free_full(d->imgs, dt_free_gpointer);
1033 d->imgs = NULL;
1034 d->nb_imgs = 0;
1035 }
1036 dt_free(filename);
1037 }
1038
1039 gtk_widget_destroy(filechooser);
1040 // dt_control_queue_redraw_center();
1041}
1042
1043static int _sort_timezones(gconstpointer a, gconstpointer b)
1044{
1045 const tz_tuple_t *tz_a = (tz_tuple_t *)a;
1046 const tz_tuple_t *tz_b = (tz_tuple_t *)b;
1047
1048#ifdef _WIN32
1049 gboolean utc_neg_a = g_str_has_prefix(tz_a->display, "(UTC-");
1050 gboolean utc_neg_b = g_str_has_prefix(tz_b->display, "(UTC-");
1051
1052 gboolean utc_pos_a = g_str_has_prefix(tz_a->display, "(UTC+");
1053 gboolean utc_pos_b = g_str_has_prefix(tz_b->display, "(UTC+");
1054
1055 if(utc_neg_a && utc_neg_b)
1056 {
1057 char *iter_a = tz_a->display + strlen("(UTC-");
1058 char *iter_b = tz_b->display + strlen("(UTC-");
1059
1060 while(((*iter_a >= '0' && *iter_a <= '9') || *iter_a == ':') &&
1061 ((*iter_b >= '0' && *iter_b <= '9') || *iter_b == ':'))
1062 {
1063 if(*iter_a != *iter_b) return *iter_b - *iter_a;
1064 iter_a++;
1065 iter_b++;
1066 }
1067 }
1068 else if(utc_neg_a && utc_pos_b) return -1;
1069 else if(utc_pos_a && utc_neg_b) return 1;
1070#endif
1071
1072 return g_strcmp0(tz_a->display, tz_b->display);
1073}
1074
1075// create a list of possible time zones
1077{
1078 GList *timezones = NULL;
1079
1080#ifndef _WIN32
1081 // possible locations for zone.tab:
1082 // - /usr/share/zoneinfo
1083 // - /usr/lib/zoneinfo
1084 // - getenv("TZDIR")
1085 // - apparently on solaris there is no zones.tab. we need to collect the information ourselves like this:
1086 // /bin/grep -h ^Zone /usr/share/lib/zoneinfo/src/* | /bin/awk '{print "??\t+9999+99999\t" $2}'
1087#define MAX_LINE_LENGTH 256
1088 FILE *fp;
1089 char line[MAX_LINE_LENGTH];
1090
1091 // find the file using known possible locations
1092 gchar *zone_tab = g_strdup("/usr/share/zoneinfo/zone.tab");
1093 if(!g_file_test(zone_tab, G_FILE_TEST_IS_REGULAR))
1094 {
1095 dt_free(zone_tab);
1096 zone_tab = g_strdup("/usr/lib/zoneinfo/zone.tab");
1097 if(!g_file_test(zone_tab, G_FILE_TEST_IS_REGULAR))
1098 {
1099 dt_free(zone_tab);
1100 zone_tab = g_build_filename(g_getenv("TZDIR"), "zone.tab", NULL);
1101 if(!g_file_test(zone_tab, G_FILE_TEST_IS_REGULAR))
1102 {
1103 dt_free(zone_tab);
1104 char datadir[DT_PATH_MAX] = { 0 };
1105 dt_loc_get_datadir(datadir, sizeof(datadir));
1106 zone_tab = g_build_filename(datadir, "zone.tab", NULL);
1107 if(!g_file_test(zone_tab, G_FILE_TEST_IS_REGULAR))
1108 {
1109 dt_free(zone_tab);
1110 // TODO: Solaris test
1111 return NULL;
1112 }
1113 }
1114 }
1115 }
1116
1117 // parse zone.tab and put all time zone descriptions into timezones
1118 fp = g_fopen(zone_tab, "r");
1119 dt_free(zone_tab);
1120
1121 if(IS_NULL_PTR(fp)) return NULL;
1122
1123 while(fgets(line, MAX_LINE_LENGTH, fp))
1124 {
1125 if(line[0] == '#' || line[0] == '\0') continue;
1126 gchar **tokens = g_strsplit_set(line, " \t\n", 0);
1127 // sometimes files are not separated by single tabs but multiple spaces, resulting in empty strings in tokens
1128 // so we have to look for the 3rd non-empty entry
1129 int n_found = -1, i;
1130 for(i = 0; tokens[i] && n_found < 2; i++) if(*tokens[i]) n_found++;
1131 if(n_found != 2)
1132 {
1133 g_strfreev(tokens);
1134 continue;
1135 }
1136 gchar *name = g_strdup(tokens[i - 1]);
1137 g_strfreev(tokens);
1138 if(name[0] == '\0')
1139 {
1140 dt_free(name);
1141 continue;
1142 }
1143 size_t last_char = strlen(name) - 1;
1144 if(name[last_char] == '\n') name[last_char] = '\0';
1145 tz_tuple_t *tz_tuple = (tz_tuple_t *)malloc(sizeof(tz_tuple_t));
1146 tz_tuple->display = name;
1147 tz_tuple->name = name;
1148 timezones = g_list_prepend(timezones, tz_tuple);
1149 }
1150
1151 fclose(fp);
1152
1153 // sort timezones
1154 timezones = g_list_sort(timezones, _sort_timezones);
1155
1156 tz_tuple_t *utc = (tz_tuple_t *)malloc(sizeof(tz_tuple_t));
1157 utc->display = g_strdup("UTC");
1158 utc->name = utc->display;
1159 timezones = g_list_prepend(timezones, utc);
1160
1161#undef MAX_LINE_LENGTH
1162
1163#else // !_WIN32
1164 // on Windows we have to grab the time zones from the registry
1165 char *keypath = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\";
1166 HKEY hKey;
1167
1168 if(RegOpenKeyExA(HKEY_LOCAL_MACHINE,
1169 keypath,
1170 0,
1171 KEY_READ,
1172 &hKey) == ERROR_SUCCESS)
1173 {
1174 DWORD n_subkeys, max_subkey_len;
1175
1176 if(RegQueryInfoKey(hKey,
1177 NULL,
1178 NULL,
1179 NULL,
1180 &n_subkeys,
1181 &max_subkey_len,
1182 NULL,
1183 NULL,
1184 NULL,
1185 NULL,
1186 NULL,
1187 NULL) == ERROR_SUCCESS)
1188 {
1189 wchar_t *subkeyname = (wchar_t *)malloc(sizeof(wchar_t) * (max_subkey_len + 1));
1190
1191 for(DWORD i = 1; i < n_subkeys; i++)
1192 {
1193 DWORD subkeyname_length = max_subkey_len + 1;
1194 if(RegEnumKeyExW(hKey,
1195 i,
1196 subkeyname,
1197 &subkeyname_length,
1198 NULL,
1199 NULL,
1200 NULL,
1201 NULL) == ERROR_SUCCESS)
1202 {
1203 DWORD buffer_size;
1204 char *subkeyname_utf8 = g_utf16_to_utf8(subkeyname, -1, NULL, NULL, NULL);
1205 char *subkeypath_utf8 = g_strconcat(keypath, "\\", subkeyname_utf8, NULL);
1206 wchar_t *subkeypath = g_utf8_to_utf16(subkeypath_utf8, -1, NULL, NULL, NULL);
1207 if(RegGetValueW(HKEY_LOCAL_MACHINE,
1208 subkeypath,
1209 L"Display",
1210 RRF_RT_ANY,
1211 NULL,
1212 NULL,
1213 &buffer_size) == ERROR_SUCCESS)
1214 {
1215 wchar_t *display_name = (wchar_t *)malloc(buffer_size);
1216 if(RegGetValueW(HKEY_LOCAL_MACHINE,
1217 subkeypath,
1218 L"Display",
1219 RRF_RT_ANY,
1220 NULL,
1221 display_name,
1222 &buffer_size) == ERROR_SUCCESS)
1223 {
1224 tz_tuple_t *tz = (tz_tuple_t *)malloc(sizeof(tz_tuple_t));
1225
1226 tz->name = subkeyname_utf8;
1227 tz->display = g_utf16_to_utf8(display_name, -1, NULL, NULL, NULL);
1228 timezones = g_list_prepend(timezones, tz);
1229
1230 subkeyname_utf8 = NULL; // to not free it later
1231 }
1232 dt_free(display_name);
1233 }
1234 dt_free(subkeyname_utf8);
1235 dt_free(subkeypath_utf8);
1236 dt_free(subkeypath);
1237 }
1238 }
1239
1240 dt_free(subkeyname);
1241 }
1242 }
1243
1244 RegCloseKey(hKey);
1245
1246 timezones = g_list_sort(timezones, _sort_timezones);
1247#endif // !_WIN32
1248
1249 return timezones;
1250}
1251
1252static void _display_offset(const GTimeSpan offset_int, const gboolean valid, dt_lib_module_t *self)
1253{
1255 GTimeSpan off2 = 0;
1256 if(valid)
1257 {
1258 const gboolean neg = offset_int < 0;
1259 gtk_label_set_text(GTK_LABEL(d->of.sign), neg ? "- " : "");
1260
1261 GTimeSpan off = neg ? -offset_int : offset_int;
1262
1263 /* normalize to milliseconds */
1264 gint64 total_ms = off / 1000;
1265
1266 /* extract components */
1267 gint ms = total_ms % 1000;
1268 gint64 total_s = total_ms / 1000;
1269
1270 gint s = total_s % 60;
1271 gint64 total_m = total_s / 60;
1272
1273 gint m = total_m % 60;
1274 gint64 total_h = total_m / 60;
1275
1276 gint h = total_h % 24;
1277 gint64 total_d = total_h / 24;
1278
1279 gint D = total_d % 100;
1280
1281 /* write fields */
1282 char text[8]; // enough for all cases
1283
1284 g_snprintf(text, sizeof(text), "%03d", ms);
1285 gtk_entry_set_text(GTK_ENTRY(d->of.widget[6]), text);
1286
1287 g_snprintf(text, sizeof(text), "%02d", s);
1288 gtk_entry_set_text(GTK_ENTRY(d->of.widget[5]), text);
1289
1290 g_snprintf(text, sizeof(text), "%02d", m);
1291 gtk_entry_set_text(GTK_ENTRY(d->of.widget[4]), text);
1292
1293 g_snprintf(text, sizeof(text), "%02d", h);
1294 gtk_entry_set_text(GTK_ENTRY(d->of.widget[3]), text);
1295
1296 g_snprintf(text, sizeof(text), "%02d", D);
1297 gtk_entry_set_text(GTK_ENTRY(d->of.widget[2]), text);
1298 }
1299 if(!valid || off2)
1300 {
1301 gtk_label_set_text(GTK_LABEL(d->of.sign), "");
1302 for(int i = 2; i < DT_GEOTAG_PARTS_NB; i++)
1303 gtk_entry_set_text(GTK_ENTRY(d->of.widget[i]), "-");
1304 }
1305 const gboolean locked = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->lock_offset));
1306 gtk_widget_set_sensitive(d->apply_offset, d->imgid && valid && !off2 && offset_int);
1307 gtk_widget_set_sensitive(d->lock_offset, locked || (d->imgid && valid && !off2 && offset_int));
1308 gtk_widget_set_sensitive(d->apply_datetime, d->imgid && !locked);
1309#ifdef HAVE_MAP
1310 if(dt_conf_get_bool("/views/map/enable"))
1311 _update_buttons(self);
1312#endif
1313}
1314
1315static void _display_datetime(dt_lib_datetime_t *dtw, GDateTime *datetime,
1316 const gboolean lock, dt_lib_module_t *self)
1317{
1319 for(int i = 0; lock && i < DT_GEOTAG_PARTS_NB; i++)
1320 g_signal_handlers_block_by_func(d->dt.widget[i], _datetime_entry_changed, self);
1321 if(datetime)
1322 {
1323 char value[8] = {0};
1324 snprintf(value, sizeof(value), "%04d", g_date_time_get_year(datetime));
1325 gtk_entry_set_text(GTK_ENTRY(dtw->widget[0]), value);
1326 snprintf(value, sizeof(value), "%02d", g_date_time_get_month(datetime));
1327 gtk_entry_set_text(GTK_ENTRY(dtw->widget[1]), value);
1328 snprintf(value, sizeof(value), "%02d", g_date_time_get_day_of_month(datetime));
1329 gtk_entry_set_text(GTK_ENTRY(dtw->widget[2]), value);
1330 snprintf(value, sizeof(value), "%02d", g_date_time_get_hour(datetime));
1331 gtk_entry_set_text(GTK_ENTRY(dtw->widget[3]), value);
1332 snprintf(value, sizeof(value), "%02d", g_date_time_get_minute(datetime));
1333 gtk_entry_set_text(GTK_ENTRY(dtw->widget[4]),value);
1334 snprintf(value, sizeof(value), "%02d", g_date_time_get_second(datetime));
1335 gtk_entry_set_text(GTK_ENTRY(dtw->widget[5]), value);
1336 snprintf(value, sizeof(value), "%03d", (int)(g_date_time_get_microsecond(datetime) * 0.001));
1337 gtk_entry_set_text(GTK_ENTRY(dtw->widget[6]), value);
1338 }
1339 else
1340 {
1341 for(int i = 0; i < DT_GEOTAG_PARTS_NB; i++)
1342 gtk_entry_set_text(GTK_ENTRY(dtw->widget[i]), "-");
1343 }
1344 for(int i = 0; lock && i < DT_GEOTAG_PARTS_NB; i++)
1345 g_signal_handlers_unblock_by_func(d->dt.widget[i], _datetime_entry_changed, self);
1346}
1347
1348// read the current date/time and make correction (field under/overflow)
1350{
1352
1353 const int year = atoi(gtk_entry_get_text(GTK_ENTRY(d->dt.widget[0])));
1354 const int month = atoi(gtk_entry_get_text(GTK_ENTRY(d->dt.widget[1])));
1355 const int day = atoi(gtk_entry_get_text(GTK_ENTRY(d->dt.widget[2])));
1356 const int hour = atoi(gtk_entry_get_text(GTK_ENTRY(d->dt.widget[3])));
1357 const int minute = atoi(gtk_entry_get_text(GTK_ENTRY(d->dt.widget[4])));
1358 const int second = atoi(gtk_entry_get_text(GTK_ENTRY(d->dt.widget[5])));
1359 const int millisecond = atoi(gtk_entry_get_text(GTK_ENTRY(d->dt.widget[6])));
1360 const gdouble second2 = (gdouble)second + (gdouble)millisecond * 0.001;
1361
1362 return g_date_time_new(dt_datetime_utc_tz(), year, month, day, hour, minute, second2);
1363}
1364
1365static void _new_datetime(GDateTime *datetime, dt_lib_module_t *self)
1366{
1368 if(datetime)
1369 {
1370 _display_datetime(&d->dt, datetime, TRUE, self);
1371
1372 if(d->datetime)
1373 g_date_time_unref(d->datetime);
1374 d->datetime = datetime;
1375 d->offset = g_date_time_difference(d->datetime, d->datetime0);
1376 _display_offset(d->offset, !IS_NULL_PTR(d->datetime), self);
1377#ifdef HAVE_MAP
1378 if(dt_conf_get_bool("/views/map/enable") && d->map.view)
1379 _refresh_track_list(self);
1380#endif
1381 }
1382}
1383
1385{
1387 if(!d->editing)
1388 {
1389 GDateTime *datetime = _read_datetime_entry(self);
1390 _new_datetime(datetime, self);
1391 }
1392}
1393
1394static GDateTime *_get_image_datetime(dt_lib_module_t *self)
1395{
1397 int32_t imgid = dt_control_get_mouse_over_id();
1398 if(imgid < 0) imgid = dt_act_on_get_first_image();
1399 if(imgid < 0) return NULL;
1400 GDateTime *datetime = NULL;
1401
1402 // consider act on only if no selected
1403 char datetime_s[DT_DATETIME_LENGTH];
1404 dt_image_get_datetime(imgid, datetime_s);
1405 if(datetime_s[0] != '\0')
1406 datetime = dt_datetime_exif_to_gdatetime(datetime_s, dt_datetime_utc_tz());
1407 else
1408 datetime = NULL;
1409
1410 d->imgid = imgid;
1411 return datetime;
1412}
1413
1415{
1417 const gboolean locked = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(d->lock_offset));
1418 GDateTime *datetime = _get_image_datetime(self);
1419 if(d->datetime0)
1420 g_date_time_unref(d->datetime0);
1421 d->datetime0 = datetime;
1422 _display_datetime(&d->dt0, datetime, FALSE, self);
1423 if(locked)
1424 {
1425 GDateTime *datetime2 = g_date_time_add(datetime, d->offset);
1426 _new_datetime(datetime2, self);
1427 }
1428 else
1429 {
1430 _display_offset(d->offset = 0, !IS_NULL_PTR(datetime), self);
1431 if(datetime)
1432 {
1433 g_date_time_ref(datetime);
1434 _new_datetime(datetime, self);
1435 }
1436 }
1437}
1438
1439static void _image_info_changed(gpointer instance, gpointer imgs, dt_lib_module_t *self)
1440{
1442 for(GList *i = imgs; i; i = g_list_next(i))
1443 {
1444 if(GPOINTER_TO_INT(i->data) == d->imgid)
1445 {
1447 break;
1448 }
1449 }
1450#ifdef HAVE_MAP
1451 if(dt_conf_get_bool("/views/map/enable") && d->map.view)
1452 {
1454 _refresh_track_list(self);
1455 }
1456#endif
1457}
1458
1459
1460static void _mouse_over_image_callback(gpointer instance, dt_lib_module_t *self)
1461{
1462 //if(dt_lib_gui_get_expanded(self)) // this segfaults internally, for some reason
1464}
1465
1466static void _selection_changed_callback(gpointer instance, dt_lib_module_t *self)
1467{
1469#ifdef HAVE_MAP
1470 if(dt_conf_get_bool("/views/map/enable"))
1471 {
1473 if(d->map.view)
1474 {
1476 _refresh_track_list(self);
1477 }
1478 }
1479#endif
1480}
1481
1482static gboolean _datetime_scroll_over(GtkWidget *w, GdkEventScroll *event, dt_lib_module_t *self)
1483{
1485 if(!d->editing)
1486 {
1487 int i = 0;
1488 for(i = 0; i < DT_GEOTAG_PARTS_NB; i++)
1489 if(w == d->dt.widget[i]) break;
1490
1491 int delta_y;
1492 int increment = 0;
1493 if(dt_gui_get_scroll_unit_deltas(event, NULL, &delta_y))
1494 {
1495 if (delta_y < 0) increment = 1;
1496 else if (delta_y > 0) increment = -1;
1497 }
1498
1499 if(dt_modifier_is(event->state, GDK_SHIFT_MASK))
1500 increment *= 10;
1501
1502 GDateTime *datetime;
1503 switch(i)
1504 {
1505 case 0:
1506 datetime = g_date_time_add_years(d->datetime, increment);
1507 break;
1508 case 1:
1509 datetime = g_date_time_add_months(d->datetime, increment);
1510 break;
1511 case 2:
1512 datetime = g_date_time_add_days(d->datetime, increment);
1513 break;
1514 case 3:
1515 datetime = g_date_time_add_hours(d->datetime, increment);
1516 break;
1517 case 4:
1518 datetime = g_date_time_add_minutes(d->datetime, increment);
1519 break;
1520 case 5:
1521 datetime = g_date_time_add_seconds(d->datetime, increment);
1522 break;
1523 case 6:
1524 datetime = g_date_time_add(d->datetime, increment * 1000);
1525 break;
1526 default:
1527 datetime = NULL;
1528 }
1529
1530 _new_datetime(datetime, self);
1531 }
1532
1533 return TRUE;
1534}
1535
1536// type 0 date/time, 1 original date/time, 2 offset
1538{
1539 GtkWidget *flow = gtk_flow_box_new();
1540 gtk_flow_box_set_max_children_per_line(GTK_FLOW_BOX(flow), 2);
1541 gtk_flow_box_set_column_spacing(GTK_FLOW_BOX(flow), DT_GUI_BOX_SPACING);
1542
1543 GtkBox *box = NULL;
1544 for(int i = 0; i < DT_GEOTAG_PARTS_NB; i++)
1545 {
1546 if(IS_NULL_PTR(box)) box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING));
1547
1548 if(i == 0 && type == 2)
1549 {
1550 dt->sign = gtk_label_new("");
1551 gtk_box_pack_start(box, dt->sign, FALSE, FALSE, 0);
1552 }
1553 if(i >= 2 || type != 2)
1554 {
1555 dt->widget[i] = gtk_entry_new();
1557 gtk_entry_set_width_chars(GTK_ENTRY(dt->widget[i]), i == 0 ? 4 : i == 6 ? 3 : 2);
1558 gtk_entry_set_alignment(GTK_ENTRY(dt->widget[i]), 0.5);
1559 gtk_box_pack_start(box, dt->widget[i], FALSE, FALSE, 0);
1560 if(type == 0)
1561 {
1562 gtk_widget_add_events(dt->widget[i], dt_widget_scroll_mask());
1563 }
1564 else
1565 {
1566 gtk_widget_set_sensitive(dt->widget[i], FALSE);
1567 }
1568 }
1569
1570 if(i == 2 || i == 6)
1571 {
1572 gtk_widget_set_halign(GTK_WIDGET(box), GTK_ALIGN_END);
1573 gtk_widget_set_hexpand(GTK_WIDGET(box), TRUE);
1574 gtk_container_add(GTK_CONTAINER(flow), GTK_WIDGET(box));
1575 box = NULL;
1576 }
1577 else if(i > 2 || type != 2)
1578 {
1579 GtkWidget *label = gtk_label_new(i < 2 ? "-" : i == 5 ? "." :":");
1580 if(i == 5)
1581 g_object_set_data(G_OBJECT(dt->widget[i]), "msec_label", label);
1582 gtk_box_pack_start(box, label, FALSE, FALSE, 0);
1583 }
1584 }
1585
1586 dt_gui_flow_box_as_layout(GTK_FLOW_BOX(flow));
1587
1588 return flow;
1589}
1590
1591static gboolean _datetime_key_pressed(GtkWidget *entry, GdkEventKey *event, dt_lib_module_t *self)
1592{
1594 guint key = dt_keys_mainpad_alternatives(event->keyval);
1595
1596 switch(key)
1597 {
1598 case GDK_KEY_Escape:
1599 {
1600 // reset
1602#ifdef HAVE_MAP
1603 if(dt_conf_get_bool("/views/map/enable") && d->map.view)
1604 _refresh_track_list(self);
1605#endif
1606 gtk_window_set_focus(GTK_WINDOW(dt_gui_main_window()), NULL);
1607 d->editing = FALSE;
1608 return FALSE;
1609 }
1610 // allow 0 .. 9, left/right/home/end movement using arrow keys and del/backspace
1611 case GDK_KEY_0:
1612 case GDK_KEY_1:
1613 case GDK_KEY_2:
1614 case GDK_KEY_3:
1615 case GDK_KEY_4:
1616 case GDK_KEY_5:
1617 case GDK_KEY_6:
1618 case GDK_KEY_7:
1619 case GDK_KEY_8:
1620 case GDK_KEY_9:
1621 case GDK_KEY_Delete:
1622 case GDK_KEY_BackSpace:
1623 case GDK_KEY_Left:
1624 case GDK_KEY_Right:
1625 case GDK_KEY_Home:
1626 case GDK_KEY_End:
1627 d->editing = TRUE;
1628 return FALSE;
1629
1630 case GDK_KEY_Tab:
1631 case GDK_KEY_ISO_Left_Tab:
1632 case GDK_KEY_Return:
1633 d->editing = FALSE;
1634 g_signal_emit_by_name(d->dt.widget[0], "changed");
1635 return FALSE;
1636
1637 default: // block everything else
1638 return TRUE;
1639 }
1640}
1641
1643{
1645 const gchar *tz = gtk_entry_get_text(GTK_ENTRY(d->timezone));
1646
1647 gchar *name = NULL;
1648 for(GList *iter = d->timezones; iter; iter = g_list_next(iter))
1649 {
1650 tz_tuple_t *tz_tuple = (tz_tuple_t *)iter->data;
1651 if(!strcmp(tz_tuple->display, tz))
1652 name = tz_tuple->name;
1653 }
1654 if(d->tz_camera) g_time_zone_unref(d->tz_camera);
1655 d->tz_camera = !name ? g_time_zone_new_utc() : g_time_zone_new(name);
1656 dt_conf_set_string("plugins/lighttable/geotagging/tz", name ? name : "UTC");
1657 gtk_entry_set_text(GTK_ENTRY(d->timezone), name ? name : "UTC");
1658 gtk_label_set_text(GTK_LABEL (d->timezone_changed), "");
1659
1660 gtk_window_set_focus(GTK_WINDOW(dt_gui_main_window()), NULL);
1661#ifdef HAVE_MAP
1662 if(dt_conf_get_bool("/views/map/enable") && d->map.view)
1663 _refresh_track_list(self);
1664#endif
1665}
1666
1667static gboolean _timezone_key_pressed(GtkWidget *entry, GdkEventKey *event, dt_lib_module_t *self)
1668{
1669 guint key = dt_keys_mainpad_alternatives(event->keyval);
1670
1671 switch(key)
1672 {
1673 case GDK_KEY_Return:
1674 case GDK_KEY_Tab:
1675 _timezone_save(self);
1676 return TRUE;
1677 case GDK_KEY_Escape:
1678 gtk_window_set_focus(GTK_WINDOW(dt_gui_main_window()), NULL);
1679 return TRUE;
1680 default: ;
1682 gtk_label_set_text(GTK_LABEL (d->timezone_changed), " *");
1683 break;
1684 }
1685 return FALSE;
1686}
1687
1688static gboolean _timezone_focus_out(GtkWidget *entry, GdkEventKey *event, dt_lib_module_t *self)
1689{
1690 _timezone_save(self);
1691 return FALSE;
1692}
1693
1694static gboolean _completion_match_func(GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter,
1695 gpointer user_data)
1696{
1697 gboolean res = FALSE;
1698
1699 GtkEditable *e = (GtkEditable *)gtk_entry_completion_get_entry(completion);
1700 if(!GTK_IS_EDITABLE(e))
1701 return FALSE;
1702
1703 GtkTreeModel *model = gtk_entry_completion_get_model(completion);
1704 const int column = gtk_entry_completion_get_text_column(completion);
1705
1706 if(gtk_tree_model_get_column_type(model, column) != G_TYPE_STRING)
1707 return FALSE;
1708
1709 char *tag = NULL;
1710 gtk_tree_model_get(model, iter, column, &tag, -1);
1711 if(tag)
1712 {
1713 char *normalized = g_utf8_normalize(tag, -1, G_NORMALIZE_ALL);
1714 if(normalized)
1715 {
1716 char *casefold = g_utf8_casefold(normalized, -1);
1717 if(casefold)
1718 {
1719 res = g_strstr_len(casefold, -1, key) != NULL;
1720 }
1721 dt_free(casefold);
1722 }
1723 dt_free(normalized);
1724 dt_free(tag);
1725 }
1726
1727 return res;
1728}
1729
1730static void _toggle_lock_button_callback(GtkToggleButton *button, dt_lib_module_t *self)
1731{
1733 const gboolean locked = gtk_toggle_button_get_active(button);
1734 for(int i = 0; i < DT_GEOTAG_PARTS_NB; i++)
1735 {
1736 gtk_widget_set_sensitive(d->dt.widget[i], !locked);
1737 }
1738 gtk_widget_set_sensitive(d->apply_datetime, d->imgid && !locked);
1739}
1740
1741GtkTreeViewColumn *_new_tree_text_column(const char *name, const gboolean expand,
1742 const float xalign, const int m_col,
1743 const int ellipsize)
1744{
1745 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
1746 GtkTreeViewColumn *column = gtk_tree_view_column_new();
1747 gtk_tree_view_column_pack_start(column, renderer, TRUE);
1748 gtk_tree_view_column_set_attributes(column, renderer, "text", m_col, NULL);
1749 gtk_tree_view_column_set_expand(column, expand);
1750 GtkWidget *label = gtk_label_new(name);
1751 gtk_widget_show(label);
1752 gtk_tree_view_column_set_widget(column, label);
1753 gtk_label_set_ellipsize(GTK_LABEL(label), expand ? PANGO_ELLIPSIZE_MIDDLE : PANGO_ELLIPSIZE_NONE);
1754 g_object_set(renderer, "xalign", xalign, "ellipsize", ellipsize, NULL);
1755 return column;
1756}
1757
1759{
1760 const gboolean milliseconds = dt_conf_get_bool("lighttable/ui/milliseconds");
1761 gtk_widget_set_visible(d->dt.widget[6], milliseconds);
1762 gtk_widget_set_visible(d->dt0.widget[6], milliseconds);
1763 gtk_widget_set_visible(d->of.widget[6], milliseconds);
1764 gtk_widget_set_visible(g_object_get_data(G_OBJECT(d->dt.widget[5]), "msec_label"), milliseconds);
1765 gtk_widget_set_visible(g_object_get_data(G_OBJECT(d->dt0.widget[5]), "msec_label"), milliseconds);
1766 gtk_widget_set_visible(g_object_get_data(G_OBJECT(d->of.widget[5]), "msec_label"), milliseconds);
1767}
1768
1769static void _dt_pref_change_callback(gpointer instance, dt_lib_module_t *self)
1770{
1772}
1773
1775{
1777 self->data = (void *)d;
1778 d->timezones = _lib_geotagging_get_timezones();
1779 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1780
1781 GtkGrid *grid = GTK_GRID(gtk_grid_new());
1782 gtk_grid_set_column_spacing(grid, DT_GUI_BOX_SPACING);
1783 int line = 0;
1784
1785 GtkWidget *label = dt_ui_label_new(_("date/time"));
1786 gtk_grid_attach(grid, label, 0, line, 2, 1);
1787 gtk_widget_set_tooltip_text(label, _("enter the new date/time (YYYY:MM:DD hh:mm:ss[.sss])"
1788 "\nkey in the new numbers or scroll over the cell"));
1789
1790 GtkWidget *box = _gui_init_datetime(&d->dt, 0, self);
1791 gtk_grid_attach(grid, box, 2, line++, 2, 1);
1792
1793 label = dt_ui_label_new(_("original date/time"));
1794 gtk_grid_attach(grid, label, 0, line, 2, 1);
1795
1796 box = _gui_init_datetime(&d->dt0, 1, self);
1797 gtk_grid_attach(grid, box, 2, line++, 2, 1);
1798
1799 label = dt_ui_label_new(_("date/time offset"));
1800 gtk_grid_attach(grid, label, 0, line, 2, 1);
1801 gtk_widget_set_tooltip_text(label, _("offset or difference ([-]dd hh:mm:ss[.sss])"));
1802
1803 d->lock_offset = dtgtk_togglebutton_new(dtgtk_cairo_paint_lock, 0, NULL);
1804 gtk_widget_set_tooltip_text(d->lock_offset, _("lock date/time offset value to apply it onto another selection"));
1805 gtk_widget_set_halign(d->lock_offset, GTK_ALIGN_START);
1806 gtk_grid_attach(grid, d->lock_offset, 2, line, 1, 1);
1807 g_signal_connect(G_OBJECT(d->lock_offset), "clicked", G_CALLBACK(_toggle_lock_button_callback), (gpointer)self);
1808
1809 box = _gui_init_datetime(&d->of, 2, self);
1810 gtk_grid_attach(grid, box, 3, line++, 1, 1);
1811
1812 // apply
1813 d->apply_offset = dt_action_button_new(self, N_("apply offset"), _apply_offset_callback, self,
1814 _("apply offset to selected images"), 0, 0);
1815 gtk_grid_attach(grid, d->apply_offset , 0, line, 2, 1);
1816
1817 d->apply_datetime = dt_action_button_new(self, N_("apply date/time"), _apply_datetime_callback, self,
1818 _("apply the same date/time to selected images"), 0, 0);
1819 gtk_grid_attach(grid, d->apply_datetime , 2, line++, 2, 1);
1820
1821 // time zone entry
1822 label = dt_ui_label_new(_(dt_confgen_get_label("plugins/lighttable/geotagging/tz")));
1823 gtk_widget_set_tooltip_text(label, _(dt_confgen_get_tooltip("plugins/lighttable/geotagging/tz")));
1824
1825 gtk_grid_attach(grid, label, 0, line, 2, 1);
1826
1827 d->timezone = gtk_entry_new();
1829 gtk_widget_set_tooltip_text(d->timezone, _("start typing to show a list of permitted values and select your timezone.\npress enter to confirm, so that the asterisk * disappears"));
1830 d->timezone_changed = dt_ui_label_new("");
1831
1832 GtkWidget *timezone_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1833 gtk_box_pack_start(GTK_BOX(timezone_box), d->timezone, TRUE, TRUE, 0);
1834 gtk_box_pack_end(GTK_BOX(timezone_box), d->timezone_changed, FALSE, FALSE, 0);
1835
1836 gtk_grid_attach(grid, timezone_box, 2, line++, 2, 1);
1837
1838 GtkCellRenderer *renderer;
1839 GtkTreeIter tree_iter;
1840 GtkListStore *model = gtk_list_store_new(2, G_TYPE_STRING /*display*/, G_TYPE_STRING /*name*/);
1841 GtkWidget *tz_selection = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
1842 renderer = gtk_cell_renderer_text_new();
1843 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(tz_selection), renderer, FALSE);
1844 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(tz_selection), renderer, "text", 0, NULL);
1845
1846 gchar *tz = dt_conf_get_string("plugins/lighttable/geotagging/tz");
1847 d->tz_camera = (IS_NULL_PTR(tz)) ? g_time_zone_new_utc() : g_time_zone_new(tz);
1848 for(GList *iter = d->timezones; iter; iter = g_list_next(iter))
1849 {
1850 tz_tuple_t *tz_tuple = (tz_tuple_t *)iter->data;
1851 gtk_list_store_append(model, &tree_iter);
1852 gtk_list_store_set(model, &tree_iter, 0, tz_tuple->display, 1, tz_tuple->name, -1);
1853 if(!strcmp(tz_tuple->name, tz))
1854 gtk_entry_set_text(GTK_ENTRY(d->timezone), tz_tuple->display);
1855 }
1856 dt_free(tz);
1857
1858 // add entry completion
1859 GtkEntryCompletion *completion = gtk_entry_completion_new();
1860 gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(model));
1861 gtk_entry_completion_set_text_column(completion, 0);
1862 gtk_entry_completion_set_inline_completion(completion, TRUE);
1863 gtk_entry_completion_set_popup_set_width(completion, FALSE);
1864 gtk_entry_completion_set_match_func(completion, _completion_match_func, NULL, NULL);
1865 gtk_entry_completion_set_minimum_key_length(completion, 0);
1866 gtk_entry_set_completion(GTK_ENTRY(d->timezone), completion);
1867 g_signal_connect(G_OBJECT(d->timezone), "key-press-event", G_CALLBACK(_timezone_key_pressed), self);
1868 g_signal_connect(G_OBJECT(d->timezone), "focus-out-event", G_CALLBACK(_timezone_focus_out), self);
1869
1870 // gpx
1871 d->gpx_button = dt_action_button_new(self, N_("apply GPX track file..."), _choose_gpx_callback, self,
1872 _("parses a GPX file and updates location of selected images"), 0, 0);
1873 gtk_grid_attach(grid, d->gpx_button, 0, line++, 4, 1);
1874 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(grid), TRUE, TRUE, 0);
1875#ifdef HAVE_MAP
1876 if(dt_conf_get_bool("/views/map/enable"))
1877 {
1878 grid = GTK_GRID(gtk_grid_new());
1879 gtk_grid_set_column_spacing(grid, DT_GUI_BOX_SPACING);
1880 line = 0;
1881
1882 d->map.gpx_section = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1883 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(d->map.gpx_section), TRUE, TRUE, 0);
1884
1885 label = dt_ui_section_label_new(_("GPX file"));
1886 gtk_grid_attach(grid, label, 0, line++, 4, 1);
1887
1888 d->map.gpx_button = dtgtk_button_new(dtgtk_cairo_paint_directory, CPF_NONE, NULL);
1889 gtk_widget_set_hexpand(d->map.gpx_button, FALSE);
1890 gtk_widget_set_halign(d->map.gpx_button, GTK_ALIGN_START);
1891 gtk_widget_set_tooltip_text(d->map.gpx_button, _("select a GPX track file..."));
1892 gtk_grid_attach(grid, d->map.gpx_button, 0, line, 1, 1);
1893 g_signal_connect(G_OBJECT(d->map.gpx_button), "clicked", G_CALLBACK(_choose_gpx_callback), self);
1894
1895 d->map.gpx_file = dt_ui_label_new("");
1896 gtk_label_set_ellipsize(GTK_LABEL(d->map.gpx_file ), PANGO_ELLIPSIZE_MIDDLE);
1897 gtk_widget_set_hexpand(d->map.gpx_file, TRUE);
1898 gtk_grid_attach(grid, d->map.gpx_file, 1, line++, 3, 1);
1899 gtk_box_pack_start(GTK_BOX(d->map.gpx_section), GTK_WIDGET(grid), TRUE, TRUE, 0);
1900
1901 model = gtk_list_store_new(DT_GEO_TRACKS_NUM_COLS, G_TYPE_BOOLEAN, G_TYPE_STRING,
1902 G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING);
1903
1904 d->map.gpx_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1905 g_object_unref(model);
1906 gtk_widget_set_name(d->map.gpx_view, "gpx_list");
1907 gtk_widget_set_tooltip_text(GTK_WIDGET(d->map.gpx_view),
1908 _("list of track segments in the GPX file, for each segment:"
1909 "\n- the start date/time in local time (LT)"
1910 "\n- the number of track points"
1911 "\n- the number of matching images"
1912 " based on images date/time, offset and time zone"
1913 "\n- more detailed time information hovering the row"));
1914 renderer = gtk_cell_renderer_toggle_new();
1915 g_signal_connect(renderer, "toggled", G_CALLBACK(_track_seg_toggled), self);
1916 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes("", renderer, "active", DT_GEO_TRACKS_ACTIVE, NULL);
1917 gtk_tree_view_append_column(GTK_TREE_VIEW(d->map.gpx_view), column);
1918 d->map.sel_tracks = column;
1919 GtkWidget *button = gtk_check_button_new();
1920 gtk_widget_show(button);
1921 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), FALSE);
1922 gtk_tree_view_column_set_widget(column, button);
1923 gtk_tree_view_column_set_alignment(column, 0.5);
1924 g_signal_connect(column, "clicked", G_CALLBACK(_all_tracks_toggled), self);
1925
1926 column = _new_tree_text_column(_("start time"), TRUE, 0.0, DT_GEO_TRACKS_DATETIME, PANGO_ELLIPSIZE_START);
1927 gtk_tree_view_append_column(GTK_TREE_VIEW(d->map.gpx_view), column);
1928 column = _new_tree_text_column(_("points"), FALSE, 1.0, DT_GEO_TRACKS_POINTS, PANGO_ELLIPSIZE_NONE);
1929 gtk_tree_view_append_column(GTK_TREE_VIEW(d->map.gpx_view), column);
1930 column = _new_tree_text_column(_("images"), FALSE, 1.0, DT_GEO_TRACKS_IMAGES, PANGO_ELLIPSIZE_NONE);
1931 gtk_tree_view_append_column(GTK_TREE_VIEW(d->map.gpx_view), column);
1932
1933 g_object_set(G_OBJECT(d->map.gpx_view), "has-tooltip", TRUE, NULL);
1934 g_signal_connect(G_OBJECT(d->map.gpx_view), "query-tooltip", G_CALLBACK(_row_tooltip_setup), self);
1935
1936 // avoid ugly console pixman messages due to headers
1937 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(d->map.gpx_view), FALSE);
1938 GtkWidget *w = dt_ui_scroll_wrap(GTK_WIDGET(d->map.gpx_view), 100,
1939 "plugins/lighttable/geotagging/heighttracklist", DT_UI_RESIZE_DYNAMIC);
1940 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(d->map.gpx_view), TRUE);
1941 gtk_box_pack_start(GTK_BOX(d->map.gpx_section), w, TRUE, TRUE, 0);
1942
1943 grid = GTK_GRID(gtk_grid_new());
1944 gtk_grid_set_column_spacing(grid, DT_GUI_BOX_SPACING);
1945 line = 0;
1946
1947 d->map.preview_button = gtk_check_button_new_with_label(_("preview images"));
1948 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(d->map.preview_button), TRUE);
1949 gtk_widget_set_sensitive(d->map.preview_button, FALSE);
1950 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(d->map.preview_button))), PANGO_ELLIPSIZE_END);
1951 gtk_grid_attach(grid, d->map.preview_button, 0, line, 1, 1);
1952 gtk_widget_set_tooltip_text(d->map.preview_button, _("show on map matching images"));
1953 g_signal_connect(GTK_TOGGLE_BUTTON(d->map.preview_button), "toggled", G_CALLBACK(_images_preview_toggled), self);
1954
1955 d->map.select_button = dt_action_button_new(self, N_("select images"), _select_images, self,
1956 _("select matching images"), 0, 0);
1957 gtk_widget_set_hexpand(d->map.select_button, TRUE);
1958 gtk_widget_set_sensitive(d->map.select_button, FALSE);
1959 gtk_grid_attach(grid, d->map.select_button, 1, line, 1, 1);
1960
1961 d->map.nb_imgs_label = dt_ui_label_new("0/0");
1962 gtk_widget_set_halign(d->map.nb_imgs_label, GTK_ALIGN_END);
1963 gtk_widget_set_tooltip_text(GTK_WIDGET(d->map.nb_imgs_label),
1964 _("number of matching images versus selected images"));
1965 gtk_grid_attach(grid, d->map.nb_imgs_label, 2, line++, 1, 1);
1966
1967 d->map.apply_gpx_button = dt_action_button_new(self, N_("apply geo-location"), _apply_gpx, self,
1968 _("apply geo-location to matching images"), 0, 0);
1969 gtk_widget_set_hexpand(d->map.apply_gpx_button, TRUE);
1970 gtk_widget_set_sensitive(d->map.apply_gpx_button, FALSE);
1971 gtk_grid_attach(grid, d->map.apply_gpx_button, 0, line++, 3, 1);
1972
1973 gtk_box_pack_start(GTK_BOX(d->map.gpx_section), GTK_WIDGET(grid), TRUE, TRUE, 0);
1974
1975 d->map.view = FALSE;
1976 gtk_widget_show_all(self->widget);
1977 gtk_widget_set_no_show_all(self->widget, TRUE);
1978 _update_layout(self);
1979 }
1980#endif
1981 d->imgid = UNKNOWN_IMAGE;
1982 d->datetime = d->datetime0 = _get_image_datetime(self);
1983 if(d->datetime)
1984 g_date_time_ref(d->datetime);
1985 _display_datetime(&d->dt0, d->datetime0, FALSE, self);
1986 _display_datetime(&d->dt, d->datetime, TRUE, self);
1987 d->offset = 0;
1988 _display_offset(d->offset, TRUE, self);
1989
1990 for(int i = 0; i < DT_GEOTAG_PARTS_NB; i++)
1991 {
1992 g_signal_connect(d->dt.widget[i], "changed", G_CALLBACK(_datetime_entry_changed), self);
1993 g_signal_connect(d->dt.widget[i], "key-press-event", G_CALLBACK(_datetime_key_pressed), self);
1994 g_signal_connect(d->dt.widget[i], "scroll-event", G_CALLBACK(_datetime_scroll_over), self);
1995 }
1997 G_CALLBACK(_mouse_over_image_callback), self);
1999 G_CALLBACK(_selection_changed_callback), self);
2001 G_CALLBACK(_image_info_changed), self);
2003 G_CALLBACK(_dt_pref_change_callback), self);
2004#ifdef HAVE_MAP
2005 if(dt_conf_get_bool("/views/map/enable"))
2006 {
2008 G_CALLBACK(_view_changed), self);
2010 G_CALLBACK(_geotag_changed), self);
2011 }
2012#endif
2013
2015 gtk_widget_show_all(self->widget);
2016 gtk_widget_set_no_show_all(self->widget, TRUE);
2017}
2018
2020{
2025#ifdef HAVE_MAP
2028#endif
2029 if(IS_NULL_PTR(self->data)) return;
2030
2032 g_list_free_full(d->timezones, free_tz_tuple);
2033 d->timezones = NULL;
2034 g_time_zone_unref(d->tz_camera);
2035 if(d->datetime)
2036 g_date_time_unref(d->datetime);
2037 if(d->datetime0)
2038 g_date_time_unref(d->datetime0);
2039
2040 if(d->imgs)
2041 {
2042#ifdef HAVE_MAP
2043 if(dt_conf_get_bool("/views/map/enable"))
2044 {
2046 }
2047#endif
2048 g_list_free_full(d->imgs, dt_free_gpointer);
2049 d->imgs = NULL;
2050 }
2051 d->imgs = NULL;
2052 d->imgs = 0;
2053#ifdef HAVE_MAP
2054 if(dt_conf_get_bool("/views/map/enable"))
2055 {
2059 }
2060#endif
2061 dt_free(self->data);
2062 self->data = NULL;
2063}
2064
2065// clang-format off
2066// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2067// vim: shiftwidth=2 expandtab tabstop=2 cindent
2068// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2069// clang-format on
void dt_accels_disconnect_on_text_input(GtkWidget *widget)
Disconnect accels while a text or search entry has the focus, and reconnect them when it loses it....
Handle default and user-set shortcuts (accelerators)
int32_t dt_act_on_get_first_image()
Definition act_on.c:67
GtkWidget * dt_gui_main_window(void)
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:283
GtkWidget * dtgtk_button_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
Definition button.c:135
#define MAX_LINE_LENGTH
Definition colorchart.c:41
static const float x
const int t
const float v
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
int dt_conf_get_bool(const char *name)
const char * dt_confgen_get_tooltip(const char *name)
Translated long description of name, borrowed and valid for the process lifetime. NULL when name is n...
gchar * dt_conf_get_string(const char *name)
Read the stored string for name as a private copy.
void dt_conf_set_string(const char *name, const char *val)
void dt_conf_set_folder_from_file_chooser(const char *name, GtkFileChooser *chooser)
const char * dt_confgen_get_label(const char *name)
Translated short description of name, borrowed and valid for the process lifetime....
gboolean dt_conf_get_folder_to_file_chooser(const char *name, GtkFileChooser *chooser)
void dt_image_get_datetime(const int32_t imgid, char *datetime)
void dt_gui_flow_box_as_layout(GtkFlowBox *box)
Definition container.c:98
int32_t dt_control_get_mouse_over_id()
Definition control.c:986
void dt_control_gpx_apply(const gchar *filename, int32_t filmid, const gchar *tz, GList *imgs)
void dt_control_datetime(const GTimeSpan offset, const char *datetime, GList *imgs)
struct dt_view_manager_t * dt_view_manager_get_global(void)
Definition darktable.c:599
gboolean dt_datetime_img_to_exif(char *exif, const size_t exif_size, const dt_image_t *img)
Definition datetime.c:255
gboolean dt_datetime_gdatetime_to_exif(char *exif, const size_t exif_size, GDateTime *gdt)
Definition datetime.c:281
GDateTime * dt_datetime_exif_to_gdatetime(const char *exif, const GTimeZone *tz)
Definition datetime.c:260
GTimeZone * dt_datetime_utc_tz(void)
Definition datetime.c:40
#define DT_DATETIME_LENGTH
Definition datetime.h:38
GtkWidget * preview
what the selected row actually captures
GHashTable * selected
set of checked row labels, mirrored to conf on every change
GtkWidget * view
the tree view
const int res
Definition dtpthread.h:351
void dt_loc_get_datadir(char *datadir, size_t bufsize)
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:118
@ MAP_DISPLAY_THUMB
Definition geo.h:31
@ MAP_DISPLAY_TRACK
Definition geo.h:29
static gboolean _timezone_focus_out(GtkWidget *entry, GdkEventKey *event, dt_lib_module_t *self)
static void _apply_gpx(GtkWidget *widget, dt_lib_module_t *self)
Definition geotagging.c:753
static GDateTime * _localtime_text_to_utc_timeval(const char *date_time, GTimeZone *tz_camera, GTimeZone *tz_utc, GTimeSpan offset)
Definition geotagging.c:260
static void _choose_gpx_callback(GtkWidget *widget, dt_lib_module_t *self)
Definition geotagging.c:976
static void _refresh_display_all_tracks(dt_lib_module_t *self)
Definition geotagging.c:574
static void free_tz_tuple(gpointer data)
Definition geotagging.c:169
static void _setup_selected_images_list(dt_lib_module_t *self)
Definition geotagging.c:932
static void _view_changed(gpointer instance, dt_view_t *old_view, dt_view_t *new_view, dt_lib_module_t *self)
Definition geotagging.c:778
static gboolean _update_map_box(const guint segid, GList *pts, dt_lib_module_t *self)
Definition geotagging.c:465
static gboolean _row_tooltip_setup(GtkWidget *view, gint x, gint y, gboolean kb_mode, GtkTooltip *tooltip, dt_lib_module_t *self)
Definition geotagging.c:818
static gboolean _timezone_key_pressed(GtkWidget *entry, GdkEventKey *event, dt_lib_module_t *self)
static void _show_milliseconds(dt_lib_geotagging_t *d)
static int _sort_timezones(gconstpointer a, gconstpointer b)
static void _refresh_selected_images_datetime(dt_lib_module_t *self)
Definition geotagging.c:805
static void _select_images(GtkWidget *widget, dt_lib_module_t *self)
Definition geotagging.c:653
static void _apply_offset_callback(GtkWidget *widget, dt_lib_module_t *self)
Definition geotagging.c:205
static GtkWidget * _gui_init_datetime(dt_lib_datetime_t *dt, const int type, dt_lib_module_t *self)
static void _refresh_displayed_images(dt_lib_module_t *self)
Definition geotagging.c:449
static GDateTime * _get_image_datetime(dt_lib_module_t *self)
static void _refresh_track_list(dt_lib_module_t *self)
Definition geotagging.c:667
static void _update_layout(dt_lib_module_t *self)
Definition geotagging.c:771
static void _track_seg_toggled(GtkCellRendererToggle *cell_renderer, gchar *path_str, dt_lib_module_t *self)
Definition geotagging.c:597
static void _display_datetime(dt_lib_datetime_t *dtw, GDateTime *datetime, const gboolean lock, dt_lib_module_t *self)
static GtkWidget * _set_up_label(const char *name, const int align, GtkWidget *grid, const int col, const int line, const int ellipsize)
Definition geotagging.c:238
void gui_cleanup(dt_lib_module_t *self)
static void _preview_gpx_file(GtkWidget *widget, dt_lib_module_t *self)
Definition geotagging.c:848
static void _selection_changed_callback(gpointer instance, dt_lib_module_t *self)
static void _datetime_entry_changed(GtkWidget *entry, dt_lib_module_t *self)
static gchar * _utc_timeval_to_utc_text(GDateTime *utc_dt, const gboolean full)
Definition geotagging.c:300
static gboolean _datetime_scroll_over(GtkWidget *w, GdkEventScroll *event, dt_lib_module_t *self)
static void _display_offset(const GTimeSpan offset_int, const gboolean valid, dt_lib_module_t *self)
static void _apply_datetime_callback(GtkWidget *widget, dt_lib_module_t *self)
Definition geotagging.c:213
static gchar * _datetime_tooltip(GDateTime *start, GDateTime *end, GTimeZone *tz)
Definition geotagging.c:306
static void _mouse_over_image_callback(gpointer instance, dt_lib_module_t *self)
static void _images_preview_toggled(GtkToggleButton *button, dt_lib_module_t *self)
Definition geotagging.c:662
static void _dt_pref_change_callback(gpointer instance, dt_lib_module_t *self)
static gint _cmp_imgid_asc(gconstpointer a, gconstpointer b)
Definition geotagging.c:927
GdkRGBA color[]
Definition geotagging.c:541
static void _show_gpx_tracks(dt_lib_module_t *self)
Definition geotagging.c:703
static gboolean _completion_match_func(GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter, gpointer user_data)
#define DT_GEOTAG_PARTS_NB
Definition geotagging.c:87
static GDateTime * _read_datetime_entry(dt_lib_module_t *self)
dt_tracks_cols_t
Definition geotagging.c:108
@ DT_GEO_TRACKS_DATETIME
Definition geotagging.c:110
@ DT_GEO_TRACKS_NUM_COLS
Definition geotagging.c:115
@ DT_GEO_TRACKS_POINTS
Definition geotagging.c:111
@ DT_GEO_TRACKS_ACTIVE
Definition geotagging.c:109
@ DT_GEO_TRACKS_TOOLTIP
Definition geotagging.c:114
@ DT_GEO_TRACKS_SEGID
Definition geotagging.c:113
@ DT_GEO_TRACKS_IMAGES
Definition geotagging.c:112
static void _geotag_changed(gpointer instance, GList *imgs, const int locid, dt_lib_module_t *self)
Definition geotagging.c:794
static GList * _lib_geotagging_get_timezones(void)
uint32_t container(dt_lib_module_t *self)
Definition geotagging.c:194
GtkTreeViewColumn * _new_tree_text_column(const char *name, const gboolean expand, const float xalign, const int m_col, const int ellipsize)
static GList * _get_images_on_active_tracks(dt_lib_module_t *self)
Definition geotagging.c:421
static void _update_buttons(dt_lib_module_t *self)
Definition geotagging.c:405
static void _remove_images_from_map(dt_lib_module_t *self)
Definition geotagging.c:320
static void _refresh_images_displayed_on_track(const int segid, const gboolean active, dt_lib_module_t *self)
Definition geotagging.c:334
static void _refresh_image_datetime(dt_lib_module_t *self)
static void _update_nb_images(dt_lib_module_t *self)
Definition geotagging.c:382
void gui_init(dt_lib_module_t *self)
static int _count_images_per_track(dt_gpx_track_segment_t *t, dt_gpx_track_segment_t *n, dt_lib_module_t *self)
Definition geotagging.c:273
int position()
Definition geotagging.c:199
static void _image_info_changed(gpointer instance, gpointer imgs, dt_lib_module_t *self)
const char ** views(dt_lib_module_t *self)
Definition geotagging.c:184
static gboolean _lib_geotagging_filter_gpx(const GtkFileFilterInfo *filter_info, gpointer data)
Definition geotagging.c:224
static void _all_tracks_toggled(GtkTreeViewColumn *column, dt_lib_module_t *self)
Definition geotagging.c:625
static gchar * _utc_timeval_to_localtime_text(GDateTime *utc_dt, GTimeZone *tz_camera, const gboolean full)
Definition geotagging.c:251
static void _toggle_lock_button_callback(GtkToggleButton *button, dt_lib_module_t *self)
static gboolean _refresh_display_track(const gboolean active, const int segid, dt_lib_module_t *self)
Definition geotagging.c:548
static void _remove_tracks_from_map(dt_lib_module_t *self)
Definition geotagging.c:518
static void _new_datetime(GDateTime *datetime, dt_lib_module_t *self)
static void _timezone_save(dt_lib_module_t *self)
static gboolean _datetime_key_pressed(GtkWidget *entry, GdkEventKey *event, dt_lib_module_t *self)
void dt_gpx_destroy(struct dt_gpx_t *gpx)
Definition gpx.c:153
GList * dt_gpx_get_trkpts(struct dt_gpx_t *gpx, const guint segid)
Definition gpx.c:449
gboolean dt_gpx_get_location(struct dt_gpx_t *gpx, GDateTime *timestamp, dt_image_geoloc_t *geoloc)
Definition gpx.c:171
dt_gpx_t * dt_gpx_new(const gchar *filename)
Definition gpx.c:89
GList * dt_gpx_get_trkseg(struct dt_gpx_t *gpx)
Definition gpx.c:444
#define UNKNOWN_IMAGE
Definition image.h:78
const char * tooltip
Definition image.h:310
dt_image_t * dt_image_cache_get(const int32_t imgid, char mode)
void dt_image_cache_read_release(const dt_image_t *img)
const char * model
GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition label.c:114
GtkWidget * dt_ui_label_new(const gchar *str)
Definition label.c:125
GtkWidget * dt_action_button_new(dt_lib_module_t *self, const gchar *label, gpointer callback, gpointer data, const gchar *tooltip, guint accel_key, GdkModifierType mods)
Definition lib.c:1336
_lib_location_type_t type
Definition location.c:1
#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 macros.h:96
static void dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
Definition mem_alloc.h:184
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
char * key
#define DT_MODULE(MODVER)
Instantiate the version handshake every module must export. Use once, at file scope,...
void dt_osx_disallow_fullscreen(GtkWidget *widget)
Definition osx.mm:105
#define DT_PATH_MAX
Buffer size for a filesystem path anywhere in Ansel.
Definition paths.h:57
const char * name
Definition pdf.h:90
GtkWidget * dt_ui_scroll_wrap(GtkWidget *w, gint min_size, const char *config_str, dt_ui_resize_mode_t mode)
Wrap a scrollable content widget in a recessed, vertically resizable scrolled window.
@ DT_UI_RESIZE_DYNAMIC
Definition scroll_wrap.h:42
struct dt_selection_t * dt_selection_get_global(void)
Definition selection.c:80
GList * dt_selection_get_list(struct dt_selection_t *selection)
Definition selection.c:185
void dt_selection_select_list(struct dt_selection_t *selection, const GList *const l)
Definition selection.c:330
void dt_selection_clear(dt_selection_t *selection)
Definition selection.c:271
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:407
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_GEOTAG_CHANGED
This signal is raised when a geotag is added/deleted/changed
Definition signal.h:139
@ DT_SIGNAL_MOUSE_OVER_IMAGE_CHANGE
This signal is raised when mouse hovers over image thumbs both on lighttable and in the filmstrip....
Definition signal.h:62
@ DT_SIGNAL_IMAGE_INFO_CHANGED
This signal is raised when any of image info has changed
Definition signal.h:147
@ DT_SIGNAL_PREFERENCES_CHANGE
This signal is raised after preferences have been changed no parameters no return.
Definition signal.h:276
@ DT_SIGNAL_SELECTION_CHANGED
This signal is raised when the selection is changed no param, no returned value.
Definition signal.h:130
@ DT_SIGNAL_VIEWMANAGER_VIEW_CHANGED
This signal is raised by viewmanager when a view has changed. 1 : dt_view_t * the old view 2 : dt_vie...
Definition signal.h:84
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
static const char *const day[7]
Definition strptime.c:97
static char utc[]
Definition strptime.c:92
Definition gpx.c:49
uint32_t segid
Definition gpx.c:59
double latitude
Definition image.h:332
double longitude
Definition image.h:332
GtkWidget * sign
Definition geotagging.c:92
GtkWidget * widget[7]
Definition geotagging.c:91
dt_lib_tracks_t * tracks
Definition geotagging.c:146
GtkWidget * apply_datetime
Definition geotagging.c:134
GtkWidget * select_button
Definition geotagging.c:150
struct dt_lib_geotagging_t::@50 map
GtkWidget * gpx_view
Definition geotagging.c:144
GtkWidget * apply_offset
Definition geotagging.c:132
GDateTime * datetime
Definition geotagging.c:125
GDateTime * datetime0
Definition geotagging.c:126
dt_lib_datetime_t dt0
Definition geotagging.c:123
dt_lib_datetime_t of
Definition geotagging.c:124
GtkWidget * preview_button
Definition geotagging.c:149
GtkWidget * timezone
Definition geotagging.c:135
GtkTreeViewColumn * sel_tracks
Definition geotagging.c:151
GtkWidget * apply_gpx_button
Definition geotagging.c:149
GtkWidget * nb_imgs_label
Definition geotagging.c:150
GtkWidget * timezone_changed
Definition geotagging.c:137
GtkWidget * lock_offset
Definition geotagging.c:133
GTimeZone * tz_camera
Definition geotagging.c:139
GtkWidget * gpx_section
Definition geotagging.c:149
dt_map_box_t map_box
Definition geotagging.c:147
GtkWidget * gpx_file
Definition geotagging.c:144
dt_lib_datetime_t dt
Definition geotagging.c:122
struct dt_gpx_t * gpx
Definition geotagging.c:145
GtkWidget * gpx_button
Definition geotagging.c:138
GModule *void * data
Definition lib.h:79
GtkWidget * widget
Definition lib.h:83
dt_map_box_t map_box
Definition geotagging.c:99
dt_lib_tracks_data_t td[1]
Definition geotagging.c:104
dt_image_geoloc_t gl
Definition geotagging.c:162
gchar dt[DT_DATETIME_LENGTH]
Definition geotagging.c:160
uint32_t segid
Definition geotagging.c:159
gboolean counted
Definition geotagging.c:161
int32_t imgid
Definition geotagging.c:158
GObject * image
Definition geotagging.c:163
struct dt_view_manager_t::@60::@64 map
char module_name[64]
Definition view.h:155
char * name
Definition geotagging.c:84
char * display
Definition geotagging.c:84
dt_pthread_mutex_t lock
Definition supervisor.c:123
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
GtkWidget * dtgtk_togglebutton_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
GObject * dt_view_map_add_marker(const dt_view_manager_t *vm, dt_geo_map_display_t type, GList *points)
Definition view.c:1368
void dt_view_map_center_on_bbox(const dt_view_manager_t *vm, gdouble lon1, gdouble lat1, gdouble lon2, gdouble lat2)
Definition view.c:1350
gboolean dt_view_map_remove_marker(const dt_view_manager_t *vm, dt_geo_map_display_t type, GObject *marker)
Definition view.c:1375
gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y)
GdkEventMask dt_widget_scroll_mask(void)
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
void dtgtk_cairo_paint_directory(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_lock(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
@ CPF_NONE
@ DT_UI_CONTAINER_PANEL_LEFT_CENTER