Contents of /trunk/src/main.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 164 - (show annotations)
Thu Nov 5 20:37:16 2009 UTC (14 years, 6 months ago) by harbaum
File MIME type: text/plain
File size: 70301 byte(s)
Fremantleized message boxes
1 /*
2 * This file is part of GPXView.
3 *
4 * GPXView is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * GPXView is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with GPXView. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include <stdio.h>
19 #include <string.h>
20 #include <math.h>
21
22 #include <curl/curl.h>
23
24 #include <time.h>
25 #include <sys/time.h>
26
27 #include "gpxview.h"
28 #include "custom_size_renderer.h"
29 #include "custom_rating_renderer.h"
30 #include "custom_type_renderer.h"
31
32 #ifdef USE_MAEMO
33 #include <hildon/hildon-banner.h>
34 #if MAEMO_VERSION_MAJOR >= 5
35 #include <hildon/hildon-note.h>
36 #endif
37 #endif
38
39 #include <locale.h>
40
41 extern char *strcasestr (__const char *__haystack, __const char *__needle);
42
43 #if defined(USE_BREAD_CRUMB_TRAIL) || defined(BCT)
44 static void crumb_add(appdata_t *appdata, char *name, int level,
45 gpointer user_data);
46
47 enum {
48 CRUMB_GPXLIST = 0,
49 CRUMB_CACHELIST,
50 CRUMB_SEARCH_GLOBAL,
51 CRUMB_SEARCH_GPX,
52 CRUMB_CACHE,
53 };
54 #endif
55
56 /* size of first buffer malloc; start small to exercise grow routines */
57 #define FIRSTSIZE 1
58
59 void errorf(const char *fmt, ...) {
60 va_list args;
61 char *buf = NULL;
62 size_t bufsize;
63 char *newbuf;
64 size_t nextsize = 0;
65 int outsize;
66
67 bufsize = 0;
68 for (;;) {
69 if (bufsize == 0) {
70 if ((buf = (char *)malloc(FIRSTSIZE)) == NULL)
71 return;
72
73 bufsize = 1;
74 } else if ((newbuf = (char *)realloc(buf, nextsize)) != NULL) {
75 buf = newbuf;
76 bufsize = nextsize;
77 } else {
78 free(buf);
79 return;
80 }
81
82 va_start(args, fmt);
83 outsize = vsnprintf(buf, bufsize, fmt, args);
84 va_end(args);
85
86 if (outsize == -1) {
87 nextsize = bufsize * 2;
88 } else if (outsize == bufsize) {
89 nextsize = bufsize * 2;
90 } else if (outsize > bufsize) {
91 nextsize = outsize + 2; // Linux!
92 } else if (outsize == bufsize - 1) {
93 nextsize = bufsize * 2;
94 } else {
95 /* Output was not truncated */
96 break;
97 }
98 }
99
100 #if !defined(USE_MAEMO) || (MAEMO_VERSION_MAJOR < 5)
101 GtkWidget *dialog = gtk_message_dialog_new(
102 GTK_WINDOW(NULL),
103 GTK_DIALOG_DESTROY_WITH_PARENT,
104 GTK_MESSAGE_ERROR,
105 GTK_BUTTONS_CLOSE, buf);
106
107 gtk_window_set_title(GTK_WINDOW(dialog), _("ERROR"));
108 #else
109 GtkWidget *dialog =
110 hildon_note_new_information(GTK_WINDOW(NULL), buf);
111 #endif
112
113 gtk_dialog_run(GTK_DIALOG(dialog));
114 gtk_widget_destroy(dialog);
115
116 free(buf);
117 }
118
119 gpx_t *choose_file(appdata_t *appdata, gboolean whole_dir) {
120 GtkWidget *dialog;
121 gpx_t *gpx = NULL;
122
123 #ifdef USE_MAEMO
124 dialog = hildon_file_chooser_dialog_new(GTK_WINDOW(appdata->window),
125 whole_dir?GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER :
126 GTK_FILE_CHOOSER_ACTION_OPEN);
127
128 #ifdef HILDON_HELP
129 hildon_help_dialog_help_enable(GTK_DIALOG(dialog),
130 HELP_ID_IMPORT, appdata->osso_context);
131 #endif
132 #else
133 dialog = gtk_file_chooser_dialog_new (whole_dir?_("Import directory"):
134 _("Import file"),
135 GTK_WINDOW(appdata->window),
136 whole_dir?GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER :
137 GTK_FILE_CHOOSER_ACTION_OPEN,
138 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
139 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
140 NULL);
141 #endif
142
143 /* use path if one is present */
144 if(appdata->path)
145 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
146 appdata->path);
147
148 gtk_widget_show_all (GTK_WIDGET(dialog));
149 if (gtk_dialog_run (GTK_DIALOG(dialog)) == GTK_FM_OK) {
150 char *filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
151
152 if(filename) {
153 gpx_dialog_t *dialog = gpx_busy_dialog_new(GTK_WIDGET(appdata->window));
154
155 if(!whole_dir)
156 gpx = gpx_parse(dialog, filename);
157 else {
158 /* cur trailing '/' if present */
159 if(strlastchr(filename) == '/')
160 filename[strlen(filename)] = 0;
161
162 gpx = gpx_parse_dir(dialog, filename);
163 }
164
165 gpx_busy_dialog_destroy(dialog);
166
167 /* save path if gpx was successfully loaded */
168 if(gpx) {
169 char *r = strrchr(filename, '/');
170
171 /* there is a delimiter, use everything left of it as path */
172 if(r && !whole_dir) {
173 *r = 0;
174 if(appdata->path) free(appdata->path);
175 appdata->path = strdup(filename);
176 /* restore path ... just in case ... */
177 *r = '/';
178 }
179
180 if(whole_dir)
181 appdata->path = strdup(filename);
182 } else
183 errorf(_("Load error"));
184
185 g_free (filename);
186 } else {
187 #ifndef USE_MAEMO
188 errorf(_("Error accessing the file."));
189 #else
190 errorf(_("Error accessing the file. This may happen if the file "
191 "resides on a remote file system. Please copy the file onto "
192 "the device (e.g. onto the memory card) and try again."));
193 #endif
194 }
195 }
196
197 gtk_widget_destroy (dialog);
198
199 return gpx;
200 }
201
202 /******************** begin of cachelist ********************/
203
204 enum {
205 CACHELIST_COL_TYPE = 0,
206 CACHELIST_COL_ID,
207 CACHELIST_COL_NAME,
208 CACHELIST_COL_SIZE,
209 CACHELIST_COL_RATING,
210 CACHELIST_COL_BEARING,
211 CACHELIST_COL_DISTANCE,
212 CACHELIST_COL_DATA,
213 CACHELIST_COL_AVAIL,
214 CACHELIST_COL_ARCHIVE,
215 CACHELIST_NUM_COLS
216 } ;
217
218 void cachelist_goto_cache(appdata_t *appdata, cache_t *cache) {
219 #if !defined(USE_BREAD_CRUMB_TRAIL) && !defined(BCT)
220 cache_dialog(appdata, cache);
221 #else
222 gtk_container_remove(GTK_CONTAINER(appdata->vbox), appdata->cur_view);
223 appdata->cur_view = cache_view(appdata, cache);
224 gtk_box_pack_start_defaults(GTK_BOX(appdata->vbox), appdata->cur_view);
225 gtk_widget_show_all(appdata->vbox);
226
227 crumb_add(appdata, cache->name, CRUMB_CACHE, cache);
228 #endif
229 }
230
231 static void cachelist_view_onRowActivated(GtkTreeView *treeview,
232 GtkTreePath *path,
233 GtkTreeViewColumn *col,
234 gpointer userdata) {
235 appdata_t *appdata = (appdata_t*)userdata;
236 GtkTreeIter iter;
237 GtkTreeModel *model = gtk_tree_view_get_model(treeview);
238
239 #ifdef USE_MAEMO
240 /* check if a cache is already selected and ignore click if yes */
241 /* (was probably a double click) */
242 if(appdata->cur_cache) return;
243 #endif
244
245 if(gtk_tree_model_get_iter(model, &iter, path)) {
246 cache_t *cache;
247 gtk_tree_model_get(model, &iter, CACHELIST_COL_DATA, &cache, -1);
248 cachelist_goto_cache(appdata, cache);
249 }
250 }
251
252 typedef struct {
253 appdata_t *appdata;
254 GtkTreePath *path;
255 gboolean done;
256 } cachelist_expose_t;
257
258 static gboolean cachelist_expose(GtkWidget *widget, GdkEventExpose *event,
259 gpointer user_data) {
260 cachelist_expose_t *ce = (cachelist_expose_t*)user_data;
261
262 if(event->type == GDK_EXPOSE) {
263 if(ce->path && !ce->done) {
264 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(widget),
265 ce->path, NULL, TRUE, 0.5, 0.5);
266 gtk_tree_path_free(ce->path);
267 ce->done = TRUE;
268 }
269 }
270
271 return FALSE;
272 }
273
274 static void cachelist_destroy(GtkWidget *widget, gpointer user_data) {
275 cachelist_expose_t *ce = (cachelist_expose_t*)user_data;
276
277 printf("cachelist timer removed\n");
278 g_assert(ce->appdata->cachelist_handler_id);
279 gtk_timeout_remove(ce->appdata->cachelist_handler_id);
280 ce->appdata->cachelist_handler_id = 0;
281
282 free(user_data);
283 }
284
285 #define CACHELIST_UPDATE_TIMEOUT (30000)
286
287 static GtkWidget *cachelist_create(appdata_t *appdata, gpx_t *gpx,
288 cache_t *sel_cache);
289
290 void cachelist_redraw(appdata_t *appdata) {
291 if(!appdata->cur_view) {
292 printf("cachelist redraw: no active view\n");
293 return;
294 }
295
296 g_assert(!appdata->cur_cache);
297 int redraw = 0;
298 if(appdata->search_results)
299 redraw = 1;
300 else {
301 if(appdata->cur_gpx)
302 redraw = 2; // redraw cachelist
303 }
304
305 if(redraw) {
306 GtkWidget *container = appdata->vbox;
307
308 #ifdef USE_STACKABLE_WINDOW
309 HildonWindowStack *stack = hildon_window_stack_get_default();
310 container = hildon_window_stack_peek(stack);
311 #endif
312
313 gtk_container_remove(GTK_CONTAINER(container), appdata->cur_view);
314 switch(redraw) {
315 case 1:
316 appdata->cur_view = cachelist_create(appdata,
317 appdata->search_results, NULL);
318 break;
319 case 2:
320 appdata->cur_view = cachelist_create(appdata,
321 appdata->cur_gpx, NULL);
322 break;
323 }
324
325 #ifdef USE_STACKABLE_WINDOW
326 if(container != appdata->vbox)
327 gtk_container_add(GTK_CONTAINER(container), appdata->cur_view);
328 else
329 #endif
330 gtk_box_pack_start_defaults(GTK_BOX(container), appdata->cur_view);
331
332 gtk_widget_show_all(container);
333 }
334 }
335
336
337 static gboolean cachelist_update(gpointer data) {
338
339 printf("cachelist timer fired!\n");
340
341 appdata_t *appdata = (appdata_t*)data;
342
343 if(appdata->cur_cache)
344 return TRUE;
345
346 #ifdef USE_MAEMO
347 if(appdata->cachelist_disable_screensaver)
348 if (osso_display_blanking_pause(appdata->osso_context) != OSSO_OK)
349 fprintf(stderr, "error with display blank\n");
350 #endif
351
352 if(appdata->cachelist_update)
353 cachelist_redraw(appdata);
354
355 return TRUE;
356 }
357
358 static void cachelist_timer_reset(appdata_t *appdata) {
359 printf("cachelist timer reset\n");
360 g_assert(appdata->cachelist_handler_id);
361 gtk_timeout_remove(appdata->cachelist_handler_id);
362 appdata->cachelist_handler_id =
363 gtk_timeout_add(CACHELIST_UPDATE_TIMEOUT, cachelist_update, appdata);
364 }
365
366 static gboolean cachelist_update_reset0(GtkWidget *widget,
367 GdkEventButton *event,
368 gpointer user_data) {
369 cachelist_timer_reset((appdata_t*)user_data);
370 return FALSE;
371 }
372
373 static void cachelist_update_reset1(GtkAdjustment *adj,
374 gpointer user_data) {
375 cachelist_timer_reset((appdata_t*)user_data);
376 }
377
378 static GtkWidget *cachelist_create(appdata_t *appdata, gpx_t *gpx,
379 cache_t *sel_cache) {
380 GtkCellRenderer *renderer;
381 GtkWidget *view;
382 GtkListStore *store;
383 GtkTreeIter iter;
384
385 if(!gpx->notes_loaded) {
386 notes_load_all(appdata, gpx);
387 gpx->notes_loaded = TRUE;
388 }
389
390 appdata->cur_items = appdata->cachelist_items;
391
392 /* first sort the caches */
393 pos_t *refpos = get_pos(appdata);
394 gpx_sort(gpx, GPX_SORT_BY_DISTANCE, refpos);
395
396 view = gtk_tree_view_new();
397
398 /* --- "Type" column --- */
399 renderer = custom_cell_renderer_type_new();
400 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
401 -1, "Type", renderer, "type", CACHELIST_COL_TYPE, NULL);
402
403 /* --- "Id" column --- */
404 if(appdata->cachelist_items & CACHELIST_ITEM_ID) {
405 renderer = gtk_cell_renderer_text_new();
406 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
407 -1, "Id", renderer, "text", CACHELIST_COL_ID, NULL);
408 }
409
410 /* --- "Name" column --- */
411 renderer = gtk_cell_renderer_text_new();
412 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL );
413
414 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(
415 "Name", renderer, "text", CACHELIST_COL_NAME, NULL);
416 gtk_tree_view_column_set_expand(column, TRUE);
417 gtk_tree_view_insert_column(GTK_TREE_VIEW(view), column, -1);
418
419 g_object_set(renderer, "foreground", "#ff0000", NULL );
420 gtk_tree_view_column_add_attribute(column, renderer, "strikethrough",
421 CACHELIST_COL_AVAIL);
422 gtk_tree_view_column_add_attribute(column, renderer,
423 "foreground-set", CACHELIST_COL_ARCHIVE);
424
425 /* --- "Size" column --- */
426 if(appdata->cachelist_items & CACHELIST_ITEM_SIZE) {
427 renderer = custom_cell_renderer_size_new();
428 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
429 -1, "Size", renderer, "size", CACHELIST_COL_SIZE, NULL);
430 }
431
432 /* --- "Rating" column --- */
433 if(appdata->cachelist_items & CACHELIST_ITEM_RATING) {
434 renderer = custom_cell_renderer_rating_new();
435 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
436 -1, "Rating", renderer, "rating", CACHELIST_COL_RATING, NULL);
437 }
438
439 /* --- "Bearing" column --- */
440 renderer = gtk_cell_renderer_pixbuf_new();
441 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
442 -1, "Bearing", renderer, "pixbuf", CACHELIST_COL_BEARING, NULL);
443
444 /* --- "Distance" column --- */
445 renderer = gtk_cell_renderer_text_new();
446 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
447 -1, "Distance", renderer, "text", CACHELIST_COL_DISTANCE, NULL);
448
449 store = gtk_list_store_new(CACHELIST_NUM_COLS, G_TYPE_INT,
450 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT,
451 G_TYPE_INT, GDK_TYPE_PIXBUF, G_TYPE_STRING,
452 G_TYPE_POINTER, G_TYPE_BOOLEAN,
453 G_TYPE_BOOLEAN);
454
455 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
456
457 GtkTreeIter sel_iter;
458 gboolean sel_iter_valid = FALSE;
459 GtkTreePath *path = NULL;
460 cache_t *cache = gpx->cache;
461 while(cache) {
462 char str[32];
463 gpx_pos_get_distance_str(str, sizeof(str),
464 *refpos, gpx_cache_pos(cache), appdata->imperial);
465
466 int dint = (int)(cache->difficulty*2-2);
467 if(dint < 0) dint = 0;
468 if(dint > 8) dint = 8;
469
470 int tint = (int)(cache->terrain*2-2);
471 if(tint < 0) tint = 0;
472 if(tint > 8) tint = 8;
473
474 /* cache type includes "solved" flag in lowest bit */
475 int type = (cache->type<<8) +
476 (cache->notes?4:0) +
477 ((cache->notes && cache->notes->override)?1:0) +
478 ((cache->notes && cache->notes->found)?2:0);
479
480 if((!(type & 2)) || !appdata->cachelist_hide_found) {
481
482 /* Append a row and fill in some data */
483 gtk_list_store_append (store, &iter);
484
485 gtk_list_store_set(store, &iter,
486 CACHELIST_COL_TYPE, type,
487 CACHELIST_COL_ID, cache->id,
488 CACHELIST_COL_NAME, cache->name,
489 CACHELIST_COL_SIZE, cache->container,
490 CACHELIST_COL_RATING, 100 * dint + tint,
491 CACHELIST_COL_BEARING,
492 icon_bearing(*refpos, gpx_cache_pos(cache)),
493 CACHELIST_COL_DISTANCE, str,
494 CACHELIST_COL_DATA, cache,
495 CACHELIST_COL_AVAIL, !cache->available ||
496 cache->archived,
497 CACHELIST_COL_ARCHIVE, cache->archived,
498 -1);
499
500 if(cache == sel_cache) {
501 sel_iter = iter;
502 sel_iter_valid = TRUE;
503 }
504 }
505
506 cache = cache->next;
507 }
508
509 gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store));
510 g_object_unref(store);
511
512 if(sel_iter_valid) {
513 gtk_tree_selection_select_iter(sel, &sel_iter);
514 path = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &sel_iter);
515 }
516
517 /* make list react on clicks */
518 g_signal_connect(view, "row-activated",
519 (GCallback)cachelist_view_onRowActivated, appdata);
520
521 cachelist_expose_t *ce = malloc(sizeof(cachelist_expose_t));
522 ce->appdata = appdata;
523 ce->path = path;
524 ce->done = FALSE;
525
526 g_signal_connect(view, "expose-event",
527 (GCallback)cachelist_expose, ce);
528 g_signal_connect(view, "destroy",
529 (GCallback)cachelist_destroy, ce);
530
531 /* put this inside a scrolled view */
532 #ifndef USE_PANNABLE_AREA
533 GtkWidget *scrolled_window = gtk_scrolled_window_new (NULL, NULL);
534 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
535 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
536 gtk_container_add(GTK_CONTAINER(scrolled_window), view);
537 #else
538 GtkWidget *pannable_area = hildon_pannable_area_new();
539
540 gtk_container_add(GTK_CONTAINER(pannable_area), view);
541 #endif
542
543 /* add a timer for automatic update */
544 g_assert(!appdata->cachelist_handler_id);
545 appdata->cachelist_handler_id =
546 gtk_timeout_add(CACHELIST_UPDATE_TIMEOUT, cachelist_update, appdata);
547
548 /* update timer is being reset if the user scrolls or selects */
549 g_signal_connect(view, "button-press-event",
550 (GCallback)cachelist_update_reset0, appdata);
551
552 #ifndef USE_PANNABLE_AREA
553 g_signal_connect(gtk_scrolled_window_get_vadjustment(
554 GTK_SCROLLED_WINDOW(scrolled_window)),
555 "value-changed",
556 (GCallback)cachelist_update_reset1, appdata);
557
558 return scrolled_window;
559 #else
560 g_signal_connect(hildon_pannable_area_get_vadjustment(
561 HILDON_PANNABLE_AREA(pannable_area)),
562 "value-changed",
563 (GCallback)cachelist_update_reset1, appdata);
564
565
566 return pannable_area;
567 #endif
568 }
569
570 #ifndef USE_MAEMO
571 void cachelist_dialog(appdata_t *appdata, gpx_t *gpx) {
572 GtkWidget *dialog =
573 gtk_dialog_new_with_buttons(gpx->name, GTK_WINDOW(appdata->window),
574 GTK_DIALOG_NO_SEPARATOR | GTK_DIALOG_MODAL |
575 GTK_DIALOG_DESTROY_WITH_PARENT,
576 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
577 NULL);
578
579 gtk_window_set_default_size(GTK_WINDOW(dialog), DIALOG_WIDTH, DIALOG_HEIGHT);
580
581 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),
582 cachelist_create(appdata, gpx, NULL));
583
584 gtk_widget_show_all(dialog);
585
586 gtk_dialog_run(GTK_DIALOG(dialog));
587 gtk_widget_destroy(dialog);
588 }
589 #else
590 #ifdef USE_STACKABLE_WINDOW
591 static void search_result_free(gpx_t *result);
592
593 void on_cachelist_destroy(GtkWidget *widget, appdata_t *appdata) {
594 if(appdata->search_results) {
595 search_result_free(appdata->search_results);
596 appdata->search_results = NULL;
597 }
598 appdata->cur_gpx = NULL;
599
600 #ifdef ENABLE_OSM_GPS_MAP
601 map_update(appdata);
602 #endif
603
604 /* restore cur_view */
605 appdata->cur_view = g_object_get_data(G_OBJECT(widget), "cur_view");
606 }
607
608 void cachelist_dialog(appdata_t *appdata, gpx_t *gpx) {
609 GtkWidget *window = hildon_stackable_window_new();
610
611 /* store last "cur_view" in window */
612 g_object_set_data(G_OBJECT(window), "cur_view", appdata->cur_view);
613
614 appdata->cur_gpx = gpx;
615 char *title = g_strdup_printf("%s - GPXView", gpx->name);
616 gtk_window_set_title(GTK_WINDOW(window), title);
617 g_free(title);
618
619 appdata->cur_view = cachelist_create(appdata, gpx, NULL);
620 gtk_container_add(GTK_CONTAINER(window), appdata->cur_view);
621
622
623 hildon_window_set_app_menu(HILDON_WINDOW(window),
624 menu_create(appdata, MENU_CACHELIST));
625
626 g_signal_connect(G_OBJECT(window), "destroy",
627 G_CALLBACK(on_cachelist_destroy), appdata);
628
629 gtk_widget_show_all(window);
630
631 #ifdef ENABLE_OSM_GPS_MAP
632 map_update(appdata);
633 #endif
634 }
635 #endif
636 #endif
637
638 /******************** end of cachelist ********************/
639
640 /******************** begin of gpxlist ********************/
641
642 enum {
643 GPXLIST_COL_ICON = 0,
644 GPXLIST_COL_FILENAME,
645 GPXLIST_COL_NAME,
646 GPXLIST_COL_DATE,
647 GPXLIST_COL_CACHES,
648 GPXLIST_COL_OPEN,
649 #ifdef USE_PANNABLE_AREA
650 GPXLIST_COL_DELETE,
651 #endif
652 GPXLIST_COL_DATA,
653 GPXLIST_NUM_COLS
654 } ;
655
656 static GdkPixbuf *gpx_icon_get(gpx_t *gpx) {
657 if(gpx->filename && g_file_test(gpx->filename, G_FILE_TEST_IS_DIR))
658 return icon_get(ICON_FILE, 1);
659
660 if(gpx->filename&& !strcasecmp(gpx->filename+strlen(gpx->filename)-4,".zip"))
661 return icon_get(ICON_FILE, 2);
662
663 return icon_get(ICON_FILE, 0);
664 }
665
666 static void gpxlist_set(GtkListStore *store, GtkTreeIter *iter, gpx_t *gpx) {
667 char date_str[32], cnum[32];
668
669 if(gpx->year && gpx->month && gpx->day) {
670 GDate *date = g_date_new_dmy(gpx->day, gpx->month, gpx->year);
671 g_date_strftime(date_str, sizeof(date_str), "%x", date);
672 g_date_free(date);
673 } else
674 strcpy(date_str, "---");
675
676 char *fname = strrchr(gpx->filename, '/');
677 if(!fname) fname = gpx->filename;
678 else fname++; /* skip '/' */
679
680 snprintf(cnum, sizeof(cnum), "%d", gpx_total_caches(gpx));
681
682 /* Append a row and fill in some data */
683 gtk_list_store_set(store, iter,
684 GPXLIST_COL_ICON, gpx_icon_get(gpx),
685 GPXLIST_COL_FILENAME, fname,
686 GPXLIST_COL_NAME, gpx->name,
687 GPXLIST_COL_DATE, gpx->closed?NULL:date_str,
688 GPXLIST_COL_OPEN, !gpx->closed,
689 GPXLIST_COL_CACHES, gpx->closed?NULL:cnum,
690 #ifdef USE_PANNABLE_AREA
691 GPXLIST_COL_DELETE, icon_get(ICON_MISC, 7),
692 #endif
693 GPXLIST_COL_DATA, gpx,
694 -1);
695 }
696
697 static void gpxlist_remove(appdata_t *appdata,
698 GtkListStore *store, GtkTreeIter *iter,
699 gpx_t *gpx) {
700
701 printf("removing %s\n", gpx->name);
702
703 /* de-chain */
704 gpx_t **prev = &appdata->gpx;
705 while(*prev != gpx) prev = &((*prev)->next);
706 *prev = gpx->next;
707
708 /* remove gconf entry if file was closed */
709 gconf_remove_closed_name(appdata, gpx->filename);
710
711 /* free gpx itself */
712 gpx_free(gpx);
713
714 /* and remove from store */
715 gtk_list_store_remove(store, iter);
716 }
717
718 static void gpxlist_close(appdata_t *appdata,
719 GtkListStore *store, GtkTreeIter *iter,
720 gpx_t *gpx) {
721
722 printf("closing %s\n", gpx->name);
723
724 g_assert(!gpx->closed);
725 gpx->closed = TRUE;
726
727 /* free all associated caches */
728 gpx_free_caches(gpx);
729
730 /* update entry */
731 gpxlist_set(store, iter, gpx);
732
733 /* save name in gconf so we know this has been closed */
734 gconf_save_closed_name(appdata, gpx->filename, gpx->name);
735 }
736
737 void gpxlist_goto_cachelist(appdata_t *appdata, gpx_t *gpx) {
738 #if !defined(USE_BREAD_CRUMB_TRAIL) && !defined(BCT)
739 #ifdef USE_STACKABLE_WINDOW
740 if(!appdata->cur_gpx)
741 #endif
742 cachelist_dialog(appdata, gpx);
743 #ifdef USE_STACKABLE_WINDOW
744 else
745 printf("selected gpx, but cachelist window already present\n");
746 #endif
747 #else
748 gtk_container_remove(GTK_CONTAINER(appdata->vbox), appdata->cur_view);
749 appdata->cur_view = cachelist_create(appdata, gpx, NULL);
750 gtk_box_pack_start_defaults(GTK_BOX(appdata->vbox), appdata->cur_view);
751 gtk_widget_show_all(appdata->vbox);
752
753 crumb_add(appdata, gpx->name, CRUMB_CACHELIST, gpx);
754 #endif
755 }
756
757 static void gpxlist_view_onRowActivated(GtkTreeView *treeview,
758 GtkTreePath *path,
759 GtkTreeViewColumn *col,
760 gpointer userdata) {
761 appdata_t *appdata = (appdata_t*)userdata;
762 GtkTreeIter iter;
763 GtkTreeModel *model = gtk_tree_view_get_model(treeview);
764
765 #ifdef USE_MAEMO
766 /* check if a cache is already selected and ignore click if yes */
767 /* (was probably a double click) */
768 if(appdata->cur_gpx) return;
769 #endif
770
771 if (gtk_tree_model_get_iter(model, &iter, path)) {
772 gpx_t *gpx;
773 gtk_tree_model_get(model, &iter, GPXLIST_COL_DATA, &gpx, -1);
774
775 #ifdef USE_PANNABLE_AREA
776 /* get name of column the user clicked on */
777 const char *col_name = NULL;
778 if(col) col_name = gtk_tree_view_column_get_title(col);
779
780 if(col_name && !strcmp(col_name, "Del")) {
781 printf("clicked delete\n");
782
783 #if !defined(USE_MAEMO) || (MAEMO_VERSION_MAJOR < 5)
784 /* ask user what he wants */
785 GtkWidget *dialog = gtk_message_dialog_new(
786 GTK_WINDOW(appdata->window),
787 GTK_DIALOG_DESTROY_WITH_PARENT,
788 GTK_MESSAGE_QUESTION,
789 GTK_BUTTONS_CANCEL,
790 _("Do you want to close this entry only or do "
791 "you want to remove it completely from the list?"));
792
793 gtk_dialog_add_buttons(GTK_DIALOG(dialog),
794 _("Remove"), 1,
795 _("Close"), 2,
796 NULL);
797
798 gtk_window_set_title(GTK_WINDOW(dialog), _("Close or remove entry?"));
799 #else
800 GtkWidget *dialog =
801 hildon_note_new_confirmation_add_buttons(GTK_WINDOW(appdata->window),
802 _("Do you want to close this entry only or do "
803 "you want to remove it completely from the list?"),
804 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
805 _("Remove"), 1,
806 _("Close"), 2,
807 NULL);
808 #endif
809
810 if(gpx->closed)
811 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), 2, FALSE);
812
813 /* set the active flag again if the user answered "no" */
814 switch(gtk_dialog_run(GTK_DIALOG(dialog))) {
815
816 case 1:
817 gpxlist_remove(appdata, GTK_LIST_STORE(model), &iter, gpx);
818 break;
819
820 case 2:
821 gpxlist_close(appdata, GTK_LIST_STORE(model), &iter, gpx);
822 break;
823
824 default:
825 break;
826 }
827
828 gtk_widget_destroy(dialog);
829
830 } else
831 #endif
832 {
833
834 /* this gpx file may be closed. Since the user definitely wants */
835 /* to use it, we just open it again */
836 if(gpx->closed) {
837 gpx_dialog_t *dialog =
838 gpx_busy_dialog_new(GTK_WIDGET(appdata->window));
839 gpx_t *new = NULL;
840
841 if(g_file_test(gpx->filename, G_FILE_TEST_IS_DIR))
842 new = gpx_parse_dir(dialog, gpx->filename);
843 else
844 new = gpx_parse(dialog, gpx->filename);
845
846 if(new) {
847 gpx_t **prev = &(appdata->gpx);
848 while(*prev && *prev != gpx)
849 prev = &(*prev)->next;
850
851 /* this entry _must_ be in the list */
852 g_assert(*prev);
853
854 /* replace gpx entry with the new "open" one */
855 (*prev) = new;
856 new->next = gpx->next;
857 gpx->next = NULL;
858
859 /* free old closed one */
860 gpx_free(gpx);
861
862 gpx = new;
863
864 /* finally update the visible list */
865 gpxlist_set(appdata->gpxstore, &iter, gpx);
866
867 /* and remove gconf entry */
868 gconf_remove_closed_name(appdata, gpx->filename);
869
870 #ifndef USE_PANNABLE_AREA
871 gtk_widget_set_sensitive(appdata->menu_close, TRUE);
872 #endif
873 } else {
874 printf("unable to reopen file %s\n", gpx->filename);
875 return;
876 }
877
878 gpx_busy_dialog_destroy(dialog);
879 }
880
881 gpxlist_goto_cachelist(appdata, gpx);
882 }
883 }
884 }
885
886 #ifndef USE_PANNABLE_AREA
887 static gboolean
888 view_selection_func(GtkTreeSelection *selection, GtkTreeModel *model,
889 GtkTreePath *path, gboolean path_currently_selected,
890 gpointer userdata) {
891 appdata_t *appdata = (appdata_t*)userdata;
892 GtkTreeIter iter;
893
894 if(gtk_tree_model_get_iter(model, &iter, path)) {
895 gpx_t *gpx;
896 gtk_tree_model_get(model, &iter, GPXLIST_COL_DATA, &gpx, -1);
897
898 gtk_widget_set_sensitive(appdata->menu_remove, !path_currently_selected);
899
900 if(!gpx->closed)
901 gtk_widget_set_sensitive(appdata->menu_close, !path_currently_selected);
902 }
903
904 return TRUE; /* allow selection state to change */
905 }
906 #endif
907
908 static GtkWidget *gpxlist_create_view_and_model(appdata_t *appdata,
909 gpx_t *sel_gpx) {
910 gpx_t *gpx = appdata->gpx;
911 GtkCellRenderer *renderer;
912
913 /* saved displayed items */
914 appdata->cur_items = appdata->gpxlist_items;
915
916 #ifndef USE_PANNABLE_AREA
917 /* nothing selected yet */
918 gtk_widget_set_sensitive(appdata->menu_remove, FALSE);
919 gtk_widget_set_sensitive(appdata->menu_close, FALSE);
920 #endif
921
922 appdata->gpxview = gtk_tree_view_new ();
923
924 GtkTreeSelection *selection =
925 gtk_tree_view_get_selection(GTK_TREE_VIEW(appdata->gpxview));
926 #ifndef USE_PANNABLE_AREA
927 gtk_tree_selection_set_select_function(selection, view_selection_func,
928 appdata, NULL);
929 #endif
930
931 /* --- "Icon" column --- */
932 renderer = gtk_cell_renderer_pixbuf_new();
933 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(appdata->gpxview),
934 -1, "Icon", renderer,
935 "pixbuf", GPXLIST_COL_ICON,
936 #ifdef USE_PANNABLE_AREA
937 /* at least one entry needs to be sensitive. */
938 /* This is the delete icon if the PANNABLE_AREA is used */
939 "sensitive", GPXLIST_COL_OPEN,
940 #endif
941 NULL);
942
943 /* --- "FileName" column --- */
944 if(appdata->gpxlist_items & GPXLIST_ITEM_FILENAME) {
945 renderer = gtk_cell_renderer_text_new();
946 gtk_tree_view_insert_column_with_attributes(
947 GTK_TREE_VIEW(appdata->gpxview),
948 -1, "Filename", renderer,
949 "text", GPXLIST_COL_FILENAME,
950 "sensitive", GPXLIST_COL_OPEN,
951 NULL);
952 }
953
954 /* --- "Name" column --- */
955 renderer = gtk_cell_renderer_text_new();
956 g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL );
957
958 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(
959 "Name", renderer,
960 "text", GPXLIST_COL_NAME,
961 "sensitive", GPXLIST_COL_OPEN,
962 NULL);
963 gtk_tree_view_column_set_expand(column, TRUE);
964 gtk_tree_view_insert_column(GTK_TREE_VIEW(appdata->gpxview), column, -1);
965
966 /* --- "Date" column --- */
967 if(appdata->gpxlist_items & GPXLIST_ITEM_DATE) {
968 renderer = gtk_cell_renderer_text_new();
969 g_object_set(renderer, "xalign", 1.0, NULL );
970 gtk_tree_view_insert_column_with_attributes(
971 GTK_TREE_VIEW(appdata->gpxview),
972 -1, "Date", renderer,
973 "text", GPXLIST_COL_DATE,
974 "sensitive", GPXLIST_COL_OPEN,
975 NULL);
976 }
977
978 /* --- "Number of caches" column --- */
979 if(appdata->gpxlist_items & GPXLIST_ITEM_CNUM) {
980 renderer = gtk_cell_renderer_text_new();
981 g_object_set(renderer, "xalign", 1.0, NULL );
982 gtk_tree_view_insert_column_with_attributes(
983 GTK_TREE_VIEW(appdata->gpxview),
984 -1, "#Caches", renderer,
985 "text", GPXLIST_COL_CACHES,
986 "sensitive", GPXLIST_COL_OPEN,
987 NULL);
988 }
989
990 #ifdef USE_PANNABLE_AREA
991 /* --- "Delete" column --- */
992 renderer = gtk_cell_renderer_pixbuf_new();
993 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(appdata->gpxview),
994 -1, "Del", renderer,
995 "pixbuf", GPXLIST_COL_DELETE,
996 // "sensitive", GPXLIST_COL_OPEN,
997 NULL);
998 #endif
999
1000 /* build and fill the store */
1001 appdata->gpxstore = gtk_list_store_new(GPXLIST_NUM_COLS, GDK_TYPE_PIXBUF,
1002 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
1003 G_TYPE_STRING, G_TYPE_BOOLEAN,
1004 #ifdef USE_PANNABLE_AREA
1005 GDK_TYPE_PIXBUF,
1006 #endif
1007 G_TYPE_POINTER);
1008
1009 GtkTreePath *path = NULL;
1010 GtkTreeIter sel_iter;
1011 gboolean sel_iter_valid = FALSE;
1012 while(gpx) {
1013 GtkTreeIter iter;
1014 gtk_list_store_append(appdata->gpxstore, &iter);
1015 gpxlist_set(appdata->gpxstore, &iter, gpx);
1016
1017 if(gpx == sel_gpx) {
1018 sel_iter = iter;
1019 sel_iter_valid = TRUE;
1020 }
1021
1022 gpx = gpx->next;
1023 }
1024
1025 gtk_tree_view_set_model(GTK_TREE_VIEW(appdata->gpxview),
1026 GTK_TREE_MODEL(appdata->gpxstore));
1027
1028 g_object_unref(appdata->gpxstore);
1029
1030 if(sel_iter_valid) {
1031 gtk_tree_selection_select_iter(selection, &sel_iter);
1032 path = gtk_tree_model_get_path(GTK_TREE_MODEL(appdata->gpxstore),
1033 &sel_iter);
1034 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(appdata->gpxview),
1035 path, NULL, TRUE, 0.0, 0.0);
1036 gtk_tree_path_free(path);
1037 }
1038
1039 /* make list react on clicks */
1040 g_signal_connect(appdata->gpxview, "row-activated",
1041 (GCallback)gpxlist_view_onRowActivated, appdata);
1042
1043 /* put this inside a scrolled view */
1044 #ifndef USE_PANNABLE_AREA
1045 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1046 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
1047 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1048 gtk_container_add(GTK_CONTAINER(scrolled_window), appdata->gpxview);
1049
1050 return scrolled_window;
1051 #else
1052 GtkWidget *pannable_area = hildon_pannable_area_new();
1053 gtk_container_add(GTK_CONTAINER(pannable_area), appdata->gpxview);
1054
1055 return pannable_area;
1056 #endif
1057 }
1058
1059 /* add last entry in gpx list to visual representation */
1060 static void gpxlist_add(appdata_t *appdata, gpx_t *new) {
1061 GtkTreeIter iter;
1062
1063 gtk_list_store_append(appdata->gpxstore, &iter);
1064 gpxlist_set(appdata->gpxstore, &iter, new);
1065
1066 /* and attach entry to end of list */
1067 gpx_t **gpx = &appdata->gpx;
1068 while(*gpx) gpx = &((*gpx)->next);
1069 *gpx = new;
1070 }
1071
1072 /******************** end of gpxlist ********************/
1073
1074 /******************** begin of menu *********************/
1075
1076 typedef struct {
1077 appdata_t *appdata;
1078 GtkWidget *dialog;
1079 } about_context_t;
1080
1081 #ifdef ENABLE_BROWSER_INTERFACE
1082 void on_paypal_button_clicked(GtkButton *button, about_context_t *context) {
1083 gtk_dialog_response(GTK_DIALOG(context->dialog), GTK_RESPONSE_ACCEPT);
1084 browser_url(context->appdata,
1085 "https://www.paypal.com/cgi-bin/webscr"
1086 "?cmd=_s-xclick&hosted_button_id=7400558");
1087 }
1088 #endif
1089
1090 static void
1091 cb_menu_about(GtkWidget *window, gpointer data) {
1092 about_context_t context;
1093
1094 context.appdata = (appdata_t *)data;
1095
1096 #ifdef ENABLE_LIBLOCATION
1097 char *uses = "uses liblocation";
1098 #elif defined(ENABLE_GPSBT)
1099 char *uses = "uses gpsbt and gpsd";
1100 #else
1101 char *uses = "uses gpsd";
1102 #endif
1103
1104 const gchar *authors[] = {
1105 "Till Harbaum <till@harbaum.org>",
1106 "John Stowers <john.stowers@gmail.com>",
1107 "GCVote: Guido Wegener <guido.wegener@gmx.de>",
1108 NULL };
1109
1110 context.dialog = g_object_new(GTK_TYPE_ABOUT_DIALOG,
1111 "name", "GPXView",
1112 "version", VERSION,
1113 "copyright", _("Copyright 2008-2009"),
1114 "authors", authors,
1115 "website", _("http://www.harbaum.org/till/maemo"),
1116 "comments", _(uses),
1117 NULL);
1118
1119 #ifdef ENABLE_BROWSER_INTERFACE
1120 /* add a way to donate to the project */
1121 GtkWidget *alignment = gtk_alignment_new(0.5, 0, 0, 0);
1122
1123 GtkWidget *hbox = gtk_hbox_new(FALSE, 8);
1124 gtk_box_pack_start(GTK_BOX(hbox),
1125 gtk_label_new(_("Do you like GPXView?")),
1126 FALSE, FALSE, 0);
1127
1128 GtkWidget *button = gtk_button_new();
1129 gtk_button_set_image(GTK_BUTTON(button),
1130 icon_get_widget(ICON_MISC, 8));
1131 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
1132 g_signal_connect(button, "clicked",
1133 G_CALLBACK(on_paypal_button_clicked), &context);
1134 gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
1135
1136 gtk_container_add(GTK_CONTAINER(alignment), hbox);
1137 gtk_box_pack_start_defaults(GTK_BOX((GTK_DIALOG(context.dialog))->vbox),
1138 alignment);
1139
1140 gtk_widget_show_all(alignment);
1141 #endif
1142
1143 gtk_dialog_run(GTK_DIALOG(context.dialog));
1144 gtk_widget_destroy(context.dialog);
1145 }
1146
1147 #if defined(USE_MAEMO) && defined(HILDON_HELP)
1148 static void
1149 cb_menu_help(GtkWidget *window, gpointer data) {
1150 appdata_t *appdata = (appdata_t*)data;
1151
1152 hildon_help_show(appdata->osso_context, HELP_ID_INTRO, 0);
1153 }
1154 #endif
1155
1156 static void
1157 cb_menu_add(GtkWidget *window, gpointer data) {
1158 appdata_t *appdata = (appdata_t *)data;
1159
1160 gpx_t *new = choose_file(appdata, FALSE);
1161 if(new) gpxlist_add(appdata, new);
1162 }
1163
1164 static void
1165 cb_menu_adddir(GtkWidget *window, gpointer data) {
1166 appdata_t *appdata = (appdata_t *)data;
1167
1168 gpx_t *new = choose_file(appdata, TRUE);
1169 if(new) gpxlist_add(appdata, new);
1170 }
1171
1172 #ifndef USE_PANNABLE_AREA
1173 static void
1174 cb_menu_close(GtkWidget *window, gpointer data) {
1175 appdata_t *appdata = (appdata_t *)data;
1176 GtkTreeSelection *selection;
1177 GtkTreeModel *model;
1178 GtkTreeIter iter;
1179
1180 printf("selected close\n");
1181
1182 /* the entry cannot be closed again */
1183 gtk_widget_set_sensitive(appdata->menu_close, FALSE);
1184
1185 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(appdata->gpxview));
1186
1187 printf("gpxlist close\n");
1188
1189 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
1190 gpx_t *gpx = NULL;
1191 gtk_tree_model_get(model, &iter, GPXLIST_COL_DATA, &gpx, -1);
1192
1193 if(gpx) gpxlist_close(appdata, GTK_LIST_STORE(model), &iter, gpx);
1194 } else {
1195 g_print ("no row selected.\n");
1196 }
1197 }
1198
1199 static void
1200 cb_menu_remove(GtkWidget *window, gpointer data) {
1201 appdata_t *appdata = (appdata_t *)data;
1202
1203 /* disable menu item */
1204 gtk_widget_set_sensitive(appdata->menu_remove, FALSE);
1205 gtk_widget_set_sensitive(appdata->menu_close, FALSE);
1206
1207 GtkTreeModel *model;
1208 GtkTreeIter iter;
1209 GtkTreeSelection *selection =
1210 gtk_tree_view_get_selection(GTK_TREE_VIEW(appdata->gpxview));
1211
1212 printf("gpxlist remove\n");
1213
1214 if(gtk_tree_selection_get_selected(selection, &model, &iter)) {
1215 gpx_t *gpx = NULL;
1216 gtk_tree_model_get(model, &iter, GPXLIST_COL_DATA, &gpx, -1);
1217
1218 if(gpx) gpxlist_remove(appdata, GTK_LIST_STORE(model), &iter, gpx);
1219 } else {
1220 g_print ("no row selected.\n");
1221 }
1222 }
1223
1224 #endif // !USE_PANNABLE_AREA
1225
1226 static void search_result_free(gpx_t *result) {
1227 printf("freeing search results\n");
1228
1229 /* free found chain */
1230 cache_t *cache = result->cache;
1231 while(cache) {
1232 cache_t *next = cache->next;
1233 free(cache);
1234 cache = next;
1235 }
1236 free(result->name);
1237 free(result);
1238 }
1239
1240 #define MAX_HITS 50
1241
1242 static time_t localize_time(time_t in) {
1243 time_t ret;
1244 char *tz;
1245 struct tm *tm = localtime(&in);
1246
1247 tz = getenv("TZ");
1248 setenv("TZ", "", 1);
1249 tzset();
1250 ret = mktime(tm);
1251 if (tz)
1252 setenv("TZ", tz, 1);
1253 else
1254 unsetenv("TZ");
1255 tzset();
1256 return ret;
1257 }
1258
1259 static int days_ago(time_t in) {
1260 int day_in = localize_time(in) / (60*60*24);
1261 int day_now = localize_time(time(NULL)) / (60*60*24);
1262
1263 return day_now - day_in;
1264 }
1265
1266 gpx_t *search_do(appdata_t *appdata, gpx_t *gpx, char *phrase,
1267 int what, gboolean local) {
1268 /* walk through all caches */
1269
1270 int hits = 0;
1271 gpx_t *found = malloc(sizeof(gpx_t));
1272 memset(found, 0, sizeof(gpx_t));
1273 cache_t **cacheP = &(found->cache);
1274
1275 if(what & SEARCH_FINDS) {
1276 time_t loc_now = localize_time(time(NULL));
1277 printf("now: %ld days since 1/1/1970, days hour is %ld\n",
1278 loc_now/(60*60*24), loc_now%(60*60*24)/(60*60));
1279 }
1280
1281 while(gpx && hits < MAX_HITS) {
1282
1283 /* we need all notes ... */
1284 if(what & SEARCH_FINDS) {
1285 notes_load_all(appdata, gpx);
1286 gpx->notes_loaded = TRUE;
1287 }
1288
1289 cache_t *cache = gpx->cache;
1290
1291 while(cache && hits < MAX_HITS) {
1292 gboolean hit = FALSE;
1293
1294 if(what & SEARCH_FINDS) {
1295 if(cache->notes && cache->notes->found ) {
1296 int days = days_ago(cache->notes->ftime);
1297
1298 if(cache->id)
1299 printf("find of %s is %d days ago\n", cache->id, days);
1300
1301 if(days <= appdata->search_days)
1302 hit = 1;
1303 }
1304 } else if(cache->id && (what & SEARCH_ID) &&
1305 strcasestr(cache->id, phrase))
1306 hit = 1;
1307 else if(cache->name && (what & SEARCH_NAME) &&
1308 strcasestr(cache->name, phrase))
1309 hit = 1;
1310 else if(cache->short_description && (what & SEARCH_DESC) &&
1311 strcasestr(cache->short_description, phrase))
1312 hit = 1;
1313 else if(cache->long_description && (what & SEARCH_DESC) &&
1314 strcasestr(cache->long_description, phrase))
1315 hit = 1;
1316 else if(cache->owner && cache->owner->name && (what & SEARCH_OWNER) &&
1317 strcasestr(cache->owner->name, phrase))
1318 hit = 1;
1319
1320 if(hit) {
1321 /* chain a copy of this cache structure into the found list */
1322 *cacheP = malloc(sizeof(cache_t));
1323 memcpy(*cacheP, cache, sizeof(cache_t));
1324 (*cacheP)->next = NULL;
1325 cacheP = &((*cacheP)->next);
1326 hits++;
1327 }
1328 cache = cache->next;
1329 }
1330
1331 if(!local) gpx = gpx->next;
1332 else gpx = NULL; /* local search within one gpx only */
1333 }
1334
1335 found->name = strdup(_("Search results"));
1336
1337 return found;
1338 }
1339
1340 typedef struct {
1341 appdata_t *appdata;
1342 GtkWidget *entry, *spinner;
1343 GtkWidget *in_id, *in_name, *in_desc, *in_owner, *in_finds;
1344 } search_context_t;
1345
1346 static void callback_finds_toggled(GtkWidget *widget, gpointer data ) {
1347 search_context_t *context = (search_context_t*)data;
1348
1349 gboolean in_finds = gtk_toggle_button_get_active(
1350 GTK_TOGGLE_BUTTON(context->in_finds));
1351
1352 gtk_widget_set_sensitive(context->entry, !in_finds);
1353 gtk_widget_set_sensitive(context->in_id, !in_finds);
1354 gtk_widget_set_sensitive(context->in_name, !in_finds);
1355 gtk_widget_set_sensitive(context->in_desc, !in_finds);
1356 gtk_widget_set_sensitive(context->in_owner, !in_finds);
1357 gtk_widget_set_sensitive(context->spinner, in_finds);
1358 }
1359
1360 static void
1361 cb_menu_search(GtkWidget *window, gpointer data) {
1362 appdata_t *appdata = (appdata_t *)data;
1363
1364 search_context_t context;
1365 memset(&context, 0, sizeof(search_context_t));
1366 context.appdata = appdata;
1367
1368 GtkWidget *dialog = gtk_dialog_new_with_buttons(_("Enter search phrase"),
1369 GTK_WINDOW(appdata->window), GTK_DIALOG_MODAL,
1370 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1371 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1372 NULL);
1373
1374 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox),
1375 gtk_label_new(_("Search in:")));
1376
1377 GtkWidget *table = gtk_table_new(2, 2, TRUE);
1378 gtk_table_set_col_spacing(GTK_TABLE(table), 0, 8);
1379
1380 context.in_id = gtk_check_button_new_with_label(_("Waypoint IDs"));
1381 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(context.in_id),
1382 appdata->search & SEARCH_ID);
1383 gtk_table_attach_defaults(GTK_TABLE(table), context.in_id, 0, 1, 0, 1);
1384
1385 context.in_name = gtk_check_button_new_with_label(_("Names"));
1386 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(context.in_name),
1387 appdata->search & SEARCH_NAME);
1388 gtk_table_attach_defaults(GTK_TABLE(table), context.in_name, 1, 2, 0, 1);
1389
1390 context.in_desc = gtk_check_button_new_with_label(_("Descriptions"));
1391 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(context.in_desc),
1392 appdata->search & SEARCH_DESC);
1393 gtk_table_attach_defaults(GTK_TABLE(table), context.in_desc, 0, 1, 1, 2);
1394
1395 context.in_owner = gtk_check_button_new_with_label(_("Owner"));
1396 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(context.in_owner),
1397 appdata->search & SEARCH_OWNER);
1398 gtk_table_attach_defaults(GTK_TABLE(table), context.in_owner, 1, 2, 1, 2);
1399
1400 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), table);
1401
1402 /* -------------------------------------------------------------- */
1403
1404 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox),
1405 gtk_label_new(_("Search for:")));
1406 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox),
1407 context.entry = gtk_entry_new());
1408 if(appdata->search_str)
1409 gtk_entry_set_text(GTK_ENTRY(context.entry), appdata->search_str);
1410
1411 /* -------------------------------------------------------------- */
1412
1413 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox),
1414 gtk_hseparator_new());
1415
1416 GtkWidget *hbox = gtk_hbox_new(FALSE, 5);
1417
1418 context.in_finds = gtk_check_button_new_with_label(_("Search finds for"));
1419 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(context.in_finds),
1420 appdata->search & SEARCH_FINDS);
1421 gtk_box_pack_start_defaults(GTK_BOX(hbox), context.in_finds);
1422 g_signal_connect(G_OBJECT(context.in_finds), "toggled",
1423 G_CALLBACK(callback_finds_toggled), &context);
1424
1425 #ifndef USE_MAEMO
1426 GtkObject *adj = gtk_adjustment_new(appdata->search_days, 0, 99, 1, 10, 10);
1427 context.spinner = gtk_spin_button_new(GTK_ADJUSTMENT(adj), 1, 0);
1428 #else
1429 context.spinner = hildon_number_editor_new(0, 99);
1430 hildon_number_editor_set_value(HILDON_NUMBER_EDITOR(context.spinner),
1431 appdata->search_days);
1432 #endif
1433 gtk_box_pack_start_defaults(GTK_BOX(hbox), context.spinner);
1434
1435 gtk_box_pack_start_defaults(GTK_BOX(hbox), gtk_label_new(_("days")));
1436
1437 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox);
1438
1439 /* -------------------------------------------------------------- */
1440
1441 gtk_widget_show_all(dialog);
1442 callback_finds_toggled(NULL, &context);
1443
1444 if(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog))) {
1445 char *p = strdup(gtk_entry_get_text(GTK_ENTRY(context.entry)));
1446
1447 /* update saved search string */
1448 if(appdata->search_str) free(appdata->search_str);
1449 if(strlen(p) > 0)
1450 appdata->search_str = strdup(p);
1451
1452 #ifndef USE_MAEMO
1453 appdata->search_days = gtk_spin_button_get_value_as_int(
1454 GTK_SPIN_BUTTON(context.spinner));
1455 #else
1456 appdata->search_days = hildon_number_editor_get_value(
1457 HILDON_NUMBER_EDITOR(context.spinner));
1458 #endif
1459
1460 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(context.in_finds)))
1461 appdata->search |= SEARCH_FINDS;
1462 else
1463 appdata->search &= ~SEARCH_FINDS;
1464
1465 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(context.in_id)))
1466 appdata->search |= SEARCH_ID;
1467 else
1468 appdata->search &= ~SEARCH_ID;
1469
1470 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(context.in_name)))
1471 appdata->search |= SEARCH_NAME;
1472 else
1473 appdata->search &= ~SEARCH_NAME;
1474
1475 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(context.in_desc)))
1476 appdata->search |= SEARCH_DESC;
1477 else
1478 appdata->search &= ~SEARCH_DESC;
1479
1480 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(context.in_owner)))
1481 appdata->search |= SEARCH_OWNER;
1482 else
1483 appdata->search &= ~SEARCH_OWNER;
1484
1485 gtk_widget_destroy(dialog);
1486
1487 /* don't search if we are asked to search for nothing */
1488 if(((appdata->search & (SEARCH_ID|SEARCH_NAME|SEARCH_DESC|SEARCH_OWNER)) &&
1489 strlen(p) > 0) || (appdata->search & SEARCH_FINDS)) {
1490
1491 printf("Search for %s (flags = %x)...\n", p, appdata->search);
1492
1493 #if !defined(USE_BREAD_CRUMB_TRAIL) && !defined(BCT)
1494 gpx_t *found =
1495 search_do(appdata, appdata->gpx, p, appdata->search, FALSE);
1496
1497 /* do search result dialog here ... */
1498 cachelist_dialog(appdata, found);
1499 #ifndef USE_STACKABLE_WINDOW
1500 search_result_free(found);
1501 #else
1502 appdata->search_results = found;
1503 #endif
1504 #else
1505 gpx_t *found = NULL;
1506
1507 if(appdata->cur_gpx)
1508 found = search_do(appdata, appdata->cur_gpx, p, appdata->search, TRUE);
1509 else
1510 found = search_do(appdata, appdata->gpx, p, appdata->search, FALSE);
1511
1512 gtk_container_remove(GTK_CONTAINER(appdata->vbox), appdata->cur_view);
1513 appdata->cur_view = cachelist_create(appdata, found, NULL);
1514 gtk_box_pack_start_defaults(GTK_BOX(appdata->vbox), appdata->cur_view);
1515 gtk_widget_show_all(appdata->vbox);
1516 crumb_add(appdata, found->name,
1517 appdata->cur_gpx?CRUMB_SEARCH_GPX:CRUMB_SEARCH_GLOBAL, found);
1518 #endif
1519 } else
1520 printf("No valid search: \"%s\" with flags %x!\n", p, appdata->search);
1521
1522 free(p);
1523 } else
1524 gtk_widget_destroy(dialog);
1525 }
1526
1527 static void on_window_destroy (GtkWidget *widget, gpointer data);
1528
1529 #ifndef USE_MAEMO
1530 static void
1531 cb_menu_quit(GtkWidget *window, gpointer data) {
1532 on_window_destroy(window, data);
1533 }
1534 #endif
1535
1536 #ifndef NO_COPY_N_PASTE
1537 static void
1538 cb_menu_cut(GtkWidget *widget, gpointer data) {
1539 appdata_t *appdata = (appdata_t*)data;
1540
1541 if(appdata->active_buffer) {
1542 if(GTK_WIDGET_TYPE(appdata->active_buffer) == GTK_TYPE_TEXT_BUFFER) {
1543 gtk_text_buffer_cut_clipboard(GTK_TEXT_BUFFER(appdata->active_buffer),
1544 appdata->clipboard, TRUE);
1545 } else
1546 printf("cut: ERROR, not a text buffer\n");
1547 } else
1548 printf("cut: ERROR, no active buffer\n");
1549 }
1550
1551 static void
1552 cb_menu_copy(GtkWidget *widget, gpointer data) {
1553 appdata_t *appdata = (appdata_t*)data;
1554
1555 if(appdata->active_buffer) {
1556 if(GTK_WIDGET_TYPE(appdata->active_buffer) == GTK_TYPE_TEXT_BUFFER) {
1557 gtk_text_buffer_copy_clipboard(GTK_TEXT_BUFFER(appdata->active_buffer),
1558 appdata->clipboard);
1559 } else if(GTK_WIDGET_TYPE(appdata->active_buffer) == gtk_html_get_type()) {
1560 printf("copy from html buffer\n");
1561 html_copy_to_clipboard(appdata);
1562 } else
1563 printf("copy: ERROR, not a text nor a html buffer\n");
1564 } else
1565 printf("copy: ERROR, no active buffer\n");
1566 }
1567
1568 static void
1569 cb_menu_paste(GtkWidget *widget, gpointer data) {
1570 appdata_t *appdata = (appdata_t*)data;
1571
1572 if(appdata->active_buffer) {
1573 if(GTK_WIDGET_TYPE(appdata->active_buffer) == GTK_TYPE_TEXT_BUFFER) {
1574 gtk_text_buffer_paste_clipboard(GTK_TEXT_BUFFER(appdata->active_buffer),
1575 appdata->clipboard, NULL, TRUE);
1576 } else
1577 printf("paste: ERROR, not a text buffer\n");
1578 } else
1579 printf("paste: ERROR, no active buffer\n");
1580 }
1581 #endif
1582
1583 static void
1584 cb_menu_export_log(GtkWidget *widget, gpointer data) {
1585 appdata_t *appdata = (appdata_t*)data;
1586 notes_log_export(appdata);
1587 }
1588
1589 #ifdef ENABLE_MAEMO_MAPPER
1590 static void
1591 cb_menu_export_mmpoi(GtkWidget *widget, gpointer data) {
1592 appdata_t *appdata = (appdata_t*)data;
1593 mmpoi_export(appdata);
1594 }
1595 #endif
1596
1597 static void
1598 cb_menu_export_garmin(GtkWidget *widget, gpointer data) {
1599 appdata_t *appdata = (appdata_t*)data;
1600 garmin_export(appdata);
1601 }
1602
1603 #ifdef ENABLE_OSM_GPS_MAP
1604 static void
1605 cb_menu_map(GtkWidget *window, gpointer data) {
1606 map((appdata_t *)data);
1607 }
1608 #endif
1609
1610 static void
1611 cb_menu_geomath(GtkWidget *window, gpointer data) {
1612 geomath_dialog((appdata_t *)data);
1613 }
1614
1615 static void
1616 cb_menu_geotext(GtkWidget *window, gpointer data) {
1617 geotext_dialog((appdata_t *)data);
1618 }
1619
1620 static void
1621 cb_menu_precpos(GtkWidget *window, gpointer data) {
1622 precise_position((appdata_t *)data);
1623 }
1624
1625 #ifdef USE_STACKABLE_WINDOW
1626 typedef struct {
1627 char *label, *desc;
1628 GtkSignalFunc activate_cb;
1629 } menu_entry_t;
1630
1631 typedef struct {
1632 const char *title;
1633 const menu_entry_t *menu;
1634 int len;
1635 } submenu_t;
1636
1637 #define COLUMNS 1
1638
1639 void on_submenu_entry_clicked(GtkButton *button, GtkWidget *menu) {
1640
1641 /* force closing of submenu dialog */
1642 gtk_dialog_response(GTK_DIALOG(menu), GTK_RESPONSE_NONE);
1643 gtk_widget_hide(menu);
1644
1645 /* let gtk clean up */
1646 while(gtk_events_pending())
1647 gtk_main_iteration();
1648 }
1649
1650 static GtkWidget *app_submenu_create(appdata_t *appdata,
1651 const submenu_t *submenu) {
1652
1653 /* create a oridinary dialog box */
1654 GtkWidget *dialog = gtk_dialog_new();
1655 gtk_window_set_title(GTK_WINDOW(dialog), _(submenu->title));
1656 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1657 gtk_window_set_transient_for(GTK_WINDOW(dialog),
1658 GTK_WINDOW(appdata->window));
1659 gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
1660
1661 GtkWidget *table = gtk_table_new(submenu->len/COLUMNS, COLUMNS, TRUE);
1662 int x = 0, y = 0;
1663
1664 const menu_entry_t *menu_entries = submenu->menu;
1665 while(menu_entries->label) {
1666 GtkWidget *button = NULL;
1667
1668 button = hildon_button_new_with_text(
1669 HILDON_SIZE_FINGER_HEIGHT | HILDON_SIZE_AUTO_WIDTH,
1670 HILDON_BUTTON_ARRANGEMENT_VERTICAL,
1671 _(menu_entries->label), _(menu_entries->desc));
1672
1673 /* try to center both texts */
1674 hildon_button_set_title_alignment(HILDON_BUTTON(button), 0.5, 0.5);
1675 hildon_button_set_value_alignment(HILDON_BUTTON(button), 0.5, 0.5);
1676
1677 g_signal_connect(button, "clicked",
1678 G_CALLBACK(on_submenu_entry_clicked), dialog);
1679
1680 g_signal_connect(button, "clicked",
1681 menu_entries->activate_cb, appdata);
1682
1683 gtk_table_attach_defaults(GTK_TABLE(table), button, x, x+1, y, y+1);
1684
1685 x++;
1686 if(x == COLUMNS) { x = 0; y++; }
1687
1688 menu_entries++;
1689 }
1690
1691 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), table);
1692
1693 return dialog;
1694 }
1695
1696 /* popup the dialog shaped submenu */
1697 static void submenu_popup(GtkWidget *menu) {
1698 gtk_widget_show_all(menu);
1699 gtk_dialog_run(GTK_DIALOG(menu));
1700 gtk_widget_hide(menu);
1701 }
1702
1703 static void submenu_cleanup(GtkWidget *menu) {
1704 gtk_widget_destroy(menu);
1705 }
1706
1707 static const menu_entry_t submenu_export_entries[] = {
1708 #ifdef ENABLE_MAEMO_MAPPER
1709 { "Export to Maemo Mapper" , "Save a Maemo Mapper POI file",
1710 G_CALLBACK(cb_menu_export_mmpoi) },
1711 #endif
1712 { "Export Field Notes", "Save a Garmin Field Notes file",
1713 G_CALLBACK(cb_menu_export_log) },
1714 { "Export Garmin GPX", "Save modified waypoints in GPX file",
1715 G_CALLBACK(cb_menu_export_garmin) },
1716 { NULL, NULL, NULL }
1717 };
1718
1719 static const submenu_t submenu_export = {
1720 "Export", submenu_export_entries,
1721 sizeof(submenu_export_entries)/sizeof(menu_entry_t)-1
1722 };
1723
1724 /* the export submenu */
1725 void on_export_clicked(GtkButton *button, appdata_t *appdata) {
1726 if(!appdata->export_menu)
1727 appdata->export_menu = app_submenu_create(appdata, &submenu_export);
1728
1729 submenu_popup(appdata->export_menu);
1730 }
1731
1732 static const menu_entry_t submenu_tools_entries[] = {
1733 { "Geomath", "Geocoordinate calculation",
1734 G_CALLBACK(cb_menu_geomath) },
1735 { "Geotext", "Text analysis",
1736 G_CALLBACK(cb_menu_geotext) },
1737 { "Precise Position", "Calculate a precise GPS position",
1738 G_CALLBACK(cb_menu_precpos) },
1739 { NULL, NULL, NULL }
1740 };
1741
1742 static const submenu_t submenu_tools = {
1743 "Tools", submenu_tools_entries,
1744 sizeof(submenu_tools_entries)/sizeof(menu_entry_t)-1
1745 };
1746
1747 /* the tools submenu */
1748 void on_tools_clicked(GtkButton *button, appdata_t *appdata) {
1749 if(!appdata->tools_menu)
1750 appdata->tools_menu = app_submenu_create(appdata, &submenu_tools);
1751
1752 submenu_popup(appdata->tools_menu);
1753 }
1754
1755 HildonAppMenu *menu_create(appdata_t *appdata, int mode) {
1756 GtkWidget *button;
1757 HildonAppMenu *menu = HILDON_APP_MENU(hildon_app_menu_new());
1758
1759 /* ------- */
1760 button = gtk_button_new_with_label(_("About"));
1761 g_signal_connect_after(button, "clicked",
1762 G_CALLBACK(cb_menu_about), appdata);
1763 hildon_app_menu_append(menu, GTK_BUTTON(button));
1764
1765 button = gtk_button_new_with_label(_("Settings"));
1766 g_signal_connect_after(button, "clicked", G_CALLBACK(cb_menu_settings),
1767 appdata);
1768 hildon_app_menu_append(menu, GTK_BUTTON(button));
1769
1770 if(mode == MENU_GPXLIST) {
1771 button = gtk_button_new_with_label(_("Import file"));
1772 g_signal_connect_after(button, "clicked",
1773 G_CALLBACK(cb_menu_add), appdata);
1774 hildon_app_menu_append(menu, GTK_BUTTON(button));
1775
1776 button = gtk_button_new_with_label(_("Import directory"));
1777 g_signal_connect_after(button, "clicked",
1778 G_CALLBACK(cb_menu_adddir), appdata);
1779 hildon_app_menu_append(menu, GTK_BUTTON(button));
1780
1781 button = gtk_button_new_with_label(_("Export"));
1782 g_signal_connect_after(button, "clicked",
1783 G_CALLBACK(on_export_clicked), appdata);
1784 hildon_app_menu_append(menu, GTK_BUTTON(button));
1785
1786 button = gtk_button_new_with_label(_("Search"));
1787 g_signal_connect_after(button, "clicked",
1788 G_CALLBACK(cb_menu_search), appdata);
1789 hildon_app_menu_append(menu, GTK_BUTTON(button));
1790 }
1791
1792 button = gtk_button_new_with_label(_("Tools"));
1793 g_signal_connect_after(button, "clicked",
1794 G_CALLBACK(on_tools_clicked), appdata);
1795 hildon_app_menu_append(menu, GTK_BUTTON(button));
1796
1797 #ifdef ENABLE_OSM_GPS_MAP
1798 button = gtk_button_new_with_label(_("Map"));
1799 g_signal_connect_after(button, "clicked",
1800 G_CALLBACK(cb_menu_map), appdata);
1801 hildon_app_menu_append(menu, GTK_BUTTON(button));
1802 #endif
1803
1804 #ifdef HILDON_HELP
1805 button = gtk_button_new_with_label(_("Help"));
1806 g_signal_connect_after(button, "clicked",
1807 G_CALLBACK(cb_menu_help), appdata);
1808 hildon_app_menu_append(menu, GTK_BUTTON(button));
1809 #endif
1810
1811 gtk_widget_show_all(GTK_WIDGET(menu));
1812
1813 return menu;
1814 }
1815 #else
1816
1817 void menu_create(appdata_t *appdata) {
1818 GtkWidget *menu, *item;
1819 menu = gtk_menu_new();
1820
1821 #if defined(USE_BREAD_CRUMB_TRAIL) || defined(BCT)
1822 appdata->menu_import =
1823 #endif
1824 item = gtk_menu_item_new_with_label(_("Import"));
1825 gtk_menu_append(GTK_MENU_SHELL(menu), item);
1826 GtkWidget *submenu = gtk_menu_new();
1827 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
1828
1829 item = gtk_menu_item_new_with_label( _("File") );
1830 gtk_menu_append(GTK_MENU_SHELL(submenu), item);
1831 g_signal_connect(item, "activate", GTK_SIGNAL_FUNC(cb_menu_add), appdata);
1832
1833 item = gtk_menu_item_new_with_label( _("Directory") );
1834 gtk_menu_append(GTK_MENU_SHELL(submenu), item);
1835 g_signal_connect(item, "activate", GTK_SIGNAL_FUNC(cb_menu_adddir), appdata);
1836
1837 #ifndef USE_PANNABLE_AREA
1838 gtk_menu_append(GTK_MENU_SHELL(submenu), gtk_separator_menu_item_new());
1839
1840 appdata->menu_close =
1841 item = gtk_menu_item_new_with_label( _("Close") );
1842 gtk_menu_append(GTK_MENU_SHELL(submenu), item);
1843 g_signal_connect(item, "activate", GTK_SIGNAL_FUNC(cb_menu_close), appdata);
1844
1845 appdata->menu_remove =
1846 item = gtk_menu_item_new_with_label( _("Remove") );
1847 gtk_menu_append(GTK_MENU_SHELL(submenu), item);
1848 g_signal_connect(item, "activate", GTK_SIGNAL_FUNC(cb_menu_remove), appdata);
1849 #endif
1850
1851 #if defined(USE_BREAD_CRUMB_TRAIL) || defined(BCT)
1852 appdata->menu_export =
1853 #endif
1854 item = gtk_menu_item_new_with_label(_("Export"));
1855 gtk_menu_append(GTK_MENU_SHELL(menu), item);
1856 submenu = gtk_menu_new();
1857 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
1858
1859 #ifdef ENABLE_MAEMO_MAPPER
1860 item = gtk_menu_item_new_with_label( _("Maemo Mapper POI") );
1861 gtk_menu_append(GTK_MENU_SHELL(submenu), item);
1862 g_signal_connect(item, "activate",
1863 GTK_SIGNAL_FUNC(cb_menu_export_mmpoi), appdata);
1864 #endif
1865
1866 item = gtk_menu_item_new_with_label( _("Garmin Field Notes") );
1867 gtk_menu_append(GTK_MENU_SHELL(submenu), item);
1868 g_signal_connect(item, "activate",
1869 GTK_SIGNAL_FUNC(cb_menu_export_log), appdata);
1870
1871 item = gtk_menu_item_new_with_label( _("Garmin GPX") );
1872 gtk_menu_append(GTK_MENU_SHELL(submenu), item);
1873 g_signal_connect(item, "activate",
1874 GTK_SIGNAL_FUNC(cb_menu_export_garmin), appdata);
1875
1876 #if defined(USE_BREAD_CRUMB_TRAIL) || defined(BCT)
1877 appdata->menu_search =
1878 #endif
1879 item = gtk_menu_item_new_with_label( _("Search") );
1880 gtk_menu_append(GTK_MENU_SHELL(menu), item);
1881 g_signal_connect(item, "activate", GTK_SIGNAL_FUNC(cb_menu_search), appdata);
1882
1883 gtk_menu_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
1884
1885 /* ----------- copy'n paste submenu ----------------- */
1886 #ifndef NO_COPY_N_PASTE
1887 item = gtk_menu_item_new_with_label(_("Edit"));
1888 gtk_menu_append(GTK_MENU_SHELL(menu), item);
1889 submenu = gtk_menu_new();
1890 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
1891
1892 appdata->menu_cut = item = gtk_menu_item_new_with_label( _("Cut") );
1893 gtk_widget_set_sensitive(item, FALSE);
1894 gtk_menu_append(GTK_MENU_SHELL(submenu), item);
1895 g_signal_connect(item, "activate", GTK_SIGNAL_FUNC(cb_menu_cut), appdata);
1896 appdata->menu_copy = item = gtk_menu_item_new_with_label( _("Copy") );
1897 gtk_widget_set_sensitive(item, FALSE);
1898 gtk_menu_append(GTK_MENU_SHELL(submenu), item);
1899 g_signal_connect(item, "activate", GTK_SIGNAL_FUNC(cb_menu_copy), appdata);
1900 appdata->menu_paste = item = gtk_menu_item_new_with_label( _("Paste") );
1901 gtk_widget_set_sensitive(item, FALSE);
1902 gtk_menu_append(GTK_MENU_SHELL(submenu), item);
1903 g_signal_connect(item, "activate", GTK_SIGNAL_FUNC(cb_menu_paste), appdata);
1904 #endif
1905
1906 item = gtk_menu_item_new_with_label( _("Settings") );
1907 gtk_menu_append(GTK_MENU_SHELL(menu), item);
1908 g_signal_connect(item, "activate", GTK_SIGNAL_FUNC(cb_menu_settings),
1909 appdata);
1910
1911 gtk_menu_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
1912
1913 #ifdef ENABLE_OSM_GPS_MAP
1914 item = gtk_menu_item_new_with_label( _("Map") );
1915 gtk_menu_append(GTK_MENU_SHELL(menu), item);
1916 g_signal_connect(item, "activate",
1917 GTK_SIGNAL_FUNC(cb_menu_map), appdata);
1918 #endif
1919
1920 item = gtk_menu_item_new_with_label(_("Tools"));
1921 gtk_menu_append(GTK_MENU_SHELL(menu), item);
1922 submenu = gtk_menu_new();
1923 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
1924
1925 item = gtk_menu_item_new_with_label( _("Geomath") );
1926 gtk_menu_append(GTK_MENU_SHELL(submenu), item);
1927 g_signal_connect(item, "activate",
1928 GTK_SIGNAL_FUNC(cb_menu_geomath), appdata);
1929
1930 item = gtk_menu_item_new_with_label( _("Geotext") );
1931 gtk_menu_append(GTK_MENU_SHELL(submenu), item);
1932 g_signal_connect(item, "activate",
1933 GTK_SIGNAL_FUNC(cb_menu_geotext), appdata);
1934
1935 item = gtk_menu_item_new_with_label( _("Precise Position") );
1936 gtk_menu_append(GTK_MENU_SHELL(submenu), item);
1937 g_signal_connect(item, "activate",
1938 GTK_SIGNAL_FUNC(cb_menu_precpos), appdata);
1939
1940 gtk_menu_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
1941
1942 #if defined(USE_MAEMO) && defined(HILDON_HELP)
1943 item = gtk_menu_item_new_with_label( _("Help") );
1944 gtk_menu_append(GTK_MENU_SHELL(menu), item);
1945 g_signal_connect(item, "activate", GTK_SIGNAL_FUNC(cb_menu_help), appdata);
1946 #endif
1947
1948 item = gtk_menu_item_new_with_label( _("About") );
1949 gtk_menu_append(GTK_MENU_SHELL(menu), item);
1950 g_signal_connect(item, "activate", GTK_SIGNAL_FUNC(cb_menu_about), appdata);
1951
1952 #ifndef USE_MAEMO
1953 item = gtk_menu_item_new_with_label( _("Quit") );
1954 gtk_menu_append(GTK_MENU_SHELL(menu), item);
1955 g_signal_connect(item, "activate", GTK_SIGNAL_FUNC(cb_menu_quit), appdata);
1956 #endif
1957
1958 #ifdef USE_MAEMO
1959 hildon_window_set_menu(appdata->window, GTK_MENU(menu));
1960 #else
1961 /* attach ordinary gtk menu */
1962 GtkWidget *menu_bar = gtk_menu_bar_new();
1963
1964 GtkWidget *root_menu = gtk_menu_item_new_with_label (_("Menu"));
1965 gtk_widget_show(root_menu);
1966
1967 gtk_menu_bar_append(menu_bar, root_menu);
1968 gtk_menu_item_set_submenu(GTK_MENU_ITEM (root_menu), menu);
1969
1970 gtk_widget_show(menu_bar);
1971 gtk_box_pack_start(GTK_BOX(appdata->vbox), menu_bar, 0, 0, 0);
1972 #endif
1973 }
1974 #endif
1975
1976 /********************* end of menu **********************/
1977
1978 void cleanup(appdata_t *appdata) {
1979 gpx_free_all(appdata->gpx);
1980 if(appdata->path) free(appdata->path);
1981 if(appdata->image_path) free(appdata->image_path);
1982 if(appdata->search_str) free(appdata->search_str);
1983
1984 #ifdef USE_STACKABLE_WINDOW
1985 if(appdata->export_menu) submenu_cleanup(appdata->export_menu);
1986 if(appdata->tools_menu) submenu_cleanup(appdata->tools_menu);
1987 #endif
1988
1989 gnome_vfs_shutdown();
1990 icons_free();
1991 gps_release(appdata);
1992
1993 #ifdef USE_MAEMO
1994 if(appdata->search_results) {
1995 printf("freeing pending search\n");
1996 search_result_free(appdata->search_results);
1997 }
1998
1999 if(appdata->osso_context)
2000 osso_deinitialize(appdata->osso_context);
2001
2002 appdata->program = NULL;
2003 #endif
2004
2005 /* free chain of locations */
2006 location_t *loc = appdata->location;
2007 while(loc) {
2008 location_t *next = loc->next;
2009 if(loc->name) free(loc->name);
2010 free(loc);
2011 loc = next;
2012 }
2013
2014 puts("everything is gone");
2015 }
2016
2017 static void on_window_destroy (GtkWidget *widget, gpointer data) {
2018 appdata_t *appdata = (appdata_t*)data;
2019
2020 gconf_save_state(appdata);
2021 gtk_main_quit();
2022 appdata->window = NULL;
2023 }
2024
2025 gboolean on_window_key_press(GtkWidget *widget,
2026 GdkEventKey *event, appdata_t *appdata) {
2027 int handled = FALSE;
2028
2029 // printf("key event %d\n", event->keyval);
2030
2031 switch(event->keyval) {
2032 #ifdef USE_MAEMO
2033
2034 #ifdef HILDON_HARDKEY_INCREASE
2035 case HILDON_HARDKEY_INCREASE:
2036 html_zoom(appdata, TRUE);
2037 handled = TRUE;
2038 break;
2039 #endif
2040
2041 #ifdef HILDON_HARDKEY_DECREASE
2042 case HILDON_HARDKEY_DECREASE:
2043 html_zoom(appdata, FALSE);
2044 handled = TRUE;
2045 break;
2046 #endif
2047
2048 #ifdef HILDON_HARDKEY_FULLSCREEN
2049 case HILDON_HARDKEY_FULLSCREEN:
2050 {
2051 appdata->fullscreen = !appdata->fullscreen;
2052
2053 if(appdata->fullscreen)
2054 gtk_window_fullscreen(GTK_WINDOW(appdata->window));
2055 else
2056 gtk_window_unfullscreen(GTK_WINDOW(appdata->window));
2057
2058 handled = TRUE;
2059 }
2060 break;
2061 #endif
2062
2063 #else
2064 case '+':
2065 printf("zoom+\n");
2066 html_zoom(appdata, TRUE);
2067 handled = TRUE;
2068 break;
2069 case '-':
2070 printf("zoom-\n");
2071 html_zoom(appdata, FALSE);
2072 handled = TRUE;
2073 break;
2074 #endif
2075 }
2076
2077 return handled;
2078 }
2079
2080 #if defined(USE_BREAD_CRUMB_TRAIL) || defined(BCT)
2081 typedef struct {
2082 int level;
2083 appdata_t *appdata;
2084 gpointer data;
2085 } crumb_t;
2086
2087 static void
2088 crumb_back(gpointer data) {
2089 crumb_t *crumb = (crumb_t*)data;
2090 printf("crumb_back called with %d\n", crumb->level);
2091
2092 /* don't do anything if main window has already been destroyed */
2093 if(!crumb->appdata->window) {
2094 printf("Main window gone ...\n");
2095 return;
2096 }
2097
2098 /* whatever is being displayed: we don't need it anymore */
2099 gtk_container_remove(GTK_CONTAINER(crumb->appdata->vbox),
2100 crumb->appdata->cur_view);
2101
2102 /* back from cache to cachelist */
2103 if(crumb->level == CRUMB_CACHE) {
2104 gpx_t *gpx = crumb->appdata->search_results;
2105
2106 if(!gpx) {
2107 gtk_widget_set_sensitive(crumb->appdata->menu_search, TRUE);
2108 gtk_widget_set_sensitive(crumb->appdata->menu_export, TRUE);
2109 printf("no search data found, return to gpx\n");
2110 gpx = crumb->appdata->cur_gpx;
2111 } else
2112 printf("returning to search result\n");
2113
2114 g_assert(gpx != NULL);
2115
2116 crumb->appdata->cur_view = cachelist_create(crumb->appdata, gpx,
2117 crumb->appdata->cur_cache);
2118
2119 /* returning from cache view: invalidate cache reference */
2120 crumb->appdata->cur_cache = NULL;
2121
2122 gtk_box_pack_start_defaults(GTK_BOX(crumb->appdata->vbox),
2123 crumb->appdata->cur_view);
2124 }
2125
2126 if(crumb->level == CRUMB_SEARCH_GPX) {
2127 printf("returning from a local search!\n");
2128
2129 g_assert((gpx_t*)crumb->data == crumb->appdata->search_results);
2130
2131 search_result_free((gpx_t*)crumb->data);
2132 crumb->appdata->search_results = NULL;
2133
2134 gtk_widget_set_sensitive(crumb->appdata->menu_search, TRUE);
2135
2136 crumb->appdata->cur_view = cachelist_create(crumb->appdata,
2137 crumb->appdata->cur_gpx, NULL);
2138 gtk_box_pack_start_defaults(GTK_BOX(crumb->appdata->vbox),
2139 crumb->appdata->cur_view);
2140 }
2141
2142 /* back from cachelist to gpxlist */
2143 if((crumb->level == CRUMB_CACHELIST) ||
2144 (crumb->level == CRUMB_SEARCH_GLOBAL)) {
2145
2146 crumb->appdata->cur_view = gpxlist_create_view_and_model(
2147 crumb->appdata, crumb->appdata->cur_gpx);
2148
2149 /* returning from cachelist/global search view: */
2150 /* invalidate gpx reference */
2151 crumb->appdata->cur_gpx = NULL;
2152
2153 gtk_box_pack_start_defaults(GTK_BOX(crumb->appdata->vbox),
2154 crumb->appdata->cur_view);
2155
2156 if((crumb->level == CRUMB_SEARCH_GLOBAL) ||
2157 (crumb->level == CRUMB_SEARCH_GPX)) {
2158 g_assert((gpx_t*)crumb->data == crumb->appdata->search_results);
2159
2160 search_result_free((gpx_t*)crumb->data);
2161 crumb->appdata->search_results = NULL;
2162 }
2163
2164 /* enable gpxlist related menu entries */
2165 gtk_widget_set_sensitive(crumb->appdata->menu_import, TRUE);
2166 gtk_widget_set_sensitive(crumb->appdata->menu_search, TRUE);
2167 gtk_widget_set_sensitive(crumb->appdata->menu_export, TRUE);
2168 }
2169
2170 gtk_widget_show_all(crumb->appdata->vbox);
2171 g_free(data);
2172
2173 #ifdef ENABLE_OSM_GPS_MAP
2174 map_update(crumb->appdata);
2175 #endif
2176 }
2177
2178 static void crumb_add(appdata_t *appdata, char *name, int level,
2179 gpointer user_data) {
2180 crumb_t *crumb = malloc(sizeof(crumb_t));
2181 crumb->level = level;
2182 crumb->appdata = appdata;
2183 crumb->data = user_data;
2184
2185 printf("crumb_add with level %d\n", level);
2186
2187 /* save that we are working on search results */
2188 if((level == CRUMB_SEARCH_GLOBAL) ||
2189 (level == CRUMB_SEARCH_GPX)) {
2190 appdata->search_results = (gpx_t*)user_data;
2191
2192 /* searches cannot be nested */
2193 gtk_widget_set_sensitive(appdata->menu_search, FALSE);
2194 }
2195
2196 /* save "path" pointers in appdata */
2197 if(crumb->level == CRUMB_CACHELIST)
2198 appdata->cur_gpx = (gpx_t*)user_data;
2199
2200 if(crumb->level == CRUMB_CACHE) {
2201 appdata->cur_cache = (cache_t*)user_data;
2202 /* the cache view cannot be searched */
2203 gtk_widget_set_sensitive(appdata->menu_search, FALSE);
2204 gtk_widget_set_sensitive(appdata->menu_export, FALSE);
2205 }
2206
2207 if(level != CRUMB_GPXLIST) {
2208 /* disable gpxlist related menu entries */
2209 gtk_widget_set_sensitive(appdata->menu_import, FALSE);
2210 #ifndef USE_PANNABLE_AREA
2211 gtk_widget_set_sensitive(appdata->menu_remove, FALSE);
2212 gtk_widget_set_sensitive(appdata->menu_close, FALSE);
2213 #endif
2214 }
2215
2216 #ifdef USE_BREAD_CRUMB_TRAIL
2217 hildon_bread_crumb_trail_push_text(HILDON_BREAD_CRUMB_TRAIL(appdata->bct),
2218 name, crumb, (GDestroyNotify)crumb_back);
2219 #else
2220 bct_push_text(appdata->bct, name, crumb, (GDestroyNotify)crumb_back);
2221 #endif
2222
2223 #ifdef ENABLE_OSM_GPS_MAP
2224 map_update(appdata);
2225 #endif
2226 }
2227 #endif // USE_BREAD_CRUMB_TRAIL
2228
2229 void main_after_settings_redraw(appdata_t *appdata, int flags) {
2230 printf("main after settings redraw\n");
2231
2232 if(!appdata->cur_view) {
2233 printf("no active view\n");
2234 return;
2235 }
2236
2237 /* a cache screen cannot be changed from the settings and thus doesn't */
2238 /* need to be redrawn */
2239 if(appdata->cur_cache) {
2240 printf("No redraw in cache view required\n");
2241 return;
2242 }
2243
2244 int redraw = 0; // nothing to redraw
2245
2246 if(appdata->search_results) {
2247 if((appdata->cur_items != appdata->cachelist_items) || flags)
2248 redraw = 1;
2249 } else {
2250 if(!appdata->cur_gpx) {
2251 if(appdata->cur_items != appdata->gpxlist_items)
2252 redraw = 2; // redraw gpxlist
2253 } else {
2254 if((appdata->cur_items != appdata->cachelist_items) || flags)
2255 redraw = 3; // redraw cachelist
2256 }
2257 }
2258
2259 if(redraw) {
2260 GtkWidget *container = appdata->vbox;
2261
2262 #ifdef USE_STACKABLE_WINDOW
2263 HildonWindowStack *stack = hildon_window_stack_get_default();
2264 container = hildon_window_stack_peek(stack);
2265 #endif
2266
2267 gtk_container_remove(GTK_CONTAINER(container), appdata->cur_view);
2268 switch(redraw) {
2269 case 1:
2270 appdata->cur_view = cachelist_create(appdata,
2271 appdata->search_results, NULL);
2272 break;
2273 case 2:
2274 appdata->cur_view = gpxlist_create_view_and_model(appdata, NULL);
2275 break;
2276 case 3:
2277 appdata->cur_view = cachelist_create(appdata,
2278 appdata->cur_gpx, NULL);
2279 break;
2280 }
2281
2282 #ifdef USE_STACKABLE_WINDOW
2283 if(container != appdata->vbox)
2284 gtk_container_add(GTK_CONTAINER(container), appdata->cur_view);
2285 else
2286 #endif
2287 gtk_box_pack_start_defaults(GTK_BOX(container), appdata->cur_view);
2288
2289 gtk_widget_show_all(container);
2290 }
2291 }
2292
2293 int main(int argc, char *argv[]) {
2294 appdata_t appdata;
2295
2296 /* init appdata */
2297 memset(&appdata, 0, sizeof(appdata));
2298
2299 printf("Using locale for %s in %s\n", PACKAGE, LOCALEDIR);
2300
2301 setlocale(LC_ALL, "");
2302 bindtextdomain(PACKAGE, LOCALEDIR);
2303 bind_textdomain_codeset(PACKAGE, "UTF-8");
2304 textdomain(PACKAGE);
2305
2306 /* prepare thread system */
2307 g_thread_init(NULL);
2308
2309 gtk_init (&argc, &argv);
2310
2311 curl_global_init(CURL_GLOBAL_ALL);
2312
2313 #ifdef USE_MAEMO
2314 printf("Installing osso context for \"org.harbaum." APP "\"\n");
2315 appdata.osso_context = osso_initialize("org.harbaum."APP,
2316 VERSION, TRUE, NULL);
2317 if(appdata.osso_context == NULL) {
2318 fprintf(stderr, "error initiating osso context\n");
2319 }
2320
2321 #ifdef ENABLE_MAEMO_MAPPER
2322 dbus_register(&appdata);
2323 #endif
2324 #endif
2325
2326 icons_init();
2327
2328 if(!gnome_vfs_init()) {
2329 g_error("Gnome VFS init failed\n");
2330 }
2331
2332 #ifdef USE_MAEMO
2333 /* Create the hildon program and setup the title */
2334 appdata.program = HILDON_PROGRAM(hildon_program_get_instance());
2335 g_set_application_name("GPXView");
2336
2337 /* Create HildonWindow and set it to HildonProgram */
2338 #ifdef USE_STACKABLE_WINDOW
2339 appdata.window = HILDON_WINDOW(hildon_stackable_window_new());
2340 #else
2341 appdata.window = HILDON_WINDOW(hildon_window_new());
2342 #endif
2343 hildon_program_add_window(appdata.program, appdata.window);
2344 #else
2345 /* Create a Window. */
2346 appdata.window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2347 /* Set a decent default size for the window. */
2348 gtk_window_set_default_size(GTK_WINDOW(appdata.window), 640, 480);
2349 #endif
2350
2351 #if MAEMO_VERSION_MAJOR == 5
2352 gtk_window_set_title(GTK_WINDOW(appdata.window), "GPXView");
2353 #endif
2354
2355 g_signal_connect(G_OBJECT(appdata.window), "destroy",
2356 G_CALLBACK(on_window_destroy), &appdata);
2357
2358 g_signal_connect(G_OBJECT(appdata.window), "key_press_event",
2359 G_CALLBACK(on_window_key_press), &appdata);
2360
2361 /* prepare clipboard */
2362 appdata.clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
2363 gtk_clipboard_set_can_store(appdata.clipboard, NULL, 0);
2364
2365 appdata.vbox = gtk_vbox_new(FALSE, 2);
2366 gtk_container_add(GTK_CONTAINER(appdata.window), appdata.vbox);
2367 #ifndef USE_STACKABLE_WINDOW
2368 menu_create(&appdata);
2369 #else
2370 hildon_window_set_app_menu(HILDON_WINDOW(appdata.window),
2371 menu_create(&appdata, MENU_GPXLIST));
2372 #endif
2373
2374 #ifdef USE_BREAD_CRUMB_TRAIL
2375 appdata.bct = hildon_bread_crumb_trail_new();
2376
2377 gtk_box_pack_start(GTK_BOX(appdata.vbox), appdata.bct, FALSE,FALSE,0);
2378
2379 hildon_bread_crumb_trail_clear(HILDON_BREAD_CRUMB_TRAIL(appdata.bct));
2380 #else
2381 #ifdef BCT
2382 /* on non-hildon machines we use some custom made breadcrumbtrail */
2383 /* replacement */
2384 appdata.bct = bct_new();
2385 gtk_box_pack_start(GTK_BOX(appdata.vbox), appdata.bct, FALSE,FALSE,0);
2386 #endif
2387 #endif
2388
2389 #if defined(USE_BREAD_CRUMB_TRAIL) || defined(BCT)
2390 crumb_add(&appdata, "GPX", CRUMB_GPXLIST, NULL);
2391 #endif
2392
2393 /* wait for main gui to appear */
2394 gtk_widget_show_all(GTK_WIDGET(appdata.window));
2395 while(gtk_events_pending())
2396 gtk_main_iteration();
2397
2398 appdata.gconf_client = gconf_client_get_default();
2399 gconf_load_state(&appdata);
2400 gps_init(&appdata);
2401
2402 appdata.cur_view = gpxlist_create_view_and_model(&appdata, NULL);
2403 gtk_box_pack_start_defaults(GTK_BOX(appdata.vbox), appdata.cur_view);
2404
2405 gtk_widget_show_all(GTK_WIDGET(appdata.window));
2406 gtk_main();
2407
2408 cleanup(&appdata);
2409
2410 return 0;
2411 }