Contents of /trunk/src/notes.c

Parent Directory Parent Directory | Revision Log Revision Log


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