Contents of /trunk/src/notes.c

Parent Directory Parent Directory | Revision Log Revision Log


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