Contents of /trunk/src/notes.c

Parent Directory Parent Directory | Revision Log Revision Log


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