Parent Directory | Revision Log
Set log flag on web log
1 | /* |
2 | * Copyright (C) 2008 Till Harbaum <till@harbaum.org>. |
3 | * |
4 | * This file is part of GPXView. |
5 | * |
6 | * GPXView 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 | * GPXView 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 GPXView. If not, see <http://www.gnu.org/licenses/>. |
18 | */ |
19 | |
20 | #include <stdio.h> |
21 | #include <string.h> |
22 | #include <errno.h> |
23 | |
24 | #include <glib/gunicode.h> |
25 | |
26 | #include <libxml/parser.h> |
27 | #include <libxml/tree.h> |
28 | |
29 | #include <math.h> |
30 | |
31 | #if defined(USE_MAEMO) && (MAEMO_VERSION_MAJOR >= 5) |
32 | #include <hildon/hildon-note.h> |
33 | #include <hildon/hildon-entry.h> |
34 | #include <hildon/hildon-check-button.h> |
35 | #endif |
36 | |
37 | #if !defined(LIBXML_TREE_ENABLED) || !defined(LIBXML_OUTPUT_ENABLED) |
38 | #error "libxml doesn't support required tree or output" |
39 | #endif |
40 | |
41 | #include "gpxview.h" |
42 | |
43 | #if defined(USE_MAEMO) && (MAEMO_VERSION_MAJOR >= 5) |
44 | #include <hildon/hildon-note.h> |
45 | #endif |
46 | |
47 | void gtk_text_buffer_set_can_paste_rich_text(GtkTextBuffer *buffer, gboolean); |
48 | void gtk_text_buffer_set_rich_text_format(GtkTextBuffer *buffer, const gchar *); |
49 | |
50 | #define TAG_STATE GTK_STATE_PRELIGHT |
51 | |
52 | static notes_t *notes_load(appdata_t *appdata, cache_t *cache) { |
53 | notes_t *notes = NULL; |
54 | xmlDoc *doc = NULL; |
55 | xmlNode *root_element = NULL; |
56 | |
57 | LIBXML_TEST_VERSION; |
58 | |
59 | /* build local path */ |
60 | int path_len = strlen(appdata->image_path) + 2 * strlen(cache->id) + 6; |
61 | char *path = malloc(path_len); |
62 | snprintf(path, path_len, "%s%s/%s.gpx", |
63 | appdata->image_path, cache->id, cache->id); |
64 | |
65 | /* no such file? */ |
66 | if(!g_file_test(path, G_FILE_TEST_EXISTS)) { |
67 | free(path); |
68 | return NULL; |
69 | } |
70 | |
71 | /* parse the file and get the DOM */ |
72 | doc = xmlReadFile(path, NULL, 0); |
73 | |
74 | if(doc == NULL) { |
75 | printf("error: could not parse file %s\n", path); |
76 | free(path); |
77 | return NULL; |
78 | } |
79 | |
80 | /* Get the root element node */ |
81 | root_element = xmlDocGetRootElement(doc); |
82 | |
83 | xmlNode *cur_node = NULL; |
84 | for (cur_node = root_element; cur_node; cur_node = cur_node->next) { |
85 | if (cur_node->type == XML_ELEMENT_NODE) { |
86 | if(strcasecmp((char*)cur_node->name, "gpx") == 0) { |
87 | xmlNode *wpt_node = cur_node->children; |
88 | |
89 | while(wpt_node != NULL) { |
90 | if(wpt_node->type == XML_ELEMENT_NODE) { |
91 | if(strcasecmp((char*)wpt_node->name, "wpt") == 0) { |
92 | |
93 | notes = malloc(sizeof(notes_t)); |
94 | memset(notes, 0, sizeof(notes_t)); |
95 | notes->pos = gpx_cache_pos(cache); |
96 | |
97 | char *str; |
98 | if((str = (char*)xmlGetProp(wpt_node, BAD_CAST "override"))) { |
99 | notes->override = (strcasecmp(str, "yes") == 0); |
100 | xmlFree(str); |
101 | } |
102 | |
103 | if((str = (char*)xmlGetProp(wpt_node, BAD_CAST "found"))) { |
104 | /* check if there's a valid number -> found time */ |
105 | if(strtoul(str, NULL, 10) != 0) { |
106 | notes->found = TRUE; |
107 | notes->ftime = strtoul(str, NULL, 10); |
108 | } else { |
109 | notes->found = (strcasecmp(str, "yes") == 0); |
110 | notes->ftime = 0; |
111 | } |
112 | xmlFree(str); |
113 | } |
114 | |
115 | if((str = (char*)xmlGetProp(wpt_node, BAD_CAST "logged"))) { |
116 | notes->logged = (strcasecmp(str, "yes") == 0); |
117 | xmlFree(str); |
118 | } |
119 | |
120 | if((str = (char*)xmlGetProp(wpt_node, BAD_CAST "lat"))) { |
121 | notes->pos.lat = g_ascii_strtod(str, NULL); |
122 | xmlFree(str); |
123 | } |
124 | |
125 | if((str = (char*)xmlGetProp(wpt_node, BAD_CAST "lon"))) { |
126 | notes->pos.lon = g_ascii_strtod(str, NULL); |
127 | xmlFree(str); |
128 | } |
129 | |
130 | xmlNode *sub_node = wpt_node->children; |
131 | |
132 | while (sub_node != NULL) { |
133 | if (sub_node->type == XML_ELEMENT_NODE) { |
134 | if(strcasecmp((char*)sub_node->name, "desc") == 0) |
135 | notes->text = (char*) |
136 | xmlNodeListGetString(doc, sub_node->children, 1); |
137 | } |
138 | sub_node = sub_node->next; |
139 | } |
140 | } |
141 | } |
142 | wpt_node = wpt_node->next; |
143 | } |
144 | } |
145 | } |
146 | } |
147 | |
148 | xmlFreeDoc(doc); |
149 | xmlCleanupParser(); |
150 | |
151 | printf("got notes for cache %s\n", cache->id); |
152 | |
153 | free(path); |
154 | return notes; |
155 | } |
156 | |
157 | void notes_load_all(appdata_t *appdata, gpx_t *gpx) { |
158 | printf("Load all notes for %s\n", gpx->name); |
159 | |
160 | cache_t *cache = gpx->cache; |
161 | while(cache) { |
162 | /* a note may actually already be there if this gpx file */ |
163 | /* is e.g. assembled from search results */ |
164 | if(!cache->notes) |
165 | cache->notes = notes_load(appdata, cache); |
166 | |
167 | cache = cache->next; |
168 | } |
169 | } |
170 | |
171 | static int notes_write_file(cache_context_t *context, |
172 | char *text, pos_t pos, |
173 | gboolean override, gboolean found, |
174 | time_t ftime, gboolean logged) { |
175 | |
176 | g_assert(context); |
177 | g_assert(context->cache); |
178 | |
179 | /* build local path */ |
180 | int path_len = strlen(context->appdata->image_path) + |
181 | 2 * strlen(context->cache->id) + 6; |
182 | |
183 | char *path = malloc(path_len); |
184 | snprintf(path, path_len, "%s%s/%s.gpx", |
185 | context->appdata->image_path, |
186 | context->cache->id, context->cache->id); |
187 | |
188 | if(checkdir(path) != 0) { |
189 | printf("unable to create notes path\n"); |
190 | free(path); |
191 | return -1; |
192 | } |
193 | |
194 | LIBXML_TEST_VERSION; |
195 | |
196 | xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0"); |
197 | xmlNodePtr root_node = xmlNewNode(NULL, BAD_CAST "gpx"); |
198 | xmlDocSetRootElement(doc, root_node); |
199 | |
200 | xmlNodePtr wpt_node = xmlNewChild(root_node, NULL, BAD_CAST "wpt", NULL); |
201 | /* make sure no invalid position gets saved */ |
202 | if(!isnan(pos.lat) && !isnan(pos.lon)) { |
203 | if(override) |
204 | xmlNewProp(wpt_node, BAD_CAST "override", BAD_CAST "yes"); |
205 | else |
206 | xmlNewProp(wpt_node, BAD_CAST "override", BAD_CAST "no"); |
207 | |
208 | if(logged) |
209 | xmlNewProp(wpt_node, BAD_CAST "logged", BAD_CAST "yes"); |
210 | else |
211 | xmlNewProp(wpt_node, BAD_CAST "logged", BAD_CAST "no"); |
212 | |
213 | if(found) { |
214 | if(ftime) { |
215 | char str[32]; |
216 | snprintf(str, sizeof(str), "%lu", ftime); |
217 | xmlNewProp(wpt_node, BAD_CAST "found", BAD_CAST str); |
218 | } else |
219 | xmlNewProp(wpt_node, BAD_CAST "found", BAD_CAST "yes"); |
220 | } else |
221 | xmlNewProp(wpt_node, BAD_CAST "found", BAD_CAST "no"); |
222 | |
223 | char str[32]; |
224 | g_ascii_dtostr(str, sizeof(str), pos.lat); |
225 | xmlNewProp(wpt_node, BAD_CAST "lat", BAD_CAST str); |
226 | g_ascii_dtostr(str, sizeof(str), pos.lon); |
227 | xmlNewProp(wpt_node, BAD_CAST "lon", BAD_CAST str); |
228 | } |
229 | |
230 | int len = strlen(context->cache->id) + strlen(" - ") + |
231 | strlen(context->cache->name) + 1; |
232 | char *name = malloc(len); |
233 | snprintf(name, len, "%s - %s", context->cache->id, context->cache->name); |
234 | xmlNewChild(wpt_node, NULL, BAD_CAST "name", BAD_CAST name); |
235 | free(name); |
236 | xmlNewChild(wpt_node, NULL, BAD_CAST "sym", BAD_CAST "Pin, Blue"); |
237 | xmlNewChild(wpt_node, NULL, BAD_CAST "desc", BAD_CAST text); |
238 | |
239 | /* write everything and free it */ |
240 | printf("writing %s\n", path); |
241 | xmlSaveFormatFileEnc(path, doc, "UTF-8", 1); |
242 | xmlFreeDoc(doc); |
243 | xmlCleanupParser(); |
244 | free(path); |
245 | |
246 | return 0; |
247 | } |
248 | |
249 | static void notes_save(cache_context_t *context) { |
250 | /* only save if: there is a text which has been changed or */ |
251 | /* there is a position which differs from the original one */ |
252 | /* or has been changed */ |
253 | |
254 | if(context->notes.modified) { |
255 | #ifdef USE_STACKABLE_WINDOW |
256 | context->notes_have_been_changed = TRUE; |
257 | #endif |
258 | |
259 | printf("something has been modified, saving notes\n"); |
260 | |
261 | GtkTextIter start; |
262 | GtkTextIter end; |
263 | gtk_text_buffer_get_start_iter(context->notes.buffer, &start); |
264 | gtk_text_buffer_get_end_iter(context->notes.buffer, &end); |
265 | char *text = gtk_text_buffer_get_text(context->notes.buffer, |
266 | &start, &end, FALSE); |
267 | |
268 | pos_t pos; |
269 | pos.lat = lat_entry_get(context->notes.latw); |
270 | pos.lon = lon_entry_get(context->notes.lonw); |
271 | |
272 | gboolean override = |
273 | check_button_get_active(context->notes.overridew); |
274 | gboolean found = |
275 | check_button_get_active(context->notes.foundw); |
276 | gboolean logged = |
277 | check_button_get_active(context->notes.loggedw); |
278 | |
279 | if(pos_differ(&pos, &context->cache->pos)) |
280 | printf("position is modified\n"); |
281 | if(override || found) |
282 | printf("flags are set\n"); |
283 | if(strlen(text)) |
284 | printf("text is present\n"); |
285 | |
286 | /* check if the notes are empty */ |
287 | if(!pos_differ(&pos, &context->cache->pos) && |
288 | !override && !found && !logged && (strlen(text) == 0)) { |
289 | printf("notes are in default state, removing them if present\n"); |
290 | |
291 | /* remove note */ |
292 | int path_len = strlen(context->appdata->image_path) + |
293 | 2 * strlen(context->cache->id) + 6; |
294 | char *path = malloc(path_len); |
295 | snprintf(path, path_len, "%s%s/%s.gpx", |
296 | context->appdata->image_path, |
297 | context->cache->id, context->cache->id); |
298 | |
299 | printf("removing note %s\n", path); |
300 | remove(path); |
301 | free(path); |
302 | |
303 | /* search for matching caches and replace note there */ |
304 | gpx_t *gpx = context->appdata->gpx; |
305 | while(gpx) { |
306 | cache_t *cache = gpx->cache; |
307 | while(cache) { |
308 | if(strcmp(cache->id, context->cache->id)==0) { |
309 | if(cache->notes) { |
310 | notes_free(cache->notes); |
311 | cache->notes = NULL; |
312 | } |
313 | } |
314 | cache = cache->next; |
315 | } |
316 | gpx = gpx->next; |
317 | } |
318 | |
319 | #ifdef USE_MAEMO |
320 | /* update search results if present */ |
321 | if(context->appdata->search_results) { |
322 | printf("Updating search results\n"); |
323 | |
324 | /* add note to all matching search results */ |
325 | cache_t *cache = context->appdata->search_results->cache; |
326 | while(cache) { |
327 | if(strcmp(cache->id, context->cache->id)==0) |
328 | cache->notes = NULL; |
329 | |
330 | cache = cache->next; |
331 | } |
332 | } |
333 | #endif |
334 | |
335 | |
336 | } else { |
337 | /* we have to do two things here: */ |
338 | /* - update the notes.xml file on disk */ |
339 | /* - update the notes entry in the loaded gpx tree */ |
340 | |
341 | /* update file on disk */ |
342 | notes_write_file(context, text, pos, override, found, |
343 | context->notes.ftime, logged); |
344 | |
345 | /* search for matching caches and replace note there */ |
346 | notes_t *note = NULL; |
347 | gpx_t *gpx = context->appdata->gpx; |
348 | while(gpx) { |
349 | cache_t *cache = gpx->cache; |
350 | while(cache) { |
351 | if(strcmp(cache->id, context->cache->id)==0) { |
352 | // printf("found %s in %s\n", cache->id, gpx->name); |
353 | |
354 | if(cache->notes) |
355 | notes_free(cache->notes); |
356 | |
357 | /* create a new note for this cache */ |
358 | cache->notes = note = malloc(sizeof(notes_t)); |
359 | memset(cache->notes, 0, sizeof(notes_t)); |
360 | cache->notes->text = strdup(text); |
361 | cache->notes->pos = pos; |
362 | cache->notes->override = override; |
363 | cache->notes->found = found; |
364 | cache->notes->logged = logged; |
365 | cache->notes->ftime = context->notes.ftime; |
366 | } |
367 | cache = cache->next; |
368 | } |
369 | gpx = gpx->next; |
370 | } |
371 | |
372 | #ifdef USE_MAEMO |
373 | /* update search results if present */ |
374 | if(context->appdata->search_results) { |
375 | printf("Updating search results\n"); |
376 | |
377 | /* add note to all matching search results */ |
378 | cache_t *cache = context->appdata->search_results->cache; |
379 | while(cache) { |
380 | if(strcmp(cache->id, context->cache->id)==0) |
381 | cache->notes = note; |
382 | |
383 | cache = cache->next; |
384 | } |
385 | } |
386 | #endif |
387 | } |
388 | |
389 | if(text) free(text); |
390 | } |
391 | } |
392 | |
393 | /* this is called from the destroy event of the entire notebook */ |
394 | gint notes_destroy_event(GtkWidget *widget, gpointer data ) { |
395 | cache_context_t *context = (cache_context_t*)data; |
396 | |
397 | printf("about to destroy notes view\n"); |
398 | notes_save(context); |
399 | |
400 | return FALSE; |
401 | } |
402 | |
403 | #ifndef NO_COPY_N_PASTE |
404 | static void on_destroy_textview(GtkWidget *widget, gpointer data) { |
405 | appdata_t *appdata = (appdata_t*)data; |
406 | |
407 | printf("destroying notes textview\n"); |
408 | |
409 | /* only do this if main windows hasn't already been destroyed */ |
410 | if(!appdata->window) { |
411 | printf("destroy notes textview: main window is gone\n"); |
412 | return; |
413 | } |
414 | |
415 | if(!appdata->active_buffer) |
416 | printf("There is no active buffer!\n"); |
417 | else { |
418 | if(appdata->active_buffer == |
419 | gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget))) { |
420 | printf("This was the active buffer\n"); |
421 | |
422 | appdata->active_buffer = NULL; |
423 | |
424 | gtk_widget_set_sensitive(appdata->menu_cut, FALSE); |
425 | gtk_widget_set_sensitive(appdata->menu_copy, FALSE); |
426 | gtk_widget_set_sensitive(appdata->menu_paste, FALSE); |
427 | } |
428 | } |
429 | } |
430 | #endif |
431 | |
432 | static void ftime_update(GtkWidget *widget, cache_context_t *context, |
433 | gboolean update) { |
434 | /* check if it has been selected */ |
435 | if(check_button_get_active(widget)) { |
436 | printf("set active\n"); |
437 | |
438 | if(update) |
439 | context->notes.ftime = time(NULL); |
440 | |
441 | if(context->notes.ftime) { |
442 | struct tm *loctime = localtime(&context->notes.ftime); |
443 | char str[32]; |
444 | strftime(str, sizeof(str), "%x %X", loctime); |
445 | |
446 | gtk_label_set_text(GTK_LABEL(context->notes.datew), str); |
447 | } else |
448 | gtk_label_set_text(GTK_LABEL(context->notes.datew), ""); |
449 | |
450 | } else { |
451 | printf("invalidating time\n"); |
452 | context->notes.ftime = 0; |
453 | gtk_label_set_text(GTK_LABEL(context->notes.datew), ""); |
454 | } |
455 | } |
456 | |
457 | /* buffer edited */ |
458 | static void callback_modified(GtkWidget *widget, gpointer data ) { |
459 | cache_context_t *context = (cache_context_t*)data; |
460 | // printf("something has been edited\n"); |
461 | context->notes.modified = TRUE; |
462 | |
463 | if(widget == context->notes.foundw) { |
464 | printf("was foundw\n"); |
465 | |
466 | /* about to remove "found" flag -> ask for confirmation */ |
467 | if(!check_button_get_active(widget)) { |
468 | #if !defined(USE_MAEMO) || (MAEMO_VERSION_MAJOR < 5) |
469 | GtkWidget *dialog = gtk_message_dialog_new( |
470 | GTK_WINDOW(context->appdata->window), |
471 | GTK_DIALOG_DESTROY_WITH_PARENT, |
472 | GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, |
473 | _("Do you really want to remove the \"found\" flag? " |
474 | "This will void the recorded date of your find!")); |
475 | |
476 | gtk_window_set_title(GTK_WINDOW(dialog), _("Reset \"found\" flag?")); |
477 | |
478 | /* set the active flag again if the user answered "no" */ |
479 | if(GTK_RESPONSE_NO == gtk_dialog_run(GTK_DIALOG(dialog))) |
480 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE); |
481 | |
482 | #else |
483 | GtkWidget *dialog = |
484 | hildon_note_new_confirmation(GTK_WINDOW(context->appdata->window), |
485 | _("Do you really want to remove the \"found\" flag? " |
486 | "This will void the recorded date of your find!")); |
487 | |
488 | /* set the active flag again if the user answered "no" */ |
489 | if(GTK_RESPONSE_OK != gtk_dialog_run(GTK_DIALOG(dialog))) |
490 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE); |
491 | #endif |
492 | |
493 | gtk_widget_destroy(dialog); |
494 | } |
495 | |
496 | ftime_update(widget, context, TRUE); |
497 | } |
498 | |
499 | if(widget == context->notes.loggedw) { |
500 | printf("was loggedw\n"); |
501 | |
502 | /* about to remove "found" flag -> ask for confirmation */ |
503 | if(!check_button_get_active(widget)) { |
504 | #if !defined(USE_MAEMO) || (MAEMO_VERSION_MAJOR < 5) |
505 | GtkWidget *dialog = gtk_message_dialog_new( |
506 | GTK_WINDOW(context->appdata->window), |
507 | GTK_DIALOG_DESTROY_WITH_PARENT, |
508 | GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, |
509 | _("Do you really want to remove the \"logged\" flag? " |
510 | "This may cause problems on your next Garmin Field " |
511 | "Notes upload!")); |
512 | |
513 | gtk_window_set_title(GTK_WINDOW(dialog), _("Reset \"logged\" flag?")); |
514 | |
515 | /* set the active flag again if the user answered "no" */ |
516 | if(GTK_RESPONSE_NO == gtk_dialog_run(GTK_DIALOG(dialog))) |
517 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE); |
518 | else { |
519 | gtk_widget_set_sensitive(widget, FALSE); |
520 | gtk_widget_set_sensitive(context->notes.foundw, TRUE); |
521 | } |
522 | |
523 | #else |
524 | GtkWidget *dialog = |
525 | hildon_note_new_confirmation(GTK_WINDOW(context->appdata->window), |
526 | _("Do you really want to remove the \"logged\" flag? " |
527 | "This may cause problems on your next Garmin Field " |
528 | "Notes upload!")); |
529 | |
530 | /* set the active flag again if the user answered "no" */ |
531 | if(GTK_RESPONSE_OK != gtk_dialog_run(GTK_DIALOG(dialog))) |
532 | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE); |
533 | else { |
534 | gtk_widget_set_sensitive(widget, FALSE); |
535 | gtk_widget_set_sensitive(context->notes.foundw, TRUE); |
536 | } |
537 | |
538 | #endif |
539 | |
540 | gtk_widget_destroy(dialog); |
541 | } |
542 | } |
543 | } |
544 | |
545 | #ifndef NO_COPY_N_PASTE |
546 | static gboolean focus_in(GtkWidget *widget, GdkEventFocus *event, |
547 | gpointer data) { |
548 | appdata_t *appdata = (appdata_t*)data; |
549 | |
550 | printf("note focus in!\n"); |
551 | |
552 | /* these buffers are read/write, thus all items are enabled */ |
553 | gtk_widget_set_sensitive(appdata->menu_cut, TRUE); |
554 | gtk_widget_set_sensitive(appdata->menu_copy, TRUE); |
555 | gtk_widget_set_sensitive(appdata->menu_paste, TRUE); |
556 | |
557 | if(GTK_WIDGET_TYPE(widget) == GTK_TYPE_TEXT_VIEW) { |
558 | appdata->active_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget)); |
559 | } else |
560 | printf("not a text view\n"); |
561 | |
562 | return FALSE; |
563 | } |
564 | #endif |
565 | |
566 | #ifndef FREMANTLE |
567 | static gboolean focus_out(GtkWidget *widget, GdkEventFocus *event, |
568 | gpointer data) { |
569 | cache_context_t *context = (cache_context_t*)data; |
570 | |
571 | notes_save(context); |
572 | #if !defined(USE_MAEMO) && defined(ENABLE_OSM_GPS_MAP) |
573 | map_update(context->appdata); |
574 | #endif |
575 | |
576 | return FALSE; |
577 | } |
578 | #else |
579 | static void coo_changed(GtkWidget *widget, gpointer data) { |
580 | notes_save((cache_context_t*)data); |
581 | } |
582 | #endif |
583 | |
584 | GtkWidget *cache_notes(cache_context_t *context) { |
585 | cache_t *cache = context->cache; |
586 | |
587 | context->notes.modified = FALSE; |
588 | |
589 | if(context->cache->notes) |
590 | context->notes.ftime = context->cache->notes->ftime; |
591 | else |
592 | context->notes.ftime = 0; |
593 | |
594 | GtkWidget *vbox = gtk_vbox_new(FALSE, 2); |
595 | |
596 | /* -------------- custom coordinate ---------------- */ |
597 | |
598 | GtkWidget *table = gtk_table_new(2, 4, FALSE); |
599 | #if !defined(USE_MAEMO) || (MAEMO_VERSION_MAJOR < 5) |
600 | gtk_table_set_col_spacing(GTK_TABLE(table), 0, 16); |
601 | #endif |
602 | |
603 | gtk_table_set_col_spacing(GTK_TABLE(table), 2, 16); |
604 | |
605 | gtk_table_attach_defaults(GTK_TABLE(table), |
606 | gtk_label_new(_("New coordinate:")), 0, 1, 0, 1); |
607 | context->notes.overridew = check_button_new_with_label(_("Override")); |
608 | check_button_set_active(context->notes.overridew, |
609 | cache->notes && cache->notes->override); |
610 | gtk_table_attach_defaults(GTK_TABLE(table), |
611 | context->notes.overridew, 2, 3, 0, 1); |
612 | |
613 | GtkWidget *hbox = gtk_hbox_new(FALSE, 2); |
614 | |
615 | context->notes.foundw = check_button_new_with_label(_("Found")); |
616 | check_button_set_active(context->notes.foundw, |
617 | cache->notes && cache->notes->found); |
618 | gtk_box_pack_start_defaults(GTK_BOX(hbox), context->notes.foundw); |
619 | |
620 | context->notes.loggedw = check_button_new_with_label(_("Logged")); |
621 | check_button_set_active(context->notes.loggedw, |
622 | cache->notes && cache->notes->logged); |
623 | gtk_box_pack_start_defaults(GTK_BOX(hbox), context->notes.loggedw); |
624 | |
625 | gtk_table_attach_defaults(GTK_TABLE(table), hbox, 3, 4, 0, 1); |
626 | |
627 | /* only found and logged caches have a log flag the user can change */ |
628 | if(!(cache->notes && cache->notes->found && cache->notes->logged)) |
629 | gtk_widget_set_sensitive(context->notes.loggedw, FALSE); |
630 | else |
631 | gtk_widget_set_sensitive(context->notes.foundw, FALSE); |
632 | |
633 | context->notes.datew = gtk_label_new(""); |
634 | gtk_misc_set_alignment(GTK_MISC(context->notes.datew), 0.5f, 0.5f); |
635 | gtk_table_attach_defaults(GTK_TABLE(table), |
636 | context->notes.datew, 3, 4, 1, 2); |
637 | ftime_update(context->notes.foundw, context, FALSE); |
638 | |
639 | pos_t pos = gpx_cache_pos(cache); |
640 | if(cache->notes) pos = cache->notes->pos; |
641 | |
642 | context->notes.latw = lat_entry_new(pos.lat); |
643 | context->notes.lonw = lon_entry_new(pos.lon); |
644 | GtkWidget *picker = |
645 | preset_coordinate_picker(context->appdata, |
646 | context->notes.latw, context->notes.lonw); |
647 | |
648 | /* on fremantle we react on "changed" event instead since this */ |
649 | /* means that editing is done */ |
650 | #ifdef FREMANTLE |
651 | g_signal_connect(G_OBJECT(context->notes.latw), "changed", |
652 | G_CALLBACK(coo_changed), context); |
653 | g_signal_connect(G_OBJECT(context->notes.lonw), "changed", |
654 | G_CALLBACK(coo_changed), context); |
655 | #else |
656 | g_signal_connect(G_OBJECT(context->notes.latw), "focus-out-event", |
657 | G_CALLBACK(focus_out), context); |
658 | g_signal_connect(G_OBJECT(context->notes.lonw), "focus-out-event", |
659 | G_CALLBACK(focus_out), context); |
660 | #endif |
661 | |
662 | GtkWidget *ihbox = gtk_hbox_new(FALSE, 0); |
663 | |
664 | gtk_box_pack_start_defaults(GTK_BOX(ihbox), context->notes.latw); |
665 | gtk_box_pack_start_defaults(GTK_BOX(ihbox), context->notes.lonw); |
666 | gtk_box_pack_start_defaults(GTK_BOX(ihbox), picker); |
667 | |
668 | gtk_table_attach_defaults(GTK_TABLE(table), |
669 | ihbox, 0, 3, 1, 2); |
670 | |
671 | #if !defined(USE_MAEMO) || (MAEMO_VERSION_MAJOR < 5) |
672 | hbox = gtk_hbox_new(FALSE, 0); |
673 | gtk_box_pack_start(GTK_BOX(hbox), table, FALSE, FALSE, 0); |
674 | gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); |
675 | #else |
676 | gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); |
677 | #endif |
678 | |
679 | #ifndef USE_PANNABLE_AREA |
680 | GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL); |
681 | gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), |
682 | GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); |
683 | #else |
684 | GtkWidget *pannable_area = hildon_pannable_area_new(); |
685 | #endif |
686 | |
687 | context->notes.buffer = gtk_text_buffer_new(NULL); |
688 | |
689 | if(cache->notes && cache->notes->text) |
690 | gtk_text_buffer_set_text(context->notes.buffer, cache->notes->text, -1); |
691 | |
692 | #ifndef USE_HILDON_TEXT_VIEW |
693 | GtkWidget *view = gtk_text_view_new_with_buffer(context->notes.buffer); |
694 | #else |
695 | GtkWidget *view = hildon_text_view_new(); |
696 | hildon_text_view_set_buffer(HILDON_TEXT_VIEW(view), context->notes.buffer); |
697 | #endif |
698 | |
699 | /* if the cache has been marked found in the logs already, there's */ |
700 | /* no need/use to be able to change all this */ |
701 | if(cache->found) { |
702 | check_button_set_active(context->notes.loggedw, TRUE); |
703 | check_button_set_active(context->notes.foundw, TRUE); |
704 | gtk_widget_set_sensitive(table, FALSE); |
705 | |
706 | gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE); |
707 | gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE); |
708 | } else |
709 | gtk_text_view_set_editable(GTK_TEXT_VIEW(view), TRUE); |
710 | |
711 | gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD); |
712 | gtk_text_view_set_left_margin(GTK_TEXT_VIEW(view), 2 ); |
713 | gtk_text_view_set_right_margin(GTK_TEXT_VIEW(view), 2 ); |
714 | |
715 | #ifdef USE_MAEMO |
716 | /* Enable Rich Text Support */ |
717 | gtk_text_buffer_set_can_paste_rich_text(context->notes.buffer, TRUE ); |
718 | gtk_text_buffer_set_rich_text_format(context->notes.buffer, "RTF" ); |
719 | #endif |
720 | |
721 | #ifndef NO_COPY_N_PASTE |
722 | g_signal_connect(G_OBJECT(view), "focus-in-event", |
723 | G_CALLBACK(focus_in), context->appdata); |
724 | g_signal_connect(G_OBJECT(view), "destroy", |
725 | G_CALLBACK(on_destroy_textview), context->appdata); |
726 | #endif |
727 | |
728 | #ifndef USE_PANNABLE_AREA |
729 | gtk_container_add(GTK_CONTAINER(scrolled_window), view); |
730 | |
731 | gtk_scrolled_window_set_shadow_type( GTK_SCROLLED_WINDOW(scrolled_window), |
732 | GTK_SHADOW_IN); |
733 | gtk_box_pack_start(GTK_BOX(vbox), scrolled_window, TRUE, TRUE, 0); |
734 | #else |
735 | gtk_container_add(GTK_CONTAINER(pannable_area), view); |
736 | gtk_box_pack_start(GTK_BOX(vbox), pannable_area, TRUE, TRUE, 0); |
737 | #endif |
738 | |
739 | gtk_text_view_place_cursor_onscreen(GTK_TEXT_VIEW(view)); |
740 | |
741 | g_signal_connect(G_OBJECT(context->notes.buffer), "modified-changed", |
742 | G_CALLBACK(callback_modified), context); |
743 | g_signal_connect(G_OBJECT(context->notes.buffer), "changed", |
744 | G_CALLBACK(callback_modified), context); |
745 | g_signal_connect(G_OBJECT(context->notes.lonw), "changed", |
746 | G_CALLBACK(callback_modified), context); |
747 | g_signal_connect(G_OBJECT(context->notes.latw), "changed", |
748 | G_CALLBACK(callback_modified), context); |
749 | g_signal_connect(G_OBJECT(context->notes.overridew), "toggled", |
750 | G_CALLBACK(callback_modified), context); |
751 | g_signal_connect(G_OBJECT(context->notes.foundw), "toggled", |
752 | G_CALLBACK(callback_modified), context); |
753 | g_signal_connect(G_OBJECT(context->notes.loggedw), "toggled", |
754 | G_CALLBACK(callback_modified), context); |
755 | |
756 | return vbox; |
757 | } |
758 | |
759 | void notes_logged(cache_context_t *context) { |
760 | |
761 | /* if you log it, you sure also found it */ |
762 | check_button_set_active(context->notes.foundw, TRUE); |
763 | check_button_set_active(context->notes.loggedw, TRUE); |
764 | |
765 | gtk_widget_set_sensitive(context->notes.foundw, FALSE); |
766 | gtk_widget_set_sensitive(context->notes.loggedw, TRUE); |
767 | |
768 | ftime_update(context->notes.foundw, context, TRUE); |
769 | } |
770 | |
771 | void notes_free(notes_t *notes) { |
772 | if(notes) { |
773 | if(notes->text) xmlFree(notes->text); |
774 | free(notes); |
775 | } |
776 | } |
777 | |
778 | pos_t notes_get_pos(cache_context_t *context) { |
779 | pos_t pos = context->cache->pos; |
780 | if(check_button_get_active(context->notes.overridew)) { |
781 | pos.lat = lat_entry_get(context->notes.latw); |
782 | pos.lon = lon_entry_get(context->notes.lonw); |
783 | } |
784 | return pos; |
785 | } |
786 | |
787 | gboolean notes_get_override(cache_context_t *context) { |
788 | /* get override value */ |
789 | return check_button_get_active(context->notes.overridew); |
790 | } |
791 | |
792 | typedef struct { |
793 | GtkWidget *info_label; |
794 | GtkWidget *dialog; |
795 | appdata_t *appdata; |
796 | } export_context_t; |
797 | |
798 | typedef struct log_chain_s { |
799 | cache_t *cache; |
800 | struct log_chain_s *next; |
801 | } log_chain_t; |
802 | |
803 | void notes_log_export(appdata_t *appdata) { |
804 | gpx_t *gpx = appdata->gpx; |
805 | log_chain_t *log = NULL, *llog, **clog = &log; |
806 | |
807 | export_context_t context; |
808 | memset(&context, 0, sizeof(export_context_t)); |
809 | context.appdata = appdata; |
810 | |
811 | printf("export log\n"); |
812 | |
813 | int logs2export = 0; |
814 | |
815 | /* make sure all notes are loaded */ |
816 | while(gpx) { |
817 | if(!gpx->notes_loaded) { |
818 | notes_load_all(appdata, gpx); |
819 | gpx->notes_loaded = TRUE; |
820 | } |
821 | |
822 | cache_t *cache = gpx->cache; |
823 | while(cache) { |
824 | if(cache->notes && cache->notes->found && !cache->notes->logged) { |
825 | gboolean already_chained = FALSE; |
826 | llog = log; |
827 | while(llog) { |
828 | if(strcasecmp(llog->cache->id, cache->id) == 0) |
829 | already_chained = TRUE; |
830 | |
831 | llog = llog->next; |
832 | } |
833 | |
834 | if(!already_chained) { |
835 | logs2export++; |
836 | |
837 | printf("chaining log for %s\n", cache->id); |
838 | |
839 | *clog = g_new0(log_chain_t, 1); |
840 | (*clog)->cache = cache; |
841 | clog = &((*clog)->next); |
842 | } else |
843 | printf("dup for %s\n", cache->id); |
844 | |
845 | } |
846 | |
847 | cache = cache->next; |
848 | } |
849 | |
850 | gpx = gpx->next; |
851 | } |
852 | |
853 | |
854 | /* ------------- confirmation dialog ---------------- */ |
855 | char *old_fieldnotes_path = strdup(appdata->fieldnotes_path); |
856 | |
857 | context.dialog = gtk_dialog_new_with_buttons(_("Garmin Field Notes Export"), |
858 | GTK_WINDOW(appdata->window), GTK_DIALOG_MODAL, |
859 | GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, |
860 | GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, |
861 | NULL); |
862 | |
863 | #if defined(USE_MAEMO) && defined(HILDON_HELP) |
864 | hildon_help_dialog_help_enable(GTK_DIALOG(context.dialog), |
865 | HELP_ID_FIELDNOTES, appdata->osso_context); |
866 | #endif |
867 | |
868 | GtkWidget *vbox = gtk_vbox_new(FALSE,0); |
869 | |
870 | /* ------------------ info text -------------- */ |
871 | |
872 | char *msg = g_strdup_printf(_("This will export the notes of %d caches " |
873 | "into the given file. This file can be " |
874 | "uploaded to geocaching.com for logging."), |
875 | logs2export); |
876 | |
877 | context.info_label = gtk_label_new(msg); |
878 | g_free(msg); |
879 | gtk_label_set_line_wrap_mode(GTK_LABEL(context.info_label), PANGO_WRAP_WORD); |
880 | gtk_label_set_line_wrap(GTK_LABEL(context.info_label), TRUE); |
881 | gtk_misc_set_alignment(GTK_MISC(context.info_label), 0.f, 0.5f); |
882 | gtk_box_pack_start_defaults(GTK_BOX(vbox), context.info_label); |
883 | |
884 | /* ------------------ path/file ------------------ */ |
885 | gtk_box_pack_start_defaults(GTK_BOX(vbox), gtk_hseparator_new()); |
886 | |
887 | gtk_box_pack_start_defaults(GTK_BOX(vbox), |
888 | export_file(_("Save POI database"), &appdata->fieldnotes_path)); |
889 | |
890 | GtkWidget *label = |
891 | gtk_label_new(_("(a %s in the filename will be replaced by the " |
892 | "current date and time)")); |
893 | gtk_label_set_line_wrap_mode(GTK_LABEL(label), PANGO_WRAP_WORD); |
894 | gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); |
895 | gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f); |
896 | gtk_box_pack_start_defaults(GTK_BOX(vbox), label); |
897 | |
898 | /* ------------------ info ------------------ */ |
899 | |
900 | gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(context.dialog)->vbox), vbox); |
901 | |
902 | gtk_widget_show_all(context.dialog); |
903 | |
904 | if(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(context.dialog))) { |
905 | |
906 | /* ---------------- do actual export ------------------ */ |
907 | |
908 | time_t now = time(NULL); |
909 | struct tm *tm_now = localtime(&now); |
910 | char now_str[32]; |
911 | strftime(now_str, sizeof(now_str)-1, "%F_%H-%M", tm_now); |
912 | char *fname = g_strdup_printf(appdata->fieldnotes_path, now_str); |
913 | |
914 | printf("--- about to export logs to %s ---\n", fname); |
915 | FILE *file = fopen(fname, "w"); |
916 | g_free(fname); |
917 | |
918 | if(file) { |
919 | llog = log; |
920 | while(llog) { |
921 | printf("Exporting %s\n", llog->cache->id); |
922 | |
923 | /* ----------- build utc time string ----------- */ |
924 | |
925 | char *tz = getenv("TZ"); |
926 | setenv("TZ", "", 1); |
927 | tzset(); |
928 | struct tm *tm = localtime(&llog->cache->notes->ftime); |
929 | char tstr[32]; |
930 | strftime(tstr, sizeof(tstr)-1, "%FT%H:%MZ", tm); |
931 | if(tz) setenv("TZ", tz, 1); |
932 | else unsetenv("TZ"); |
933 | tzset(); |
934 | |
935 | /* escape \" in text */ |
936 | char *text = NULL; |
937 | if(llog->cache->notes->text) { |
938 | text = g_strdup(llog->cache->notes->text); |
939 | char *p = text; |
940 | while(*p) { |
941 | /* convert " to ' */ |
942 | if(*p == '\"') *p = '\''; |
943 | p++; |
944 | } |
945 | } else |
946 | text = g_strdup(""); |
947 | |
948 | /* ----------- build complete log string ------------ */ |
949 | char *str = g_strdup_printf("%s,%s,Found it,\"%s\"\n", |
950 | llog->cache->id, tstr, text); |
951 | g_free(text); |
952 | |
953 | /* ----------- convert to unicode ------------ */ |
954 | glong written = 0; |
955 | gunichar2 *uc = g_utf8_to_utf16(str, -1, NULL, &written, NULL); |
956 | g_free(str); |
957 | |
958 | /* -------------- and write it --------------- */ |
959 | fwrite(uc, written, sizeof(gunichar2), file); |
960 | g_free(uc); |
961 | |
962 | /* -------------- set "logged" flag in notes and update them ------ */ |
963 | llog->cache->notes->logged = TRUE; |
964 | |
965 | /* update flags in all copies of this cache */ |
966 | |
967 | gpx_t *tgpx = appdata->gpx; |
968 | while(tgpx) { |
969 | cache_t *tcache = tgpx->cache; |
970 | while(tcache) { |
971 | if((tcache != llog->cache) && |
972 | (strcmp(tcache->id, llog->cache->id) == 0)) { |
973 | printf("found dup cache %s in %s\n", tcache->id, tgpx->name); |
974 | |
975 | if(tcache->notes) |
976 | notes_free(tcache->notes); |
977 | |
978 | /* create a new note for this cache */ |
979 | tcache->notes = malloc(sizeof(notes_t)); |
980 | memset(tcache->notes, 0, sizeof(notes_t)); |
981 | if(llog->cache->notes->text) |
982 | tcache->notes->text = strdup(llog->cache->notes->text); |
983 | tcache->notes->pos = llog->cache->notes->pos; |
984 | tcache->notes->override = llog->cache->notes->override; |
985 | tcache->notes->found = llog->cache->notes->found; |
986 | tcache->notes->logged = llog->cache->notes->logged; |
987 | tcache->notes->ftime = llog->cache->notes->ftime; |
988 | } |
989 | tcache = tcache->next; |
990 | } |
991 | tgpx = tgpx->next; |
992 | } |
993 | |
994 | /* finally write the notes file itself */ |
995 | cache_context_t ccontext; |
996 | ccontext.appdata = appdata; |
997 | ccontext.cache = llog->cache; |
998 | |
999 | notes_write_file(&ccontext, |
1000 | llog->cache->notes->text, llog->cache->notes->pos, |
1001 | llog->cache->notes->override, llog->cache->notes->found, |
1002 | llog->cache->notes->ftime, llog->cache->notes->logged); |
1003 | |
1004 | llog = llog->next; |
1005 | } |
1006 | |
1007 | fclose(file); |
1008 | } else |
1009 | errorf(_("Can't open file:\n\n%s"), strerror(errno)); |
1010 | |
1011 | } else { |
1012 | /* restore old path, in case it has been altered but not been used */ |
1013 | free(appdata->fieldnotes_path); |
1014 | appdata->fieldnotes_path = strdup(old_fieldnotes_path); |
1015 | } |
1016 | |
1017 | gtk_widget_destroy(context.dialog); |
1018 | |
1019 | free(old_fieldnotes_path); |
1020 | |
1021 | /* free list */ |
1022 | while(log) { |
1023 | log_chain_t *next = log->next; |
1024 | g_free(log); |
1025 | log = next; |
1026 | } |
1027 | } |