Contents of /trunk/src/project.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 305 - (show annotations)
Wed Oct 7 14:17:52 2009 UTC (14 years, 7 months ago) by harbaum
File MIME type: text/plain
File size: 53742 byte(s)
Fixed startup crash
1 /*
2 * Copyright (C) 2008-2009 Till Harbaum <till@harbaum.org>.
3 *
4 * This file is part of OSM2Go.
5 *
6 * OSM2Go 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 * OSM2Go 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 OSM2Go. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 /*
21 * TODO:
22 */
23
24 #include "appdata.h"
25 #include "banner.h"
26
27 #include <sys/stat.h>
28
29 #include <libxml/parser.h>
30 #include <libxml/tree.h>
31
32 #if !defined(LIBXML_TREE_ENABLED) || !defined(LIBXML_OUTPUT_ENABLED)
33 #error "libxml doesn't support required tree or output"
34 #endif
35
36 /* there shouldn't be a reason to changes the servers url */
37 #undef SERVER_EDITABLE
38
39 typedef struct {
40 project_t *project;
41 settings_t *settings;
42 GtkWidget *dialog, *fsize, *diff_stat, *diff_remove;
43 GtkWidget *desc, *download;
44 GtkWidget *minlat, *minlon, *maxlat, *maxlon;
45 gboolean is_new;
46 #ifdef SERVER_EDITABLE
47 GtkWidget *server;
48 #endif
49 area_edit_t area_edit;
50 } project_context_t;
51
52 static gboolean project_edit(appdata_t *appdata, GtkWidget *parent,
53 settings_t *settings, project_t *project,
54 gboolean is_new);
55
56
57 /* ------------ project file io ------------- */
58
59 static gboolean project_read(appdata_t *appdata,
60 char *project_file, project_t *project) {
61
62 LIBXML_TEST_VERSION;
63
64 xmlDoc *doc = NULL;
65 xmlNode *root_element = NULL;
66
67 /* parse the file and get the DOM */
68 if((doc = xmlReadFile(project_file, NULL, 0)) == NULL) {
69 printf("error: could not parse file %s\n", project_file);
70 return FALSE;
71 }
72
73 /* Get the root element node */
74 root_element = xmlDocGetRootElement(doc);
75
76 xmlNode *cur_node = NULL;
77 for (cur_node = root_element; cur_node; cur_node = cur_node->next) {
78 if (cur_node->type == XML_ELEMENT_NODE) {
79 if(strcasecmp((char*)cur_node->name, "proj") == 0) {
80 char *str;
81
82 if((str = (char*)xmlGetProp(cur_node, BAD_CAST "dirty"))) {
83 project->data_dirty = (strcasecmp(str, "true") == 0);
84 xmlFree(str);
85 } else
86 project->data_dirty = FALSE;
87
88 xmlNode *node = cur_node->children;
89
90 while(node != NULL) {
91 if(node->type == XML_ELEMENT_NODE) {
92
93 if(strcasecmp((char*)node->name, "desc") == 0) {
94 str = (char*)xmlNodeListGetString(doc, node->children, 1);
95 project->desc = g_strdup(str);
96 printf("desc = %s\n", project->desc);
97 xmlFree(str);
98
99 } else if(strcasecmp((char*)node->name, "server") == 0) {
100 str = (char*)xmlNodeListGetString(doc, node->children, 1);
101 project->server = g_strdup(str);
102 printf("server = %s\n", project->server);
103 xmlFree(str);
104
105 } else if(project->map_state &&
106 strcasecmp((char*)node->name, "map") == 0) {
107
108 if((str = (char*)xmlGetProp(node, BAD_CAST "zoom"))) {
109 project->map_state->zoom = g_ascii_strtod(str, NULL);
110 xmlFree(str);
111 }
112 if((str = (char*)xmlGetProp(node, BAD_CAST "detail"))) {
113 project->map_state->detail = g_ascii_strtod(str, NULL);
114 xmlFree(str);
115 }
116 if((str = (char*)xmlGetProp(node, BAD_CAST "scroll-offset-x"))) {
117 project->map_state->scroll_offset.x = strtoul(str, NULL, 10);
118 xmlFree(str);
119 }
120 if((str = (char*)xmlGetProp(node, BAD_CAST "scroll-offset-y"))) {
121 project->map_state->scroll_offset.y = strtoul(str, NULL, 10);
122 xmlFree(str);
123 }
124
125 } else if(strcasecmp((char*)node->name, "wms") == 0) {
126
127 if((str = (char*)xmlGetProp(node, BAD_CAST "server"))) {
128 project->wms_server = g_strdup(str);
129 xmlFree(str);
130 }
131 if((str = (char*)xmlGetProp(node, BAD_CAST "path"))) {
132 project->wms_path = g_strdup(str);
133 xmlFree(str);
134 }
135 if((str = (char*)xmlGetProp(node, BAD_CAST "x-offset"))) {
136 project->wms_offset.x = strtoul(str, NULL, 10);
137 xmlFree(str);
138 }
139 if((str = (char*)xmlGetProp(node, BAD_CAST "y-offset"))) {
140 project->wms_offset.y = strtoul(str, NULL, 10);
141 xmlFree(str);
142 }
143
144 } else if(strcasecmp((char*)node->name, "osm") == 0) {
145 str = (char*)xmlNodeListGetString(doc, node->children, 1);
146 printf("osm = %s\n", str);
147
148 /* make this a relative path if possible */
149 /* if the project path actually is a prefix of this, */
150 /* then just remove this prefix */
151 if((str[0] == '/') &&
152 (strlen(str) > strlen(project->path)) &&
153 !strncmp(str, project->path, strlen(project->path))) {
154
155 project->osm = g_strdup(str + strlen(project->path));
156 printf("osm name converted to relative %s\n", project->osm);
157 } else
158 project->osm = g_strdup(str);
159
160 xmlFree(str);
161
162 } else if(strcasecmp((char*)node->name, "min") == 0) {
163 if((str = (char*)xmlGetProp(node, BAD_CAST "lat"))) {
164 project->min.lat = g_ascii_strtod(str, NULL);
165 xmlFree(str);
166 }
167 if((str = (char*)xmlGetProp(node, BAD_CAST "lon"))) {
168 project->min.lon = g_ascii_strtod(str, NULL);
169 xmlFree(str);
170 }
171
172 } else if(strcasecmp((char*)node->name, "max") == 0) {
173 if((str = (char*)xmlGetProp(node, BAD_CAST "lat"))) {
174 project->max.lat = g_ascii_strtod(str, NULL);
175 xmlFree(str);
176 }
177 if((str = (char*)xmlGetProp(node, BAD_CAST "lon"))) {
178 project->max.lon = g_ascii_strtod(str, NULL);
179 xmlFree(str);
180 }
181 }
182 }
183 node = node->next;
184 }
185 }
186 }
187 }
188
189 xmlFreeDoc(doc);
190 xmlCleanupParser();
191
192 return TRUE;
193 }
194
195 gboolean project_save(GtkWidget *parent, project_t *project) {
196 char str[32];
197 char *project_file = g_strdup_printf("%s%s.proj",
198 project->path, project->name);
199
200 printf("saving project to %s\n", project_file);
201
202 /* check if project path exists */
203 if(!g_file_test(project->path, G_FILE_TEST_IS_DIR)) {
204 /* make sure project base path exists */
205 if(g_mkdir_with_parents(project->path, S_IRWXU) != 0) {
206 errorf(GTK_WIDGET(parent),
207 _("Unable to create project path %s"), project->path);
208 return FALSE;
209 }
210 }
211
212 LIBXML_TEST_VERSION;
213
214 xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0");
215 xmlNodePtr node, root_node = xmlNewNode(NULL, BAD_CAST "proj");
216 xmlNewProp(root_node, BAD_CAST "name", BAD_CAST project->name);
217 if(project->data_dirty)
218 xmlNewProp(root_node, BAD_CAST "dirty", BAD_CAST "true");
219
220 xmlDocSetRootElement(doc, root_node);
221
222 if(project->server)
223 xmlNewChild(root_node, NULL, BAD_CAST "server",
224 BAD_CAST project->server);
225
226 if(project->desc)
227 xmlNewChild(root_node, NULL, BAD_CAST "desc", BAD_CAST project->desc);
228
229 xmlNewChild(root_node, NULL, BAD_CAST "osm", BAD_CAST project->osm);
230
231 node = xmlNewChild(root_node, NULL, BAD_CAST "min", NULL);
232 g_ascii_formatd(str, sizeof(str), LL_FORMAT, project->min.lat);
233 xmlNewProp(node, BAD_CAST "lat", BAD_CAST str);
234 g_ascii_formatd(str, sizeof(str), LL_FORMAT, project->min.lon);
235 xmlNewProp(node, BAD_CAST "lon", BAD_CAST str);
236
237 node = xmlNewChild(root_node, NULL, BAD_CAST "max", NULL);
238 g_ascii_formatd(str, sizeof(str), LL_FORMAT, project->max.lat);
239 xmlNewProp(node, BAD_CAST "lat", BAD_CAST str);
240 g_ascii_formatd(str, sizeof(str), LL_FORMAT, project->max.lon);
241 xmlNewProp(node, BAD_CAST "lon", BAD_CAST str);
242
243 if(project->map_state) {
244 node = xmlNewChild(root_node, NULL, BAD_CAST "map", BAD_CAST NULL);
245 g_ascii_formatd(str, sizeof(str), "%.04f", project->map_state->zoom);
246 xmlNewProp(node, BAD_CAST "zoom", BAD_CAST str);
247 g_ascii_formatd(str, sizeof(str), "%.04f", project->map_state->detail);
248 xmlNewProp(node, BAD_CAST "detail", BAD_CAST str);
249 snprintf(str, sizeof(str), "%d", project->map_state->scroll_offset.x);
250 xmlNewProp(node, BAD_CAST "scroll-offset-x", BAD_CAST str);
251 snprintf(str, sizeof(str), "%d", project->map_state->scroll_offset.y);
252 xmlNewProp(node, BAD_CAST "scroll-offset-y", BAD_CAST str);
253 }
254
255 node = xmlNewChild(root_node, NULL, BAD_CAST "wms", NULL);
256 if(project->wms_server)
257 xmlNewProp(node, BAD_CAST "server", BAD_CAST project->wms_server);
258 if(project->wms_path)
259 xmlNewProp(node, BAD_CAST "path", BAD_CAST project->wms_path);
260 snprintf(str, sizeof(str), "%d", project->wms_offset.x);
261 xmlNewProp(node, BAD_CAST "x-offset", BAD_CAST str);
262 snprintf(str, sizeof(str), "%d", project->wms_offset.y);
263 xmlNewProp(node, BAD_CAST "y-offset", BAD_CAST str);
264
265 xmlSaveFormatFileEnc(project_file, doc, "UTF-8", 1);
266 xmlFreeDoc(doc);
267 xmlCleanupParser();
268
269 g_free(project_file);
270
271 return TRUE;
272 }
273
274 /* ------------ freeing projects --------------------- */
275
276 void project_free(project_t *project) {
277 if(!project) return;
278
279 if(project->name) g_free(project->name);
280 if(project->desc) g_free(project->desc);
281 if(project->server) g_free(project->server);
282
283 if(project->wms_server) g_free(project->wms_server);
284 if(project->wms_path) g_free(project->wms_path);
285
286 if(project->path) g_free(project->path);
287 if(project->osm) g_free(project->osm);
288
289 map_state_free(project->map_state);
290
291 g_free(project);
292 }
293
294 /* ------------ project selection dialog ------------- */
295
296 static char *project_fullname(settings_t *settings, const char *name) {
297 return g_strdup_printf("%s%s/%s.proj", settings->base_path, name, name);
298 }
299
300 gboolean project_exists(settings_t *settings, const char *name) {
301 gboolean ok = FALSE;
302 char *fulldir = g_strdup_printf("%s%s", settings->base_path, name);
303
304 if(g_file_test(fulldir, G_FILE_TEST_IS_DIR)) {
305
306 /* check for project file */
307 char *fullname = project_fullname(settings, name);
308
309 if(g_file_test(fullname, G_FILE_TEST_IS_REGULAR))
310 ok = TRUE;
311
312 g_free(fullname);
313 }
314 g_free(fulldir);
315
316 return ok;
317 }
318
319 static project_t *project_scan(appdata_t *appdata) {
320 project_t *projects = NULL, **current = &projects;
321
322 /* scan for projects */
323 GDir *dir = g_dir_open(appdata->settings->base_path, 0, NULL);
324 const char *name = NULL;
325 do {
326 if((name = g_dir_read_name(dir))) {
327 if(project_exists(appdata->settings, name)) {
328 printf("found project %s\n", name);
329
330 /* try to read project and append it to chain */
331 *current = g_new0(project_t, 1);
332 (*current)->name = g_strdup(name);
333 (*current)->path = g_strdup_printf("%s%s/",
334 appdata->settings->base_path, name);
335
336 char *fullname = project_fullname(appdata->settings, name);
337 if(project_read(appdata, fullname, *current))
338 current = &((*current)->next);
339 else {
340 g_free(*current);
341 *current = NULL;
342 }
343 g_free(fullname);
344 }
345 }
346 } while(name);
347
348 g_dir_close(dir);
349
350 return projects;
351 }
352
353 typedef struct {
354 appdata_t *appdata;
355 project_t *project;
356 GtkWidget *dialog, *list;
357 settings_t *settings;
358 } select_context_t;
359
360 enum {
361 PROJECT_COL_NAME = 0,
362 PROJECT_COL_STATUS,
363 PROJECT_COL_DESCRIPTION,
364 PROJECT_COL_DATA,
365 PROJECT_NUM_COLS
366 };
367
368 static gboolean osm_file_exists(char *path, char *name) {
369 gboolean exists = FALSE;
370
371 if(name[0] == '/')
372 exists = g_file_test(name, G_FILE_TEST_IS_REGULAR);
373 else {
374 char *full = g_strjoin(NULL, path, name, NULL);
375 exists = g_file_test(full, G_FILE_TEST_IS_REGULAR);
376 g_free(full);
377 }
378 return exists;
379 }
380
381 static void view_selected(select_context_t *context, project_t *project) {
382 /* check if the selected project also has a valid osm file */
383 gtk_dialog_set_response_sensitive(GTK_DIALOG(context->dialog),
384 GTK_RESPONSE_ACCEPT,
385 project && osm_file_exists(project->path, project->osm));
386 }
387
388 static gboolean
389 view_selection_func(GtkTreeSelection *selection, GtkTreeModel *model,
390 GtkTreePath *path, gboolean path_currently_selected,
391 gpointer userdata) {
392 select_context_t *context = (select_context_t*)userdata;
393 GtkTreeIter iter;
394
395 if(gtk_tree_model_get_iter(model, &iter, path)) {
396 project_t *project = NULL;
397 gtk_tree_model_get(model, &iter, PROJECT_COL_DATA, &project, -1);
398 g_assert(gtk_tree_path_get_depth(path) == 1);
399
400 view_selected(context, project);
401 }
402
403 return TRUE; /* allow selection state to change */
404 }
405
406 /* get the currently selected project in the list, NULL if none */
407 static project_t *project_get_selected(GtkWidget *list) {
408 project_t *project = NULL;
409 GtkTreeModel *model;
410 GtkTreeIter iter;
411
412 g_assert(list_get_selected(list, &model, &iter));
413 gtk_tree_model_get(model, &iter, PROJECT_COL_DATA, &project, -1);
414
415 return project;
416 }
417
418 /* ------------------------- create a new project ---------------------- */
419
420 /* returns true of str contains one of the characters in chars */
421 static gboolean strchrs(char *str, char *chars) {
422 while(*chars) {
423 char *p = str;
424 while(*p) {
425 if(*p == *chars)
426 return TRUE;
427
428 p++;
429 }
430 chars++;
431 }
432 return FALSE;
433 }
434
435 typedef struct {
436 GtkWidget *dialog;
437 settings_t *settings;
438 } name_callback_context_t;
439
440 static void callback_modified_name(GtkWidget *widget, gpointer data) {
441 name_callback_context_t *context = (name_callback_context_t*)data;
442
443 char *name = (char*)gtk_entry_get_text(GTK_ENTRY(widget));
444
445 /* name must not contain some special chars */
446 gboolean ok = FALSE;
447
448 /* check if there's a name */
449 if(name && strlen(name) > 0) {
450 /* check if it consists of valid characters */
451 if(!strchrs(name, "\\*?()\n\t\r")) {
452 /* check if such a project already exists */
453 if(!project_exists(context->settings, name))
454 ok = TRUE;
455 }
456 }
457
458 gtk_dialog_set_response_sensitive(GTK_DIALOG(context->dialog),
459 GTK_RESPONSE_ACCEPT, ok);
460 }
461
462
463 gboolean project_delete(select_context_t *context, project_t *project) {
464
465 printf("deleting project \"%s\"\n", project->name);
466
467 /* check if we are to delete the currently open project */
468 if(context->appdata->project &&
469 !strcmp(context->appdata->project->name, project->name)) {
470
471 if(!yes_no_f(context->dialog, NULL, 0, 0,
472 _("Delete current project?"),
473 _("The project you are about to delete is the one "
474 "you are currently working on!\n\n"
475 "Do you want to delete it anyway?")))
476 return FALSE;
477
478 project_close(context->appdata);
479 }
480
481 /* remove entire directory from disk */
482 GDir *dir = g_dir_open(project->path, 0, NULL);
483 const char *name = NULL;
484 do {
485 if((name = g_dir_read_name(dir))) {
486 char *fullname = g_strdup_printf("%s/%s", project->path, name);
487 g_remove(fullname);
488 g_free(fullname);
489 }
490 } while(name);
491
492 /* remove the projects directory */
493 g_remove(project->path);
494
495 /* remove from view */
496 GtkTreeIter iter;
497 GtkTreeModel *model = list_get_model(context->list);
498 gboolean deleted = FALSE;
499 if(gtk_tree_model_get_iter_first(model, &iter)) {
500 do {
501 project_t *prj = NULL;
502 gtk_tree_model_get(model, &iter, PROJECT_COL_DATA, &prj, -1);
503 if(prj && (prj == project)) {
504 printf("found %s to remove\n", prj->name);
505 /* and remove from store */
506 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
507 deleted = TRUE;
508 }
509 } while(!deleted && gtk_tree_model_iter_next(model, &iter));
510 }
511
512 /* de-chain entry from project list */
513 project_t **project_list = &context->project;
514 while(*project_list) {
515 if(*project_list == project)
516 *project_list = (*project_list)->next;
517 else
518 project_list = &((*project_list)->next);
519 }
520
521 /* free project structure */
522 project_free(project);
523
524 /* disable ok button button */
525 view_selected(context, NULL);
526
527 return TRUE;
528 }
529
530 project_t *project_new(select_context_t *context) {
531 printf("creating project with default values\n");
532
533 /* -------------- first choose a name for the project --------------- */
534 GtkWidget *dialog =
535 misc_dialog_new(MISC_DIALOG_NOSIZE, _("Project name"),
536 GTK_WINDOW(context->dialog),
537 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
538 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
539 NULL);
540
541 GtkWidget *hbox = gtk_hbox_new(FALSE, 8);
542 gtk_box_pack_start_defaults(GTK_BOX(hbox), gtk_label_new(_("Name:")));
543
544 name_callback_context_t name_context = { dialog, context->settings };
545 GtkWidget *entry = gtk_entry_new();
546 gtk_box_pack_start_defaults(GTK_BOX(hbox), entry);
547 g_signal_connect(G_OBJECT(entry), "changed",
548 G_CALLBACK(callback_modified_name), &name_context);
549
550 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox);
551
552 /* don't allow user to click ok until a valid area has been specified */
553 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
554 GTK_RESPONSE_ACCEPT, FALSE);
555
556 gtk_widget_show_all(dialog);
557 if(GTK_RESPONSE_ACCEPT != gtk_dialog_run(GTK_DIALOG(dialog))) {
558 gtk_widget_destroy(dialog);
559 return NULL;
560 }
561
562 project_t *project = g_new0(project_t, 1);
563 project->name = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
564 gtk_widget_destroy(dialog);
565
566
567 project->path = g_strdup_printf("%s%s/",
568 context->settings->base_path, project->name);
569 project->desc = NULL;
570
571 /* no data downloaded yet */
572 project->data_dirty = TRUE;
573
574 /* adjust default server stored in settings if required */
575 if(strstr(context->settings->server, "0.5") != NULL) {
576 strstr(context->settings->server, "0.5")[2] = '6';
577 printf("adjusting server path in settings to 0.6\n");
578 }
579
580 /* use global server/access settings */
581 project->server = g_strdup(context->settings->server);
582
583 /* build project osm file name */
584 project->osm = g_strdup_printf("%s.osm", project->name);
585
586 /* around the castle in karlsruhe, germany ... */
587 project->min.lat = NAN; project->min.lon = NAN;
588 project->max.lat = NAN; project->max.lon = NAN;
589
590 /* create project file on disk */
591 project_save(context->dialog, project);
592
593 if(!project_edit(context->appdata, context->dialog,
594 context->settings, project, TRUE)) {
595 printf("new/edit cancelled!!\n");
596
597 project_delete(context, project);
598
599 project = NULL;
600 }
601
602 /* enable/disable edit/remove buttons */
603 view_selected(context, project);
604
605 return project;
606 }
607
608 // predecs
609 static void project_get_status_icon_stock_id(select_context_t *context, project_t *project, gchar **stock_id);
610
611 static void on_project_new(GtkButton *button, gpointer data) {
612 select_context_t *context = (select_context_t*)data;
613 project_t **project = &context->project;
614 *project = project_new(context);
615 if(*project) {
616
617 GtkTreeModel *model = list_get_model(context->list);
618
619 GtkTreeIter iter;
620 gchar *status_stock_id = NULL;
621 project_get_status_icon_stock_id(context, *project, &status_stock_id);
622 gtk_list_store_append(GTK_LIST_STORE(model), &iter);
623 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
624 PROJECT_COL_NAME, (*project)->name,
625 PROJECT_COL_STATUS, status_stock_id,
626 PROJECT_COL_DESCRIPTION, (*project)->desc,
627 PROJECT_COL_DATA, *project,
628 -1);
629
630 GtkTreeSelection *selection = list_get_selection(context->list);
631 gtk_tree_selection_select_iter(selection, &iter);
632 }
633 }
634
635 static void on_project_delete(GtkButton *button, gpointer data) {
636 select_context_t *context = (select_context_t*)data;
637 project_t *project = project_get_selected(context->list);
638
639 char *str = g_strdup_printf(_("Do you really want to delete the "
640 "project \"%s\"?"), project->name);
641 GtkWidget *dialog = gtk_message_dialog_new(
642 GTK_WINDOW(context->dialog),
643 GTK_DIALOG_DESTROY_WITH_PARENT,
644 GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, str);
645 g_free(str);
646
647 gtk_window_set_title(GTK_WINDOW(dialog), _("Delete project?"));
648
649 /* set the active flag again if the user answered "no" */
650 if(GTK_RESPONSE_NO == gtk_dialog_run(GTK_DIALOG(dialog))) {
651 gtk_widget_destroy(dialog);
652 return;
653 }
654
655 gtk_widget_destroy(dialog);
656
657 if(!project_delete(context, project))
658 printf("unable to delete project\n");
659 }
660
661 static void on_project_edit(GtkButton *button, gpointer data) {
662 select_context_t *context = (select_context_t*)data;
663 project_t *project = project_get_selected(context->list);
664 g_assert(project);
665
666 if(project_edit(context->appdata, context->dialog,
667 context->settings, project, FALSE)) {
668 GtkTreeModel *model;
669 GtkTreeIter iter;
670
671 /* description etc. may have changed, so update list */
672 GtkTreeSelection *selection = list_get_selection(context->list);
673 g_assert(gtk_tree_selection_get_selected(selection, &model, &iter));
674
675 // gtk_tree_model_get(model, &iter, PROJECT_COL_DATA, &project, -1);
676 gchar *status_stock_id = NULL;
677 project_get_status_icon_stock_id(context, project, &status_stock_id);
678 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
679 PROJECT_COL_NAME, project->name,
680 PROJECT_COL_STATUS, status_stock_id,
681 PROJECT_COL_DESCRIPTION, project->desc,
682 -1);
683
684
685 /* check if we have actually editing the currently open project */
686 if(context->appdata->project &&
687 !strcmp(context->appdata->project->name, project->name)) {
688 project_t *cur = context->appdata->project;
689
690 printf("edited project was actually the active one!\n");
691
692 /* update the currently active project also */
693
694 /* update description */
695 if(cur->desc) { free(cur->desc); cur->desc = NULL; }
696 if(project->desc) cur->desc = g_strdup(project->desc);
697
698 /* update server */
699 if(cur->server) { free(cur->server); cur->server = NULL; }
700 if(project->server) cur->server = g_strdup(project->server);
701
702 /* update coordinates */
703 if((cur->min.lat != project->min.lat) ||
704 (cur->max.lat != project->max.lat) ||
705 (cur->min.lon != project->min.lon) ||
706 (cur->max.lon != project->max.lon)) {
707 appdata_t *appdata = context->appdata;
708
709 /* save modified coordinates */
710 cur->min.lat = project->min.lat;
711 cur->max.lat = project->max.lat;
712 cur->min.lon = project->min.lon;
713 cur->max.lon = project->max.lon;
714
715 /* try to do this automatically */
716
717 /* if we have valid osm data loaded: save state first */
718 if(appdata->osm) {
719 /* redraw the entire map by destroying all map items */
720 diff_save(appdata->project, appdata->osm);
721 map_clear(appdata, MAP_LAYER_ALL);
722
723 osm_free(&appdata->icon, appdata->osm);
724 appdata->osm = NULL;
725 }
726
727 /* and load the (hopefully) new file */
728 appdata->osm = osm_parse(appdata->project->path,
729 appdata->project->osm);
730 diff_restore(appdata, appdata->project, appdata->osm);
731 map_paint(appdata);
732
733 main_ui_enable(appdata);
734 }
735 }
736 }
737
738 /* enable/disable edit/remove buttons */
739 view_selected(context, project);
740 }
741
742
743 gboolean project_osm_present(project_t *project) {
744 char *osm_name = g_strdup_printf("%s/%s.osm", project->path, project->name);
745 gboolean is_present = g_file_test(osm_name, G_FILE_TEST_EXISTS);
746 g_free(osm_name);
747 return is_present;
748 }
749
750 static void
751 project_get_status_icon_stock_id(select_context_t *context,
752 project_t *project, gchar **stock_id) {
753
754 appdata_t *appdata = context->appdata;
755
756 /* is this the currently open project? */
757 if(appdata->project &&
758 !strcmp(appdata->project->name, project->name))
759 *stock_id = GTK_STOCK_OPEN;
760 else if(!project_osm_present(project))
761 *stock_id = GTK_STOCK_DIALOG_WARNING;
762 else if(diff_present(project))
763 *stock_id = GTK_STOCK_PROPERTIES;
764 else
765 *stock_id = GTK_STOCK_FILE;
766
767 // TODO: check for outdatedness too. Which icon to use?
768 }
769
770 static GtkWidget *project_list_widget(select_context_t *context) {
771 context->list = list_new(LIST_HILDON_WITHOUT_HEADERS);
772
773 list_set_selection_function(context->list, view_selection_func, context);
774
775 list_set_columns(context->list,
776 _("Name"), PROJECT_COL_NAME, 0,
777 _("State"), PROJECT_COL_STATUS, LIST_FLAG_STOCK_ICON,
778 _("Description"), PROJECT_COL_DESCRIPTION, LIST_FLAG_ELLIPSIZE,
779 NULL);
780
781
782 /* build the store */
783 GtkListStore *store = gtk_list_store_new(PROJECT_NUM_COLS,
784 G_TYPE_STRING, // name
785 G_TYPE_STRING, // status
786 G_TYPE_STRING, // desc
787 G_TYPE_POINTER); // data
788
789 GtkTreeIter iter;
790 project_t *project = context->project;
791 while(project) {
792 gchar *status_stock_id = NULL;
793 project_get_status_icon_stock_id(context, project, &status_stock_id);
794 /* Append a row and fill in some data */
795 gtk_list_store_append(store, &iter);
796 gtk_list_store_set(store, &iter,
797 PROJECT_COL_NAME, project->name,
798 PROJECT_COL_STATUS, status_stock_id,
799 PROJECT_COL_DESCRIPTION, project->desc,
800 PROJECT_COL_DATA, project,
801 -1);
802 project = project->next;
803 }
804
805 list_set_store(context->list, store);
806 g_object_unref(store);
807
808 list_set_static_buttons(context->list, LIST_BTN_NEW | LIST_BTN_BIG,
809 G_CALLBACK(on_project_new), G_CALLBACK(on_project_edit),
810 G_CALLBACK(on_project_delete), context);
811
812 return context->list;
813 }
814
815 static char *project_select(appdata_t *appdata) {
816 char *name = NULL;
817
818 select_context_t *context = g_new0(select_context_t, 1);
819 context->appdata = appdata;
820 context->settings = appdata->settings;
821 context->project = project_scan(appdata);
822
823 /* create project selection dialog */
824 context->dialog =
825 misc_dialog_new(MISC_DIALOG_MEDIUM,_("Project selection"),
826 GTK_WINDOW(appdata->window),
827 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
828 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
829 NULL);
830
831 /* under fremantle the dialog does not have an "Open" button */
832 /* as it's closed when a project is being selected */
833 gtk_dialog_set_default_response(GTK_DIALOG(context->dialog),
834 GTK_RESPONSE_ACCEPT);
835
836 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(context->dialog)->vbox),
837 project_list_widget(context));
838
839 /* don't all user to click ok until something is selected */
840 gtk_dialog_set_response_sensitive(GTK_DIALOG(context->dialog),
841 GTK_RESPONSE_ACCEPT, FALSE);
842
843 gtk_widget_show_all(context->dialog);
844 if(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(context->dialog)))
845 name = g_strdup(project_get_selected(context->list)->name);
846
847 gtk_widget_destroy(context->dialog);
848
849 /* free all entries */
850 project_t *project = context->project;
851 while(project) {
852 project_t *next = project->next;
853 project_free(project);
854 project = next;
855 }
856
857 g_free(context);
858
859 return name;
860 }
861
862 /* ---------------------------------------------------- */
863
864 /* return file length or -1 on error */
865 static gsize file_length(char *path, char *name) {
866 char *str = NULL;
867
868 if(name[0] == '/') str = g_strdup(name);
869 else str = g_strjoin(NULL, path, name, NULL);
870
871 GMappedFile *gmap = g_mapped_file_new(str, FALSE, NULL);
872 g_free(str);
873
874 if(!gmap) return -1;
875 gsize size = g_mapped_file_get_length(gmap);
876 g_mapped_file_free(gmap);
877 return size;
878 }
879
880 void project_filesize(project_context_t *context) {
881 char *str = NULL;
882
883 printf("Checking size of %s\n", context->project->osm);
884
885 if(!osm_file_exists(context->project->path, context->project->osm)) {
886 GdkColor color;
887 gdk_color_parse("red", &color);
888 gtk_widget_modify_fg(context->fsize, GTK_STATE_NORMAL, &color);
889
890 str = g_strdup(_("Not downloaded!"));
891
892 gtk_dialog_set_response_sensitive(GTK_DIALOG(context->dialog),
893 GTK_RESPONSE_ACCEPT, !context->is_new);
894
895 } else {
896 gtk_widget_modify_fg(context->fsize, GTK_STATE_NORMAL, NULL);
897
898 if(!context->project->data_dirty)
899 str = g_strdup_printf(_("%d bytes present"),
900 file_length(context->project->path,
901 context->project->osm));
902 else
903 str = g_strdup_printf(_("Outdated, please download!"));
904
905 gtk_dialog_set_response_sensitive(GTK_DIALOG(context->dialog),
906 GTK_RESPONSE_ACCEPT, !context->is_new ||
907 !context->project->data_dirty);
908 }
909
910 if(str) {
911 gtk_label_set_text(GTK_LABEL(context->fsize), str);
912 g_free(str);
913 }
914 }
915
916 /* a project may currently be open. "unsaved changes" then also */
917 /* means that the user may have unsaved changes */
918 static gboolean project_active_n_dirty(project_context_t *context) {
919
920 if(!context->area_edit.appdata->osm) return FALSE;
921
922 if(context->area_edit.appdata->project &&
923 !strcmp(context->area_edit.appdata->project->name,
924 context->project->name)) {
925
926 printf("editing the currently open project\n");
927
928 return(!diff_is_clean(context->area_edit.appdata->osm, TRUE));
929 }
930
931 return FALSE;
932 }
933
934 void project_diffstat(project_context_t *context) {
935 char *str = NULL;
936
937 if(diff_present(context->project) || project_active_n_dirty(context)) {
938 /* this should prevent the user from changing the area */
939 str = g_strdup(_("unsaved changes pending"));
940 } else
941 str = g_strdup(_("no pending changes"));
942
943 gtk_label_set_text(GTK_LABEL(context->diff_stat), str);
944 g_free(str);
945 }
946
947 static gboolean
948 project_pos_is_valid(project_t *project) {
949 return(!isnan(project->min.lat) &&
950 !isnan(project->min.lon) &&
951 !isnan(project->max.lat) &&
952 !isnan(project->max.lon));
953 }
954
955 static void on_edit_clicked(GtkButton *button, gpointer data) {
956 project_context_t *context = (project_context_t*)data;
957
958 if(diff_present(context->project) || project_active_n_dirty(context))
959 messagef(context->dialog,
960 _("Pending changes"),
961 _("You have pending changes in this project.\n\n"
962 "Changing the area may cause pending changes to be "
963 "lost if they are outside the updated area."));
964
965 if(area_edit(&context->area_edit)) {
966 printf("coordinates changed!!\n");
967
968 pos_lon_label_set(context->minlat, context->project->min.lat);
969 pos_lon_label_set(context->minlon, context->project->min.lon);
970 pos_lon_label_set(context->maxlat, context->project->max.lat);
971 pos_lon_label_set(context->maxlon, context->project->max.lon);
972
973 gboolean pos_valid = project_pos_is_valid(context->project);
974 gtk_widget_set_sensitive(context->download, pos_valid);
975
976 /* (re-) download area */
977 if (pos_valid)
978 {
979 if(osm_download(GTK_WIDGET(context->dialog),
980 context->area_edit.appdata->settings, context->project))
981 context->project->data_dirty = FALSE;
982 }
983 project_filesize(context);
984 }
985 }
986
987 static void on_download_clicked(GtkButton *button, gpointer data) {
988 project_context_t *context = (project_context_t*)data;
989
990 printf("download %s\n", context->project->osm);
991
992 if(osm_download(context->dialog, context->settings, context->project))
993 context->project->data_dirty = FALSE;
994 else
995 printf("download failed\n");
996
997 project_filesize(context);
998 }
999
1000 static void on_diff_remove_clicked(GtkButton *button, gpointer data) {
1001 project_context_t *context = (project_context_t*)data;
1002
1003 printf("clicked diff remove\n");
1004
1005 GtkWidget *dialog = gtk_message_dialog_new(
1006 GTK_WINDOW(context->dialog),
1007 GTK_DIALOG_DESTROY_WITH_PARENT,
1008 GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
1009 _("Do you really want to discard your changes? This "
1010 "permanently undo all changes you've made so far and which "
1011 "you didn't upload yet."));
1012
1013 gtk_window_set_title(GTK_WINDOW(dialog), _("Discard changes?"));
1014
1015 if(GTK_RESPONSE_YES == gtk_dialog_run(GTK_DIALOG(dialog))) {
1016 appdata_t *appdata = context->area_edit.appdata;
1017
1018 diff_remove(context->project);
1019
1020 /* if this is the currently open project, we need to undo */
1021 /* the map changes as well */
1022
1023 if(appdata->project &&
1024 !strcmp(appdata->project->name, context->project->name)) {
1025
1026 printf("undo all on current project: delete map changes as well\n");
1027
1028 /* just reload the map */
1029 map_clear(appdata, MAP_LAYER_OBJECTS_ONLY);
1030 osm_free(&appdata->icon, appdata->osm);
1031 appdata->osm = osm_parse(appdata->project->path, appdata->project->osm);
1032 map_paint(appdata);
1033 }
1034
1035 /* update button/label state */
1036 project_diffstat(context);
1037 gtk_widget_set_sensitive(context->diff_remove, FALSE);
1038 }
1039
1040 gtk_widget_destroy(dialog);
1041 }
1042
1043 gboolean project_check_demo(GtkWidget *parent, project_t *project) {
1044 if(!project->server)
1045 messagef(parent, "Demo project",
1046 "This is a preinstalled demo project. This means that the "
1047 "basic project parameters cannot be changed and no data can "
1048 "be up- or downloaded via the OSM servers.\n\n"
1049 "Please setup a new project to do these things.");
1050
1051 return !project->server;
1052 }
1053
1054 /* create a left aligned label (normal ones are centered) */
1055 static GtkWidget *gtk_label_left_new(char *str) {
1056 GtkWidget *label = gtk_label_new(str);
1057 gtk_misc_set_alignment(GTK_MISC(label), 0.f, .5f);
1058 return label;
1059 }
1060
1061 static gboolean
1062 project_edit(appdata_t *appdata, GtkWidget *parent, settings_t *settings,
1063 project_t *project, gboolean is_new) {
1064 gboolean ok = FALSE;
1065
1066 if(project_check_demo(parent, project))
1067 return ok;
1068
1069 /* ------------ project edit dialog ------------- */
1070
1071 project_context_t *context = g_new0(project_context_t, 1);
1072 context->project = project;
1073 context->area_edit.settings = context->settings = settings;
1074 context->area_edit.appdata = appdata;
1075 context->is_new = is_new;
1076 context->area_edit.min = &project->min;
1077 context->area_edit.max = &project->max;
1078
1079 /* cancel is enabled for "new" projects only */
1080 if(is_new) {
1081 char *str = g_strdup_printf(_("New project - %s"), project->name);
1082
1083 context->area_edit.parent =
1084 context->dialog = misc_dialog_new(MISC_DIALOG_WIDE, str,
1085 GTK_WINDOW(parent),
1086 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1087 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
1088 g_free(str);
1089 } else {
1090 char *str = g_strdup_printf(_("Edit project - %s"), project->name);
1091
1092 context->area_edit.parent =
1093 context->dialog = misc_dialog_new(MISC_DIALOG_WIDE, str,
1094 GTK_WINDOW(parent),
1095 GTK_STOCK_CLOSE, GTK_RESPONSE_ACCEPT, NULL);
1096 g_free(str);
1097 }
1098
1099 gtk_dialog_set_default_response(GTK_DIALOG(context->dialog),
1100 GTK_RESPONSE_ACCEPT);
1101
1102 GtkWidget *label;
1103 GtkWidget *table = gtk_table_new(5, 5, FALSE); // x, y
1104 gtk_table_set_col_spacing(GTK_TABLE(table), 0, 8);
1105 gtk_table_set_col_spacing(GTK_TABLE(table), 3, 8);
1106
1107 label = gtk_label_left_new(_("Description:"));
1108 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 0, 1);
1109 context->desc = gtk_entry_new();
1110 gtk_entry_set_activates_default(GTK_ENTRY(context->desc), TRUE);
1111 if(project->desc)
1112 gtk_entry_set_text(GTK_ENTRY(context->desc), project->desc);
1113 gtk_table_attach_defaults(GTK_TABLE(table), context->desc, 1, 4, 0, 1);
1114 gtk_table_set_row_spacing(GTK_TABLE(table), 0, 4);
1115
1116 label = gtk_label_left_new(_("Latitude:"));
1117 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2);
1118 context->minlat = pos_lat_label_new(project->min.lat);
1119 gtk_table_attach_defaults(GTK_TABLE(table), context->minlat, 1, 2, 1, 2);
1120 label = gtk_label_new(_("to"));
1121 gtk_table_attach_defaults(GTK_TABLE(table), label, 2, 3, 1, 2);
1122 context->maxlat = pos_lon_label_new(project->max.lat);
1123 gtk_table_attach_defaults(GTK_TABLE(table), context->maxlat, 3, 4, 1, 2);
1124
1125 label = gtk_label_left_new(_("Longitude:"));
1126 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 2, 3);
1127 context->minlon = pos_lat_label_new(project->min.lon);
1128 gtk_table_attach_defaults(GTK_TABLE(table), context->minlon, 1, 2, 2, 3);
1129 label = gtk_label_new(_("to"));
1130 gtk_table_attach_defaults(GTK_TABLE(table), label, 2, 3, 2, 3);
1131 context->maxlon = pos_lon_label_new(project->max.lon);
1132 gtk_table_attach_defaults(GTK_TABLE(table), context->maxlon, 3, 4, 2, 3);
1133
1134 GtkWidget *edit = gtk_button_new_with_label(_("Edit"));
1135 gtk_signal_connect(GTK_OBJECT(edit), "clicked",
1136 (GtkSignalFunc)on_edit_clicked, context);
1137 gtk_table_attach(GTK_TABLE(table), edit, 4, 5, 1, 3,
1138 GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,0,0);
1139
1140 gtk_table_set_row_spacing(GTK_TABLE(table), 2, 4);
1141
1142 #ifdef SERVER_EDITABLE
1143 label = gtk_label_left_new(_("Server:"));
1144 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 3, 4);
1145 context->server = gtk_entry_new();
1146 gtk_entry_set_activates_default(GTK_ENTRY(context->server), TRUE);
1147 HILDON_ENTRY_NO_AUTOCAP(context->server);
1148 gtk_entry_set_text(GTK_ENTRY(context->server), project->server);
1149 gtk_table_attach_defaults(GTK_TABLE(table), context->server, 1, 4, 3, 4);
1150
1151 gtk_table_set_row_spacing(GTK_TABLE(table), 3, 4);
1152 #endif
1153
1154 label = gtk_label_left_new(_("Map data:"));
1155 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 4, 5);
1156 context->fsize = gtk_label_left_new(_(""));
1157 project_filesize(context);
1158 gtk_table_attach_defaults(GTK_TABLE(table), context->fsize, 1, 4, 4, 5);
1159 context->download = gtk_button_new_with_label(_("Download"));
1160 gtk_signal_connect(GTK_OBJECT(context->download), "clicked",
1161 (GtkSignalFunc)on_download_clicked, context);
1162 gtk_widget_set_sensitive(context->download, project_pos_is_valid(project));
1163
1164 gtk_table_attach_defaults(GTK_TABLE(table), context->download, 4, 5, 4, 5);
1165
1166 gtk_table_set_row_spacing(GTK_TABLE(table), 4, 4);
1167
1168 label = gtk_label_left_new(_("Changes:"));
1169 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 5, 6);
1170 context->diff_stat = gtk_label_left_new(_(""));
1171 project_diffstat(context);
1172 gtk_table_attach_defaults(GTK_TABLE(table), context->diff_stat, 1, 4, 5, 6);
1173 context->diff_remove = gtk_button_new_with_label(_("Undo all"));
1174 if(!diff_present(project) && !project_active_n_dirty(context))
1175 gtk_widget_set_sensitive(context->diff_remove, FALSE);
1176 gtk_signal_connect(GTK_OBJECT(context->diff_remove), "clicked",
1177 (GtkSignalFunc)on_diff_remove_clicked, context);
1178 gtk_table_attach_defaults(GTK_TABLE(table), context->diff_remove, 4, 5, 5, 6);
1179
1180 /* ---------------------------------------------------------------- */
1181
1182 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(context->dialog)->vbox),
1183 table);
1184
1185 /* disable "ok" if there's no valid file downloaded */
1186 if(is_new)
1187 gtk_dialog_set_response_sensitive(GTK_DIALOG(context->dialog),
1188 GTK_RESPONSE_ACCEPT,
1189 osm_file_exists(project->path, project->name));
1190
1191 gtk_widget_show_all(context->dialog);
1192
1193 /* the return value may actually be != ACCEPT, but only if the editor */
1194 /* is run for a new project which is completely removed afterwards if */
1195 /* cancel has been selected */
1196 ok = (GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(context->dialog)));
1197
1198 /* transfer values from edit dialog into project structure */
1199
1200 /* fetch values from dialog */
1201 if(context->project->desc) g_free(context->project->desc);
1202 context->project->desc = g_strdup(gtk_entry_get_text(
1203 GTK_ENTRY(context->desc)));
1204 if(strlen(context->project->desc) == 0) {
1205 g_free(context->project->desc);
1206 context->project->desc = NULL;
1207 }
1208
1209 #ifdef SERVER_EDITABLE
1210 if(context->project->server) g_free(context->project->server);
1211 context->project->server = g_strdup(gtk_entry_get_text(
1212 GTK_ENTRY(context->server)));
1213 if(strlen(context->project->server) == 0) {
1214 g_free(context->project->server);
1215 context->project->server = NULL;
1216 }
1217 #endif
1218
1219 /* save project */
1220 project_save(context->dialog, project);
1221
1222 gtk_widget_destroy(context->dialog);
1223 g_free(context);
1224
1225 return ok;
1226 }
1227
1228 gboolean project_open(appdata_t *appdata, char *name) {
1229 project_t *project = g_new0(project_t, 1);
1230
1231 /* link to map state if a map already exists */
1232 if(appdata->map) {
1233 printf("Project: Using map state\n");
1234 project->map_state = appdata->map->state;
1235 } else {
1236 printf("Project: Creating new map_state\n");
1237 project->map_state = map_state_new();
1238 }
1239
1240 map_state_reset(project->map_state);
1241 project->map_state->refcount++;
1242
1243 /* build project path */
1244 project->path = g_strdup_printf("%s%s/",
1245 appdata->settings->base_path, name);
1246 project->name = g_strdup(name);
1247
1248 char *project_file = g_strdup_printf("%s%s.proj", project->path, name);
1249
1250 printf("project file = %s\n", project_file);
1251 if(!g_file_test(project_file, G_FILE_TEST_IS_REGULAR)) {
1252 printf("requested project file doesn't exist\n");
1253 project_free(project);
1254 g_free(project_file);
1255 return FALSE;
1256 }
1257
1258 if(!project_read(appdata, project_file, project)) {
1259 printf("error reading project file\n");
1260 project_free(project);
1261 g_free(project_file);
1262 return FALSE;
1263 }
1264
1265 g_free(project_file);
1266
1267 /* --------- project structure ok: load its OSM file --------- */
1268 appdata->project = project;
1269
1270 printf("project_open: loading osm %s\n", project->osm);
1271 appdata->osm = osm_parse(project->path, project->osm);
1272 if(!appdata->osm) {
1273 printf("OSM parsing failed\n");
1274 return FALSE;
1275 }
1276
1277 printf("parsing ok\n");
1278
1279 return TRUE;
1280 }
1281
1282 gboolean project_close(appdata_t *appdata) {
1283 if(!appdata->project) return FALSE;
1284
1285 printf("closing current project\n");
1286
1287 /* redraw the entire map by destroying all map items and redrawing them */
1288 if(appdata->osm)
1289 diff_save(appdata->project, appdata->osm);
1290
1291 /* Save track and turn off the handler callback */
1292 track_save(appdata->project, appdata->track.track);
1293 track_clear(appdata, appdata->track.track);
1294 appdata->track.track = NULL;
1295
1296 map_clear(appdata, MAP_LAYER_ALL);
1297
1298 if(appdata->osm) {
1299 osm_free(&appdata->icon, appdata->osm);
1300 appdata->osm = NULL;
1301 }
1302
1303 /* remember in settings that no project is open */
1304 if(appdata->settings->project)
1305 {
1306 g_free(appdata->settings->project);
1307 appdata->settings->project = NULL;
1308 }
1309
1310 /* update project file on disk */
1311 project_save(GTK_WIDGET(appdata->window), appdata->project);
1312
1313 project_free(appdata->project);
1314 appdata->project = NULL;
1315
1316 return TRUE;
1317 }
1318
1319 #define _PROJECT_LOAD_BUF_SIZ 64
1320
1321 gboolean project_load(appdata_t *appdata, char *name) {
1322 char *proj_name = NULL;
1323
1324 if(!name) {
1325 /* make user select a project */
1326 proj_name = project_select(appdata);
1327 if(!proj_name) {
1328 printf("no project selected\n");
1329 return FALSE;
1330 }
1331 }
1332 else {
1333 proj_name = g_strdup(name);
1334 }
1335
1336 char banner_txt[_PROJECT_LOAD_BUF_SIZ];
1337 memset(banner_txt, 0, _PROJECT_LOAD_BUF_SIZ);
1338
1339 snprintf(banner_txt, _PROJECT_LOAD_BUF_SIZ, _("Loading %s"), proj_name);
1340 banner_busy_start(appdata, TRUE, banner_txt);
1341
1342 /* close current project */
1343 banner_busy_tick();
1344
1345 if(appdata->project)
1346 project_close(appdata);
1347
1348 /* open project itself */
1349 banner_busy_tick();
1350
1351 if(!project_open(appdata, proj_name)) {
1352 printf("error opening requested project\n");
1353
1354 if(appdata->project) {
1355 project_free(appdata->project);
1356 appdata->project = NULL;
1357 }
1358
1359 if(appdata->osm) {
1360 osm_free(&appdata->icon, appdata->osm);
1361 appdata->osm = NULL;
1362 }
1363
1364 snprintf(banner_txt, _PROJECT_LOAD_BUF_SIZ,
1365 _("Error opening %s"), proj_name);
1366 banner_busy_stop(appdata);
1367 banner_show_info(appdata, banner_txt);
1368
1369 g_free(proj_name);
1370 return FALSE;
1371 }
1372
1373 if(!appdata->window) {
1374 g_free(proj_name);
1375 return FALSE;
1376 }
1377
1378 /* check if OSM data is valid */
1379 banner_busy_tick();
1380 if(!osm_sanity_check(GTK_WIDGET(appdata->window), appdata->osm)) {
1381 printf("project/osm sanity checks failed, unloading project\n");
1382
1383 if(appdata->project) {
1384 project_free(appdata->project);
1385 appdata->project = NULL;
1386 }
1387
1388 if(appdata->osm) {
1389 osm_free(&appdata->icon, appdata->osm);
1390 appdata->osm = NULL;
1391 }
1392
1393 snprintf(banner_txt, _PROJECT_LOAD_BUF_SIZ,
1394 _("Error opening %s"), proj_name);
1395 banner_busy_stop(appdata);
1396 banner_show_info(appdata, banner_txt);
1397
1398 g_free(proj_name);
1399 return FALSE;
1400 }
1401
1402 /* load diff possibly preset */
1403 banner_busy_tick();
1404 if(!appdata->window) goto fail;
1405
1406 diff_restore(appdata, appdata->project, appdata->osm);
1407
1408 /* prepare colors etc, draw data and adjust scroll/zoom settings */
1409 banner_busy_tick();
1410 if(!appdata->window) goto fail;
1411
1412 map_init(appdata);
1413
1414 /* restore a track */
1415 banner_busy_tick();
1416 if(!appdata->window) goto fail;
1417
1418 appdata->track.track = track_restore(appdata, appdata->project);
1419 if(appdata->track.track)
1420 map_track_draw(appdata->map, appdata->track.track);
1421
1422 /* finally load a background if present */
1423 banner_busy_tick();
1424 if(!appdata->window) goto fail;
1425 wms_load(appdata);
1426
1427 /* save the name of the project for the perferences */
1428 if(appdata->settings->project)
1429 g_free(appdata->settings->project);
1430 appdata->settings->project = g_strdup(appdata->project->name);
1431
1432 banner_busy_stop(appdata);
1433
1434 #if 0
1435 snprintf(banner_txt, _PROJECT_LOAD_BUF_SIZ, _("Loaded %s"), proj_name);
1436 banner_show_info(appdata, banner_txt);
1437 #endif
1438
1439 statusbar_set(appdata, NULL, 0);
1440
1441 g_free(proj_name);
1442
1443 return TRUE;
1444
1445 fail:
1446 printf("project loading interrupted by user\n");
1447
1448 if(appdata->project) {
1449 project_free(appdata->project);
1450 appdata->project = NULL;
1451 }
1452
1453 if(appdata->osm) {
1454 osm_free(&appdata->icon, appdata->osm);
1455 appdata->osm = NULL;
1456 }
1457
1458 g_free(proj_name);
1459
1460 return FALSE;
1461 }
1462
1463 /* ------------------- project setup wizard ----------------- */
1464
1465 struct wizard_s;
1466
1467 typedef struct wizard_page_s {
1468 const gchar *title;
1469 GtkWidget* (*setup)(struct wizard_page_s *page);
1470 void (*update)(struct wizard_page_s *page);
1471 GtkAssistantPageType type;
1472 gboolean complete;
1473 /* everything before here is initialized statically */
1474
1475 struct wizard_s *wizard;
1476 GtkWidget *widget;
1477 gint index;
1478
1479 union {
1480 struct {
1481 GtkWidget *check[4];
1482 GtkWidget *label[4];
1483 } source_selection;
1484
1485 } state;
1486
1487 } wizard_page_t;
1488
1489 typedef struct wizard_s {
1490 gboolean running;
1491
1492 int page_num;
1493 wizard_page_t *page;
1494 appdata_t *appdata;
1495 guint handler_id;
1496 GtkWidget *assistant;
1497 } wizard_t;
1498
1499
1500 static gint on_assistant_destroy(GtkWidget *widget, wizard_t *wizard) {
1501 printf("destroy callback\n");
1502 wizard->running = FALSE;
1503 return FALSE;
1504 }
1505
1506 static void on_assistant_cancel(GtkWidget *widget, wizard_t *wizard) {
1507 printf("cancel callback\n");
1508 wizard->running = FALSE;
1509 }
1510
1511 static void on_assistant_close(GtkWidget *widget, wizard_t *wizard) {
1512 printf("close callback\n");
1513 wizard->running = FALSE;
1514 }
1515
1516 static GtkWidget *wizard_text(const char *text) {
1517 GtkTextBuffer *buffer = gtk_text_buffer_new(NULL);
1518 gtk_text_buffer_set_text(buffer, text, -1);
1519
1520 #ifndef USE_HILDON_TEXT_VIEW
1521 GtkWidget *view = gtk_text_view_new_with_buffer(buffer);
1522 #else
1523 GtkWidget *view = hildon_text_view_new();
1524 hildon_text_view_set_buffer(HILDON_TEXT_VIEW(view), buffer);
1525 #endif
1526
1527 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD);
1528 gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
1529 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(view), 2 );
1530 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(view), 2 );
1531 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE );
1532
1533 return view;
1534 }
1535
1536 /* ---------------- page 1: intro ----------------- */
1537 static GtkWidget *wizard_create_intro_page(wizard_page_t *page) {
1538 static const char *text =
1539 "This wizard will guide you through the setup of a new project.\n\n"
1540 "An osm2go project covers a certain area of the world as seen "
1541 "by openstreetmap.org. The wizard will help you downloading "
1542 "the data describing that area and will enable you to make changes "
1543 "to it using osm2go.";
1544
1545 return wizard_text(text);
1546 }
1547
1548 /* ---------------- page 2: source selection ----------------- */
1549 static gboolean gtk_widget_get_sensitive(GtkWidget *widget) {
1550 GValue is_sensitive= { 0, };
1551 g_value_init(&is_sensitive, G_TYPE_BOOLEAN);
1552 g_object_get_property(G_OBJECT(widget), "sensitive", &is_sensitive);
1553 return g_value_get_boolean(&is_sensitive);
1554 }
1555
1556 static void wizard_update_source_selection_page(wizard_page_t *page) {
1557
1558 gboolean gps_on = page->wizard->appdata &&
1559 page->wizard->appdata->settings &&
1560 page->wizard->appdata->settings->enable_gps;
1561 gboolean gps_fix = gps_on && gps_get_pos(page->wizard->appdata, NULL, NULL);
1562
1563 gtk_widget_set_sensitive(page->state.source_selection.check[0], gps_fix);
1564 if(gps_fix)
1565 gtk_label_set_text(GTK_LABEL(page->state.source_selection.label[0]),
1566 "(GPS has a valid position)");
1567 else if(gps_on)
1568 gtk_label_set_text(GTK_LABEL(page->state.source_selection.label[0]),
1569 "(GPS has no valid position)");
1570 else
1571 gtk_label_set_text(GTK_LABEL(page->state.source_selection.label[0]),
1572 "(GPS is disabled)");
1573
1574 #ifndef USE_HILDON
1575 gtk_widget_set_sensitive(page->state.source_selection.check[1], FALSE);
1576 gtk_label_set_text(GTK_LABEL(page->state.source_selection.label[1]),
1577 "(Maemo Mapper not available)");
1578
1579 #endif
1580
1581 /* check if the user selected something that is actually selectable */
1582 /* only allow him to continue then */
1583 gboolean sel_ok = FALSE;
1584 int i;
1585 for(i=0;i<4;i++) {
1586 if(gtk_toggle_button_get_active(
1587 GTK_TOGGLE_BUTTON(page->state.source_selection.check[i])))
1588 sel_ok = gtk_widget_get_sensitive(page->state.source_selection.check[i]);
1589 }
1590
1591 /* set page to "completed" if a valid entry is selected */
1592 gtk_assistant_set_page_complete(
1593 GTK_ASSISTANT(page->wizard->assistant), page->widget, sel_ok);
1594 }
1595
1596 /* the user has changed the selected source, update dialog */
1597 static void on_wizard_source_selection_toggled(GtkToggleButton *togglebutton,
1598 gpointer user_data) {
1599 if(gtk_toggle_button_get_active(togglebutton))
1600 wizard_update_source_selection_page((wizard_page_t*)user_data);
1601 }
1602
1603 static GtkWidget *wizard_create_source_selection_page(wizard_page_t *page) {
1604 GtkWidget *vbox = gtk_vbox_new(FALSE, 0);
1605
1606 gtk_box_pack_start_defaults(GTK_BOX(vbox),
1607 wizard_text("Please choose how to determine the area you "
1608 "are planning to work on."));
1609
1610 GtkWidget *hbox = gtk_hbox_new(FALSE, 0);
1611 GtkWidget *vbox2 = gtk_vbox_new(FALSE, 0);
1612
1613 /* add selection buttons */
1614 int i;
1615 for(i=0;i<4;i++) {
1616 static const char *labels[] = {
1617 "Select from map",
1618 "Use current GPS position",
1619 "Get from Maemo Mapper",
1620 "Specify area manually"
1621 };
1622
1623 page->state.source_selection.check[i] =
1624 gtk_radio_button_new_with_label_from_widget(
1625 i?GTK_RADIO_BUTTON(page->state.source_selection.check[0]):NULL,
1626 _(labels[i]));
1627 g_signal_connect(G_OBJECT(page->state.source_selection.check[i]),
1628 "toggled", G_CALLBACK(on_wizard_source_selection_toggled), page);
1629 gtk_box_pack_start(GTK_BOX(vbox2), page->state.source_selection.check[i],
1630 TRUE, TRUE, 2);
1631 page->state.source_selection.label[i] = gtk_label_new("");
1632 gtk_box_pack_start(GTK_BOX(vbox2), page->state.source_selection.label[i],
1633 TRUE, TRUE, 2);
1634 }
1635
1636 gtk_box_pack_start(GTK_BOX(hbox), vbox2, TRUE, FALSE, 0);
1637 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
1638 return vbox;
1639 }
1640
1641 /* this is called once a second while the wizard is running and can be used */
1642 /* to update pages etc */
1643 static gboolean wizard_update(gpointer data) {
1644 wizard_t *wizard = (wizard_t*)data;
1645 gint page = gtk_assistant_get_current_page(GTK_ASSISTANT(wizard->assistant));
1646
1647 if(wizard->page[page].update)
1648 ; // wizard->page[page].update(&wizard->page[page]);
1649 else
1650 printf("nothing to animate on page %d\n", page);
1651
1652 return TRUE;
1653 }
1654
1655 void project_wizard(appdata_t *appdata) {
1656 wizard_page_t page[] = {
1657 { "Introduction", wizard_create_intro_page, NULL,
1658 GTK_ASSISTANT_PAGE_INTRO, TRUE},
1659 { "Area source selection", wizard_create_source_selection_page,
1660 wizard_update_source_selection_page,
1661 GTK_ASSISTANT_PAGE_CONTENT, FALSE},
1662 { "Click the Check Button", NULL, NULL,
1663 GTK_ASSISTANT_PAGE_CONTENT, FALSE},
1664 { "Click the Button", NULL, NULL,
1665 GTK_ASSISTANT_PAGE_PROGRESS, FALSE},
1666 { "Confirmation", NULL, NULL,
1667 GTK_ASSISTANT_PAGE_CONFIRM, TRUE},
1668 };
1669
1670 wizard_t wizard = {
1671 TRUE,
1672
1673 /* the pages themselves */
1674 sizeof(page) / sizeof(wizard_page_t), page,
1675 appdata, 0, NULL
1676 };
1677
1678 wizard.assistant = gtk_assistant_new();
1679 gtk_widget_set_size_request(wizard.assistant, 450, 300);
1680
1681 /* Add five pages to the GtkAssistant dialog. */
1682 int i;
1683 for (i = 0; i < wizard.page_num; i++) {
1684 wizard.page[i].wizard = &wizard;
1685
1686 if(wizard.page[i].setup)
1687 wizard.page[i].widget =
1688 wizard.page[i].setup(&wizard.page[i]);
1689 else {
1690 char *str = g_strdup_printf("Page %d", i);
1691 wizard.page[i].widget = gtk_label_new(str);
1692 g_free(str);
1693 }
1694
1695 page[i].index = gtk_assistant_append_page(GTK_ASSISTANT(wizard.assistant),
1696 wizard.page[i].widget);
1697
1698 gtk_assistant_set_page_title(GTK_ASSISTANT(wizard.assistant),
1699 wizard.page[i].widget, wizard.page[i].title);
1700 gtk_assistant_set_page_type(GTK_ASSISTANT(wizard.assistant),
1701 wizard.page[i].widget, wizard.page[i].type);
1702
1703 /* Set the introduction and conclusion pages as complete so they can be
1704 * incremented or closed. */
1705 gtk_assistant_set_page_complete(GTK_ASSISTANT(wizard.assistant),
1706 wizard.page[i].widget, wizard.page[i].complete);
1707
1708 if(wizard.page[i].update)
1709 wizard.page[i].update(&wizard.page[i]);
1710 }
1711
1712 /* install handler for timed updates */
1713 wizard.handler_id = gtk_timeout_add(1000, wizard_update, &wizard);
1714
1715 /* make it a modal subdialog of the main window */
1716 gtk_window_set_modal(GTK_WINDOW(wizard.assistant), TRUE);
1717 gtk_window_set_transient_for(GTK_WINDOW(wizard.assistant),
1718 GTK_WINDOW(appdata->window));
1719
1720 gtk_widget_show_all(wizard.assistant);
1721
1722 g_signal_connect(G_OBJECT(wizard.assistant), "destroy",
1723 G_CALLBACK(on_assistant_destroy), &wizard);
1724
1725 g_signal_connect(G_OBJECT(wizard.assistant), "cancel",
1726 G_CALLBACK(on_assistant_cancel), &wizard);
1727
1728 g_signal_connect(G_OBJECT(wizard.assistant), "close",
1729 G_CALLBACK(on_assistant_close), &wizard);
1730
1731 do {
1732 if(gtk_events_pending())
1733 gtk_main_iteration();
1734 else
1735 usleep(1000);
1736
1737 } while(wizard.running);
1738
1739 gtk_timeout_remove(wizard.handler_id);
1740
1741 gtk_widget_destroy(wizard.assistant);
1742 }
1743
1744
1745 // vim:et:ts=8:sw=2:sts=2:ai