]> git.street.me.uk Git - andy/viking.git/blob - src/file.c
[QA] Tools are enabled per active layer
[andy/viking.git] / src / file.c
1 /*
2  * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
3  *
4  * Copyright (C) 2003-2005, Evan Battaglia <gtoevan@gmx.net>
5  * Copyright (C) 2012, Guilhem Bonnefille <guilhem.bonnefille@gmail.com>
6  * Copyright (C) 2012-2013, Rob Norris <rw_norris@hotmail.com>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  *
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27 #include "viking.h"
28
29 #include "gpx.h"
30 #include "babel.h"
31
32 #include <string.h>
33 #include <stdlib.h>
34 #include <stdio.h>
35 #ifdef HAVE_UNISTD_H
36 #include <unistd.h>
37 #endif
38 #ifdef WINDOWS
39 #define realpath(X,Y) _fullpath(Y,X,MAX_PATH)
40 #endif
41 #include <glib.h>
42 #include <glib/gstdio.h>
43 #include <glib/gi18n.h>
44
45 #include "file.h"
46
47 #define TEST_BOOLEAN(str) (! ((str)[0] == '\0' || (str)[0] == '0' || (str)[0] == 'n' || (str)[0] == 'N' || (str)[0] == 'f' || (str)[0] == 'F') )
48 #define VIK_MAGIC "#VIK"
49 #define GPX_MAGIC "<?xm"
50 #define VIK_MAGIC_LEN 4
51
52 #define VIKING_FILE_VERSION 1
53
54 typedef struct _Stack Stack;
55
56 struct _Stack {
57   Stack *under;
58   gpointer *data;
59 };
60
61 static void pop(Stack **stack) {
62   Stack *tmp = (*stack)->under;
63   g_free ( *stack );
64   *stack = tmp;
65 }
66
67 static void push(Stack **stack)
68 {
69   Stack *tmp = g_malloc ( sizeof ( Stack ) );
70   tmp->under = *stack;
71   *stack = tmp;
72 }
73
74 static gboolean check_magic ( FILE *f, const gchar *magic_number )
75 {
76   gchar magic[VIK_MAGIC_LEN];
77   gboolean rv = FALSE;
78   gint8 i;
79   if ( fread(magic, 1, sizeof(magic), f) == sizeof(magic) &&
80       strncmp(magic, magic_number, sizeof(magic)) == 0 )
81     rv = TRUE;
82   for ( i = sizeof(magic)-1; i >= 0; i-- ) /* the ol' pushback */
83     ungetc(magic[i],f);
84   return rv;
85 }
86
87
88 static gboolean str_starts_with ( const gchar *haystack, const gchar *needle, guint16 len_needle, gboolean must_be_longer )
89 {
90   if ( strlen(haystack) > len_needle - (!must_be_longer) && strncasecmp ( haystack, needle, len_needle ) == 0 )
91     return TRUE;
92   return FALSE;
93 }
94
95 void file_write_layer_param ( FILE *f, const gchar *name, VikLayerParamType type, VikLayerParamData data ) {
96       /* string lists are handled differently. We get a GList (that shouldn't
97        * be freed) back for get_param and if it is null we shouldn't write
98        * anything at all (otherwise we'd read in a list with an empty string,
99        * not an empty string list.
100        */
101       if ( type == VIK_LAYER_PARAM_STRING_LIST ) {
102         if ( data.sl ) {
103           GList *iter = (GList *)data.sl;
104           while ( iter ) {
105             fprintf ( f, "%s=", name );
106             fprintf ( f, "%s\n", (gchar *)(iter->data) );
107             iter = iter->next;
108           }
109         }
110       } else {
111         fprintf ( f, "%s=", name );
112         switch ( type )
113         {
114           case VIK_LAYER_PARAM_DOUBLE: {
115   //          char buf[15]; /* locale independent */
116   //          fprintf ( f, "%s\n", (char *) g_dtostr (data.d, buf, sizeof (buf)) ); break;
117               fprintf ( f, "%f\n", data.d );
118               break;
119          }
120           case VIK_LAYER_PARAM_UINT: fprintf ( f, "%d\n", data.u ); break;
121           case VIK_LAYER_PARAM_INT: fprintf ( f, "%d\n", data.i ); break;
122           case VIK_LAYER_PARAM_BOOLEAN: fprintf ( f, "%c\n", data.b ? 't' : 'f' ); break;
123           case VIK_LAYER_PARAM_STRING: fprintf ( f, "%s\n", data.s ? data.s : "" ); break;
124           case VIK_LAYER_PARAM_COLOR: fprintf ( f, "#%.2x%.2x%.2x\n", (int)(data.c.red/256),(int)(data.c.green/256),(int)(data.c.blue/256)); break;
125           default: break;
126         }
127       }
128 }
129
130 static void write_layer_params_and_data ( VikLayer *l, FILE *f )
131 {
132   VikLayerParam *params = vik_layer_get_interface(l->type)->params;
133   VikLayerFuncGetParam get_param = vik_layer_get_interface(l->type)->get_param;
134
135   fprintf ( f, "name=%s\n", l->name ? l->name : "" );
136   if ( !l->visible )
137     fprintf ( f, "visible=f\n" );
138
139   if ( params && get_param )
140   {
141     VikLayerParamData data;
142     guint16 i, params_count = vik_layer_get_interface(l->type)->params_count;
143     for ( i = 0; i < params_count; i++ )
144     {
145       data = get_param(l, i, TRUE);
146       file_write_layer_param(f, params[i].name, params[i].type, data);
147     }
148   }
149   if ( vik_layer_get_interface(l->type)->write_file_data )
150   {
151     fprintf ( f, "\n\n~LayerData\n" );
152     vik_layer_get_interface(l->type)->write_file_data ( l, f );
153     fprintf ( f, "~EndLayerData\n" );
154   }
155   /* foreach param:
156      write param, and get_value, etc.
157      then run layer data, and that's it.
158   */
159 }
160
161 static void file_write ( VikAggregateLayer *top, FILE *f, gpointer vp )
162 {
163   Stack *stack = NULL;
164   VikLayer *current_layer;
165   struct LatLon ll;
166   VikViewportDrawMode mode;
167   gchar *modestring = NULL;
168
169   push(&stack);
170   stack->data = (gpointer) vik_aggregate_layer_get_children(VIK_AGGREGATE_LAYER(top));
171   stack->under = NULL;
172
173   /* crazhy CRAZHY */
174   vik_coord_to_latlon ( vik_viewport_get_center ( VIK_VIEWPORT(vp) ), &ll );
175
176   mode = vik_viewport_get_drawmode ( VIK_VIEWPORT(vp) );
177   switch ( mode ) {
178     case VIK_VIEWPORT_DRAWMODE_UTM: modestring = "utm"; break;
179     case VIK_VIEWPORT_DRAWMODE_EXPEDIA: modestring = "expedia"; break;
180     case VIK_VIEWPORT_DRAWMODE_MERCATOR: modestring = "mercator"; break;
181     case VIK_VIEWPORT_DRAWMODE_LATLON: modestring = "latlon"; break;
182     default:
183       g_critical("Houston, we've had a problem. mode=%d", mode);
184   }
185
186   fprintf ( f, "#VIKING GPS Data file " VIKING_URL "\n" );
187   fprintf ( f, "FILE_VERSION=%d\n", VIKING_FILE_VERSION );
188   fprintf ( f, "\nxmpp=%f\nympp=%f\nlat=%f\nlon=%f\nmode=%s\ncolor=%s\nhighlightcolor=%s\ndrawscale=%s\ndrawcentermark=%s\ndrawhighlight=%s\n",
189       vik_viewport_get_xmpp ( VIK_VIEWPORT(vp) ), vik_viewport_get_ympp ( VIK_VIEWPORT(vp) ), ll.lat, ll.lon,
190       modestring, vik_viewport_get_background_color(VIK_VIEWPORT(vp)),
191       vik_viewport_get_highlight_color(VIK_VIEWPORT(vp)),
192       vik_viewport_get_draw_scale(VIK_VIEWPORT(vp)) ? "t" : "f",
193       vik_viewport_get_draw_centermark(VIK_VIEWPORT(vp)) ? "t" : "f",
194       vik_viewport_get_draw_highlight(VIK_VIEWPORT(vp)) ? "t" : "f" );
195
196   if ( ! VIK_LAYER(top)->visible )
197     fprintf ( f, "visible=f\n" );
198
199   while (stack && stack->data)
200   {
201     current_layer = VIK_LAYER(((GList *)stack->data)->data);
202     fprintf ( f, "\n~Layer %s\n", vik_layer_get_interface(current_layer->type)->fixed_layer_name );
203     write_layer_params_and_data ( current_layer, f );
204     if ( current_layer->type == VIK_LAYER_AGGREGATE && !vik_aggregate_layer_is_empty(VIK_AGGREGATE_LAYER(current_layer)) )
205     {
206       push(&stack);
207       stack->data = (gpointer) vik_aggregate_layer_get_children(VIK_AGGREGATE_LAYER(current_layer));
208     }
209     else if ( current_layer->type == VIK_LAYER_GPS && !vik_gps_layer_is_empty(VIK_GPS_LAYER(current_layer)) )
210     {
211       push(&stack);
212       stack->data = (gpointer) vik_gps_layer_get_children(VIK_GPS_LAYER(current_layer));
213     }
214     else
215     {
216       stack->data = (gpointer) ((GList *)stack->data)->next;
217       fprintf ( f, "~EndLayer\n\n" );
218       while ( stack && (!stack->data) )
219       {
220         pop(&stack);
221         if ( stack )
222         {
223           stack->data = (gpointer) ((GList *)stack->data)->next;
224           fprintf ( f, "~EndLayer\n\n" );
225         }
226       }
227     }
228   }
229 /*
230   get vikaggregatelayer's children (?)
231   foreach write ALL params,
232   then layer data (IF function exists)
233   then endlayer
234
235   impl:
236   stack of layers (LIST) we are working on
237   when layer->next == NULL ...
238   we move on.
239 */
240 }
241
242 static void string_list_delete ( gpointer key, gpointer l, gpointer user_data )
243 {
244   /* 20071021 bugfix */
245   GList *iter = (GList *) l;
246   while ( iter ) {
247     g_free ( iter->data );
248     iter = iter->next;
249   }
250   g_list_free ( (GList *) l );
251 }
252
253 static void string_list_set_param (gint i, GList *list, gpointer *layer_and_vp)
254 {
255   VikLayerParamData x;
256   x.sl = list;
257   vik_layer_set_param ( VIK_LAYER(layer_and_vp[0]), i, x, layer_and_vp[1], TRUE );
258 }
259
260 /**
261  * Read in a Viking file and return how successful the parsing was
262  * ATM this will always work, in that even if there are parsing problems
263  *  then there will be no new values to override the defaults
264  *
265  * TODO flow up line number(s) / error messages of problems encountered...
266  *
267  */
268 static gboolean file_read ( VikAggregateLayer *top, FILE *f, const gchar *dirpath, VikViewport *vp )
269 {
270   Stack *stack;
271   struct LatLon ll = { 0.0, 0.0 };
272   gchar buffer[4096];
273   gchar *line;
274   guint16 len;
275   long line_num = 0;
276
277   VikLayerParam *params = NULL; /* for current layer, so we don't have to keep on looking up interface */
278   guint8 params_count = 0;
279
280   GHashTable *string_lists = g_hash_table_new(g_direct_hash,g_direct_equal);
281
282   gboolean successful_read = TRUE;
283
284   push(&stack);
285   stack->under = NULL;
286   stack->data = (gpointer) top;
287
288   while ( fgets ( buffer, 4096, f ) )
289   {
290     line_num++;
291
292     line = buffer;
293     while ( *line == ' ' || *line =='\t' )
294       line++;
295
296     if ( line[0] == '#' )
297       continue;
298     
299
300     len = strlen(line);
301     if ( len > 0 && line[len-1] == '\n' )
302       line[--len] = '\0';
303     if ( len > 0 && line[len-1] == '\r' )
304       line[--len] = '\0';
305
306     if ( len == 0 )
307       continue;
308
309
310     if ( line[0] == '~' )
311     {
312       line++; len--;
313       if ( *line == '\0' )
314         continue;
315       else if ( str_starts_with ( line, "Layer ", 6, TRUE ) )
316       {
317         int parent_type = VIK_LAYER(stack->data)->type;
318         if ( ( ! stack->data ) || ((parent_type != VIK_LAYER_AGGREGATE) && (parent_type != VIK_LAYER_GPS)) )
319         {
320           successful_read = FALSE;
321           g_warning ( "Line %ld: Layer command inside non-Aggregate Layer (type %d)", line_num, parent_type );
322           push(&stack); /* inside INVALID layer */
323           stack->data = NULL;
324           continue;
325         }
326         else
327         {
328           VikLayerTypeEnum type = vik_layer_type_from_string ( line+6 );
329           push(&stack);
330           if ( type == VIK_LAYER_NUM_TYPES )
331           {
332             successful_read = FALSE;
333             g_warning ( "Line %ld: Unknown type %s", line_num, line+6 );
334             stack->data = NULL;
335           }
336           else if (parent_type == VIK_LAYER_GPS)
337           {
338             stack->data = (gpointer) vik_gps_layer_get_a_child(VIK_GPS_LAYER(stack->under->data));
339             params = vik_layer_get_interface(type)->params;
340             params_count = vik_layer_get_interface(type)->params_count;
341           }
342           else
343           {
344             stack->data = (gpointer) vik_layer_create ( type, vp, NULL, FALSE );
345             params = vik_layer_get_interface(type)->params;
346             params_count = vik_layer_get_interface(type)->params_count;
347           }
348         }
349       }
350       else if ( str_starts_with ( line, "EndLayer", 8, FALSE ) )
351       {
352         if ( stack->under == NULL ) {
353           successful_read = FALSE;
354           g_warning ( "Line %ld: Mismatched ~EndLayer command", line_num );
355         }
356         else
357         {
358           /* add any string lists we've accumulated */
359           gpointer layer_and_vp[2];
360           layer_and_vp[0] = stack->data;
361           layer_and_vp[1] = vp;
362           g_hash_table_foreach ( string_lists, (GHFunc) string_list_set_param, layer_and_vp );
363           g_hash_table_remove_all ( string_lists );
364
365           if ( stack->data && stack->under->data )
366           {
367             if (VIK_LAYER(stack->under->data)->type == VIK_LAYER_AGGREGATE) {
368               vik_aggregate_layer_add_layer ( VIK_AGGREGATE_LAYER(stack->under->data), VIK_LAYER(stack->data), FALSE );
369               vik_layer_post_read ( VIK_LAYER(stack->data), vp, TRUE );
370             }
371             else if (VIK_LAYER(stack->under->data)->type == VIK_LAYER_GPS) {
372               /* TODO: anything else needs to be done here ? */
373             }
374             else {
375               successful_read = FALSE;
376               g_warning ( "Line %ld: EndLayer command inside non-Aggregate Layer (type %d)", line_num, VIK_LAYER(stack->data)->type );
377             }
378           }
379           pop(&stack);
380         }
381       }
382       else if ( str_starts_with ( line, "LayerData", 9, FALSE ) )
383       {
384         if ( stack->data && vik_layer_get_interface(VIK_LAYER(stack->data)->type)->read_file_data )
385         {
386           /* must read until hits ~EndLayerData */
387           if ( ! vik_layer_get_interface(VIK_LAYER(stack->data)->type)->read_file_data ( VIK_LAYER(stack->data), f, dirpath ) )
388             successful_read = FALSE;
389         }
390         else
391         { /* simply skip layer data over */
392           while ( fgets ( buffer, 4096, f ) )
393           {
394             line_num++;
395
396             line = buffer;
397
398             len = strlen(line);
399             if ( len > 0 && line[len-1] == '\n' )
400               line[--len] = '\0';
401             if ( len > 0 && line[len-1] == '\r' )
402               line[--len] = '\0';
403             if ( strcasecmp ( line, "~EndLayerData" ) == 0 )
404               break;
405           }
406           continue;
407         }
408       }
409       else
410       {
411         successful_read = FALSE;
412         g_warning ( "Line %ld: Unknown tilde command", line_num );
413       }
414     }
415     else
416     {
417       gint32 eq_pos = -1;
418       guint16 i;
419       if ( ! stack->data )
420         continue;
421
422       for ( i = 0; i < len; i++ )
423         if ( line[i] == '=' )
424           eq_pos = i;
425
426       if ( stack->under == NULL && eq_pos == 12 && strncasecmp ( line, "FILE_VERSION", eq_pos ) == 0) {
427         gint version = strtol(line+13, NULL, 10);
428         g_debug ( "%s: reading file version %d", __FUNCTION__, version );
429         if ( version > VIKING_FILE_VERSION )
430           successful_read = FALSE;
431         // However we'll still carry and attempt to read whatever we can
432       }
433       else if ( stack->under == NULL && eq_pos == 4 && strncasecmp ( line, "xmpp", eq_pos ) == 0) /* "hard coded" params: global & for all layer-types */
434         vik_viewport_set_xmpp ( VIK_VIEWPORT(vp), strtod ( line+5, NULL ) );
435       else if ( stack->under == NULL && eq_pos == 4 && strncasecmp ( line, "ympp", eq_pos ) == 0)
436         vik_viewport_set_ympp ( VIK_VIEWPORT(vp), strtod ( line+5, NULL ) );
437       else if ( stack->under == NULL && eq_pos == 3 && strncasecmp ( line, "lat", eq_pos ) == 0 )
438         ll.lat = strtod ( line+4, NULL );
439       else if ( stack->under == NULL && eq_pos == 3 && strncasecmp ( line, "lon", eq_pos ) == 0 )
440         ll.lon = strtod ( line+4, NULL );
441       else if ( stack->under == NULL && eq_pos == 4 && strncasecmp ( line, "mode", eq_pos ) == 0 && strcasecmp ( line+5, "utm" ) == 0)
442         vik_viewport_set_drawmode ( VIK_VIEWPORT(vp), VIK_VIEWPORT_DRAWMODE_UTM);
443       else if ( stack->under == NULL && eq_pos == 4 && strncasecmp ( line, "mode", eq_pos ) == 0 && strcasecmp ( line+5, "expedia" ) == 0)
444         vik_viewport_set_drawmode ( VIK_VIEWPORT(vp), VIK_VIEWPORT_DRAWMODE_EXPEDIA );
445       else if ( stack->under == NULL && eq_pos == 4 && strncasecmp ( line, "mode", eq_pos ) == 0 && strcasecmp ( line+5, "google" ) == 0)
446       {
447         successful_read = FALSE;
448         g_warning ( _("Draw mode '%s' no more supported"), "google" );
449       }
450       else if ( stack->under == NULL && eq_pos == 4 && strncasecmp ( line, "mode", eq_pos ) == 0 && strcasecmp ( line+5, "kh" ) == 0)
451       {
452         successful_read = FALSE;
453         g_warning ( _("Draw mode '%s' no more supported"), "kh" );
454       }
455       else if ( stack->under == NULL && eq_pos == 4 && strncasecmp ( line, "mode", eq_pos ) == 0 && strcasecmp ( line+5, "mercator" ) == 0)
456         vik_viewport_set_drawmode ( VIK_VIEWPORT(vp), VIK_VIEWPORT_DRAWMODE_MERCATOR );
457       else if ( stack->under == NULL && eq_pos == 4 && strncasecmp ( line, "mode", eq_pos ) == 0 && strcasecmp ( line+5, "latlon" ) == 0)
458         vik_viewport_set_drawmode ( VIK_VIEWPORT(vp), VIK_VIEWPORT_DRAWMODE_LATLON );
459       else if ( stack->under == NULL && eq_pos == 5 && strncasecmp ( line, "color", eq_pos ) == 0 )
460         vik_viewport_set_background_color ( VIK_VIEWPORT(vp), line+6 );
461       else if ( stack->under == NULL && eq_pos == 14 && strncasecmp ( line, "highlightcolor", eq_pos ) == 0 )
462         vik_viewport_set_highlight_color ( VIK_VIEWPORT(vp), line+15 );
463       else if ( stack->under == NULL && eq_pos == 9 && strncasecmp ( line, "drawscale", eq_pos ) == 0 )
464         vik_viewport_set_draw_scale ( VIK_VIEWPORT(vp), TEST_BOOLEAN(line+10) );
465       else if ( stack->under == NULL && eq_pos == 14 && strncasecmp ( line, "drawcentermark", eq_pos ) == 0 )
466         vik_viewport_set_draw_centermark ( VIK_VIEWPORT(vp), TEST_BOOLEAN(line+15) );
467       else if ( stack->under == NULL && eq_pos == 13 && strncasecmp ( line, "drawhighlight", eq_pos ) == 0 )
468         vik_viewport_set_draw_highlight ( VIK_VIEWPORT(vp), TEST_BOOLEAN(line+14) );
469       else if ( stack->under && eq_pos == 4 && strncasecmp ( line, "name", eq_pos ) == 0 )
470         vik_layer_rename ( VIK_LAYER(stack->data), line+5 );
471       else if ( eq_pos == 7 && strncasecmp ( line, "visible", eq_pos ) == 0 )
472         VIK_LAYER(stack->data)->visible = TEST_BOOLEAN(line+8);
473       else if ( eq_pos != -1 && stack->under )
474       {
475         gboolean found_match = FALSE;
476
477         /* go thru layer params. if len == eq_pos && starts_with jazz, set it. */
478         /* also got to check for name and visible. */
479
480         if ( ! params )
481         {
482           successful_read = FALSE;
483           g_warning ( "Line %ld: No options for this kind of layer", line_num );
484           continue;
485         }
486
487         for ( i = 0; i < params_count; i++ )
488           if ( strlen(params[i].name) == eq_pos && strncasecmp ( line, params[i].name, eq_pos ) == 0 )
489           {
490             VikLayerParamData x;
491             line += eq_pos+1;
492             if ( params[i].type == VIK_LAYER_PARAM_STRING_LIST ) {
493               GList *l = g_list_append ( g_hash_table_lookup ( string_lists, GINT_TO_POINTER ((gint) i) ), 
494                                          g_strdup(line) );
495               g_hash_table_replace ( string_lists, GINT_TO_POINTER ((gint)i), l );
496               /* add the value to a list, possibly making a new list.
497                * this will be passed to the layer when we read an ~EndLayer */
498             } else {
499               switch ( params[i].type )
500               {
501                 case VIK_LAYER_PARAM_DOUBLE: x.d = strtod(line, NULL); break;
502                 case VIK_LAYER_PARAM_UINT: x.u = strtoul(line, NULL, 10); break;
503                 case VIK_LAYER_PARAM_INT: x.i = strtol(line, NULL, 10); break;
504                 case VIK_LAYER_PARAM_BOOLEAN: x.b = TEST_BOOLEAN(line); break;
505                 case VIK_LAYER_PARAM_COLOR: memset(&(x.c), 0, sizeof(x.c)); /* default: black */
506                                           gdk_color_parse ( line, &(x.c) ); break;
507                 /* STRING or STRING_LIST -- if STRING_LIST, just set param to add a STRING */
508                 default: x.s = line;
509               }
510               vik_layer_set_param ( VIK_LAYER(stack->data), i, x, vp, TRUE );
511             }
512             found_match = TRUE;
513             break;
514           }
515         if ( ! found_match ) {
516           // ATM don't flow up this issue because at least one internal parameter has changed from version 1.3
517           //   and don't what to worry users about raising such issues
518           // TODO Maybe hold old values here - compare the line value against them and if a match
519           //       generate a different style of message in the GUI...
520           // successful_read = FALSE;
521           g_warning ( "Line %ld: Unknown parameter. Line:\n%s", line_num, line );
522         }
523       }
524       else {
525         successful_read = FALSE;
526         g_warning ( "Line %ld: Invalid parameter or parameter outside of layer.", line_num );
527       }
528     }
529 /* could be:
530 [Layer Type=Bla]
531 [EndLayer]
532 [LayerData]
533 name=this
534 #comment
535 */
536   }
537
538   while ( stack )
539   {
540     if ( stack->under && stack->under->data && stack->data )
541     {
542       vik_aggregate_layer_add_layer ( VIK_AGGREGATE_LAYER(stack->under->data), VIK_LAYER(stack->data), FALSE );
543       vik_layer_post_read ( VIK_LAYER(stack->data), vp, TRUE );
544     }
545     pop(&stack);
546   }
547
548   if ( ll.lat != 0.0 || ll.lon != 0.0 )
549     vik_viewport_set_center_latlon ( VIK_VIEWPORT(vp), &ll, TRUE );
550
551   if ( ( ! VIK_LAYER(top)->visible ) && VIK_LAYER(top)->realized )
552     vik_treeview_item_set_visible ( VIK_LAYER(top)->vt, &(VIK_LAYER(top)->iter), FALSE ); 
553
554   /* delete anything we've forgotten about -- should only happen when file ends before an EndLayer */
555   g_hash_table_foreach ( string_lists, string_list_delete, NULL );
556   g_hash_table_destroy ( string_lists );
557
558   return successful_read;
559 }
560
561 /*
562 read thru file
563 if "[Layer Type="
564   push(&stack)
565   new default layer of type (str_to_type) (check interface->name)
566 if "[EndLayer]"
567   VikLayer *vl = stack->data;
568   pop(&stack);
569   vik_aggregate_layer_add_layer(stack->data, vl);
570 if "[LayerData]"
571   vik_layer_data ( VIK_LAYER_DATA(stack->data), f, vp );
572
573 */
574
575 /* ---------------------------------------------------- */
576
577 static FILE *xfopen ( const char *fn, const char *mode )
578 {
579   if ( strcmp(fn,"-") == 0 )
580     return stdin;
581   else
582     return g_fopen(fn, "r");
583 }
584
585 static void xfclose ( FILE *f )
586 {
587   if ( f != stdin && f != stdout ) {
588     fclose ( f );
589     f = NULL;
590   }
591 }
592
593 /*
594  * Function to determine if a filename is a 'viking' type file
595  */
596 gboolean check_file_magic_vik ( const gchar *filename )
597 {
598   gboolean result = FALSE;
599   FILE *ff = xfopen ( filename, "r" );
600   if ( ff ) {
601     result = check_magic ( ff, VIK_MAGIC );
602     xfclose ( ff );
603   }
604   return result;
605 }
606
607 /**
608  * append_file_ext:
609  *
610  * Append a file extension, if not already present.
611  *
612  * Returns: a newly allocated string
613  */
614 gchar *append_file_ext ( const gchar *filename, VikLoadType_t type )
615 {
616   gchar *new_name = NULL;
617   const gchar *ext = NULL;
618
619   /* Select an extension */
620   switch (type)
621   {
622   case FILE_TYPE_GPX:
623     ext = ".gpx";
624     break;
625   case FILE_TYPE_KML:
626     ext = ".kml";
627     break;
628   case FILE_TYPE_GPSMAPPER:
629   case FILE_TYPE_GPSPOINT:
630   default:
631     /* Do nothing, ext already set to NULL */
632     break;
633   }
634
635   /* Do */
636   if ( ext != NULL && ! check_file_ext ( filename, ext ) )
637     new_name = g_strconcat ( filename, ext, NULL );
638   else
639     /* Simply duplicate */
640     new_name = g_strdup ( filename );
641
642   return new_name;
643 }
644
645 VikLoadType_t a_file_load ( VikAggregateLayer *top, VikViewport *vp, const gchar *filename_or_uri )
646 {
647   g_return_val_if_fail ( vp != NULL, LOAD_TYPE_READ_FAILURE );
648
649   char *filename = (char *)filename_or_uri;
650   if (strncmp(filename, "file://", 7) == 0) {
651     // Consider replacing this with:
652     // filename = g_filename_from_uri ( entry, NULL, NULL );
653     // Since this doesn't support URIs properly (i.e. will failure if is it has %20 characters in it)
654     filename = filename + 7;
655     g_debug ( "Loading file %s from URI %s", filename, filename_or_uri );
656   }
657   FILE *f = xfopen ( filename, "r" );
658
659   if ( ! f )
660     return LOAD_TYPE_READ_FAILURE;
661
662   VikLoadType_t load_answer = LOAD_TYPE_OTHER_SUCCESS;
663
664   gchar *dirpath = g_path_get_dirname ( filename );
665   // Attempt loading the primary file type first - our internal .vik file:
666   if ( check_magic ( f, VIK_MAGIC ) )
667   {
668     if ( file_read ( top, f, dirpath, vp ) )
669       load_answer = LOAD_TYPE_VIK_SUCCESS;
670     else
671       load_answer = LOAD_TYPE_VIK_FAILURE_NON_FATAL;
672   }
673   else
674   {
675         // For all other file types which consist of tracks, routes and/or waypoints,
676         //  must be loaded into a new TrackWaypoint layer (hence it be created)
677     gboolean success = TRUE; // Detect load failures - mainly to remove the layer created as it's not required
678
679     VikLayer *vtl = vik_layer_create ( VIK_LAYER_TRW, vp, NULL, FALSE );
680     vik_layer_rename ( vtl, a_file_basename ( filename ) );
681
682     // In fact both kml & gpx files start the same as they are in xml
683     if ( check_file_ext ( filename, ".kml" ) && check_magic ( f, GPX_MAGIC ) ) {
684       // Implicit Conversion
685       if ( ! ( success = a_babel_convert_from ( VIK_TRW_LAYER(vtl), "-i kml", filename, NULL, NULL, NULL ) ) ) {
686         load_answer = LOAD_TYPE_GPSBABEL_FAILURE;
687       }
688     }
689     // NB use a extension check first, as a GPX file header may have a Byte Order Mark (BOM) in it
690     //    - which currently confuses our check_magic function
691     else if ( check_file_ext ( filename, ".gpx" ) || check_magic ( f, GPX_MAGIC ) ) {
692       if ( ! ( success = a_gpx_read_file ( VIK_TRW_LAYER(vtl), f ) ) ) {
693         load_answer = LOAD_TYPE_GPX_FAILURE;
694       }
695     }
696     else {
697       // Try final supported file type
698       if ( ! ( success = a_gpspoint_read_file ( VIK_TRW_LAYER(vtl), f, dirpath ) ) ) {
699         // Failure here means we don't know how to handle the file
700         load_answer = LOAD_TYPE_UNSUPPORTED_FAILURE;
701       }
702     }
703     g_free ( dirpath );
704
705     // Clean up when we can't handle the file
706     if ( ! success ) {
707       // free up layer
708       g_object_unref ( vtl );
709     }
710     else {
711       // Complete the setup from the successful load
712       vik_layer_post_read ( vtl, vp, TRUE );
713       vik_aggregate_layer_add_layer ( top, vtl, FALSE );
714       vik_trw_layer_auto_set_view ( VIK_TRW_LAYER(vtl), vp );
715     }
716   }
717   xfclose(f);
718   return load_answer;
719 }
720
721 gboolean a_file_save ( VikAggregateLayer *top, gpointer vp, const gchar *filename )
722 {
723   FILE *f;
724
725   if (strncmp(filename, "file://", 7) == 0)
726     filename = filename + 7;
727
728   f = g_fopen(filename, "w");
729
730   if ( ! f )
731     return FALSE;
732
733   // Enable relative paths in .vik files to work
734   gchar *cwd = g_get_current_dir();
735   gchar *dir = g_path_get_dirname ( filename );
736   if ( dir ) {
737     if ( g_chdir ( dir ) ) {
738       g_warning ( "Could not change directory to %s", dir );
739     }
740     g_free (dir);
741   }
742
743   file_write ( top, f, vp );
744
745   // Restore previous working directory
746   if ( cwd ) {
747     if ( g_chdir ( cwd ) ) {
748       g_warning ( "Could not return to directory %s", cwd );
749     }
750     g_free (cwd);
751   }
752
753   fclose(f);
754   f = NULL;
755
756   return TRUE;
757 }
758
759
760 const gchar *a_file_basename ( const gchar *filename )
761 {
762   const gchar *t = filename + strlen(filename) - 1;
763   while ( --t > filename )
764     if ( *(t-1) == G_DIR_SEPARATOR )
765       break;
766   if ( t >= filename )
767     return t;
768   return filename;
769 }
770
771 /* example: 
772      gboolean is_gpx = check_file_ext ( "a/b/c.gpx", ".gpx" );
773 */
774 gboolean check_file_ext ( const gchar *filename, const gchar *fileext )
775 {
776   g_return_val_if_fail ( filename != NULL, FALSE );
777   g_return_val_if_fail ( fileext && fileext[0]=='.', FALSE );
778   const gchar *basename = a_file_basename(filename);
779   if (!basename)
780     return FALSE;
781
782   const char * dot = strrchr(basename, '.');
783   if (dot && !strcmp(dot, fileext))
784     return TRUE;
785
786   return FALSE;
787 }
788
789 /**
790  * a_file_export:
791  * @vtl: The TrackWaypoint to export data from
792  * @filename: The name of the file to be written
793  * @file_type: Choose one of the supported file types for the export
794  * @trk: If specified then only export this track rather than the whole layer
795  * @write_hidden: Whether to write invisible items
796  *
797  * A general export command to convert from Viking TRW layer data to an external supported format.
798  * The write_hidden option is provided mainly to be able to transfer selected items when uploading to a GPS
799  */
800 gboolean a_file_export ( VikTrwLayer *vtl, const gchar *filename, VikFileType_t file_type, VikTrack *trk, gboolean write_hidden )
801 {
802   GpxWritingOptions options = { FALSE, FALSE, write_hidden, FALSE };
803   FILE *f = g_fopen ( filename, "w" );
804   if ( f )
805   {
806     if ( trk ) {
807       switch ( file_type ) {
808         case FILE_TYPE_GPX:
809           // trk defined so can set the option
810           options.is_route = trk->is_route;
811           a_gpx_write_track_file ( trk, f, &options );
812           break;
813         default:
814           g_critical("Houston, we've had a problem. file_type=%d", file_type);
815       }
816     } else {
817       switch ( file_type ) {
818         case FILE_TYPE_GPSMAPPER:
819           a_gpsmapper_write_file ( vtl, f );
820           break;
821         case FILE_TYPE_GPX:
822           a_gpx_write_file ( vtl, f, &options );
823           break;
824         case FILE_TYPE_GPSPOINT:
825           a_gpspoint_write_file ( vtl, f );
826           break;
827         case FILE_TYPE_KML:
828           fclose ( f );
829           f = NULL;
830           switch ( a_vik_get_kml_export_units () ) {
831             case VIK_KML_EXPORT_UNITS_STATUTE:
832               return a_babel_convert_to ( vtl, NULL, "-o kml", filename, NULL, NULL );
833               break;
834             case VIK_KML_EXPORT_UNITS_NAUTICAL:
835               return a_babel_convert_to ( vtl, NULL, "-o kml,units=n", filename, NULL, NULL );
836               break;
837             default:
838               // VIK_KML_EXPORT_UNITS_METRIC:
839               return a_babel_convert_to ( vtl, NULL, "-o kml,units=m", filename, NULL, NULL );
840               break;
841           }
842           break;
843         default:
844           g_critical("Houston, we've had a problem. file_type=%d", file_type);
845       }
846     }
847     fclose ( f );
848     f = NULL;
849     return TRUE;
850   }
851   return FALSE;
852 }
853
854 /**
855  * a_file_export_babel:
856  */
857 gboolean a_file_export_babel ( VikTrwLayer *vtl, const gchar *filename, const gchar *format,
858                                gboolean tracks, gboolean routes, gboolean waypoints )
859 {
860   gchar *args = g_strdup_printf("%s %s %s -o %s",
861                                 tracks ? "-t" : "",
862                                 routes ? "-r" : "",
863                                 waypoints ? "-w" : "",
864                                 format);
865   gboolean result = a_babel_convert_to ( vtl, NULL, args, filename, NULL, NULL );
866   g_free(args);
867   return result;
868 }
869
870 /**
871  * Just a wrapper around realpath, which itself is platform dependent
872  */
873 char *file_realpath ( const char *path, char *real )
874 {
875   return realpath ( path, real );
876 }
877
878 #ifndef MAXPATHLEN
879 #define MAXPATHLEN 1024
880 #endif
881 /**
882  * Always return the canonical filename in a newly allocated string
883  */
884 char *file_realpath_dup ( const char *path )
885 {
886         char real[MAXPATHLEN];
887
888         g_return_val_if_fail(path != NULL, NULL);
889
890         if (file_realpath(path, real))
891                 return g_strdup(real);
892
893         return g_strdup(path);
894 }
895
896 /**
897  * Permission granted to use this code after personal correspondance
898  * Slightly reworked for better cross platform use, glibisms, function rename and a compacter format
899  *
900  * FROM http://www.codeguru.com/cpp/misc/misc/fileanddirectorynaming/article.php/c263
901  */
902
903 // GetRelativeFilename(), by Rob Fisher.
904 // rfisher@iee.org
905 // http://come.to/robfisher
906
907 // The number of characters at the start of an absolute filename.  e.g. in DOS,
908 // absolute filenames start with "X:\" so this value should be 3, in UNIX they start
909 // with "\" so this value should be 1.
910 #ifdef WINDOWS
911 #define ABSOLUTE_NAME_START 3
912 #else
913 #define ABSOLUTE_NAME_START 1
914 #endif
915
916 // Given the absolute current directory and an absolute file name, returns a relative file name.
917 // For example, if the current directory is C:\foo\bar and the filename C:\foo\whee\text.txt is given,
918 // GetRelativeFilename will return ..\whee\text.txt.
919
920 const gchar *file_GetRelativeFilename ( gchar *currentDirectory, gchar *absoluteFilename )
921 {
922   gint afMarker = 0, rfMarker = 0;
923   gint cdLen = 0, afLen = 0;
924   gint i = 0;
925   gint levels = 0;
926   static gchar relativeFilename[MAXPATHLEN+1];
927
928   cdLen = strlen(currentDirectory);
929   afLen = strlen(absoluteFilename);
930
931   // make sure the names are not too long or too short
932   if (cdLen > MAXPATHLEN || cdLen < ABSOLUTE_NAME_START+1 ||
933       afLen > MAXPATHLEN || afLen < ABSOLUTE_NAME_START+1) {
934     return NULL;
935   }
936
937   // Handle DOS names that are on different drives:
938   if (currentDirectory[0] != absoluteFilename[0]) {
939     // not on the same drive, so only absolute filename will do
940     strcpy(relativeFilename, absoluteFilename);
941     return relativeFilename;
942   }
943
944   // they are on the same drive, find out how much of the current directory
945   // is in the absolute filename
946   i = ABSOLUTE_NAME_START;
947   while (i < afLen && i < cdLen && currentDirectory[i] == absoluteFilename[i]) {
948     i++;
949   }
950
951   if (i == cdLen && (absoluteFilename[i] == G_DIR_SEPARATOR || absoluteFilename[i-1] == G_DIR_SEPARATOR)) {
952     // the whole current directory name is in the file name,
953     // so we just trim off the current directory name to get the
954     // current file name.
955     if (absoluteFilename[i] == G_DIR_SEPARATOR) {
956       // a directory name might have a trailing slash but a relative
957       // file name should not have a leading one...
958       i++;
959     }
960
961     strcpy(relativeFilename, &absoluteFilename[i]);
962     return relativeFilename;
963   }
964
965   // The file is not in a child directory of the current directory, so we
966   // need to step back the appropriate number of parent directories by
967   // using "..\"s.  First find out how many levels deeper we are than the
968   // common directory
969   afMarker = i;
970   levels = 1;
971
972   // count the number of directory levels we have to go up to get to the
973   // common directory
974   while (i < cdLen) {
975     i++;
976     if (currentDirectory[i] == G_DIR_SEPARATOR) {
977       // make sure it's not a trailing slash
978       i++;
979       if (currentDirectory[i] != '\0') {
980         levels++;
981       }
982     }
983   }
984
985   // move the absolute filename marker back to the start of the directory name
986   // that it has stopped in.
987   while (afMarker > 0 && absoluteFilename[afMarker-1] != G_DIR_SEPARATOR) {
988     afMarker--;
989   }
990
991   // check that the result will not be too long
992   if (levels * 3 + afLen - afMarker > MAXPATHLEN) {
993     return NULL;
994   }
995
996   // add the appropriate number of "..\"s.
997   rfMarker = 0;
998   for (i = 0; i < levels; i++) {
999     relativeFilename[rfMarker++] = '.';
1000     relativeFilename[rfMarker++] = '.';
1001     relativeFilename[rfMarker++] = G_DIR_SEPARATOR;
1002   }
1003
1004   // copy the rest of the filename into the result string
1005   strcpy(&relativeFilename[rfMarker], &absoluteFilename[afMarker]);
1006
1007   return relativeFilename;
1008 }
1009 /* END http://www.codeguru.com/cpp/misc/misc/fileanddirectorynaming/article.php/c263 */