Contents of /trunk/src/notes.c

Parent Directory Parent Directory | Revision Log Revision Log


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