]> git.street.me.uk Git - andy/viking.git/blob - src/viktrwlayer_geotag.c
Carto CSS support to generate Mapnik XML.
[andy/viking.git] / src / viktrwlayer_geotag.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3  * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
4  *
5  * Copyright (C) 2011, Rob Norris <rw_norris@hotmail.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  */
22 /*
23  *  Similar to the track and trackpoint properties dialogs,
24  *   this is made a separate file for ease of grouping related stuff together
25  */
26 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif
29 #include <math.h>
30 #include <time.h>
31 #include <string.h>
32 #include <stdlib.h>
33 #include <gtk/gtk.h>
34 #include <glib/gi18n.h>
35 #include "viking.h"
36 #include "vikfilelist.h"
37 #include "geotag_exif.h"
38 #include "thumbnails.h"
39 #include "background.h"
40
41 // Function taken from GPSCorrelate 1.6.1
42 // ConvertToUnixTime Copyright 2005 Daniel Foote. GPL2+
43
44 #define EXIF_DATE_FORMAT "%d:%d:%d %d:%d:%d"
45
46 time_t ConvertToUnixTime(char* StringTime, char* Format, int TZOffsetHours, int TZOffsetMinutes)
47 {
48         /* Read the time using the specified format.
49          * The format and string being read from must
50          * have the most significant time on the left,
51          * and the least significant on the right:
52          * ie, Year on the left, seconds on the right. */
53
54         /* Sanity check... */
55         if (StringTime == NULL || Format == NULL)
56         {
57                 return 0;
58         }
59
60         /* Define and set up our structure. */
61         struct tm Time;
62         Time.tm_wday = 0;
63         Time.tm_yday = 0;
64         Time.tm_isdst = -1;
65
66         /* Read out the time from the string using our format. */
67         sscanf(StringTime, Format, &Time.tm_year, &Time.tm_mon,
68                         &Time.tm_mday, &Time.tm_hour,
69                         &Time.tm_min, &Time.tm_sec);
70
71         /* Adjust the years for the mktime function to work. */
72         Time.tm_year -= 1900;
73         Time.tm_mon  -= 1;
74
75         /* Add our timezone offset to the time.
76          * We don't check to see if it overflowed anything;
77          * mktime does this and fixes it for us. */
78         /* Note also that we SUBTRACT these times. We want the
79          * result to be in UTC. */
80
81         Time.tm_hour -= TZOffsetHours;
82         Time.tm_min  -= TZOffsetMinutes;
83
84         /* Calculate and return the unix time. */
85         return mktime(&Time);
86 }
87
88 // GPSCorrelate END
89
90 typedef struct {
91         GtkWidget *dialog;
92         VikFileList *files;
93         VikTrwLayer *vtl;    // to pass on
94         VikWaypoint *wpt;    // Use specified waypoint or otherwise the track(s) if NULL
95         VikTrack *track;     // Use specified track or all tracks if NULL
96         GtkCheckButton *create_waypoints_b;
97         GtkLabel *overwrite_waypoints_l; // Referenced so the sensitivity can be changed
98         GtkCheckButton *overwrite_waypoints_b;
99         GtkCheckButton *write_exif_b;
100         GtkLabel *overwrite_gps_exif_l; // Referenced so the sensitivity can be changed
101         GtkCheckButton *overwrite_gps_exif_b;
102         GtkLabel *no_change_mtime_l; // Referenced so the sensitivity can be changed
103         GtkCheckButton *no_change_mtime_b;
104         GtkCheckButton *interpolate_segments_b;
105         GtkEntry *time_zone_b; // TODO consider a more user friendly tz widget eg libtimezonemap or similar
106         GtkEntry *time_offset_b;
107 } GeoTagWidgets;
108
109 static GeoTagWidgets *geotag_widgets_new()
110 {
111         GeoTagWidgets *widgets = g_malloc0(sizeof(GeoTagWidgets));
112         return widgets;
113 }
114
115 static void geotag_widgets_free ( GeoTagWidgets *widgets )
116 {
117         // Need to free VikFileList??
118         g_free(widgets);
119 }
120
121 typedef struct {
122         gboolean create_waypoints;
123         gboolean overwrite_waypoints;
124         gboolean write_exif;
125         gboolean overwrite_gps_exif;
126         gboolean no_change_mtime;
127         gboolean interpolate_segments;
128         gint time_offset;
129         gint TimeZoneHours;
130         gint TimeZoneMins;
131 } option_values_t;
132
133 typedef struct {
134         VikTrwLayer *vtl;
135         gchar *image;
136         VikWaypoint *wpt;    // Use specified waypoint or otherwise the track(s) if NULL
137         VikTrack *track;     // Use specified track or all tracks if NULL
138         // User options...
139         option_values_t ov;
140         GList *files;
141         time_t PhotoTime;
142         // Store answer from interpolation for an image
143         gboolean found_match;
144         VikCoord coord;
145         gdouble altitude;
146         // If anything has changed
147         gboolean redraw;
148 } geotag_options_t;
149
150 #define VIK_SETTINGS_GEOTAG_CREATE_WAYPOINT      "geotag_create_waypoints"
151 #define VIK_SETTINGS_GEOTAG_OVERWRITE_WAYPOINTS  "geotag_overwrite_waypoints"
152 #define VIK_SETTINGS_GEOTAG_WRITE_EXIF           "geotag_write_exif"
153 #define VIK_SETTINGS_GEOTAG_OVERWRITE_GPS_EXIF   "geotag_overwrite_gps"
154 #define VIK_SETTINGS_GEOTAG_NO_CHANGE_MTIME      "geotag_no_change_mtime"
155 #define VIK_SETTINGS_GEOTAG_INTERPOLATE_SEGMENTS "geotag_interpolate_segments"
156 #define VIK_SETTINGS_GEOTAG_TIME_OFFSET          "geotag_time_offset"
157 #define VIK_SETTINGS_GEOTAG_TIME_OFFSET_HOURS    "geotag_time_offset_hours"
158 #define VIK_SETTINGS_GEOTAG_TIME_OFFSET_MINS     "geotag_time_offset_mins"
159
160 static void save_default_values ( option_values_t default_values )
161 {
162         a_settings_set_boolean ( VIK_SETTINGS_GEOTAG_CREATE_WAYPOINT, default_values.create_waypoints );
163         a_settings_set_boolean ( VIK_SETTINGS_GEOTAG_OVERWRITE_WAYPOINTS, default_values.overwrite_waypoints );
164         a_settings_set_boolean ( VIK_SETTINGS_GEOTAG_WRITE_EXIF, default_values.write_exif );
165         a_settings_set_boolean ( VIK_SETTINGS_GEOTAG_OVERWRITE_GPS_EXIF, default_values.overwrite_gps_exif );
166         a_settings_set_boolean ( VIK_SETTINGS_GEOTAG_NO_CHANGE_MTIME, default_values.no_change_mtime );
167         a_settings_set_boolean ( VIK_SETTINGS_GEOTAG_INTERPOLATE_SEGMENTS, default_values.interpolate_segments );
168         a_settings_set_integer ( VIK_SETTINGS_GEOTAG_TIME_OFFSET, default_values.time_offset );
169         a_settings_set_integer ( VIK_SETTINGS_GEOTAG_TIME_OFFSET_HOURS, default_values.TimeZoneHours );
170         a_settings_set_integer ( VIK_SETTINGS_GEOTAG_TIME_OFFSET_MINS, default_values.TimeZoneMins );
171 }
172
173 static option_values_t get_default_values ( )
174 {
175         option_values_t default_values;
176         if ( ! a_settings_get_boolean ( VIK_SETTINGS_GEOTAG_CREATE_WAYPOINT, &default_values.create_waypoints ) )
177                 default_values.create_waypoints = TRUE;
178         if ( ! a_settings_get_boolean ( VIK_SETTINGS_GEOTAG_OVERWRITE_WAYPOINTS, &default_values.overwrite_waypoints ) )
179                 default_values.overwrite_waypoints = TRUE;
180         if ( ! a_settings_get_boolean ( VIK_SETTINGS_GEOTAG_WRITE_EXIF, &default_values.write_exif ) )
181                 default_values.write_exif = TRUE;
182         if ( ! a_settings_get_boolean ( VIK_SETTINGS_GEOTAG_OVERWRITE_GPS_EXIF, &default_values.overwrite_gps_exif ) )
183                 default_values.overwrite_gps_exif = FALSE;
184         if ( ! a_settings_get_boolean ( VIK_SETTINGS_GEOTAG_NO_CHANGE_MTIME, &default_values.no_change_mtime ) )
185                 default_values.no_change_mtime = TRUE;
186         if ( ! a_settings_get_boolean ( VIK_SETTINGS_GEOTAG_INTERPOLATE_SEGMENTS, &default_values.interpolate_segments ) )
187                 default_values.interpolate_segments = TRUE;
188         if ( ! a_settings_get_integer ( VIK_SETTINGS_GEOTAG_TIME_OFFSET, &default_values.time_offset ) )
189                 default_values.time_offset = 0;
190         if ( ! a_settings_get_integer ( VIK_SETTINGS_GEOTAG_TIME_OFFSET_HOURS, &default_values.TimeZoneHours ) )
191                 default_values.TimeZoneHours = 0;
192         if ( ! a_settings_get_integer ( VIK_SETTINGS_GEOTAG_TIME_OFFSET_MINS, &default_values.TimeZoneMins ) )
193                 default_values.TimeZoneMins = 0;
194         return default_values;
195 }
196
197 /**
198  * Correlate the image against the specified track
199  */
200 static void trw_layer_geotag_track ( const gpointer id, VikTrack *track, geotag_options_t *options )
201 {
202         // If already found match then don't need to check this track
203         if ( options->found_match )
204                 return;
205
206         VikTrackpoint *trkpt;
207         VikTrackpoint *trkpt_next;
208
209         GList *mytrkpt;
210         for ( mytrkpt = track->trackpoints; mytrkpt; mytrkpt = mytrkpt->next ) {
211
212                 // Do something for this trackpoint...
213
214                 trkpt = VIK_TRACKPOINT(mytrkpt->data);
215
216                 // is it exactly this point?
217                 if ( options->PhotoTime == trkpt->timestamp ) {
218                         options->coord = trkpt->coord;
219                         options->altitude = trkpt->altitude;
220                         options->found_match = TRUE;
221                         break;
222                 }
223
224                 // Now need two trackpoints, hence check next is available
225                 if ( !mytrkpt->next ) break;
226                 trkpt_next = VIK_TRACKPOINT(mytrkpt->next->data);
227
228                 // TODO need to use 'has_timestamp' property
229                 if ( trkpt->timestamp == trkpt_next->timestamp ) continue;
230                 if ( trkpt->timestamp > trkpt_next->timestamp ) continue;
231
232                 // When interpolating between segments, no need for any special segment handling
233                 if ( !options->ov.interpolate_segments )
234                         // Don't check between segments
235                         if ( trkpt_next->newsegment )
236                                 // Simply move on to consider next point
237                                 continue;
238
239                 // Too far
240                 if ( trkpt->timestamp > options->PhotoTime ) break;
241
242                 // Is is between this and the next point?
243                 if ( (options->PhotoTime > trkpt->timestamp) && (options->PhotoTime < trkpt_next->timestamp) ) {
244                         options->found_match = TRUE;
245                         // Interpolate
246                         /* Calculate the "scale": a decimal giving the relative distance
247                          * in time between the two points. Ie, a number between 0 and 1 -
248                          * 0 is the first point, 1 is the next point, and 0.5 would be
249                          * half way. */
250                         gdouble scale = (gdouble)trkpt_next->timestamp - (gdouble)trkpt->timestamp;
251                         scale = ((gdouble)options->PhotoTime - (gdouble)trkpt->timestamp) / scale;
252
253                         struct LatLon ll_result, ll1, ll2;
254
255                         vik_coord_to_latlon ( &(trkpt->coord), &ll1 );
256                         vik_coord_to_latlon ( &(trkpt_next->coord), &ll2 );
257
258                         ll_result.lat = ll1.lat + ((ll2.lat - ll1.lat) * scale);
259
260                         // NB This won't cope with going over the 180 degrees longitude boundary
261                         ll_result.lon = ll1.lon + ((ll2.lon - ll1.lon) * scale);
262
263                         // set coord
264                         vik_coord_load_from_latlon ( &(options->coord), VIK_COORD_LATLON, &ll_result );
265
266                         // Interpolate elevation
267                         options->altitude = trkpt->altitude + ((trkpt_next->altitude - trkpt->altitude) * scale);
268                         break;
269                 }
270         }
271 }
272
273 /**
274  * Simply align the images the waypoint position
275  */
276 static void trw_layer_geotag_waypoint ( geotag_options_t *options )
277 {
278         // Write EXIF if specified - although a fairly useless process if you've turned it off!
279         if ( options->ov.write_exif ) {
280                 gboolean has_gps_exif = FALSE;
281                 gchar* datetime = a_geotag_get_exif_date_from_file ( options->image, &has_gps_exif );
282                 // If image already has gps info - don't attempt to change it unless forced
283                 if ( options->ov.overwrite_gps_exif || !has_gps_exif ) {
284                         gint ans = a_geotag_write_exif_gps ( options->image, options->wpt->coord, options->wpt->altitude, options->ov.no_change_mtime );
285                         if ( ans != 0 ) {
286                                 gchar *message = g_strdup_printf ( _("Failed updating EXIF on %s"), options->image );
287                                 vik_window_statusbar_update ( VIK_WINDOW(VIK_GTK_WINDOW_FROM_LAYER(options->vtl)), message, VIK_STATUSBAR_INFO );
288                                 g_free ( message );
289                         }
290                 }
291                 g_free ( datetime );
292         }
293 }
294
295 /**
296  * Correlate the image to any track within the TrackWaypoint layer
297  */
298 static void trw_layer_geotag_process ( geotag_options_t *options )
299 {
300         if ( !options->vtl || !IS_VIK_LAYER(options->vtl) )
301                 return;
302
303         if ( !options->image )
304                 return;
305
306         if ( options->wpt ) {
307                 trw_layer_geotag_waypoint ( options );
308                 return;
309         }
310
311         gboolean has_gps_exif = FALSE;
312         gchar* datetime = a_geotag_get_exif_date_from_file ( options->image, &has_gps_exif );
313
314         if ( datetime ) {
315         
316                 // If image already has gps info - don't attempt to change it.
317                 if ( !options->ov.overwrite_gps_exif && has_gps_exif ) {
318                         if ( options->ov.create_waypoints ) {
319                                 // Create waypoint with file information
320                                 gchar *name = NULL;
321                                 VikWaypoint *wp = a_geotag_create_waypoint_from_file ( options->image, vik_trw_layer_get_coord_mode (options->vtl), &name );
322                                 if ( !wp ) {
323                                         // Couldn't create Waypoint
324                                         g_free ( datetime );
325                                         return;
326                                 }
327                                 if ( !name )
328                                         name = g_strdup ( a_file_basename ( options->image ) );
329
330                                 gboolean updated_waypoint = FALSE;
331
332                                 if ( options->ov.overwrite_waypoints ) {
333                                         VikWaypoint *current_wp = vik_trw_layer_get_waypoint ( options->vtl, name );
334                                         if ( current_wp ) {
335                                                 // Existing wp found, so set new position, comment and image
336                                                 current_wp = a_geotag_waypoint_positioned ( options->image, wp->coord, wp->altitude, &name, current_wp );
337                                                 updated_waypoint = TRUE;
338                                         }
339                                 }
340
341                                 if ( !updated_waypoint ) {
342                                         vik_trw_layer_filein_add_waypoint ( options->vtl, name, wp );
343                                 }
344
345                                 g_free ( name );
346                                 
347                                 // Mark for redraw
348                                 options->redraw = TRUE;
349                         }
350                         g_free ( datetime );
351                         return;
352                 }
353
354                 options->PhotoTime = ConvertToUnixTime ( datetime, EXIF_DATE_FORMAT, options->ov.TimeZoneHours, options->ov.TimeZoneMins);
355                 g_free ( datetime );
356                 
357                 // Apply any offset
358                 options->PhotoTime = options->PhotoTime + options->ov.time_offset;
359
360                 options->found_match = FALSE;
361
362                 if ( options->track ) {
363                         // Single specified track
364                         // NB Doesn't care about track id
365                         trw_layer_geotag_track ( NULL, options->track, options );
366                 }
367                 else {
368                         // Try all tracks
369                         GHashTable *tracks = vik_trw_layer_get_tracks ( options->vtl );
370                         if ( g_hash_table_size (tracks) > 0 ) {
371                                 g_hash_table_foreach ( tracks, (GHFunc) trw_layer_geotag_track, options );
372                         }
373                 }
374
375                 // Match found ?
376                 if ( options->found_match ) {
377
378                         if ( options->ov.create_waypoints ) {
379
380                                 gboolean updated_waypoint = FALSE;
381
382                                 if ( options->ov.overwrite_waypoints ) {
383                                 
384                                         // Update existing WP
385                                         // Find a WP with current name
386                                         gchar *name = NULL;
387                                         name = g_strdup ( a_file_basename ( options->image ) );
388                                         VikWaypoint *wp = vik_trw_layer_get_waypoint ( options->vtl, name );
389                                         if ( wp ) {
390                                                 // Found, so set new position, comment and image
391                                                 wp = a_geotag_waypoint_positioned ( options->image, options->coord, options->altitude, &name, wp );
392                                                 updated_waypoint = TRUE;
393                                         }
394                                         g_free ( name );
395                                 }
396
397                                 if ( !updated_waypoint ) {
398                                         // Create waypoint with found position
399                                         gchar *name = NULL;
400                                         VikWaypoint *wp = a_geotag_waypoint_positioned ( options->image, options->coord, options->altitude, &name, NULL );
401                                         if ( !name )
402                                                 name = g_strdup ( a_file_basename ( options->image ) );
403                                         vik_trw_layer_filein_add_waypoint ( options->vtl, name, wp );
404                                         g_free ( name );
405                                 }
406
407                                 // Mark for redraw
408                                 options->redraw = TRUE;
409                         }
410
411                         // Write EXIF if specified
412                         if ( options->ov.write_exif ) {
413                                 gint ans = a_geotag_write_exif_gps ( options->image, options->coord, options->altitude, options->ov.no_change_mtime );
414                                 if ( ans != 0 ) {
415                                         gchar *message = g_strdup_printf ( _("Failed updating EXIF on %s"), options->image );
416                                         vik_window_statusbar_update ( VIK_WINDOW(VIK_GTK_WINDOW_FROM_LAYER(options->vtl)), message, VIK_STATUSBAR_INFO );
417                                         g_free ( message );
418                                 }
419                         }
420                 }
421         }
422 }
423
424 /*
425  * Tidy up
426  */
427 static void trw_layer_geotag_thread_free ( geotag_options_t *gtd )
428 {
429         if ( gtd->files )
430                 g_list_free ( gtd->files );
431         g_free ( gtd );
432 }
433
434 /**
435  * Run geotagging process in a separate thread
436  */
437 static int trw_layer_geotag_thread ( geotag_options_t *options, gpointer threaddata )
438 {
439         guint total = g_list_length(options->files), done = 0;
440
441         // TODO decide how to report any issues to the user ...
442
443         // Foreach file attempt to geotag it
444         while ( options->files ) {
445                 options->image = (gchar *) ( options->files->data );
446                 trw_layer_geotag_process ( options );
447                 options->files = options->files->next;
448
449                 // Update thread progress and detect stop requests
450                 int result = a_background_thread_progress ( threaddata, ((gdouble) ++done) / total );
451                 if ( result != 0 )
452                         return -1; /* Abort thread */
453         }
454
455         if ( options->redraw ) {
456                 if ( IS_VIK_LAYER(options->vtl) ) {
457                         trw_layer_calculate_bounds_waypoints ( options->vtl );
458                         // Ensure any new images get shown
459                         trw_layer_verify_thumbnails ( options->vtl, NULL ); // NB second parameter not used ATM
460                         // Force redraw as verify only redraws if there are new thumbnails (they may already exist)
461                         vik_layer_emit_update ( VIK_LAYER(options->vtl) ); // NB Update from background
462                 }
463         }
464
465         return 0;
466 }
467
468 /**
469  * Parse user input from dialog response
470  */
471 static void trw_layer_geotag_response_cb ( GtkDialog *dialog, gint resp, GeoTagWidgets *widgets )
472 {
473         switch (resp) {
474     case GTK_RESPONSE_DELETE_EVENT: /* received delete event (not from buttons) */
475     case GTK_RESPONSE_REJECT:
476                 break;
477         default: {
478                 //GTK_RESPONSE_ACCEPT:
479                 // Get options
480                 geotag_options_t *options = g_malloc ( sizeof(geotag_options_t) );
481                 options->vtl = widgets->vtl;
482                 options->wpt = widgets->wpt;
483                 options->track = widgets->track;
484                 // Values extracted from the widgets:
485                 options->ov.create_waypoints = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->create_waypoints_b) );
486                 options->ov.overwrite_waypoints = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->overwrite_waypoints_b) );
487                 options->ov.write_exif = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->write_exif_b) );
488                 options->ov.overwrite_gps_exif = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->overwrite_gps_exif_b) );
489                 options->ov.no_change_mtime = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->no_change_mtime_b) );
490                 options->ov.interpolate_segments = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->interpolate_segments_b) );
491                 options->ov.TimeZoneHours = 0;
492                 options->ov.TimeZoneMins = 0;
493                 const gchar* TZString = gtk_entry_get_text(GTK_ENTRY(widgets->time_zone_b));
494                 /* Check the string. If there is a colon, then (hopefully) it's a time in xx:xx format.
495                  * If not, it's probably just a +/-xx format. In all other cases,
496                  * it will be interpreted as +/-xx, which, if given a string, returns 0. */
497                 if (strstr(TZString, ":")) {
498                         /* Found colon. Split into two. */
499                         sscanf(TZString, "%d:%d", &options->ov.TimeZoneHours, &options->ov.TimeZoneMins);
500                         if (options->ov.TimeZoneHours < 0)
501                                 options->ov.TimeZoneMins *= -1;
502                 } else {
503                         /* No colon. Just parse. */
504                         options->ov.TimeZoneHours = atoi(TZString);
505                 }
506                 options->ov.time_offset = atoi ( gtk_entry_get_text ( GTK_ENTRY(widgets->time_offset_b) ) );
507
508                 options->redraw = FALSE;
509
510                 // Save settings for reuse
511                 save_default_values ( options->ov );
512
513                 options->files = g_list_copy ( vik_file_list_get_files ( widgets->files ) );
514
515                 gint len = g_list_length ( options->files );
516                 gchar *tmp = g_strdup_printf ( _("Geotagging %d Images..."), len );
517
518                 // Processing lots of files can take time - so run a background effort
519                 a_background_thread ( BACKGROUND_POOL_LOCAL,
520                                       VIK_GTK_WINDOW_FROM_LAYER(options->vtl),
521                                       tmp,
522                                       (vik_thr_func) trw_layer_geotag_thread,
523                                       options,
524                                       (vik_thr_free_func) trw_layer_geotag_thread_free,
525                                       NULL,
526                                       len );
527
528                 g_free ( tmp );
529
530                 break;
531         }
532         }
533         geotag_widgets_free ( widgets );
534         gtk_widget_destroy ( GTK_WIDGET(dialog) );
535 }
536
537 /**
538  * Handle widget sensitivities
539  */
540 static void write_exif_b_cb ( GtkWidget *gw, GeoTagWidgets *gtw )
541 {
542         // Overwriting & file modification times are irrelevant if not going to write EXIF!
543         if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(gtw->write_exif_b) ) ) {
544                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->overwrite_gps_exif_b), TRUE );
545                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->overwrite_gps_exif_l), TRUE );
546                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->no_change_mtime_b), TRUE );
547                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->no_change_mtime_l), TRUE );
548         }
549         else {
550                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->overwrite_gps_exif_b), FALSE );
551                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->overwrite_gps_exif_l), FALSE );
552                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->no_change_mtime_b), FALSE );
553                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->no_change_mtime_l), FALSE );
554         }
555 }
556
557 static void create_waypoints_b_cb ( GtkWidget *gw, GeoTagWidgets *gtw )
558 {
559         // Overwriting waypoints are irrelevant if not going to create them!
560         if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(gtw->create_waypoints_b) ) ) {
561                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->overwrite_waypoints_b), TRUE );
562                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->overwrite_waypoints_l), TRUE );
563         }
564         else {
565                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->overwrite_waypoints_b), FALSE );
566                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->overwrite_waypoints_l), FALSE );
567         }
568 }
569
570 /**
571  * trw_layer_geotag_dialog:
572  * @parent: The Window of the calling process
573  * @vtl: The VikTrwLayer to use for correlating images to tracks
574  * @track: Optional - The particular track to use (if specified) for correlating images
575  * @track_name: Optional - The name of specified track to use
576  */
577 void trw_layer_geotag_dialog ( GtkWindow *parent,
578                                VikTrwLayer *vtl,
579                                VikWaypoint *wpt,
580                                VikTrack *track )
581 {
582         GeoTagWidgets *widgets = geotag_widgets_new();
583
584         widgets->dialog = gtk_dialog_new_with_buttons ( _("Geotag Images"),
585                                                                                                         parent,
586                                                                                                         GTK_DIALOG_DESTROY_WITH_PARENT,
587                                                                                                         GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
588                                                                                                         GTK_STOCK_OK,     GTK_RESPONSE_ACCEPT,
589                                                                                                         NULL );
590         GtkFileFilter *filter = gtk_file_filter_new ();
591         gtk_file_filter_set_name ( filter, _("JPG") );
592         gtk_file_filter_add_mime_type ( filter, "image/jpeg");
593
594         widgets->files = VIK_FILE_LIST(vik_file_list_new ( _("Images"), filter ));
595         widgets->vtl = vtl;
596         widgets->wpt = wpt;
597         widgets->track = track;
598         widgets->create_waypoints_b = GTK_CHECK_BUTTON ( gtk_check_button_new () );
599         widgets->overwrite_waypoints_l = GTK_LABEL ( gtk_label_new ( _("Overwrite Existing Waypoints:") ) );
600         widgets->overwrite_waypoints_b = GTK_CHECK_BUTTON ( gtk_check_button_new () );
601         widgets->write_exif_b = GTK_CHECK_BUTTON ( gtk_check_button_new () );
602         widgets->overwrite_gps_exif_l = GTK_LABEL ( gtk_label_new ( _("Overwrite Existing GPS Information:") ) );
603         widgets->overwrite_gps_exif_b = GTK_CHECK_BUTTON ( gtk_check_button_new () );
604         widgets->no_change_mtime_l = GTK_LABEL ( gtk_label_new ( _("Keep File Modification Timestamp:") ) );
605         widgets->no_change_mtime_b = GTK_CHECK_BUTTON ( gtk_check_button_new () );
606         widgets->interpolate_segments_b = GTK_CHECK_BUTTON ( gtk_check_button_new () );
607         widgets->time_zone_b = GTK_ENTRY ( gtk_entry_new () );
608         widgets->time_offset_b = GTK_ENTRY ( gtk_entry_new () );
609
610         gtk_entry_set_width_chars ( widgets->time_zone_b, 7);
611         gtk_entry_set_width_chars ( widgets->time_offset_b, 7);
612
613         // Defaults
614         option_values_t default_values = get_default_values ();
615
616         gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(widgets->create_waypoints_b), default_values.create_waypoints );
617         gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(widgets->overwrite_waypoints_b), default_values.overwrite_waypoints );
618         gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(widgets->write_exif_b), default_values.write_exif );
619         gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(widgets->overwrite_gps_exif_b), default_values.overwrite_gps_exif );
620         gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(widgets->no_change_mtime_b), default_values.no_change_mtime );
621         gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(widgets->interpolate_segments_b), default_values.interpolate_segments );
622         gchar tmp_string[7];
623         snprintf (tmp_string, 7, "%+02d:%02d", default_values.TimeZoneHours, abs (default_values.TimeZoneMins) );
624         gtk_entry_set_text ( widgets->time_zone_b, tmp_string );
625         snprintf (tmp_string, 7, "%d", default_values.time_offset );
626         gtk_entry_set_text ( widgets->time_offset_b, tmp_string );
627
628         // Ensure sensitivities setup
629         write_exif_b_cb ( GTK_WIDGET(widgets->write_exif_b), widgets );
630         g_signal_connect ( G_OBJECT(widgets->write_exif_b), "toggled", G_CALLBACK(write_exif_b_cb), widgets );
631
632         create_waypoints_b_cb ( GTK_WIDGET(widgets->create_waypoints_b), widgets );
633         g_signal_connect ( G_OBJECT(widgets->create_waypoints_b), "toggled", G_CALLBACK(create_waypoints_b_cb), widgets );
634
635         GtkWidget *cw_hbox = gtk_hbox_new ( FALSE, 0 );
636         GtkWidget *create_waypoints_l = gtk_label_new ( _("Create Waypoints:") );
637         gtk_box_pack_start ( GTK_BOX(cw_hbox), create_waypoints_l, FALSE, FALSE, 5 );
638         gtk_box_pack_start ( GTK_BOX(cw_hbox), GTK_WIDGET(widgets->create_waypoints_b), FALSE, FALSE, 5 );
639
640         GtkWidget *ow_hbox = gtk_hbox_new ( FALSE, 0 );
641         gtk_box_pack_start ( GTK_BOX(ow_hbox), GTK_WIDGET(widgets->overwrite_waypoints_l), FALSE, FALSE, 5 );
642         gtk_box_pack_start ( GTK_BOX(ow_hbox), GTK_WIDGET(widgets->overwrite_waypoints_b), FALSE, FALSE, 5 );
643
644         GtkWidget *we_hbox = gtk_hbox_new ( FALSE, 0 );
645         gtk_box_pack_start ( GTK_BOX(we_hbox), gtk_label_new ( _("Write EXIF:") ), FALSE, FALSE, 5 );
646         gtk_box_pack_start ( GTK_BOX(we_hbox), GTK_WIDGET(widgets->write_exif_b), FALSE, FALSE, 5 );
647
648         GtkWidget *og_hbox = gtk_hbox_new ( FALSE, 0 );
649         gtk_box_pack_start ( GTK_BOX(og_hbox), GTK_WIDGET(widgets->overwrite_gps_exif_l), FALSE, FALSE, 5 );
650         gtk_box_pack_start ( GTK_BOX(og_hbox), GTK_WIDGET(widgets->overwrite_gps_exif_b), FALSE, FALSE, 5 );
651
652         GtkWidget *fm_hbox = gtk_hbox_new ( FALSE, 0 );
653         gtk_box_pack_start ( GTK_BOX(fm_hbox), GTK_WIDGET(widgets->no_change_mtime_l), FALSE, FALSE, 5 );
654         gtk_box_pack_start ( GTK_BOX(fm_hbox), GTK_WIDGET(widgets->no_change_mtime_b), FALSE, FALSE, 5 );
655
656         GtkWidget *is_hbox = gtk_hbox_new ( FALSE, 0 );
657         GtkWidget *interpolate_segments_l = gtk_label_new ( _("Interpolate Between Track Segments:") );
658         gtk_box_pack_start ( GTK_BOX(is_hbox), interpolate_segments_l, FALSE, FALSE, 5 );
659         gtk_box_pack_start ( GTK_BOX(is_hbox), GTK_WIDGET(widgets->interpolate_segments_b), FALSE, FALSE, 5 );
660
661         GtkWidget *to_hbox = gtk_hbox_new ( FALSE, 0 );
662         GtkWidget *time_offset_l = gtk_label_new ( _("Image Time Offset (Seconds):") );
663         gtk_box_pack_start ( GTK_BOX(to_hbox), time_offset_l, FALSE, FALSE, 5 );
664         gtk_box_pack_start ( GTK_BOX(to_hbox), GTK_WIDGET(widgets->time_offset_b), FALSE, FALSE, 5 );
665         gtk_widget_set_tooltip_text ( GTK_WIDGET(widgets->time_offset_b), _("The number of seconds to ADD to the photos time to make it match the GPS data. Calculate this with (GPS - Photo). Can be negative or positive. Useful to adjust times when a camera's timestamp was incorrect.") );
666
667         GtkWidget *tz_hbox = gtk_hbox_new ( FALSE, 0 );
668         GtkWidget *time_zone_l = gtk_label_new ( _("Image Timezone:") );
669         gtk_box_pack_start ( GTK_BOX(tz_hbox), time_zone_l, FALSE, FALSE, 5 );
670         gtk_box_pack_start ( GTK_BOX(tz_hbox), GTK_WIDGET(widgets->time_zone_b), FALSE, FALSE, 5 );
671         gtk_widget_set_tooltip_text ( GTK_WIDGET(widgets->time_zone_b), _("The timezone that was used when the images were created. For example, if a camera is set to AWST or +8:00 hours. Enter +8:00 here so that the correct adjustment to the images' time can be made. GPS data is always in UTC.") );
672
673         gchar *track_string = NULL;
674         if ( widgets->wpt ) {
675                 track_string = g_strdup_printf ( _("Using waypoint: %s"), wpt->name );
676                 // Control sensitivities
677                 gtk_widget_set_sensitive ( GTK_WIDGET(widgets->create_waypoints_b), FALSE );
678                 gtk_widget_set_sensitive ( GTK_WIDGET(create_waypoints_l), FALSE );
679                 gtk_widget_set_sensitive ( GTK_WIDGET(widgets->overwrite_waypoints_b), FALSE );
680                 gtk_widget_set_sensitive ( GTK_WIDGET(widgets->overwrite_waypoints_l), FALSE );
681                 gtk_widget_set_sensitive ( GTK_WIDGET(widgets->interpolate_segments_b), FALSE );
682                 gtk_widget_set_sensitive ( GTK_WIDGET(interpolate_segments_l), FALSE );
683                 gtk_widget_set_sensitive ( GTK_WIDGET(widgets->time_offset_b), FALSE );
684                 gtk_widget_set_sensitive ( GTK_WIDGET(time_offset_l), FALSE );
685                 gtk_widget_set_sensitive ( GTK_WIDGET(widgets->time_zone_b), FALSE );
686                 gtk_widget_set_sensitive ( GTK_WIDGET(time_zone_l), FALSE );
687         }
688         else if ( widgets->track )
689                 track_string = g_strdup_printf ( _("Using track: %s"), track->name );
690         else
691                 track_string = g_strdup_printf ( _("Using all tracks in: %s"), VIK_LAYER(widgets->vtl)->name );
692
693         gtk_box_pack_start ( GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(widgets->dialog))), gtk_label_new ( track_string ), FALSE, FALSE, 5 );
694
695         gtk_box_pack_start ( GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(widgets->dialog))), GTK_WIDGET(widgets->files), TRUE, TRUE, 0 );
696
697         gtk_box_pack_start ( GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(widgets->dialog))), cw_hbox,  FALSE, FALSE, 0);
698         gtk_box_pack_start ( GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(widgets->dialog))), ow_hbox,  FALSE, FALSE, 0);
699         gtk_box_pack_start ( GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(widgets->dialog))), we_hbox,  FALSE, FALSE, 0);
700         gtk_box_pack_start ( GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(widgets->dialog))), og_hbox,  FALSE, FALSE, 0);
701         gtk_box_pack_start ( GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(widgets->dialog))), fm_hbox,  FALSE, FALSE, 0);
702         gtk_box_pack_start ( GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(widgets->dialog))), is_hbox,  FALSE, FALSE, 0);
703         gtk_box_pack_start ( GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(widgets->dialog))), to_hbox,  FALSE, FALSE, 0);
704         gtk_box_pack_start ( GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(widgets->dialog))), tz_hbox,  FALSE, FALSE, 0);
705
706         g_signal_connect ( widgets->dialog, "response", G_CALLBACK(trw_layer_geotag_response_cb), widgets );
707
708         gtk_dialog_set_default_response ( GTK_DIALOG(widgets->dialog), GTK_RESPONSE_REJECT );
709
710         gtk_widget_show_all ( widgets->dialog );
711
712         g_free ( track_string );
713 }