]>
Commit | Line | Data |
---|---|---|
b3eb3b98 RN |
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" | |
404328d1 | 39 | #include "background.h" |
b3eb3b98 | 40 | |
404328d1 | 41 | // Function taken from GPSCorrelate 1.6.1 |
b3eb3b98 RN |
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 | |
41f4abac | 94 | VikWaypoint *wpt; // Use specified waypoint or otherwise the track(s) if NULL |
b3eb3b98 RN |
95 | VikTrack *track; // Use specified track or all tracks if NULL |
96 | GtkCheckButton *create_waypoints_b; | |
cbac0d22 RN |
97 | GtkLabel *overwrite_waypoints_l; // Referenced so the sensitivity can be changed |
98 | GtkCheckButton *overwrite_waypoints_b; | |
b3eb3b98 RN |
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; | |
cbac0d22 | 123 | gboolean overwrite_waypoints; |
b3eb3b98 RN |
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; | |
41f4abac | 136 | VikWaypoint *wpt; // Use specified waypoint or otherwise the track(s) if NULL |
b3eb3b98 RN |
137 | VikTrack *track; // Use specified track or all tracks if NULL |
138 | // User options... | |
139 | option_values_t ov; | |
404328d1 | 140 | GList *files; |
b3eb3b98 RN |
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 | ||
21f0a3ca RN |
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 | } | |
b3eb3b98 RN |
196 | |
197 | /** | |
198 | * Correlate the image against the specified track | |
199 | */ | |
89a068d8 | 200 | static void trw_layer_geotag_track ( const gpointer id, VikTrack *track, geotag_options_t *options ) |
b3eb3b98 RN |
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 | ||
5e610fc3 RN |
209 | GList *mytrkpt; |
210 | for ( mytrkpt = track->trackpoints; mytrkpt; mytrkpt = mytrkpt->next ) { | |
b3eb3b98 RN |
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 | } | |
41f4abac RN |
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 ); | |
b3eb3b98 RN |
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 | { | |
404328d1 | 300 | if ( !options->vtl || !IS_VIK_LAYER(options->vtl) ) |
b3eb3b98 RN |
301 | return; |
302 | ||
303 | if ( !options->image ) | |
304 | return; | |
305 | ||
41f4abac RN |
306 | if ( options->wpt ) { |
307 | trw_layer_geotag_waypoint ( options ); | |
308 | return; | |
309 | } | |
310 | ||
b3eb3b98 RN |
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 ); | |
0fb2c85d RN |
322 | if ( !wp ) { |
323 | // Couldn't create Waypoint | |
324 | g_free ( datetime ); | |
325 | return; | |
326 | } | |
b3eb3b98 RN |
327 | if ( !name ) |
328 | name = g_strdup ( a_file_basename ( options->image ) ); | |
cbac0d22 RN |
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 | |
5263679f | 336 | (void)a_geotag_waypoint_positioned ( options->image, wp->coord, wp->altitude, &name, current_wp ); |
cbac0d22 RN |
337 | updated_waypoint = TRUE; |
338 | } | |
339 | } | |
340 | ||
341 | if ( !updated_waypoint ) { | |
342 | vik_trw_layer_filein_add_waypoint ( options->vtl, name, wp ); | |
343 | } | |
344 | ||
b3eb3b98 RN |
345 | g_free ( name ); |
346 | ||
347 | // Mark for redraw | |
348 | options->redraw = TRUE; | |
349 | } | |
0847b808 | 350 | g_free ( datetime ); |
b3eb3b98 RN |
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 | |
89a068d8 | 364 | // NB Doesn't care about track id |
b3eb3b98 RN |
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 | ||
cbac0d22 RN |
380 | gboolean updated_waypoint = FALSE; |
381 | ||
382 | if ( options->ov.overwrite_waypoints ) { | |
b3eb3b98 | 383 | |
cbac0d22 RN |
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 | |
5263679f | 391 | (void)a_geotag_waypoint_positioned ( options->image, options->coord, options->altitude, &name, wp ); |
cbac0d22 RN |
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 | ||
b3eb3b98 RN |
407 | // Mark for redraw |
408 | options->redraw = TRUE; | |
409 | } | |
410 | ||
411 | // Write EXIF if specified | |
412 | if ( options->ov.write_exif ) { | |
41f4abac RN |
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 | } | |
b3eb3b98 RN |
419 | } |
420 | } | |
421 | } | |
422 | } | |
423 | ||
404328d1 RN |
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) ) { | |
2cec1c4e | 457 | trw_layer_calculate_bounds_waypoints ( options->vtl ); |
404328d1 RN |
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) | |
da121f9b | 461 | vik_layer_emit_update ( VIK_LAYER(options->vtl) ); // NB Update from background |
404328d1 RN |
462 | } |
463 | } | |
464 | ||
465 | return 0; | |
466 | } | |
467 | ||
b3eb3b98 RN |
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 | |
404328d1 RN |
480 | geotag_options_t *options = g_malloc ( sizeof(geotag_options_t) ); |
481 | options->vtl = widgets->vtl; | |
41f4abac | 482 | options->wpt = widgets->wpt; |
404328d1 | 483 | options->track = widgets->track; |
b3eb3b98 | 484 | // Values extracted from the widgets: |
404328d1 | 485 | options->ov.create_waypoints = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->create_waypoints_b) ); |
cbac0d22 | 486 | options->ov.overwrite_waypoints = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->overwrite_waypoints_b) ); |
404328d1 RN |
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; | |
b3eb3b98 RN |
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. */ | |
404328d1 RN |
499 | sscanf(TZString, "%d:%d", &options->ov.TimeZoneHours, &options->ov.TimeZoneMins); |
500 | if (options->ov.TimeZoneHours < 0) | |
501 | options->ov.TimeZoneMins *= -1; | |
b3eb3b98 RN |
502 | } else { |
503 | /* No colon. Just parse. */ | |
404328d1 | 504 | options->ov.TimeZoneHours = atoi(TZString); |
b3eb3b98 | 505 | } |
404328d1 | 506 | options->ov.time_offset = atoi ( gtk_entry_get_text ( GTK_ENTRY(widgets->time_offset_b) ) ); |
b3eb3b98 | 507 | |
404328d1 | 508 | options->redraw = FALSE; |
b3eb3b98 RN |
509 | |
510 | // Save settings for reuse | |
21f0a3ca | 511 | save_default_values ( options->ov ); |
b3eb3b98 | 512 | |
404328d1 RN |
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 | |
c75da936 RN |
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 ); | |
404328d1 RN |
527 | |
528 | g_free ( tmp ); | |
b3eb3b98 RN |
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 | ||
cbac0d22 RN |
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 | } | |
b3eb3b98 RN |
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 | */ | |
41f4abac RN |
577 | void trw_layer_geotag_dialog ( GtkWindow *parent, |
578 | VikTrwLayer *vtl, | |
579 | VikWaypoint *wpt, | |
580 | VikTrack *track ) | |
b3eb3b98 RN |
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 ); | |
8a2df21a RN |
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 )); | |
b3eb3b98 | 595 | widgets->vtl = vtl; |
41f4abac | 596 | widgets->wpt = wpt; |
b3eb3b98 RN |
597 | widgets->track = track; |
598 | widgets->create_waypoints_b = GTK_CHECK_BUTTON ( gtk_check_button_new () ); | |
cbac0d22 RN |
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 () ); | |
b3eb3b98 RN |
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 | ||
21f0a3ca RN |
613 | // Defaults |
614 | option_values_t default_values = get_default_values (); | |
615 | ||
b3eb3b98 | 616 | gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(widgets->create_waypoints_b), default_values.create_waypoints ); |
cbac0d22 | 617 | gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(widgets->overwrite_waypoints_b), default_values.overwrite_waypoints ); |
b3eb3b98 RN |
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 ); | |
b3eb3b98 RN |
630 | g_signal_connect ( G_OBJECT(widgets->write_exif_b), "toggled", G_CALLBACK(write_exif_b_cb), widgets ); |
631 | ||
cbac0d22 RN |
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 | ||
b3eb3b98 | 635 | GtkWidget *cw_hbox = gtk_hbox_new ( FALSE, 0 ); |
41f4abac RN |
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 ); | |
b3eb3b98 RN |
638 | gtk_box_pack_start ( GTK_BOX(cw_hbox), GTK_WIDGET(widgets->create_waypoints_b), FALSE, FALSE, 5 ); |
639 | ||
cbac0d22 RN |
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 | ||
b3eb3b98 RN |
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 ); | |
41f4abac RN |
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 ); | |
b3eb3b98 RN |
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 ); | |
41f4abac RN |
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 ); | |
b3eb3b98 RN |
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 ); | |
41f4abac RN |
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 ); | |
b3eb3b98 RN |
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; | |
41f4abac RN |
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 ); | |
b3eb3b98 RN |
690 | else |
691 | track_string = g_strdup_printf ( _("Using all tracks in: %s"), VIK_LAYER(widgets->vtl)->name ); | |
692 | ||
9b082b39 RN |
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); | |
b3eb3b98 RN |
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 | } |