Contents of /trunk/src/notes.c

Parent Directory Parent Directory | Revision Log Revision Log


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