Contents of /trunk/src/notes.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 303 - (show annotations)
Tue Sep 14 09:28:57 2010 UTC (13 years, 7 months ago) by harbaum
File MIME type: text/plain
File size: 31032 byte(s)
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 }