]> git.street.me.uk Git - andy/viking.git/blob - src/viktrwlayer_geotag.c
[Geotagging] Update RPM spec for new dependency.
[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         VikTrack *track;     // Use specified track or all tracks if NULL
95         GtkCheckButton *create_waypoints_b;
96         GtkLabel *overwrite_waypoints_l; // Referenced so the sensitivity can be changed
97         GtkCheckButton *overwrite_waypoints_b;
98         GtkCheckButton *write_exif_b;
99         GtkLabel *overwrite_gps_exif_l; // Referenced so the sensitivity can be changed
100         GtkCheckButton *overwrite_gps_exif_b;
101         GtkLabel *no_change_mtime_l; // Referenced so the sensitivity can be changed
102         GtkCheckButton *no_change_mtime_b;
103         GtkCheckButton *interpolate_segments_b;
104         GtkEntry *time_zone_b; // TODO consider a more user friendly tz widget eg libtimezonemap or similar
105         GtkEntry *time_offset_b;
106 } GeoTagWidgets;
107
108 static GeoTagWidgets *geotag_widgets_new()
109 {
110         GeoTagWidgets *widgets = g_malloc0(sizeof(GeoTagWidgets));
111         return widgets;
112 }
113
114 static void geotag_widgets_free ( GeoTagWidgets *widgets )
115 {
116         // Need to free VikFileList??
117         g_free(widgets);
118 }
119
120 typedef struct {
121         gboolean create_waypoints;
122         gboolean overwrite_waypoints;
123         gboolean write_exif;
124         gboolean overwrite_gps_exif;
125         gboolean no_change_mtime;
126         gboolean interpolate_segments;
127         gint time_offset;
128         gint TimeZoneHours;
129         gint TimeZoneMins;
130 } option_values_t;
131
132 typedef struct {
133         VikTrwLayer *vtl;
134         gchar *image;
135         VikTrack *track;     // Use specified track or all tracks if NULL
136         // User options...
137         option_values_t ov;
138         GList *files;
139         time_t PhotoTime;
140         // Store answer from interpolation for an image
141         gboolean found_match;
142         VikCoord coord;
143         gdouble altitude;
144         // If anything has changed
145         gboolean redraw;
146 } geotag_options_t;
147
148 static option_values_t default_values = {
149         TRUE,
150         TRUE,
151         TRUE,
152         FALSE,
153         TRUE,
154         TRUE,
155         0,
156         0,
157         0,
158 };
159
160 /**
161  * Correlate the image against the specified track
162  */
163 static void trw_layer_geotag_track ( const gchar *name, VikTrack *track, geotag_options_t *options )
164 {
165         // If already found match then don't need to check this track
166         if ( options->found_match )
167                 return;
168
169         VikTrackpoint *trkpt;
170         VikTrackpoint *trkpt_next;
171
172         GList *mytrkpt = track->trackpoints;
173         for ( mytrkpt = mytrkpt; mytrkpt; mytrkpt = mytrkpt->next ) {
174
175                 // Do something for this trackpoint...
176
177                 trkpt = VIK_TRACKPOINT(mytrkpt->data);
178
179                 // is it exactly this point?
180                 if ( options->PhotoTime == trkpt->timestamp ) {
181                         options->coord = trkpt->coord;
182                         options->altitude = trkpt->altitude;
183                         options->found_match = TRUE;
184                         break;
185                 }
186
187                 // Now need two trackpoints, hence check next is available
188                 if ( !mytrkpt->next ) break;
189                 trkpt_next = VIK_TRACKPOINT(mytrkpt->next->data);
190
191                 // TODO need to use 'has_timestamp' property
192                 if ( trkpt->timestamp == trkpt_next->timestamp ) continue;
193                 if ( trkpt->timestamp > trkpt_next->timestamp ) continue;
194
195                 // When interpolating between segments, no need for any special segment handling
196                 if ( !options->ov.interpolate_segments )
197                         // Don't check between segments
198                         if ( trkpt_next->newsegment )
199                                 // Simply move on to consider next point
200                                 continue;
201
202                 // Too far
203                 if ( trkpt->timestamp > options->PhotoTime ) break;
204
205                 // Is is between this and the next point?
206                 if ( (options->PhotoTime > trkpt->timestamp) && (options->PhotoTime < trkpt_next->timestamp) ) {
207                         options->found_match = TRUE;
208                         // Interpolate
209                         /* Calculate the "scale": a decimal giving the relative distance
210                          * in time between the two points. Ie, a number between 0 and 1 -
211                          * 0 is the first point, 1 is the next point, and 0.5 would be
212                          * half way. */
213                         gdouble scale = (gdouble)trkpt_next->timestamp - (gdouble)trkpt->timestamp;
214                         scale = ((gdouble)options->PhotoTime - (gdouble)trkpt->timestamp) / scale;
215
216                         struct LatLon ll_result, ll1, ll2;
217
218                         vik_coord_to_latlon ( &(trkpt->coord), &ll1 );
219                         vik_coord_to_latlon ( &(trkpt_next->coord), &ll2 );
220
221                         ll_result.lat = ll1.lat + ((ll2.lat - ll1.lat) * scale);
222
223                         // NB This won't cope with going over the 180 degrees longitude boundary
224                         ll_result.lon = ll1.lon + ((ll2.lon - ll1.lon) * scale);
225
226                         // set coord
227                         vik_coord_load_from_latlon ( &(options->coord), VIK_COORD_LATLON, &ll_result );
228
229                         // Interpolate elevation
230                         options->altitude = trkpt->altitude + ((trkpt_next->altitude - trkpt->altitude) * scale);
231                         break;
232                 }
233                         
234         }
235 }
236
237 /**
238  * Correlate the image to any track within the TrackWaypoint layer
239  */
240 static void trw_layer_geotag_process ( geotag_options_t *options )
241 {
242         if ( !options->vtl || !IS_VIK_LAYER(options->vtl) )
243                 return;
244
245         if ( !options->image )
246                 return;
247
248         gboolean has_gps_exif = FALSE;
249         gchar* datetime = a_geotag_get_exif_date_from_file ( options->image, &has_gps_exif );
250
251         if ( datetime ) {
252         
253                 // If image already has gps info - don't attempt to change it.
254                 if ( !options->ov.overwrite_gps_exif && has_gps_exif ) {
255                         if ( options->ov.create_waypoints ) {
256                                 // Create waypoint with file information
257                                 gchar *name = NULL;
258                                 VikWaypoint *wp = a_geotag_create_waypoint_from_file ( options->image, vik_trw_layer_get_coord_mode (options->vtl), &name );
259                                 if ( !wp ) {
260                                         // Couldn't create Waypoint
261                                         g_free ( datetime );
262                                         return;
263                                 }
264                                 if ( !name )
265                                         name = g_strdup ( a_file_basename ( options->image ) );
266
267                                 gboolean updated_waypoint = FALSE;
268
269                                 if ( options->ov.overwrite_waypoints ) {
270                                         VikWaypoint *current_wp = vik_trw_layer_get_waypoint ( options->vtl, name );
271                                         if ( current_wp ) {
272                                                 // Existing wp found, so set new position, comment and image
273                                                 current_wp = a_geotag_waypoint_positioned ( options->image, wp->coord, wp->altitude, &name, current_wp );
274                                                 updated_waypoint = TRUE;
275                                         }
276                                 }
277
278                                 if ( !updated_waypoint ) {
279                                         vik_trw_layer_filein_add_waypoint ( options->vtl, name, wp );
280                                 }
281
282                                 g_free ( name );
283                                 
284                                 // Mark for redraw
285                                 options->redraw = TRUE;
286                         }
287                         g_free ( datetime );
288                         return;
289                 }
290
291                 options->PhotoTime = ConvertToUnixTime ( datetime, EXIF_DATE_FORMAT, options->ov.TimeZoneHours, options->ov.TimeZoneMins);
292                 g_free ( datetime );
293                 
294                 // Apply any offset
295                 options->PhotoTime = options->PhotoTime + options->ov.time_offset;
296
297                 options->found_match = FALSE;
298
299                 if ( options->track ) {
300                         // Single specified track
301                         // NB Doesn't care about track name
302                         trw_layer_geotag_track ( NULL, options->track, options );
303                 }
304                 else {
305                         // Try all tracks
306                         GHashTable *tracks = vik_trw_layer_get_tracks ( options->vtl );
307                         if ( g_hash_table_size (tracks) > 0 ) {
308                                 g_hash_table_foreach ( tracks, (GHFunc) trw_layer_geotag_track, options );
309                         }
310                 }
311
312                 // Match found ?
313                 if ( options->found_match ) {
314
315                         if ( options->ov.create_waypoints ) {
316
317                                 gboolean updated_waypoint = FALSE;
318
319                                 if ( options->ov.overwrite_waypoints ) {
320                                 
321                                         // Update existing WP
322                                         // Find a WP with current name
323                                         gchar *name = NULL;
324                                         name = g_strdup ( a_file_basename ( options->image ) );
325                                         VikWaypoint *wp = vik_trw_layer_get_waypoint ( options->vtl, name );
326                                         if ( wp ) {
327                                                 // Found, so set new position, comment and image
328                                                 wp = a_geotag_waypoint_positioned ( options->image, options->coord, options->altitude, &name, wp );
329                                                 updated_waypoint = TRUE;
330                                         }
331                                         g_free ( name );
332                                 }
333
334                                 if ( !updated_waypoint ) {
335                                         // Create waypoint with found position
336                                         gchar *name = NULL;
337                                         VikWaypoint *wp = a_geotag_waypoint_positioned ( options->image, options->coord, options->altitude, &name, NULL );
338                                         if ( !name )
339                                                 name = g_strdup ( a_file_basename ( options->image ) );
340                                         vik_trw_layer_filein_add_waypoint ( options->vtl, name, wp );
341                                         g_free ( name );
342                                 }
343
344                                 // Mark for redraw
345                                 options->redraw = TRUE;
346                         }
347
348                         // Write EXIF if specified
349                         if ( options->ov.write_exif ) {
350                                 a_geotag_write_exif_gps ( options->image, options->coord, options->altitude, options->ov.no_change_mtime );
351                         }
352                 }
353         }
354 }
355
356 /*
357  * Tidy up
358  */
359 static void trw_layer_geotag_thread_free ( geotag_options_t *gtd )
360 {
361         if ( gtd->files )
362                 g_list_free ( gtd->files );
363         g_free ( gtd );
364 }
365
366 /**
367  * Run geotagging process in a separate thread
368  */
369 static int trw_layer_geotag_thread ( geotag_options_t *options, gpointer threaddata )
370 {
371         guint total = g_list_length(options->files), done = 0;
372
373         // TODO decide how to report any issues to the user ...
374
375         // Foreach file attempt to geotag it
376         while ( options->files ) {
377                 options->image = (gchar *) ( options->files->data );
378                 trw_layer_geotag_process ( options );
379                 options->files = options->files->next;
380
381                 // Update thread progress and detect stop requests
382                 int result = a_background_thread_progress ( threaddata, ((gdouble) ++done) / total );
383                 if ( result != 0 )
384                         return -1; /* Abort thread */
385         }
386
387         if ( options->redraw ) {
388                 if ( IS_VIK_LAYER(options->vtl) ) {
389                         // Ensure any new images get shown
390                         trw_layer_verify_thumbnails ( options->vtl, NULL ); // NB second parameter not used ATM
391                         // Force redraw as verify only redraws if there are new thumbnails (they may already exist)
392                         vik_layer_emit_update ( VIK_LAYER(options->vtl), TRUE ); // Update from background
393                 }
394         }
395
396         return 0;
397 }
398
399 /**
400  * Parse user input from dialog response
401  */
402 static void trw_layer_geotag_response_cb ( GtkDialog *dialog, gint resp, GeoTagWidgets *widgets )
403 {
404         switch (resp) {
405     case GTK_RESPONSE_DELETE_EVENT: /* received delete event (not from buttons) */
406     case GTK_RESPONSE_REJECT:
407                 break;
408         default: {
409                 //GTK_RESPONSE_ACCEPT:
410                 // Get options
411                 geotag_options_t *options = g_malloc ( sizeof(geotag_options_t) );
412                 options->vtl = widgets->vtl;
413                 options->track = widgets->track;
414                 // Values extracted from the widgets:
415                 options->ov.create_waypoints = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->create_waypoints_b) );
416                 options->ov.overwrite_waypoints = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->overwrite_waypoints_b) );
417                 options->ov.write_exif = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->write_exif_b) );
418                 options->ov.overwrite_gps_exif = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->overwrite_gps_exif_b) );
419                 options->ov.no_change_mtime = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->no_change_mtime_b) );
420                 options->ov.interpolate_segments = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->interpolate_segments_b) );
421                 options->ov.TimeZoneHours = 0;
422                 options->ov.TimeZoneMins = 0;
423                 const gchar* TZString = gtk_entry_get_text(GTK_ENTRY(widgets->time_zone_b));
424                 /* Check the string. If there is a colon, then (hopefully) it's a time in xx:xx format.
425                  * If not, it's probably just a +/-xx format. In all other cases,
426                  * it will be interpreted as +/-xx, which, if given a string, returns 0. */
427                 if (strstr(TZString, ":")) {
428                         /* Found colon. Split into two. */
429                         sscanf(TZString, "%d:%d", &options->ov.TimeZoneHours, &options->ov.TimeZoneMins);
430                         if (options->ov.TimeZoneHours < 0)
431                                 options->ov.TimeZoneMins *= -1;
432                 } else {
433                         /* No colon. Just parse. */
434                         options->ov.TimeZoneHours = atoi(TZString);
435                 }
436                 options->ov.time_offset = atoi ( gtk_entry_get_text ( GTK_ENTRY(widgets->time_offset_b) ) );
437
438                 options->redraw = FALSE;
439
440                 // Save settings for reuse
441                 default_values = options->ov;
442
443                 options->files = g_list_copy ( vik_file_list_get_files ( widgets->files ) );
444
445                 gint len = g_list_length ( options->files );
446                 gchar *tmp = g_strdup_printf ( _("Geotagging %d Images..."), len );
447
448                 // Processing lots of files can take time - so run a background effort
449                 a_background_thread ( VIK_GTK_WINDOW_FROM_LAYER(options->vtl),
450                                                           tmp,
451                                                           (vik_thr_func) trw_layer_geotag_thread,
452                                                           options,
453                                                           (vik_thr_free_func) trw_layer_geotag_thread_free,
454                                                           NULL,
455                                                           len );
456
457                 g_free ( tmp );
458
459                 break;
460         }
461         }
462         geotag_widgets_free ( widgets );
463         gtk_widget_destroy ( GTK_WIDGET(dialog) );
464 }
465
466 /**
467  * Handle widget sensitivities
468  */
469 static void write_exif_b_cb ( GtkWidget *gw, GeoTagWidgets *gtw )
470 {
471         // Overwriting & file modification times are irrelevant if not going to write EXIF!
472         if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(gtw->write_exif_b) ) ) {
473                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->overwrite_gps_exif_b), TRUE );
474                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->overwrite_gps_exif_l), TRUE );
475                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->no_change_mtime_b), TRUE );
476                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->no_change_mtime_l), TRUE );
477         }
478         else {
479                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->overwrite_gps_exif_b), FALSE );
480                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->overwrite_gps_exif_l), FALSE );
481                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->no_change_mtime_b), FALSE );
482                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->no_change_mtime_l), FALSE );
483         }
484 }
485
486 static void create_waypoints_b_cb ( GtkWidget *gw, GeoTagWidgets *gtw )
487 {
488         // Overwriting waypoints are irrelevant if not going to create them!
489         if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(gtw->create_waypoints_b) ) ) {
490                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->overwrite_waypoints_b), TRUE );
491                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->overwrite_waypoints_l), TRUE );
492         }
493         else {
494                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->overwrite_waypoints_b), FALSE );
495                 gtk_widget_set_sensitive ( GTK_WIDGET(gtw->overwrite_waypoints_l), FALSE );
496         }
497 }
498
499 /**
500  * trw_layer_geotag_dialog:
501  * @parent: The Window of the calling process
502  * @vtl: The VikTrwLayer to use for correlating images to tracks
503  * @track: Optional - The particular track to use (if specified) for correlating images
504  * @track_name: Optional - The name of specified track to use
505  */
506 void trw_layer_geotag_dialog ( GtkWindow *parent, VikTrwLayer *vtl, VikTrack *track, const gchar *track_name )
507 {
508         GeoTagWidgets *widgets = geotag_widgets_new();
509
510         widgets->dialog = gtk_dialog_new_with_buttons ( _("Geotag Images"),
511                                                                                                         parent,
512                                                                                                         GTK_DIALOG_DESTROY_WITH_PARENT,
513                                                                                                         GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
514                                                                                                         GTK_STOCK_OK,     GTK_RESPONSE_ACCEPT,
515                                                                                                         NULL );
516         widgets->files = VIK_FILE_LIST(vik_file_list_new ( _("Images") )); // TODO would be nice to be able to set a filefilter
517         widgets->vtl = vtl;
518         widgets->track = track;
519         widgets->create_waypoints_b = GTK_CHECK_BUTTON ( gtk_check_button_new () );
520         widgets->overwrite_waypoints_l = GTK_LABEL ( gtk_label_new ( _("Overwrite Existing Waypoints:") ) );
521         widgets->overwrite_waypoints_b = GTK_CHECK_BUTTON ( gtk_check_button_new () );
522         widgets->write_exif_b = GTK_CHECK_BUTTON ( gtk_check_button_new () );
523         widgets->overwrite_gps_exif_l = GTK_LABEL ( gtk_label_new ( _("Overwrite Existing GPS Information:") ) );
524         widgets->overwrite_gps_exif_b = GTK_CHECK_BUTTON ( gtk_check_button_new () );
525         widgets->no_change_mtime_l = GTK_LABEL ( gtk_label_new ( _("Keep File Modification Timestamp:") ) );
526         widgets->no_change_mtime_b = GTK_CHECK_BUTTON ( gtk_check_button_new () );
527         widgets->interpolate_segments_b = GTK_CHECK_BUTTON ( gtk_check_button_new () );
528         widgets->time_zone_b = GTK_ENTRY ( gtk_entry_new () );
529         widgets->time_offset_b = GTK_ENTRY ( gtk_entry_new () );
530
531         gtk_entry_set_width_chars ( widgets->time_zone_b, 7);
532         gtk_entry_set_width_chars ( widgets->time_offset_b, 7);
533
534         // Defaults - TODO restore previous values / save settings somewhere??
535         gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(widgets->create_waypoints_b), default_values.create_waypoints );
536         gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(widgets->overwrite_waypoints_b), default_values.overwrite_waypoints );
537         gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(widgets->write_exif_b), default_values.write_exif );
538         gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(widgets->overwrite_gps_exif_b), default_values.overwrite_gps_exif );
539         gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(widgets->no_change_mtime_b), default_values.no_change_mtime );
540         gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(widgets->interpolate_segments_b), default_values.interpolate_segments );
541         gchar tmp_string[7];
542         snprintf (tmp_string, 7, "%+02d:%02d", default_values.TimeZoneHours, abs (default_values.TimeZoneMins) );
543         gtk_entry_set_text ( widgets->time_zone_b, tmp_string );
544         snprintf (tmp_string, 7, "%d", default_values.time_offset );
545         gtk_entry_set_text ( widgets->time_offset_b, tmp_string );
546
547         // Ensure sensitivities setup
548         write_exif_b_cb ( GTK_WIDGET(widgets->write_exif_b), widgets );
549         g_signal_connect ( G_OBJECT(widgets->write_exif_b), "toggled", G_CALLBACK(write_exif_b_cb), widgets );
550
551         create_waypoints_b_cb ( GTK_WIDGET(widgets->create_waypoints_b), widgets );
552         g_signal_connect ( G_OBJECT(widgets->create_waypoints_b), "toggled", G_CALLBACK(create_waypoints_b_cb), widgets );
553
554         GtkWidget *cw_hbox = gtk_hbox_new ( FALSE, 0 );
555         gtk_box_pack_start ( GTK_BOX(cw_hbox), gtk_label_new ( _("Create Waypoints:") ), FALSE, FALSE, 5 );
556         gtk_box_pack_start ( GTK_BOX(cw_hbox), GTK_WIDGET(widgets->create_waypoints_b), FALSE, FALSE, 5 );
557
558         GtkWidget *ow_hbox = gtk_hbox_new ( FALSE, 0 );
559         gtk_box_pack_start ( GTK_BOX(ow_hbox), GTK_WIDGET(widgets->overwrite_waypoints_l), FALSE, FALSE, 5 );
560         gtk_box_pack_start ( GTK_BOX(ow_hbox), GTK_WIDGET(widgets->overwrite_waypoints_b), FALSE, FALSE, 5 );
561
562         GtkWidget *we_hbox = gtk_hbox_new ( FALSE, 0 );
563         gtk_box_pack_start ( GTK_BOX(we_hbox), gtk_label_new ( _("Write EXIF:") ), FALSE, FALSE, 5 );
564         gtk_box_pack_start ( GTK_BOX(we_hbox), GTK_WIDGET(widgets->write_exif_b), FALSE, FALSE, 5 );
565
566         GtkWidget *og_hbox = gtk_hbox_new ( FALSE, 0 );
567         gtk_box_pack_start ( GTK_BOX(og_hbox), GTK_WIDGET(widgets->overwrite_gps_exif_l), FALSE, FALSE, 5 );
568         gtk_box_pack_start ( GTK_BOX(og_hbox), GTK_WIDGET(widgets->overwrite_gps_exif_b), FALSE, FALSE, 5 );
569
570         GtkWidget *fm_hbox = gtk_hbox_new ( FALSE, 0 );
571         gtk_box_pack_start ( GTK_BOX(fm_hbox), GTK_WIDGET(widgets->no_change_mtime_l), FALSE, FALSE, 5 );
572         gtk_box_pack_start ( GTK_BOX(fm_hbox), GTK_WIDGET(widgets->no_change_mtime_b), FALSE, FALSE, 5 );
573
574         GtkWidget *is_hbox = gtk_hbox_new ( FALSE, 0 );
575         gtk_box_pack_start ( GTK_BOX(is_hbox), gtk_label_new ( _("Interpolate Between Track Segments:") ), FALSE, FALSE, 5 );
576         gtk_box_pack_start ( GTK_BOX(is_hbox), GTK_WIDGET(widgets->interpolate_segments_b), FALSE, FALSE, 5 );
577
578         GtkWidget *to_hbox = gtk_hbox_new ( FALSE, 0 );
579         gtk_box_pack_start ( GTK_BOX(to_hbox), gtk_label_new ( _("Image Time Offset (Seconds):") ), FALSE, FALSE, 5 );
580         gtk_box_pack_start ( GTK_BOX(to_hbox), GTK_WIDGET(widgets->time_offset_b), FALSE, FALSE, 5 );
581         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.") );
582
583         GtkWidget *tz_hbox = gtk_hbox_new ( FALSE, 0 );
584         gtk_box_pack_start ( GTK_BOX(tz_hbox), gtk_label_new ( _("Image Timezone:") ), FALSE, FALSE, 5 );
585         gtk_box_pack_start ( GTK_BOX(tz_hbox), GTK_WIDGET(widgets->time_zone_b), FALSE, FALSE, 5 );
586         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.") );
587
588         gchar *track_string = NULL;
589         if ( widgets->track )
590                 track_string = g_strdup_printf ( _("Using track: %s"), track_name );
591         else
592                 track_string = g_strdup_printf ( _("Using all tracks in: %s"), VIK_LAYER(widgets->vtl)->name );
593
594         gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(widgets->dialog)->vbox), gtk_label_new ( track_string ), FALSE, FALSE, 5 );
595
596         gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(widgets->dialog)->vbox), GTK_WIDGET(widgets->files), TRUE, TRUE, 0 );
597
598         gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(widgets->dialog)->vbox), cw_hbox,  FALSE, FALSE, 0);
599         gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(widgets->dialog)->vbox), ow_hbox,  FALSE, FALSE, 0);
600         gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(widgets->dialog)->vbox), we_hbox,  FALSE, FALSE, 0);
601         gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(widgets->dialog)->vbox), og_hbox,  FALSE, FALSE, 0);
602         gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(widgets->dialog)->vbox), fm_hbox,  FALSE, FALSE, 0);
603         gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(widgets->dialog)->vbox), is_hbox,  FALSE, FALSE, 0);
604         gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(widgets->dialog)->vbox), to_hbox,  FALSE, FALSE, 0);
605         gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(widgets->dialog)->vbox), tz_hbox,  FALSE, FALSE, 0);
606
607         g_signal_connect ( widgets->dialog, "response", G_CALLBACK(trw_layer_geotag_response_cb), widgets );
608
609         gtk_dialog_set_default_response ( GTK_DIALOG(widgets->dialog), GTK_RESPONSE_REJECT );
610
611         gtk_widget_show_all ( widgets->dialog );
612
613         g_free ( track_string );
614 }