Contents of /trunk/src/notes.c

Parent Directory Parent Directory | Revision Log Revision Log


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