]>
Commit | Line | Data |
---|---|---|
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) 2013-2017, 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 | * Dependencies in this file can be on anything. | |
24 | * For functions with simple system dependencies put it in util.c | |
25 | */ | |
26 | #include <math.h> | |
27 | #include <glib/gstdio.h> | |
28 | #include <glib/gi18n.h> | |
29 | #include <gtk/gtk.h> | |
30 | ||
31 | #include "viking.h" | |
32 | #include "vikutils.h" | |
33 | #include "globals.h" | |
34 | #include "download.h" | |
35 | #include "preferences.h" | |
36 | #include "vikmapslayer.h" | |
37 | #include "settings.h" | |
38 | #include "ui_util.h" | |
39 | #include "dir.h" | |
40 | #include "misc/kdtree.h" | |
41 | #include "misc/gtkhtml-private.h" | |
42 | ||
43 | #define FMT_MAX_NUMBER_CODES 9 | |
44 | ||
45 | /** | |
46 | * vu_trackpoint_formatted_message: | |
47 | * @format_code: String describing the message to generate | |
48 | * @trkpt: The trackpoint for which the message is generated about | |
49 | * @trkpt_prev: A trackpoint (presumed previous) for interpolating values with the other trackpoint (such as speed) | |
50 | * @trk: The track in which the trackpoints reside | |
51 | * @climb: Vertical speed (Out of band (i.e. not in a trackpoint) value for display currently only for GPSD usage) | |
52 | * | |
53 | * TODO: One day replace this cryptic format code with some kind of tokenizer parsing | |
54 | * thus would make it more user friendly and maybe even GUI controlable. | |
55 | * However for now at least there is some semblance of user control | |
56 | */ | |
57 | gchar* vu_trackpoint_formatted_message ( gchar *format_code, VikTrackpoint *trkpt, VikTrackpoint *trkpt_prev, VikTrack *trk, gdouble climb ) | |
58 | { | |
59 | if ( !trkpt ) | |
60 | return NULL; | |
61 | ||
62 | gint len = 0; | |
63 | if ( format_code ) | |
64 | len = strlen ( format_code ); | |
65 | if ( len > FMT_MAX_NUMBER_CODES ) | |
66 | len = FMT_MAX_NUMBER_CODES; | |
67 | ||
68 | gchar* values[FMT_MAX_NUMBER_CODES]; | |
69 | int i; | |
70 | for ( i = 0; i < FMT_MAX_NUMBER_CODES; i++ ) { | |
71 | values[i] = '\0'; | |
72 | } | |
73 | ||
74 | gchar *speed_units_str = NULL; | |
75 | vik_units_speed_t speed_units = a_vik_get_units_speed (); | |
76 | switch (speed_units) { | |
77 | case VIK_UNITS_SPEED_MILES_PER_HOUR: | |
78 | speed_units_str = g_strdup ( _("mph") ); | |
79 | break; | |
80 | case VIK_UNITS_SPEED_METRES_PER_SECOND: | |
81 | speed_units_str = g_strdup ( _("m/s") ); | |
82 | break; | |
83 | case VIK_UNITS_SPEED_KNOTS: | |
84 | speed_units_str = g_strdup ( _("knots") ); | |
85 | break; | |
86 | default: | |
87 | // VIK_UNITS_SPEED_KILOMETRES_PER_HOUR: | |
88 | speed_units_str = g_strdup ( _("km/h") ); | |
89 | break; | |
90 | } | |
91 | ||
92 | gchar *separator = g_strdup ( " | " ); | |
93 | ||
94 | for ( i = 0; i < len; i++ ) { | |
95 | switch ( g_ascii_toupper ( format_code[i] ) ) { | |
96 | case 'G': values[i] = g_strdup ( _("GPSD") ); break; // GPS Preamble | |
97 | case 'K': values[i] = g_strdup ( _("Trkpt") ); break; // Trkpt Preamble | |
98 | ||
99 | case 'S': { | |
100 | gdouble speed = 0.0; | |
101 | gchar *speedtype = NULL; | |
102 | if ( isnan(trkpt->speed) && trkpt_prev ) { | |
103 | if ( trkpt->has_timestamp && trkpt_prev->has_timestamp ) { | |
104 | if ( trkpt->timestamp != trkpt_prev->timestamp ) { | |
105 | ||
106 | // Work out from previous trackpoint location and time difference | |
107 | speed = vik_coord_diff(&(trkpt->coord), &(trkpt_prev->coord)) / ABS(trkpt->timestamp - trkpt_prev->timestamp); | |
108 | speedtype = g_strdup ( "*" ); // Interpolated | |
109 | } | |
110 | else | |
111 | speedtype = g_strdup ( "**" ); | |
112 | } | |
113 | else | |
114 | speedtype = g_strdup ( "**" ); | |
115 | } | |
116 | else { | |
117 | speed = trkpt->speed; | |
118 | speedtype = g_strdup ( "" ); | |
119 | } | |
120 | switch (speed_units) { | |
121 | case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR: | |
122 | speed = VIK_MPS_TO_KPH(speed); | |
123 | break; | |
124 | case VIK_UNITS_SPEED_MILES_PER_HOUR: | |
125 | speed = VIK_MPS_TO_MPH(speed); | |
126 | break; | |
127 | case VIK_UNITS_SPEED_KNOTS: | |
128 | speed = VIK_MPS_TO_KNOTS(speed); | |
129 | break; | |
130 | default: | |
131 | // VIK_UNITS_SPEED_METRES_PER_SECOND: | |
132 | // Already in m/s so nothing to do | |
133 | break; | |
134 | } | |
135 | ||
136 | values[i] = g_strdup_printf ( _("%sSpeed%s %.1f%s"), separator, speedtype, speed, speed_units_str ); | |
137 | g_free ( speedtype ); | |
138 | break; | |
139 | } | |
140 | ||
141 | case 'B': { | |
142 | gdouble speed = 0.0; | |
143 | gchar *speedtype = NULL; | |
144 | if ( isnan(climb) && trkpt_prev ) { | |
145 | if ( trkpt->has_timestamp && trkpt_prev->has_timestamp ) { | |
146 | if ( trkpt->timestamp != trkpt_prev->timestamp ) { | |
147 | // Work out from previous trackpoint altitudes and time difference | |
148 | // 'speed' can be negative if going downhill | |
149 | speed = (trkpt->altitude - trkpt_prev->altitude) / ABS(trkpt->timestamp - trkpt_prev->timestamp); | |
150 | speedtype = g_strdup ( "*" ); // Interpolated | |
151 | } | |
152 | else | |
153 | speedtype = g_strdup ( "**" ); // Unavailable | |
154 | } | |
155 | else | |
156 | speedtype = g_strdup ( "**" ); | |
157 | } | |
158 | else { | |
159 | speed = climb; | |
160 | speedtype = g_strdup ( "" ); | |
161 | } | |
162 | switch (speed_units) { | |
163 | case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR: | |
164 | speed = VIK_MPS_TO_KPH(speed); | |
165 | break; | |
166 | case VIK_UNITS_SPEED_MILES_PER_HOUR: | |
167 | speed = VIK_MPS_TO_MPH(speed); | |
168 | break; | |
169 | case VIK_UNITS_SPEED_KNOTS: | |
170 | speed = VIK_MPS_TO_KNOTS(speed); | |
171 | break; | |
172 | default: | |
173 | // VIK_UNITS_SPEED_METRES_PER_SECOND: | |
174 | // Already in m/s so nothing to do | |
175 | break; | |
176 | } | |
177 | // Go for 2dp as expect low values for vertical speeds | |
178 | values[i] = g_strdup_printf ( _("%sClimb%s %.2f%s"), separator, speedtype, speed, speed_units_str ); | |
179 | g_free ( speedtype ); | |
180 | break; | |
181 | } | |
182 | ||
183 | case 'A': { | |
184 | vik_units_height_t height_units = a_vik_get_units_height (); | |
185 | switch (height_units) { | |
186 | case VIK_UNITS_HEIGHT_FEET: | |
187 | values[i] = g_strdup_printf ( _("%sAlt %dfeet"), separator, (int)round(VIK_METERS_TO_FEET(trkpt->altitude)) ); | |
188 | break; | |
189 | default: | |
190 | //VIK_UNITS_HEIGHT_METRES: | |
191 | values[i] = g_strdup_printf ( _("%sAlt %dm"), separator, (int)round(trkpt->altitude) ); | |
192 | break; | |
193 | } | |
194 | break; | |
195 | } | |
196 | ||
197 | case 'C': { | |
198 | gint heading = isnan(trkpt->course) ? 0 : (gint)round(trkpt->course); | |
199 | values[i] = g_strdup_printf ( _("%sCourse %03d\302\260" ), separator, heading ); | |
200 | break; | |
201 | } | |
202 | ||
203 | case 'P': { | |
204 | if ( trkpt_prev ) { | |
205 | gint diff = (gint) round ( vik_coord_diff ( &(trkpt->coord), &(trkpt_prev->coord) ) ); | |
206 | ||
207 | gchar *dist_units_str = NULL; | |
208 | vik_units_distance_t dist_units = a_vik_get_units_distance (); | |
209 | // expect the difference between track points to be small hence use metres or yards | |
210 | switch (dist_units) { | |
211 | case VIK_UNITS_DISTANCE_MILES: | |
212 | case VIK_UNITS_DISTANCE_NAUTICAL_MILES: | |
213 | dist_units_str = g_strdup ( _("yards") ); | |
214 | break; | |
215 | default: | |
216 | // VIK_UNITS_DISTANCE_KILOMETRES: | |
217 | dist_units_str = g_strdup ( _("m") ); | |
218 | break; | |
219 | } | |
220 | ||
221 | values[i] = g_strdup_printf ( _("%sDistance diff %d%s"), separator, diff, dist_units_str ); | |
222 | ||
223 | g_free ( dist_units_str ); | |
224 | } | |
225 | break; | |
226 | } | |
227 | ||
228 | case 'T': { | |
229 | gchar *msg; | |
230 | if ( trkpt->has_timestamp ) { | |
231 | // Compact date time format | |
232 | msg = vu_get_time_string ( &(trkpt->timestamp), "%x %X", &(trkpt->coord), NULL ); | |
233 | } | |
234 | else | |
235 | msg = g_strdup ("--"); | |
236 | values[i] = g_strdup_printf ( _("%sTime %s"), separator, msg ); | |
237 | g_free ( msg ); | |
238 | break; | |
239 | } | |
240 | ||
241 | case 'M': { | |
242 | if ( trkpt_prev ) { | |
243 | if ( trkpt->has_timestamp && trkpt_prev->has_timestamp ) { | |
244 | time_t t_diff = trkpt->timestamp - trkpt_prev->timestamp; | |
245 | values[i] = g_strdup_printf ( _("%sTime diff %lds"), separator, t_diff ); | |
246 | } | |
247 | } | |
248 | break; | |
249 | } | |
250 | ||
251 | case 'X': values[i] = g_strdup_printf ( _("%sNo. of Sats %d"), separator, trkpt->nsats ); break; | |
252 | ||
253 | case 'F': { | |
254 | if ( trk ) { | |
255 | // Distance to the end 'Finish' (along the track) | |
256 | gdouble distd = vik_track_get_length_to_trackpoint (trk, trkpt); | |
257 | gdouble diste = vik_track_get_length_including_gaps ( trk ); | |
258 | gdouble dist = diste - distd; | |
259 | gchar *dist_units_str = NULL; | |
260 | vik_units_distance_t dist_units = a_vik_get_units_distance (); | |
261 | switch (dist_units) { | |
262 | case VIK_UNITS_DISTANCE_MILES: | |
263 | dist_units_str = g_strdup ( _("miles") ); | |
264 | dist = VIK_METERS_TO_MILES(dist); | |
265 | break; | |
266 | case VIK_UNITS_DISTANCE_NAUTICAL_MILES: | |
267 | dist_units_str = g_strdup ( _("NM") ); | |
268 | dist = VIK_METERS_TO_NAUTICAL_MILES(dist); | |
269 | break; | |
270 | default: | |
271 | // VIK_UNITS_DISTANCE_KILOMETRES: | |
272 | dist_units_str = g_strdup ( _("km") ); | |
273 | dist = dist / 1000.0; | |
274 | break; | |
275 | } | |
276 | values[i] = g_strdup_printf ( _("%sTo End %.2f%s"), separator, dist, dist_units_str ); | |
277 | g_free ( dist_units_str ); | |
278 | } | |
279 | break; | |
280 | } | |
281 | ||
282 | case 'D': { | |
283 | if ( trk ) { | |
284 | // Distance from start (along the track) | |
285 | gdouble distd = vik_track_get_length_to_trackpoint (trk, trkpt); | |
286 | gchar *dist_units_str = NULL; | |
287 | vik_units_distance_t dist_units = a_vik_get_units_distance (); | |
288 | switch (dist_units) { | |
289 | case VIK_UNITS_DISTANCE_MILES: | |
290 | dist_units_str = g_strdup ( _("miles") ); | |
291 | distd = VIK_METERS_TO_MILES(distd); | |
292 | break; | |
293 | case VIK_UNITS_DISTANCE_NAUTICAL_MILES: | |
294 | dist_units_str = g_strdup ( _("NM") ); | |
295 | distd = VIK_METERS_TO_NAUTICAL_MILES(distd); | |
296 | break; | |
297 | default: | |
298 | // VIK_UNITS_DISTANCE_KILOMETRES: | |
299 | dist_units_str = g_strdup ( _("km") ); | |
300 | distd = distd / 1000.0; | |
301 | break; | |
302 | } | |
303 | values[i] = g_strdup_printf ( _("%sDistance along %.2f%s"), separator, distd, dist_units_str ); | |
304 | g_free ( dist_units_str ); | |
305 | } | |
306 | break; | |
307 | } | |
308 | ||
309 | case 'L': { | |
310 | // Location (Lat/Long) | |
311 | gchar *lat = NULL, *lon = NULL; | |
312 | struct LatLon ll; | |
313 | vik_coord_to_latlon (&(trkpt->coord), &ll); | |
314 | a_coords_latlon_to_string ( &ll, &lat, &lon ); | |
315 | values[i] = g_strdup_printf ( "%s%s %s", separator, lat, lon ); | |
316 | g_free ( lat ); | |
317 | g_free ( lon ); | |
318 | break; | |
319 | } | |
320 | ||
321 | case 'N': // Name of track | |
322 | if ( trk ) | |
323 | values[i] = g_strdup_printf ( _("%sTrack: %s"), separator, trk->name ); | |
324 | break; | |
325 | ||
326 | case 'E': // Name of trackpoint if available | |
327 | if ( trkpt->name ) | |
328 | values[i] = g_strdup_printf ( "%s%s", separator, trkpt->name ); | |
329 | else | |
330 | values[i] = g_strdup ( "" ); | |
331 | break; | |
332 | ||
333 | default: | |
334 | break; | |
335 | } | |
336 | } | |
337 | ||
338 | g_free ( separator ); | |
339 | g_free ( speed_units_str ); | |
340 | ||
341 | gchar *msg = g_strconcat ( values[0], values[1], values[2], values[3], values[4], values[5], values[6], values[7], values[8], NULL ); | |
342 | ||
343 | for ( i = 0; i < FMT_MAX_NUMBER_CODES; i++ ) { | |
344 | if ( values[i] != '\0' ) | |
345 | g_free ( values[i] ); | |
346 | } | |
347 | ||
348 | return msg; | |
349 | } | |
350 | ||
351 | typedef struct { | |
352 | GtkWindow *window; // Layer needed for redrawing | |
353 | gchar *version; // Image list | |
354 | } new_version_thread_data; | |
355 | ||
356 | static gboolean new_version_available_message ( new_version_thread_data *nvtd ) | |
357 | { | |
358 | // Only a simple goto website option is offered | |
359 | // Trying to do an installation update is platform specific | |
360 | if ( a_dialog_yes_or_no ( nvtd->window, | |
361 | _("There is a newer version of Viking available: %s\n\nDo you wish to go to Viking's website now?"), nvtd->version ) ) | |
362 | // NB 'VIKING_URL' redirects to the Wiki, here we want to go the main site. | |
363 | open_url ( nvtd->window, "http://sourceforge.net/projects/viking/" ); | |
364 | ||
365 | g_free ( nvtd->version ); | |
366 | g_free ( nvtd ); | |
367 | return FALSE; | |
368 | } | |
369 | ||
370 | #define VIK_SETTINGS_VERSION_CHECKED_DATE "version_checked_date" | |
371 | ||
372 | static void latest_version_thread ( GtkWindow *window ) | |
373 | { | |
374 | // Need to allow a few redirects, as SF file is often served from different server | |
375 | DownloadFileOptions options = { FALSE, FALSE, NULL, 5, NULL, NULL, NULL }; | |
376 | gchar *filename = a_download_uri_to_tmp_file ( "http://sourceforge.net/projects/viking/files/VERSION", &options ); | |
377 | //gchar *filename = g_strdup ( "VERSION" ); | |
378 | if ( !filename ) { | |
379 | return; | |
380 | } | |
381 | ||
382 | GMappedFile *mf = g_mapped_file_new ( filename, FALSE, NULL ); | |
383 | if ( !mf ) | |
384 | return; | |
385 | ||
386 | gchar *text = g_mapped_file_get_contents ( mf ); | |
387 | ||
388 | gint latest_version = viking_version_to_number ( text ); | |
389 | gint my_version = viking_version_to_number ( VIKING_VERSION ); | |
390 | ||
391 | g_debug ( "The lastest version is: %s", text ); | |
392 | ||
393 | if ( my_version < latest_version ) { | |
394 | new_version_thread_data *nvtd = g_malloc ( sizeof(new_version_thread_data) ); | |
395 | nvtd->window = window; | |
396 | nvtd->version = g_strdup ( text ); | |
397 | gdk_threads_add_idle ( (GSourceFunc) new_version_available_message, nvtd ); | |
398 | } | |
399 | else | |
400 | g_debug ( "Running the lastest version: %s", VIKING_VERSION ); | |
401 | ||
402 | g_mapped_file_unref ( mf ); | |
403 | if ( filename ) { | |
404 | g_remove ( filename ); | |
405 | g_free ( filename ); | |
406 | } | |
407 | ||
408 | // Update last checked time | |
409 | GTimeVal time; | |
410 | g_get_current_time ( &time ); | |
411 | a_settings_set_string ( VIK_SETTINGS_VERSION_CHECKED_DATE, g_time_val_to_iso8601(&time) ); | |
412 | } | |
413 | ||
414 | #define VIK_SETTINGS_VERSION_CHECK_PERIOD "version_check_period_days" | |
415 | ||
416 | /** | |
417 | * vu_check_latest_version: | |
418 | * @window: Somewhere where we may need use the display to inform the user about the version status | |
419 | * | |
420 | * Periodically checks the released latest VERSION file on the website to compare with the running version | |
421 | * | |
422 | */ | |
423 | void vu_check_latest_version ( GtkWindow *window ) | |
424 | { | |
425 | if ( ! a_vik_get_check_version () ) | |
426 | return; | |
427 | ||
428 | gboolean do_check = FALSE; | |
429 | ||
430 | gint check_period; | |
431 | if ( ! a_settings_get_integer ( VIK_SETTINGS_VERSION_CHECK_PERIOD, &check_period ) ) { | |
432 | check_period = 14; | |
433 | } | |
434 | ||
435 | // Get last checked date... | |
436 | GDate *gdate_last = g_date_new(); | |
437 | GDate *gdate_now = g_date_new(); | |
438 | GTimeVal time_last; | |
439 | gchar *last_checked_date = NULL; | |
440 | ||
441 | // When no previous date available - set to do the version check | |
442 | if ( a_settings_get_string ( VIK_SETTINGS_VERSION_CHECKED_DATE, &last_checked_date) ) { | |
443 | if ( g_time_val_from_iso8601 ( last_checked_date, &time_last ) ) { | |
444 | g_date_set_time_val ( gdate_last, &time_last ); | |
445 | } | |
446 | else | |
447 | do_check = TRUE; | |
448 | } | |
449 | else | |
450 | do_check = TRUE; | |
451 | ||
452 | GTimeVal time_now; | |
453 | g_get_current_time ( &time_now ); | |
454 | g_date_set_time_val ( gdate_now, &time_now ); | |
455 | ||
456 | if ( ! do_check ) { | |
457 | // Dates available so do the comparison | |
458 | g_date_add_days ( gdate_last, check_period ); | |
459 | if ( g_date_compare ( gdate_last, gdate_now ) < 0 ) | |
460 | do_check = TRUE; | |
461 | } | |
462 | ||
463 | g_date_free ( gdate_last ); | |
464 | g_date_free ( gdate_now ); | |
465 | ||
466 | if ( do_check ) { | |
467 | #if GLIB_CHECK_VERSION (2, 32, 0) | |
468 | g_thread_try_new ( "latest_version_thread", (GThreadFunc)latest_version_thread, window, NULL ); | |
469 | #else | |
470 | g_thread_create ( (GThreadFunc)latest_version_thread, window, FALSE, NULL ); | |
471 | #endif | |
472 | } | |
473 | } | |
474 | ||
475 | /** | |
476 | * vu_set_auto_features_on_first_run: | |
477 | * | |
478 | * Ask the user's opinion to set some of Viking's default behaviour | |
479 | */ | |
480 | void vu_set_auto_features_on_first_run ( void ) | |
481 | { | |
482 | gboolean auto_features = FALSE; | |
483 | gboolean set_defaults = FALSE; | |
484 | ||
485 | if ( a_vik_very_first_run () ) { | |
486 | ||
487 | GtkWidget *win = gtk_window_new ( GTK_WINDOW_TOPLEVEL ); | |
488 | ||
489 | if ( a_dialog_yes_or_no ( GTK_WINDOW(win), | |
490 | _("This appears to be Viking's very first run.\n\nDo you wish to enable automatic internet features?\n\nIndividual settings can be controlled in the Preferences."), NULL ) ) | |
491 | auto_features = TRUE; | |
492 | ||
493 | // Default to more standard cache layout for new users (well new installs at least) | |
494 | maps_layer_set_cache_default ( VIK_MAPS_CACHE_LAYOUT_OSM ); | |
495 | set_defaults = TRUE; | |
496 | } | |
497 | ||
498 | if ( auto_features ) { | |
499 | // Set Maps to autodownload | |
500 | // Ensure the default is true | |
501 | maps_layer_set_autodownload_default ( TRUE ); | |
502 | set_defaults = TRUE; | |
503 | ||
504 | // Simplistic repeat of preference settings | |
505 | // Only the name & type are important for setting a preference via this 'external' way | |
506 | ||
507 | // Enable auto add map + | |
508 | // Enable IP lookup | |
509 | VikLayerParam pref_add_map[] = { { VIK_LAYER_NUM_TYPES, VIKING_PREFERENCES_STARTUP_NAMESPACE "add_default_map_layer", VIK_LAYER_PARAM_BOOLEAN, VIK_LAYER_GROUP_NONE, NULL, VIK_LAYER_WIDGET_CHECKBUTTON, NULL, NULL, NULL, NULL, NULL, NULL, }, }; | |
510 | VikLayerParam pref_startup_method[] = { { VIK_LAYER_NUM_TYPES, VIKING_PREFERENCES_STARTUP_NAMESPACE "startup_method", VIK_LAYER_PARAM_UINT, VIK_LAYER_GROUP_NONE, NULL, VIK_LAYER_WIDGET_COMBOBOX, NULL, NULL, NULL, NULL, NULL, NULL}, }; | |
511 | ||
512 | VikLayerParamData vlp_data; | |
513 | vlp_data.b = TRUE; | |
514 | a_preferences_run_setparam ( vlp_data, pref_add_map ); | |
515 | ||
516 | vlp_data.u = VIK_STARTUP_METHOD_AUTO_LOCATION; | |
517 | a_preferences_run_setparam ( vlp_data, pref_startup_method ); | |
518 | ||
519 | // Only on Windows make checking for the latest version on by default | |
520 | // For other systems it's expected a Package manager or similar controls the installation, so leave it off | |
521 | #ifdef WINDOWS | |
522 | VikLayerParam pref_startup_version_check[] = { { VIK_LAYER_NUM_TYPES, VIKING_PREFERENCES_STARTUP_NAMESPACE "check_version", VIK_LAYER_PARAM_BOOLEAN, VIK_LAYER_GROUP_NONE, NULL, VIK_LAYER_WIDGET_CHECKBUTTON, NULL, NULL, NULL, NULL, }, }; | |
523 | vlp_data.b = TRUE; | |
524 | a_preferences_run_setparam ( vlp_data, pref_startup_version_check ); | |
525 | #endif | |
526 | ||
527 | // Ensure settings are saved for next time | |
528 | a_preferences_save_to_file (); | |
529 | } | |
530 | ||
531 | // Ensure defaults are saved if changed | |
532 | if ( set_defaults ) | |
533 | a_layer_defaults_save (); | |
534 | } | |
535 | ||
536 | /** | |
537 | * vu_get_canonical_filename: | |
538 | * | |
539 | * Returns: Canonical absolute filename | |
540 | * | |
541 | * Any time a path may contain a relative component, so need to prepend that directory it is relative to | |
542 | * Then resolve the full path to get the normal canonical filename | |
543 | */ | |
544 | gchar *vu_get_canonical_filename ( VikLayer *vl, const gchar *filename ) | |
545 | { | |
546 | gchar *canonical = NULL; | |
547 | if ( !filename ) | |
548 | return NULL; | |
549 | ||
550 | if ( g_path_is_absolute ( filename ) ) | |
551 | canonical = g_strdup ( filename ); | |
552 | else { | |
553 | const gchar *vw_filename = vik_window_get_filename ( VIK_WINDOW_FROM_WIDGET (vl->vvp) ); | |
554 | gchar *dirpath = NULL; | |
555 | if ( vw_filename ) | |
556 | dirpath = g_path_get_dirname ( vw_filename ); | |
557 | else | |
558 | dirpath = g_get_current_dir(); // Fallback - if here then probably can't create the correct path | |
559 | ||
560 | gchar *full = NULL; | |
561 | if ( g_path_is_absolute ( dirpath ) ) | |
562 | full = g_strconcat ( dirpath, G_DIR_SEPARATOR_S, filename, NULL ); | |
563 | else | |
564 | full = g_strconcat ( g_get_current_dir(), G_DIR_SEPARATOR_S, dirpath, G_DIR_SEPARATOR_S, filename, NULL ); | |
565 | ||
566 | canonical = file_realpath_dup ( full ); // resolved | |
567 | g_free ( full ); | |
568 | g_free ( dirpath ); | |
569 | } | |
570 | ||
571 | return canonical; | |
572 | } | |
573 | ||
574 | static struct kdtree *kd = NULL; | |
575 | ||
576 | /** | |
577 | * load_ll_tz_dir | |
578 | * @dir: The directory from which to load the latlontz.txt file | |
579 | * | |
580 | * Returns: The number of elements within the latlontz.txt loaded | |
581 | */ | |
582 | static gint load_ll_tz_dir ( const gchar *dir ) | |
583 | { | |
584 | gint inserted = 0; | |
585 | gchar *lltz = g_build_filename ( dir, "latlontz.txt", NULL ); | |
586 | if ( g_access(lltz, R_OK) == 0 ) { | |
587 | gchar buffer[4096]; | |
588 | long line_num = 0; | |
589 | FILE *ff = g_fopen ( lltz, "r" ); | |
590 | if ( ff ) { | |
591 | while ( fgets ( buffer, 4096, ff ) ) { | |
592 | line_num++; | |
593 | gchar **components = g_strsplit (buffer, " ", 3); | |
594 | guint nn = g_strv_length ( components ); | |
595 | if ( nn == 3 ) { | |
596 | double pt[2] = { g_ascii_strtod (components[0], NULL), g_ascii_strtod (components[1], NULL) }; | |
597 | gchar *timezone = g_strchomp ( components[2] ); | |
598 | if ( kd_insert ( kd, pt, timezone ) ) | |
599 | g_critical ( "Insertion problem of %s for line %ld of latlontz.txt", timezone, line_num ); | |
600 | else | |
601 | inserted++; | |
602 | // NB Don't free timezone as it's part of the kdtree data now | |
603 | g_free ( components[0] ); | |
604 | g_free ( components[1] ); | |
605 | } else { | |
606 | g_warning ( "Line %ld of latlontz.txt does not have 3 parts", line_num ); | |
607 | } | |
608 | g_free ( components ); | |
609 | } | |
610 | fclose ( ff ); | |
611 | } | |
612 | else { | |
613 | g_warning ( "%s: Could not open %s", __FUNCTION__, lltz); | |
614 | } | |
615 | } | |
616 | g_free ( lltz ); | |
617 | ||
618 | return inserted; | |
619 | } | |
620 | ||
621 | /** | |
622 | * vu_setup_lat_lon_tz_lookup: | |
623 | * | |
624 | * Can be called multiple times but only initializes the lookup once | |
625 | */ | |
626 | void vu_setup_lat_lon_tz_lookup () | |
627 | { | |
628 | // Only setup once | |
629 | if ( kd ) | |
630 | return; | |
631 | ||
632 | kd = kd_create(2); | |
633 | ||
634 | // Look in the directories of data path | |
635 | gchar **data_dirs = a_get_viking_data_path(); | |
636 | guint loaded = 0; | |
637 | // Process directories in reverse order for priority | |
638 | guint n_data_dirs = g_strv_length ( data_dirs ); | |
639 | for (; n_data_dirs > 0; n_data_dirs--) { | |
640 | loaded += load_ll_tz_dir(data_dirs[n_data_dirs-1]); | |
641 | } | |
642 | g_strfreev ( data_dirs ); | |
643 | ||
644 | g_debug ( "%s: Loaded %d elements", __FUNCTION__, loaded ); | |
645 | if ( loaded == 0 ) | |
646 | g_critical ( "%s: No lat/lon/timezones loaded", __FUNCTION__ ); | |
647 | } | |
648 | ||
649 | /** | |
650 | * vu_finalize_lat_lon_tz_lookup: | |
651 | * | |
652 | * Clear memory used by the lookup. | |
653 | * only call on program exit | |
654 | */ | |
655 | void vu_finalize_lat_lon_tz_lookup () | |
656 | { | |
657 | if ( kd ) { | |
658 | kd_data_destructor ( kd, g_free ); | |
659 | kd_free ( kd ); | |
660 | } | |
661 | } | |
662 | ||
663 | static double dist_sq( double *a1, double *a2, int dims ) { | |
664 | double dist_sq = 0, diff; | |
665 | while( --dims >= 0 ) { | |
666 | diff = (a1[dims] - a2[dims]); | |
667 | dist_sq += diff*diff; | |
668 | } | |
669 | return dist_sq; | |
670 | } | |
671 | ||
672 | static gchar* time_string_adjusted ( time_t *time, gint offset_s ) | |
673 | { | |
674 | time_t *mytime = time; | |
675 | *mytime = *mytime + offset_s; | |
676 | gchar *str = g_malloc ( 64 ); | |
677 | // Append asterisks to indicate use of simplistic model (i.e. no TZ) | |
678 | strftime ( str, 64, "%a %X %x **", gmtime(mytime) ); | |
679 | return str; | |
680 | } | |
681 | ||
682 | static gchar* time_string_tz ( time_t *time, const gchar *format, GTimeZone *tz ) | |
683 | { | |
684 | GDateTime *utc = g_date_time_new_from_unix_utc (*time); | |
685 | if ( !utc ) { | |
686 | g_warning ( "%s: result from g_date_time_new_from_unix_utc() is NULL", __FUNCTION__ ); | |
687 | return NULL; | |
688 | } | |
689 | GDateTime *local = g_date_time_to_timezone ( utc, tz ); | |
690 | if ( !local ) { | |
691 | g_date_time_unref ( utc ); | |
692 | g_warning ( "%s: result from g_date_time_to_timezone() is NULL", __FUNCTION__ ); | |
693 | return NULL; | |
694 | } | |
695 | gchar *str = g_date_time_format ( local, format ); | |
696 | ||
697 | g_date_time_unref ( local ); | |
698 | g_date_time_unref ( utc ); | |
699 | return str; | |
700 | } | |
701 | ||
702 | #define VIK_SETTINGS_NEAREST_TZ_FACTOR "utils_nearest_tz_factor" | |
703 | /** | |
704 | * vu_get_tz_at_location: | |
705 | * | |
706 | * @vc: Position for which the time zone is desired | |
707 | * | |
708 | * Returns: TimeZone string of the nearest known location. String may be NULL. | |
709 | * | |
710 | * Use the k-d tree method (http://en.wikipedia.org/wiki/Kd-tree) to quickly retreive | |
711 | * the nearest location to the given position. | |
712 | */ | |
713 | gchar* vu_get_tz_at_location ( const VikCoord* vc ) | |
714 | { | |
715 | gchar *tz = NULL; | |
716 | if ( !vc || !kd ) | |
717 | return tz; | |
718 | ||
719 | struct LatLon ll; | |
720 | vik_coord_to_latlon ( vc, &ll ); | |
721 | double pt[2] = { ll.lat, ll.lon }; | |
722 | ||
723 | gdouble nearest; | |
724 | if ( !a_settings_get_double(VIK_SETTINGS_NEAREST_TZ_FACTOR, &nearest) ) | |
725 | nearest = 1.0; | |
726 | ||
727 | struct kdres *presults = kd_nearest_range ( kd, pt, nearest ); | |
728 | while( !kd_res_end( presults ) ) { | |
729 | double pos[2]; | |
730 | gchar *ans = (gchar*)kd_res_item ( presults, pos ); | |
731 | // compute the distance of the current result from the pt | |
732 | double dist = sqrt( dist_sq( pt, pos, 2 ) ); | |
733 | if ( dist < nearest ) { | |
734 | //printf( "NEARER node at (%.3f, %.3f, %.3f) is %.3f away is %s\n", pos[0], pos[1], pos[2], dist, ans ); | |
735 | nearest = dist; | |
736 | tz = ans; | |
737 | } | |
738 | kd_res_next ( presults ); | |
739 | } | |
740 | g_debug ( "TZ lookup found %d results - picked %s", kd_res_size(presults), tz ); | |
741 | kd_res_free ( presults ); | |
742 | ||
743 | return tz; | |
744 | } | |
745 | ||
746 | /** | |
747 | * vu_get_time_string: | |
748 | * | |
749 | * @time_t: The time of which the string is wanted | |
750 | * @format The format of the time string - such as "%c" | |
751 | * @vc: Position of object for the time output - maybe NULL | |
752 | * (only applicable for VIK_TIME_REF_WORLD) | |
753 | * @tz: TimeZone string - maybe NULL. | |
754 | * (only applicable for VIK_TIME_REF_WORLD) | |
755 | * Useful to pass in the cached value from vu_get_tz_at_location() to save looking it up again for the same position | |
756 | * | |
757 | * Returns: A string of the time according to the time display property | |
758 | */ | |
759 | gchar* vu_get_time_string ( time_t *time, const gchar *format, const VikCoord* vc, const gchar *tz ) | |
760 | { | |
761 | if ( !format ) return NULL; | |
762 | gchar *str = NULL; | |
763 | switch ( a_vik_get_time_ref_frame() ) { | |
764 | case VIK_TIME_REF_UTC: | |
765 | str = g_malloc ( 64 ); | |
766 | strftime ( str, 64, format, gmtime(time) ); // Always 'GMT' | |
767 | break; | |
768 | case VIK_TIME_REF_WORLD: | |
769 | if ( vc && !tz ) { | |
770 | // No timezone specified so work it out | |
771 | gchar *mytz = vu_get_tz_at_location ( vc ); | |
772 | if ( mytz ) { | |
773 | GTimeZone *gtz = g_time_zone_new ( mytz ); | |
774 | str = time_string_tz ( time, format, gtz ); | |
775 | g_time_zone_unref ( gtz ); | |
776 | } | |
777 | else { | |
778 | // No results (e.g. could be in the middle of a sea) | |
779 | // Fallback to simplistic method that doesn't take into account Timezones of countries. | |
780 | struct LatLon ll; | |
781 | vik_coord_to_latlon ( vc, &ll ); | |
782 | str = time_string_adjusted ( time, round ( ll.lon / 15.0 ) * 3600 ); | |
783 | } | |
784 | } | |
785 | else { | |
786 | // Use specified timezone | |
787 | GTimeZone *gtz = g_time_zone_new ( tz ); | |
788 | str = time_string_tz ( time, format, gtz ); | |
789 | g_time_zone_unref ( gtz ); | |
790 | } | |
791 | break; | |
792 | default: // VIK_TIME_REF_LOCALE | |
793 | str = g_malloc ( 64 ); | |
794 | strftime ( str, 64, format, localtime(time) ); | |
795 | break; | |
796 | } | |
797 | return str; | |
798 | } | |
799 | ||
800 | /** | |
801 | * vu_command_line: | |
802 | * | |
803 | * Apply any startup values that have been specified from the command line | |
804 | * Values are defaulted in such a manner not to be applied when they haven't been specified | |
805 | * | |
806 | */ | |
807 | void vu_command_line ( VikWindow *vw, gdouble latitude, gdouble longitude, gint zoom_osm_level, gint map_id ) | |
808 | { | |
809 | if ( !vw ) | |
810 | return; | |
811 | ||
812 | VikViewport *vvp = vik_window_viewport(vw); | |
813 | ||
814 | if ( latitude != 0.0 || longitude != 0.0 ) { | |
815 | struct LatLon ll; | |
816 | ll.lat = latitude; | |
817 | ll.lon = longitude; | |
818 | vik_viewport_set_center_latlon ( vvp, &ll, TRUE ); | |
819 | } | |
820 | ||
821 | if ( zoom_osm_level >= 0 ) { | |
822 | // Convert OSM zoom level into Viking zoom level | |
823 | gdouble mpp = exp ( (17-zoom_osm_level) * log(2) ); | |
824 | if ( mpp > 1.0 ) | |
825 | mpp = round (mpp); | |
826 | vik_viewport_set_zoom ( vvp, mpp ); | |
827 | } | |
828 | ||
829 | if ( map_id >= 0 ) { | |
830 | guint my_map_id = map_id; | |
831 | if ( my_map_id == 0 ) | |
832 | my_map_id = vik_maps_layer_get_default_map_type (); | |
833 | ||
834 | // Don't add map layer if one already exists | |
835 | GList *vmls = vik_layers_panel_get_all_layers_of_type(vik_window_layers_panel(vw), VIK_LAYER_MAPS, TRUE); | |
836 | int num_maps = g_list_length(vmls); | |
837 | gboolean add_map = TRUE; | |
838 | ||
839 | for (int i = 0; i < num_maps; i++) { | |
840 | VikMapsLayer *vml = (VikMapsLayer*)(vmls->data); | |
841 | gint id = vik_maps_layer_get_map_type(vml); | |
842 | if ( my_map_id == id ) { | |
843 | add_map = FALSE; | |
844 | break; | |
845 | } | |
846 | vmls = vmls->next; | |
847 | } | |
848 | ||
849 | if ( add_map ) { | |
850 | VikMapsLayer *vml = VIK_MAPS_LAYER ( vik_layer_create(VIK_LAYER_MAPS, vvp, FALSE) ); | |
851 | vik_maps_layer_set_map_type ( vml, my_map_id ); | |
852 | vik_layer_rename ( VIK_LAYER(vml), _("Map") ); | |
853 | vik_aggregate_layer_add_layer ( vik_layers_panel_get_top_layer(vik_window_layers_panel(vw)), VIK_LAYER(vml), TRUE ); | |
854 | vik_layer_emit_update ( VIK_LAYER(vml) ); | |
855 | } | |
856 | } | |
857 | } | |
858 | ||
859 | /** | |
860 | * Copy the displayed text of a widget (should be a GtkButton ATM) | |
861 | */ | |
862 | static void vu_copy_label ( GtkWidget *widget ) | |
863 | { | |
864 | a_clipboard_copy (VIK_CLIPBOARD_DATA_TEXT, 0, 0, 0, gtk_button_get_label(GTK_BUTTON(widget)), NULL ); | |
865 | } | |
866 | ||
867 | /** | |
868 | * Generate a single entry menu to allow copying the displayed text of a widget (should be a GtkButton ATM) | |
869 | */ | |
870 | void vu_copy_label_menu ( GtkWidget *widget, guint button ) | |
871 | { | |
872 | GtkWidget *menu = gtk_menu_new(); | |
873 | GtkWidget *item = gtk_image_menu_item_new_from_stock ( GTK_STOCK_COPY, NULL ); | |
874 | g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(vu_copy_label), widget ); | |
875 | gtk_menu_shell_append ( GTK_MENU_SHELL(menu), item ); | |
876 | gtk_widget_show ( item ); | |
877 | gtk_menu_popup ( GTK_MENU(menu), NULL, NULL, NULL, NULL, button, gtk_get_current_event_time() ); | |
878 | } | |
879 | ||
880 | /** | |
881 | * Work out the best zoom level for the LatLon area and set the viewport to that zoom level | |
882 | */ | |
883 | void vu_zoom_to_show_latlons ( VikCoordMode mode, VikViewport *vvp, struct LatLon maxmin[2] ) | |
884 | { | |
885 | /* First set the center [in case previously viewing from elsewhere] */ | |
886 | /* Then loop through zoom levels until provided positions are in view */ | |
887 | /* This method is not particularly fast - but should work well enough */ | |
888 | struct LatLon average = { (maxmin[0].lat+maxmin[1].lat)/2, (maxmin[0].lon+maxmin[1].lon)/2 }; | |
889 | VikCoord coord; | |
890 | vik_coord_load_from_latlon ( &coord, mode, &average ); | |
891 | vik_viewport_set_center_coord ( vvp, &coord, TRUE ); | |
892 | ||
893 | /* Convert into definite 'smallest' and 'largest' positions */ | |
894 | struct LatLon minmin; | |
895 | if ( maxmin[0].lat < maxmin[1].lat ) | |
896 | minmin.lat = maxmin[0].lat; | |
897 | else | |
898 | minmin.lat = maxmin[1].lat; | |
899 | ||
900 | struct LatLon maxmax; | |
901 | if ( maxmin[0].lon > maxmin[1].lon ) | |
902 | maxmax.lon = maxmin[0].lon; | |
903 | else | |
904 | maxmax.lon = maxmin[1].lon; | |
905 | ||
906 | /* Never zoom in too far - generally not that useful, as too close ! */ | |
907 | /* Always recalculate the 'best' zoom level */ | |
908 | gdouble zoom = 1.0; | |
909 | vik_viewport_set_zoom ( vvp, zoom ); | |
910 | ||
911 | gdouble min_lat, max_lat, min_lon, max_lon; | |
912 | /* Should only be a maximum of about 18 iterations from min to max zoom levels */ | |
913 | while ( zoom <= VIK_VIEWPORT_MAX_ZOOM ) { | |
914 | vik_viewport_get_min_max_lat_lon ( vvp, &min_lat, &max_lat, &min_lon, &max_lon ); | |
915 | /* NB I think the logic used in this test to determine if the bounds is within view | |
916 | fails if track goes across 180 degrees longitude. | |
917 | Hopefully that situation is not too common... | |
918 | Mind you viking doesn't really do edge locations to well anyway */ | |
919 | if ( min_lat < minmin.lat && | |
920 | max_lat > minmin.lat && | |
921 | min_lon < maxmax.lon && | |
922 | max_lon > maxmax.lon ) | |
923 | /* Found within zoom level */ | |
924 | break; | |
925 | ||
926 | /* Try next */ | |
927 | zoom = zoom * 2; | |
928 | vik_viewport_set_zoom ( vvp, zoom ); | |
929 | } | |
930 | } | |
931 | ||
932 | /** | |
933 | * Set the waypoint image given a URI | |
934 | */ | |
935 | void vu_waypoint_set_image_uri ( VikWaypoint *wp, const gchar *uri, const gchar *dirpath ) | |
936 | { | |
937 | gchar *filename = gtk_html_filename_from_uri ( uri ); | |
938 | if ( g_path_is_absolute ( filename ) ) { | |
939 | vik_waypoint_set_image ( wp, filename ); | |
940 | } | |
941 | else { | |
942 | // Try to form full path | |
943 | gchar *full = g_strconcat ( dirpath, G_DIR_SEPARATOR_S, filename, NULL ); | |
944 | vik_waypoint_set_image ( wp, full ); | |
945 | g_free ( full ); | |
946 | } | |
947 | } |