Parent Directory | Revision Log
Fremantle widget sensitivity cleanups
1 | /* |
2 | * Copyright (C) 2008 Till Harbaum <till@harbaum.org>. |
3 | * |
4 | * This file is part of OSM2Go. |
5 | * |
6 | * OSM2Go is free software: you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License as published by |
8 | * the Free Software Foundation, either version 3 of the License, or |
9 | * (at your option) any later version. |
10 | * |
11 | * OSM2Go is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | * GNU General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU General Public License |
17 | * along with OSM2Go. If not, see <http://www.gnu.org/licenses/>. |
18 | */ |
19 | |
20 | #include <libxml/parser.h> |
21 | #include <libxml/tree.h> |
22 | |
23 | #define __USE_XOPEN |
24 | #include <time.h> |
25 | |
26 | #include "appdata.h" |
27 | |
28 | #ifndef LIBXML_TREE_ENABLED |
29 | #error "Tree not enabled in libxml" |
30 | #endif |
31 | |
32 | // predecs |
33 | static void track_do_enable_gps(appdata_t *appdata); |
34 | static void track_do_disable_gps(appdata_t *appdata); |
35 | |
36 | /* make menu represent the track state */ |
37 | static void track_menu_set(appdata_t *appdata, gboolean present) { |
38 | if(!appdata->window) return; |
39 | |
40 | /* if a track is present, then it can be cleared or exported */ |
41 | gtk_widget_set_sensitive(appdata->track.menu_item_track_clear, present); |
42 | gtk_widget_set_sensitive(appdata->track.menu_item_track_export, present); |
43 | } |
44 | |
45 | gint track_seg_points(track_seg_t *seg) { |
46 | gint points = 0; |
47 | |
48 | track_point_t *point = seg->track_point; |
49 | while(point) { |
50 | points++; |
51 | point = point->next; |
52 | } |
53 | return points; |
54 | } |
55 | |
56 | static gboolean track_get_prop_pos(xmlNode *node, pos_t *pos) { |
57 | char *str_lat = (char*)xmlGetProp(node, BAD_CAST "lat"); |
58 | char *str_lon = (char*)xmlGetProp(node, BAD_CAST "lon"); |
59 | |
60 | if(!str_lon || !str_lat) { |
61 | if(!str_lon) xmlFree(str_lon); |
62 | if(!str_lat) xmlFree(str_lat); |
63 | return FALSE; |
64 | } |
65 | |
66 | pos->lat = g_ascii_strtod(str_lat, NULL); |
67 | pos->lon = g_ascii_strtod(str_lon, NULL); |
68 | |
69 | xmlFree(str_lon); |
70 | xmlFree(str_lat); |
71 | |
72 | return TRUE; |
73 | } |
74 | |
75 | static track_point_t *track_parse_trkpt(bounds_t *bounds, xmlDocPtr doc, |
76 | xmlNode *a_node) { |
77 | track_point_t *point = g_new0(track_point_t, 1); |
78 | point->altitude = NAN; |
79 | |
80 | /* parse position */ |
81 | if(!track_get_prop_pos(a_node, &point->pos)) { |
82 | g_free(point); |
83 | return NULL; |
84 | } |
85 | |
86 | /* scan for children */ |
87 | xmlNode *cur_node = NULL; |
88 | for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { |
89 | if (cur_node->type == XML_ELEMENT_NODE) { |
90 | |
91 | /* elevation (altitude) */ |
92 | if(strcasecmp((char*)cur_node->name, "ele") == 0) { |
93 | char *str = (char*)xmlNodeGetContent(cur_node); |
94 | point->altitude = g_ascii_strtod(str, NULL); |
95 | xmlFree(str); |
96 | } |
97 | |
98 | /* time */ |
99 | if(strcasecmp((char*)cur_node->name, "time") == 0) { |
100 | struct tm time; |
101 | char *str = (char*)xmlNodeGetContent(cur_node); |
102 | char *ptr = strptime(str, DATE_FORMAT, &time); |
103 | if(ptr) point->time = mktime(&time); |
104 | xmlFree(str); |
105 | } |
106 | } |
107 | } |
108 | |
109 | return point; |
110 | } |
111 | |
112 | static void track_parse_trkseg(track_t *track, bounds_t *bounds, |
113 | xmlDocPtr doc, xmlNode *a_node) { |
114 | xmlNode *cur_node = NULL; |
115 | track_point_t **point = NULL; |
116 | track_seg_t **seg = &(track->track_seg); |
117 | |
118 | /* search end of track_seg list */ |
119 | while(*seg) seg = &((*seg)->next); |
120 | |
121 | for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { |
122 | if (cur_node->type == XML_ELEMENT_NODE) { |
123 | if(strcasecmp((char*)cur_node->name, "trkpt") == 0) { |
124 | track_point_t *cpnt = track_parse_trkpt(bounds, doc, cur_node); |
125 | if(cpnt) { |
126 | if(!point) { |
127 | /* start a new segment */ |
128 | *seg = g_new0(track_seg_t, 1); |
129 | point = &((*seg)->track_point); |
130 | } |
131 | /* attach point to chain */ |
132 | *point = cpnt; |
133 | point = &((*point)->next); |
134 | } else { |
135 | /* end segment if point could not be parsed and start a new one */ |
136 | /* close segment if there is one */ |
137 | if(point) { |
138 | printf("ending track segment leaving bounds\n"); |
139 | seg = &((*seg)->next); |
140 | point = NULL; |
141 | } |
142 | } |
143 | } else |
144 | printf("found unhandled gpx/trk/trkseg/%s\n", cur_node->name); |
145 | |
146 | } |
147 | } |
148 | } |
149 | |
150 | static track_t *track_parse_trk(bounds_t *bounds, |
151 | xmlDocPtr doc, xmlNode *a_node) { |
152 | track_t *track = g_new0(track_t, 1); |
153 | xmlNode *cur_node = NULL; |
154 | |
155 | for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { |
156 | if (cur_node->type == XML_ELEMENT_NODE) { |
157 | if(strcasecmp((char*)cur_node->name, "trkseg") == 0) { |
158 | track_parse_trkseg(track, bounds, doc, cur_node); |
159 | } else |
160 | printf("found unhandled gpx/trk/%s\n", cur_node->name); |
161 | |
162 | } |
163 | } |
164 | return track; |
165 | } |
166 | |
167 | static track_t *track_parse_gpx(bounds_t *bounds, |
168 | xmlDocPtr doc, xmlNode *a_node) { |
169 | track_t *track = NULL; |
170 | xmlNode *cur_node = NULL; |
171 | |
172 | for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) { |
173 | if (cur_node->type == XML_ELEMENT_NODE) { |
174 | if(strcasecmp((char*)cur_node->name, "trk") == 0) { |
175 | if(!track) |
176 | track = track_parse_trk(bounds, doc, cur_node); |
177 | else |
178 | printf("ignoring additional track\n"); |
179 | } else |
180 | printf("found unhandled gpx/%s\n", cur_node->name); |
181 | } |
182 | } |
183 | return track; |
184 | } |
185 | |
186 | /* parse root element and search for "track" */ |
187 | static track_t *track_parse_root(bounds_t *bounds, |
188 | xmlDocPtr doc, xmlNode *a_node) { |
189 | track_t *track = NULL; |
190 | xmlNode *cur_node = NULL; |
191 | |
192 | for (cur_node = a_node; cur_node; cur_node = cur_node->next) { |
193 | if (cur_node->type == XML_ELEMENT_NODE) { |
194 | /* parse track file ... */ |
195 | if(strcasecmp((char*)cur_node->name, "gpx") == 0) |
196 | track = track_parse_gpx(bounds, doc, cur_node); |
197 | else |
198 | printf("found unhandled %s\n", cur_node->name); |
199 | } |
200 | } |
201 | return track; |
202 | } |
203 | |
204 | static track_t *track_parse_doc(bounds_t *bounds, xmlDocPtr doc) { |
205 | track_t *track; |
206 | |
207 | /* Get the root element node */ |
208 | xmlNode *root_element = xmlDocGetRootElement(doc); |
209 | |
210 | track = track_parse_root(bounds, doc, root_element); |
211 | |
212 | /*free the document */ |
213 | xmlFreeDoc(doc); |
214 | |
215 | /* |
216 | * Free the global variables that may |
217 | * have been allocated by the parser. |
218 | */ |
219 | xmlCleanupParser(); |
220 | |
221 | return track; |
222 | } |
223 | |
224 | void track_info(track_t *track) { |
225 | printf("Track is %sdirty.\n", track->dirty?"":"not "); |
226 | |
227 | gint segs = 0, points = 0; |
228 | track_seg_t *seg = track->track_seg; |
229 | while(seg) { |
230 | points += track_seg_points(seg); |
231 | segs++; |
232 | seg = seg->next; |
233 | } |
234 | |
235 | printf("%d points in %d segments\n", points, segs); |
236 | |
237 | } |
238 | |
239 | static track_t *track_read(osm_t *osm, char *filename) { |
240 | printf("============================================================\n"); |
241 | printf("loading track %s\n", filename); |
242 | |
243 | xmlDoc *doc = NULL; |
244 | |
245 | LIBXML_TEST_VERSION; |
246 | |
247 | /* parse the file and get the DOM */ |
248 | if((doc = xmlReadFile(filename, NULL, 0)) == NULL) { |
249 | xmlErrorPtr errP = xmlGetLastError(); |
250 | errorf(NULL, "While parsing \"%s\":\n\n%s", filename, errP->message); |
251 | return NULL; |
252 | } |
253 | |
254 | track_t *track = track_parse_doc(osm->bounds, doc); |
255 | |
256 | if(!track || !track->track_seg) { |
257 | printf("track was empty/invalid track\n"); |
258 | return NULL; |
259 | } |
260 | |
261 | track->dirty = TRUE; |
262 | track_info(track); |
263 | |
264 | return track; |
265 | } |
266 | |
267 | void track_point_free(track_point_t *point) { |
268 | g_free(point); |
269 | } |
270 | |
271 | void track_seg_free(track_seg_t *seg) { |
272 | track_point_t *point = seg->track_point; |
273 | while(point) { |
274 | track_point_t *next = point->next; |
275 | track_point_free(point); |
276 | point = next; |
277 | } |
278 | |
279 | g_free(seg); |
280 | } |
281 | |
282 | /* --------------------------------------------------------------- */ |
283 | |
284 | void track_clear(appdata_t *appdata, track_t *track) { |
285 | if (! track) return; |
286 | |
287 | printf("clearing track\n"); |
288 | |
289 | if(appdata && appdata->map) |
290 | map_track_remove(appdata); |
291 | |
292 | track_seg_t *seg = track->track_seg; |
293 | while(seg) { |
294 | track_seg_t *next = seg->next; |
295 | track_seg_free(seg); |
296 | seg = next; |
297 | } |
298 | |
299 | g_free(track); |
300 | |
301 | track_menu_set(appdata, FALSE); |
302 | } |
303 | |
304 | /* ---------------------- saving track --------------------------- */ |
305 | |
306 | void track_save_points(track_point_t *point, xmlNodePtr node) { |
307 | while(point) { |
308 | char str[G_ASCII_DTOSTR_BUF_SIZE]; |
309 | |
310 | xmlNodePtr node_point = xmlNewChild(node, NULL, BAD_CAST "trkpt", NULL); |
311 | |
312 | g_ascii_formatd(str, sizeof(str), LL_FORMAT, point->pos.lat); |
313 | xmlNewProp(node_point, BAD_CAST "lat", BAD_CAST str); |
314 | |
315 | g_ascii_formatd(str, sizeof(str), LL_FORMAT, point->pos.lon); |
316 | xmlNewProp(node_point, BAD_CAST "lon", BAD_CAST str); |
317 | |
318 | if(!isnan(point->altitude)) { |
319 | g_ascii_formatd(str, sizeof(str), ALT_FORMAT, point->altitude); |
320 | xmlNewTextChild(node_point, NULL, BAD_CAST "ele", BAD_CAST str); |
321 | } |
322 | |
323 | if(point->time) { |
324 | strftime(str, sizeof(str), DATE_FORMAT, localtime(&point->time)); |
325 | xmlNewTextChild(node_point, NULL, BAD_CAST "time", BAD_CAST str); |
326 | } |
327 | |
328 | point = point->next; |
329 | } |
330 | } |
331 | |
332 | void track_save_segs(track_seg_t *seg, xmlNodePtr node) { |
333 | while(seg) { |
334 | xmlNodePtr node_seg = xmlNewChild(node, NULL, BAD_CAST "trkseg", NULL); |
335 | track_save_points(seg->track_point, node_seg); |
336 | seg = seg->next; |
337 | } |
338 | } |
339 | |
340 | void track_write(char *name, track_t *track) { |
341 | printf("writing track to %s\n", name); |
342 | |
343 | LIBXML_TEST_VERSION; |
344 | |
345 | xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0"); |
346 | xmlNodePtr root_node = xmlNewNode(NULL, BAD_CAST "gpx"); |
347 | xmlNewProp(root_node, BAD_CAST "creator", BAD_CAST PACKAGE " v" VERSION); |
348 | xmlNewProp(root_node, BAD_CAST "xmlns", BAD_CAST |
349 | "http://www.topografix.com/GPX/1/0"); |
350 | |
351 | xmlNodePtr trk_node = xmlNewChild(root_node, NULL, BAD_CAST "trk", NULL); |
352 | xmlDocSetRootElement(doc, root_node); |
353 | |
354 | track_save_segs(track->track_seg, trk_node); |
355 | |
356 | xmlSaveFormatFileEnc(name, doc, "UTF-8", 1); |
357 | xmlFreeDoc(doc); |
358 | xmlCleanupParser(); |
359 | |
360 | track->dirty = FALSE; |
361 | } |
362 | |
363 | /* save track in project */ |
364 | void track_save(project_t *project, track_t *track) { |
365 | if(!project) return; |
366 | |
367 | char *trk_name = g_strdup_printf("%s/%s.trk", project->path, project->name); |
368 | |
369 | if(!track) { |
370 | g_remove(trk_name); |
371 | g_free(trk_name); |
372 | return; |
373 | } |
374 | |
375 | /* no need to save again if it has already been saved */ |
376 | if(!track->dirty) { |
377 | printf("track is not dirty, no need to save it (again)\n"); |
378 | g_free(trk_name); |
379 | return; |
380 | } |
381 | |
382 | track_write(trk_name, track); |
383 | |
384 | g_free(trk_name); |
385 | } |
386 | |
387 | void track_export(appdata_t *appdata, char *filename) { |
388 | g_assert(appdata->track.track); |
389 | track_write(filename, appdata->track.track); |
390 | } |
391 | |
392 | /* ---------------------- loading track --------------------------- */ |
393 | |
394 | track_t *track_restore(appdata_t *appdata, project_t *project) { |
395 | char *trk_name = g_strdup_printf("%s/%s.trk", project->path, project->name); |
396 | track_t *track = NULL; |
397 | |
398 | LIBXML_TEST_VERSION; |
399 | |
400 | if(!g_file_test(trk_name, G_FILE_TEST_EXISTS)) { |
401 | printf("no track present!\n"); |
402 | g_free(trk_name); |
403 | return NULL; |
404 | } |
405 | |
406 | printf("track found, loading ...\n"); |
407 | |
408 | track = track_read(appdata->osm, trk_name); |
409 | g_free(trk_name); |
410 | |
411 | track_menu_set(appdata, track != NULL); |
412 | |
413 | printf("restored track\n"); |
414 | if(track) { |
415 | track->dirty = FALSE; |
416 | track_info(track); |
417 | } |
418 | |
419 | return track; |
420 | } |
421 | |
422 | static void track_end_segment(track_t *track) { |
423 | if(!track) return; |
424 | |
425 | if(track->cur_seg) { |
426 | printf("ending a segment\n"); |
427 | |
428 | /* todo: check if segment only has 1 point */ |
429 | |
430 | track->cur_seg = NULL; |
431 | } |
432 | } |
433 | |
434 | static void track_append_position(appdata_t *appdata, pos_t *pos, float alt) { |
435 | track_t *track = appdata->track.track; |
436 | |
437 | track_menu_set(appdata, TRUE); |
438 | |
439 | /* no track at all? might be due to a "clear track" while running */ |
440 | if(!track) { |
441 | printf("restarting after \"clear\"\n"); |
442 | track = appdata->track.track = g_new0(track_t, 1); |
443 | } |
444 | |
445 | if(!track->cur_seg) { |
446 | printf("starting new segment\n"); |
447 | |
448 | track_seg_t **seg = &(track->track_seg); |
449 | while(*seg) seg = &((*seg)->next); |
450 | |
451 | *seg = track->cur_seg = g_new0(track_seg_t, 1); |
452 | } else |
453 | printf("appending to current segment\n"); |
454 | |
455 | gint seg_len = 0; |
456 | track_point_t **point = &(track->cur_seg->track_point); |
457 | while(*point) { seg_len++; point = &((*point)->next); } |
458 | |
459 | map_track_pos(appdata, pos); |
460 | |
461 | /* don't append if point is the same as last time */ |
462 | track_point_t *prev = track->cur_seg->track_point; |
463 | while(prev && prev->next) prev = prev->next; |
464 | |
465 | if(prev && prev->pos.lat == pos->lat && |
466 | prev->pos.lon == pos->lon) { |
467 | printf("same value as last point -> ignore\n"); |
468 | } else { |
469 | |
470 | *point = g_new0(track_point_t, 1); |
471 | (*point)->altitude = alt; |
472 | (*point)->time = time(NULL); |
473 | (*point)->pos = *pos; |
474 | track->dirty = TRUE; |
475 | |
476 | /* if segment length was 1 the segment can now be drawn */ |
477 | /* for the first time */ |
478 | if(!seg_len) { |
479 | printf("initial draw\n"); |
480 | g_assert(!track->cur_seg->item_chain); |
481 | map_track_draw_seg(appdata->map, track->cur_seg); |
482 | } |
483 | |
484 | /* if segment length was > 0 the segment has to be updated */ |
485 | if(seg_len > 0) { |
486 | printf("update draw with seg_len %d\n", seg_len+1); |
487 | |
488 | g_assert(track->cur_seg->item_chain); |
489 | map_track_update_seg(appdata->map, track->cur_seg); |
490 | } |
491 | } |
492 | |
493 | #ifdef USE_GOOCANVAS |
494 | if(appdata->settings && appdata->settings->follow_gps) { |
495 | lpos_t lpos; |
496 | pos2lpos(appdata->osm->bounds, pos, &lpos); |
497 | map_scroll_to_if_offscreen(appdata->map, &lpos); |
498 | } |
499 | #endif |
500 | } |
501 | |
502 | static gboolean update(gpointer data) { |
503 | appdata_t *appdata = (appdata_t*)data; |
504 | |
505 | /* ignore updates while no valid osm file is loaded, e.g. when switching */ |
506 | /* projects */ |
507 | if(!appdata->osm) |
508 | return TRUE; |
509 | |
510 | /* the map is only gone of the main screen is being closed */ |
511 | if(!appdata->map) { |
512 | printf("map has gone while tracking was active, stopping tracker\n"); |
513 | |
514 | if(appdata->track.handler_id) { |
515 | gtk_timeout_remove(appdata->track.handler_id); |
516 | appdata->track.handler_id = 0; |
517 | } |
518 | |
519 | return FALSE; |
520 | } |
521 | |
522 | if(!appdata->settings || !appdata->settings->enable_gps) { |
523 | // Turn myself off gracefully. |
524 | track_do_disable_gps(appdata); |
525 | return FALSE; |
526 | } |
527 | |
528 | pos_t pos; |
529 | float alt; |
530 | if(gps_get_pos(appdata, &pos, &alt)) { |
531 | printf("valid position %.6f/%.6f alt %.2f\n", pos.lat, pos.lon, alt); |
532 | track_append_position(appdata, &pos, alt); |
533 | } else { |
534 | printf("no valid position\n"); |
535 | /* end segment */ |
536 | track_end_segment(appdata->track.track); |
537 | map_track_pos(appdata, NULL); |
538 | } |
539 | |
540 | return TRUE; |
541 | } |
542 | |
543 | static void track_do_enable_gps(appdata_t *appdata) { |
544 | gps_enable(appdata, TRUE); |
545 | |
546 | if(!appdata->track.handler_id) { |
547 | appdata->track.handler_id = gtk_timeout_add(1000, update, appdata); |
548 | |
549 | if(!appdata->track.track) { |
550 | printf("GPS: no track yet, starting new one\n"); |
551 | appdata->track.track = g_new0(track_t, 1); |
552 | appdata->track.track->dirty = FALSE; |
553 | } else |
554 | printf("GPS: extending existing track\n"); |
555 | } |
556 | } |
557 | |
558 | static void track_do_disable_gps(appdata_t *appdata) { |
559 | gps_enable(appdata, FALSE); |
560 | |
561 | if(appdata->track.handler_id) { |
562 | gtk_timeout_remove(appdata->track.handler_id); |
563 | appdata->track.handler_id = 0; |
564 | } |
565 | |
566 | /* stopping the GPS removes the marker ... */ |
567 | map_track_pos(appdata, NULL); |
568 | |
569 | /* ... and terminates the current segment if present */ |
570 | if(appdata->track.track) |
571 | appdata->track.track->cur_seg = NULL; |
572 | } |
573 | |
574 | void track_enable_gps(appdata_t *appdata, gboolean enable) { |
575 | printf("request to %sable gps\n", enable?"en":"dis"); |
576 | |
577 | if(appdata->settings) |
578 | appdata->settings->enable_gps = enable; |
579 | |
580 | gtk_widget_set_sensitive(appdata->track.menu_item_track_follow_gps, enable); |
581 | |
582 | if(enable) track_do_enable_gps(appdata); |
583 | else track_do_disable_gps(appdata); |
584 | } |
585 | |
586 | track_t *track_import(appdata_t *appdata, char *name) { |
587 | printf("import %s\n", name); |
588 | |
589 | /* remove any existing track */ |
590 | if(appdata->track.track) { |
591 | track_clear(appdata, appdata->track.track); |
592 | appdata->track.track = NULL; |
593 | } |
594 | |
595 | track_t *track = track_read(appdata->osm, name); |
596 | track_menu_set(appdata, track != NULL); |
597 | |
598 | if(track) { |
599 | map_track_draw(appdata->map, track); |
600 | track->dirty = TRUE; |
601 | } |
602 | |
603 | return track; |
604 | } |
605 | |
606 | // vim:et:ts=8:sw=2:sts=2:ai |