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