Contents of /trunk/src/notes.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 164 - (hide 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 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     #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 harbaum 164 #if defined(USE_MAEMO) && (MAEMO_VERSION_MAJOR >= 5)
37     #include <hildon/hildon-note.h>
38     #endif
39    
40 harbaum 1 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 harbaum 133 static int notes_write_file(cache_context_t *context,
165 harbaum 1 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 harbaum 133 static void notes_save(cache_context_t *context) {
238 harbaum 1 /* 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 harbaum 133 notes_write_file(context, text, pos, override, found,
336     context->notes.ftime, logged);
337 harbaum 1
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 harbaum 133 }
385 harbaum 1
386 harbaum 133 /* 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 harbaum 1 return FALSE;
394     }
395    
396 harbaum 2 #ifndef NO_COPY_N_PASTE
397 harbaum 1 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 harbaum 2 #endif
424 harbaum 1
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 harbaum 164 #if !defined(USE_MAEMO) || (MAEMO_VERSION_MAJOR < 5)
462 harbaum 1 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 harbaum 164 #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 harbaum 1 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 harbaum 164 #if !defined(USE_MAEMO) || (MAEMO_VERSION_MAJOR < 5)
498 harbaum 1 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 harbaum 164 #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 harbaum 1 gtk_widget_destroy(dialog);
534     }
535     }
536     }
537    
538 harbaum 2 #ifndef NO_COPY_N_PASTE
539 harbaum 1 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 harbaum 2 #endif
558 harbaum 1
559 harbaum 133 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 harbaum 1 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 harbaum 133 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 harbaum 1 gtk_table_attach_defaults(GTK_TABLE(table),
632 harbaum 133 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 harbaum 1
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 harbaum 8 #ifndef USE_HILDON_TEXT_VIEW
654 harbaum 1 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 harbaum 2 #ifndef NO_COPY_N_PASTE
672 harbaum 1 g_signal_connect(G_OBJECT(view), "focus-in-event",
673 harbaum 11 G_CALLBACK(focus_in), context->appdata);
674 harbaum 1 g_signal_connect(G_OBJECT(view), "destroy",
675 harbaum 11 G_CALLBACK(on_destroy_textview), context->appdata);
676 harbaum 2 #endif
677 harbaum 1
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 harbaum 7 GtkWidget *button = gtk_button_new_with_label(_("Browse"));
883 harbaum 1 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 harbaum 133 notes_write_file(&ccontext,
1003 harbaum 1 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     }