Contents of /trunk/src/project.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 315 - (show annotations)
Wed Dec 16 20:07:58 2009 UTC (14 years, 5 months ago) by harbaum
File MIME type: text/plain
File size: 53051 byte(s)
Various fremantleization
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 = 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
642 if(!yes_no_f(context->dialog, NULL, 0, 0, _("Delete project?"), str))
643 return;
644
645 if(!project_delete(context, project))
646 printf("unable to delete project\n");
647 }
648
649 static void on_project_edit(GtkButton *button, gpointer data) {
650 select_context_t *context = (select_context_t*)data;
651 project_t *project = project_get_selected(context->list);
652 g_assert(project);
653
654 if(project_edit(context->appdata, context->dialog,
655 context->settings, project, FALSE)) {
656 GtkTreeModel *model;
657 GtkTreeIter iter;
658
659 /* description etc. may have changed, so update list */
660 GtkTreeSelection *selection = list_get_selection(context->list);
661 g_assert(gtk_tree_selection_get_selected(selection, &model, &iter));
662
663 // gtk_tree_model_get(model, &iter, PROJECT_COL_DATA, &project, -1);
664 gchar *status_stock_id = NULL;
665 project_get_status_icon_stock_id(context, project, &status_stock_id);
666 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
667 PROJECT_COL_NAME, project->name,
668 PROJECT_COL_STATUS, status_stock_id,
669 PROJECT_COL_DESCRIPTION, project->desc,
670 -1);
671
672
673 /* check if we have actually editing the currently open project */
674 if(context->appdata->project &&
675 !strcmp(context->appdata->project->name, project->name)) {
676 project_t *cur = context->appdata->project;
677
678 printf("edited project was actually the active one!\n");
679
680 /* update the currently active project also */
681
682 /* update description */
683 if(cur->desc) { free(cur->desc); cur->desc = NULL; }
684 if(project->desc) cur->desc = g_strdup(project->desc);
685
686 /* update server */
687 if(cur->server) { free(cur->server); cur->server = NULL; }
688 if(project->server) cur->server = g_strdup(project->server);
689
690 /* update coordinates */
691 if((cur->min.lat != project->min.lat) ||
692 (cur->max.lat != project->max.lat) ||
693 (cur->min.lon != project->min.lon) ||
694 (cur->max.lon != project->max.lon)) {
695 appdata_t *appdata = context->appdata;
696
697 /* save modified coordinates */
698 cur->min.lat = project->min.lat;
699 cur->max.lat = project->max.lat;
700 cur->min.lon = project->min.lon;
701 cur->max.lon = project->max.lon;
702
703 /* try to do this automatically */
704
705 /* if we have valid osm data loaded: save state first */
706 if(appdata->osm) {
707 /* redraw the entire map by destroying all map items */
708 diff_save(appdata->project, appdata->osm);
709 map_clear(appdata, MAP_LAYER_ALL);
710
711 osm_free(&appdata->icon, appdata->osm);
712 appdata->osm = NULL;
713 }
714
715 /* and load the (hopefully) new file */
716 appdata->osm = osm_parse(appdata->project->path,
717 appdata->project->osm);
718 diff_restore(appdata, appdata->project, appdata->osm);
719 map_paint(appdata);
720
721 main_ui_enable(appdata);
722 }
723 }
724 }
725
726 /* enable/disable edit/remove buttons */
727 view_selected(context, project);
728 }
729
730
731 gboolean project_osm_present(project_t *project) {
732 char *osm_name = g_strdup_printf("%s/%s.osm", project->path, project->name);
733 gboolean is_present = g_file_test(osm_name, G_FILE_TEST_EXISTS);
734 g_free(osm_name);
735 return is_present;
736 }
737
738 static void
739 project_get_status_icon_stock_id(select_context_t *context,
740 project_t *project, gchar **stock_id) {
741
742 appdata_t *appdata = context->appdata;
743
744 /* is this the currently open project? */
745 if(appdata->project &&
746 !strcmp(appdata->project->name, project->name))
747 *stock_id = GTK_STOCK_OPEN;
748 else if(!project_osm_present(project))
749 *stock_id = GTK_STOCK_DIALOG_WARNING;
750 else if(diff_present(project))
751 *stock_id = GTK_STOCK_PROPERTIES;
752 else
753 *stock_id = GTK_STOCK_FILE;
754
755 // TODO: check for outdatedness too. Which icon to use?
756 }
757
758 static GtkWidget *project_list_widget(select_context_t *context) {
759 context->list = list_new(LIST_HILDON_WITHOUT_HEADERS);
760
761 list_set_selection_function(context->list, view_selection_func, context);
762
763 list_set_columns(context->list,
764 _("Name"), PROJECT_COL_NAME, 0,
765 _("State"), PROJECT_COL_STATUS, LIST_FLAG_STOCK_ICON,
766 _("Description"), PROJECT_COL_DESCRIPTION, LIST_FLAG_ELLIPSIZE,
767 NULL);
768
769
770 /* build the store */
771 GtkListStore *store = gtk_list_store_new(PROJECT_NUM_COLS,
772 G_TYPE_STRING, // name
773 G_TYPE_STRING, // status
774 G_TYPE_STRING, // desc
775 G_TYPE_POINTER); // data
776
777 GtkTreeIter iter;
778 project_t *project = context->project;
779 while(project) {
780 gchar *status_stock_id = NULL;
781 project_get_status_icon_stock_id(context, project, &status_stock_id);
782 /* Append a row and fill in some data */
783 gtk_list_store_append(store, &iter);
784 gtk_list_store_set(store, &iter,
785 PROJECT_COL_NAME, project->name,
786 PROJECT_COL_STATUS, status_stock_id,
787 PROJECT_COL_DESCRIPTION, project->desc,
788 PROJECT_COL_DATA, project,
789 -1);
790 project = project->next;
791 }
792
793 list_set_store(context->list, store);
794 g_object_unref(store);
795
796 list_set_static_buttons(context->list, LIST_BTN_NEW,
797 G_CALLBACK(on_project_new), G_CALLBACK(on_project_edit),
798 G_CALLBACK(on_project_delete), context);
799
800 return context->list;
801 }
802
803 static char *project_select(appdata_t *appdata) {
804 char *name = NULL;
805
806 select_context_t *context = g_new0(select_context_t, 1);
807 context->appdata = appdata;
808 context->settings = appdata->settings;
809 context->project = project_scan(appdata);
810
811 /* create project selection dialog */
812 context->dialog =
813 misc_dialog_new(MISC_DIALOG_MEDIUM,_("Project selection"),
814 GTK_WINDOW(appdata->window),
815 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
816 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
817 NULL);
818
819 /* under fremantle the dialog does not have an "Open" button */
820 /* as it's closed when a project is being selected */
821 gtk_dialog_set_default_response(GTK_DIALOG(context->dialog),
822 GTK_RESPONSE_ACCEPT);
823
824 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(context->dialog)->vbox),
825 project_list_widget(context));
826
827 /* don't all user to click ok until something is selected */
828 gtk_dialog_set_response_sensitive(GTK_DIALOG(context->dialog),
829 GTK_RESPONSE_ACCEPT, FALSE);
830
831 gtk_widget_show_all(context->dialog);
832 if(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(context->dialog)))
833 name = g_strdup(project_get_selected(context->list)->name);
834
835 gtk_widget_destroy(context->dialog);
836
837 /* free all entries */
838 project_t *project = context->project;
839 while(project) {
840 project_t *next = project->next;
841 project_free(project);
842 project = next;
843 }
844
845 g_free(context);
846
847 return name;
848 }
849
850 /* ---------------------------------------------------- */
851
852 /* return file length or -1 on error */
853 static gsize file_length(char *path, char *name) {
854 char *str = NULL;
855
856 if(name[0] == '/') str = g_strdup(name);
857 else str = g_strjoin(NULL, path, name, NULL);
858
859 GMappedFile *gmap = g_mapped_file_new(str, FALSE, NULL);
860 g_free(str);
861
862 if(!gmap) return -1;
863 gsize size = g_mapped_file_get_length(gmap);
864 g_mapped_file_free(gmap);
865 return size;
866 }
867
868 void project_filesize(project_context_t *context) {
869 char *str = NULL;
870
871 printf("Checking size of %s\n", context->project->osm);
872
873 if(!osm_file_exists(context->project->path, context->project->osm)) {
874 GdkColor color;
875 gdk_color_parse("red", &color);
876 gtk_widget_modify_fg(context->fsize, GTK_STATE_NORMAL, &color);
877
878 str = g_strdup(_("Not downloaded!"));
879
880 gtk_dialog_set_response_sensitive(GTK_DIALOG(context->dialog),
881 GTK_RESPONSE_ACCEPT, !context->is_new);
882
883 } else {
884 gtk_widget_modify_fg(context->fsize, GTK_STATE_NORMAL, NULL);
885
886 if(!context->project->data_dirty)
887 str = g_strdup_printf(_("%d bytes present"),
888 file_length(context->project->path,
889 context->project->osm));
890 else
891 str = g_strdup_printf(_("Outdated, please download!"));
892
893 gtk_dialog_set_response_sensitive(GTK_DIALOG(context->dialog),
894 GTK_RESPONSE_ACCEPT, !context->is_new ||
895 !context->project->data_dirty);
896 }
897
898 if(str) {
899 gtk_label_set_text(GTK_LABEL(context->fsize), str);
900 g_free(str);
901 }
902 }
903
904 /* a project may currently be open. "unsaved changes" then also */
905 /* means that the user may have unsaved changes */
906 static gboolean project_active_n_dirty(project_context_t *context) {
907
908 if(!context->area_edit.appdata->osm) return FALSE;
909
910 if(context->area_edit.appdata->project &&
911 !strcmp(context->area_edit.appdata->project->name,
912 context->project->name)) {
913
914 printf("editing the currently open project\n");
915
916 return(!diff_is_clean(context->area_edit.appdata->osm, TRUE));
917 }
918
919 return FALSE;
920 }
921
922 void project_diffstat(project_context_t *context) {
923 char *str = NULL;
924
925 if(diff_present(context->project) || project_active_n_dirty(context)) {
926 /* this should prevent the user from changing the area */
927 str = g_strdup(_("unsaved changes pending"));
928 } else
929 str = g_strdup(_("no pending changes"));
930
931 gtk_label_set_text(GTK_LABEL(context->diff_stat), str);
932 g_free(str);
933 }
934
935 static gboolean
936 project_pos_is_valid(project_t *project) {
937 return(!isnan(project->min.lat) &&
938 !isnan(project->min.lon) &&
939 !isnan(project->max.lat) &&
940 !isnan(project->max.lon));
941 }
942
943 static void on_edit_clicked(GtkButton *button, gpointer data) {
944 project_context_t *context = (project_context_t*)data;
945
946 if(diff_present(context->project) || project_active_n_dirty(context))
947 messagef(context->dialog,
948 _("Pending changes"),
949 _("You have pending changes in this project.\n\n"
950 "Changing the area may cause pending changes to be "
951 "lost if they are outside the updated area."));
952
953 if(area_edit(&context->area_edit)) {
954 printf("coordinates changed!!\n");
955
956 pos_lon_label_set(context->minlat, context->project->min.lat);
957 pos_lon_label_set(context->minlon, context->project->min.lon);
958 pos_lon_label_set(context->maxlat, context->project->max.lat);
959 pos_lon_label_set(context->maxlon, context->project->max.lon);
960
961 gboolean pos_valid = project_pos_is_valid(context->project);
962 gtk_widget_set_sensitive(context->download, pos_valid);
963
964 /* (re-) download area */
965 if (pos_valid)
966 {
967 if(osm_download(GTK_WIDGET(context->dialog),
968 context->area_edit.appdata->settings, context->project))
969 context->project->data_dirty = FALSE;
970 }
971 project_filesize(context);
972 }
973 }
974
975 static void on_download_clicked(GtkButton *button, gpointer data) {
976 project_context_t *context = (project_context_t*)data;
977
978 printf("download %s\n", context->project->osm);
979
980 if(osm_download(context->dialog, context->settings, context->project))
981 context->project->data_dirty = FALSE;
982 else
983 printf("download failed\n");
984
985 project_filesize(context);
986 }
987
988 static void on_diff_remove_clicked(GtkButton *button, gpointer data) {
989 project_context_t *context = (project_context_t*)data;
990
991 printf("clicked diff remove\n");
992
993 if(yes_no_f(context->dialog, NULL, 0, 0, _("Discard changes?"),
994 _("Do you really want to discard your changes? This "
995 "permanently undo all changes you've made so far and which "
996 "you didn't upload yet."))) {
997 appdata_t *appdata = context->area_edit.appdata;
998
999 diff_remove(context->project);
1000
1001 /* if this is the currently open project, we need to undo */
1002 /* the map changes as well */
1003
1004 if(appdata->project &&
1005 !strcmp(appdata->project->name, context->project->name)) {
1006
1007 printf("undo all on current project: delete map changes as well\n");
1008
1009 /* just reload the map */
1010 map_clear(appdata, MAP_LAYER_OBJECTS_ONLY);
1011 osm_free(&appdata->icon, appdata->osm);
1012 appdata->osm = osm_parse(appdata->project->path, appdata->project->osm);
1013 map_paint(appdata);
1014 }
1015
1016 /* update button/label state */
1017 project_diffstat(context);
1018 gtk_widget_set_sensitive(context->diff_remove, FALSE);
1019 }
1020 }
1021
1022 gboolean project_check_demo(GtkWidget *parent, project_t *project) {
1023 if(!project->server)
1024 messagef(parent, "Demo project",
1025 "This is a preinstalled demo project. This means that the "
1026 "basic project parameters cannot be changed and no data can "
1027 "be up- or downloaded via the OSM servers.\n\n"
1028 "Please setup a new project to do these things.");
1029
1030 return !project->server;
1031 }
1032
1033 /* create a left aligned label (normal ones are centered) */
1034 static GtkWidget *gtk_label_left_new(char *str) {
1035 GtkWidget *label = gtk_label_new(str);
1036 gtk_misc_set_alignment(GTK_MISC(label), 0.f, .5f);
1037 return label;
1038 }
1039
1040 static gboolean
1041 project_edit(appdata_t *appdata, GtkWidget *parent, settings_t *settings,
1042 project_t *project, gboolean is_new) {
1043 gboolean ok = FALSE;
1044
1045 if(project_check_demo(parent, project))
1046 return ok;
1047
1048 /* ------------ project edit dialog ------------- */
1049
1050 project_context_t *context = g_new0(project_context_t, 1);
1051 context->project = project;
1052 context->area_edit.settings = context->settings = settings;
1053 context->area_edit.appdata = appdata;
1054 context->is_new = is_new;
1055 context->area_edit.min = &project->min;
1056 context->area_edit.max = &project->max;
1057
1058 /* cancel is enabled for "new" projects only */
1059 if(is_new) {
1060 char *str = g_strdup_printf(_("New project - %s"), project->name);
1061
1062 context->area_edit.parent =
1063 context->dialog = misc_dialog_new(MISC_DIALOG_WIDE, str,
1064 GTK_WINDOW(parent),
1065 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1066 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
1067 g_free(str);
1068 } else {
1069 char *str = g_strdup_printf(_("Edit project - %s"), project->name);
1070
1071 context->area_edit.parent =
1072 context->dialog = misc_dialog_new(MISC_DIALOG_WIDE, str,
1073 GTK_WINDOW(parent),
1074 GTK_STOCK_CLOSE, GTK_RESPONSE_ACCEPT, NULL);
1075 g_free(str);
1076 }
1077
1078 gtk_dialog_set_default_response(GTK_DIALOG(context->dialog),
1079 GTK_RESPONSE_ACCEPT);
1080
1081 GtkWidget *label;
1082 GtkWidget *table = gtk_table_new(5, 5, FALSE); // x, y
1083 gtk_table_set_col_spacing(GTK_TABLE(table), 0, 8);
1084 gtk_table_set_col_spacing(GTK_TABLE(table), 3, 8);
1085
1086 label = gtk_label_left_new(_("Description:"));
1087 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 0, 1);
1088 context->desc = entry_new();
1089 gtk_entry_set_activates_default(GTK_ENTRY(context->desc), TRUE);
1090 if(project->desc)
1091 gtk_entry_set_text(GTK_ENTRY(context->desc), project->desc);
1092 gtk_table_attach_defaults(GTK_TABLE(table), context->desc, 1, 5, 0, 1);
1093 gtk_table_set_row_spacing(GTK_TABLE(table), 0, 4);
1094
1095 label = gtk_label_left_new(_("Latitude:"));
1096 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2);
1097 context->minlat = pos_lat_label_new(project->min.lat);
1098 gtk_table_attach_defaults(GTK_TABLE(table), context->minlat, 1, 2, 1, 2);
1099 label = gtk_label_new(_("to"));
1100 gtk_table_attach_defaults(GTK_TABLE(table), label, 2, 3, 1, 2);
1101 context->maxlat = pos_lon_label_new(project->max.lat);
1102 gtk_table_attach_defaults(GTK_TABLE(table), context->maxlat, 3, 4, 1, 2);
1103
1104 label = gtk_label_left_new(_("Longitude:"));
1105 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 2, 3);
1106 context->minlon = pos_lat_label_new(project->min.lon);
1107 gtk_table_attach_defaults(GTK_TABLE(table), context->minlon, 1, 2, 2, 3);
1108 label = gtk_label_new(_("to"));
1109 gtk_table_attach_defaults(GTK_TABLE(table), label, 2, 3, 2, 3);
1110 context->maxlon = pos_lon_label_new(project->max.lon);
1111 gtk_table_attach_defaults(GTK_TABLE(table), context->maxlon, 3, 4, 2, 3);
1112
1113 GtkWidget *edit = button_new_with_label(_("Edit"));
1114 gtk_signal_connect(GTK_OBJECT(edit), "clicked",
1115 (GtkSignalFunc)on_edit_clicked, context);
1116 gtk_table_attach(GTK_TABLE(table), edit, 4, 5, 1, 3,
1117 GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL,0,0);
1118
1119 gtk_table_set_row_spacing(GTK_TABLE(table), 2, 4);
1120
1121 #ifdef SERVER_EDITABLE
1122 label = gtk_label_left_new(_("Server:"));
1123 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 3, 4);
1124 context->server = entry_new();
1125 gtk_entry_set_activates_default(GTK_ENTRY(context->server), TRUE);
1126 HILDON_ENTRY_NO_AUTOCAP(context->server);
1127 gtk_entry_set_text(GTK_ENTRY(context->server), project->server);
1128 gtk_table_attach_defaults(GTK_TABLE(table), context->server, 1, 4, 3, 4);
1129
1130 gtk_table_set_row_spacing(GTK_TABLE(table), 3, 4);
1131 #endif
1132
1133 label = gtk_label_left_new(_("Map data:"));
1134 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 4, 5);
1135 context->fsize = gtk_label_left_new(_(""));
1136 project_filesize(context);
1137 gtk_table_attach_defaults(GTK_TABLE(table), context->fsize, 1, 4, 4, 5);
1138 context->download = button_new_with_label(_("Download"));
1139 gtk_signal_connect(GTK_OBJECT(context->download), "clicked",
1140 (GtkSignalFunc)on_download_clicked, context);
1141 gtk_widget_set_sensitive(context->download, project_pos_is_valid(project));
1142
1143 gtk_table_attach_defaults(GTK_TABLE(table), context->download, 4, 5, 4, 5);
1144
1145 gtk_table_set_row_spacing(GTK_TABLE(table), 4, 4);
1146
1147 label = gtk_label_left_new(_("Changes:"));
1148 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 5, 6);
1149 context->diff_stat = gtk_label_left_new(_(""));
1150 project_diffstat(context);
1151 gtk_table_attach_defaults(GTK_TABLE(table), context->diff_stat, 1, 4, 5, 6);
1152 context->diff_remove = button_new_with_label(_("Undo all"));
1153 if(!diff_present(project) && !project_active_n_dirty(context))
1154 gtk_widget_set_sensitive(context->diff_remove, FALSE);
1155 gtk_signal_connect(GTK_OBJECT(context->diff_remove), "clicked",
1156 (GtkSignalFunc)on_diff_remove_clicked, context);
1157 gtk_table_attach_defaults(GTK_TABLE(table), context->diff_remove, 4, 5, 5, 6);
1158
1159 /* ---------------------------------------------------------------- */
1160
1161 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(context->dialog)->vbox),
1162 table);
1163
1164 /* disable "ok" if there's no valid file downloaded */
1165 if(is_new)
1166 gtk_dialog_set_response_sensitive(GTK_DIALOG(context->dialog),
1167 GTK_RESPONSE_ACCEPT,
1168 osm_file_exists(project->path, project->name));
1169
1170 gtk_widget_show_all(context->dialog);
1171
1172 /* the return value may actually be != ACCEPT, but only if the editor */
1173 /* is run for a new project which is completely removed afterwards if */
1174 /* cancel has been selected */
1175 ok = (GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(context->dialog)));
1176
1177 /* transfer values from edit dialog into project structure */
1178
1179 /* fetch values from dialog */
1180 if(context->project->desc) g_free(context->project->desc);
1181 context->project->desc = g_strdup(gtk_entry_get_text(
1182 GTK_ENTRY(context->desc)));
1183 if(strlen(context->project->desc) == 0) {
1184 g_free(context->project->desc);
1185 context->project->desc = NULL;
1186 }
1187
1188 #ifdef SERVER_EDITABLE
1189 if(context->project->server) g_free(context->project->server);
1190 context->project->server = g_strdup(gtk_entry_get_text(
1191 GTK_ENTRY(context->server)));
1192 if(strlen(context->project->server) == 0) {
1193 g_free(context->project->server);
1194 context->project->server = NULL;
1195 }
1196 #endif
1197
1198 /* save project */
1199 project_save(context->dialog, project);
1200
1201 gtk_widget_destroy(context->dialog);
1202 g_free(context);
1203
1204 return ok;
1205 }
1206
1207 gboolean project_open(appdata_t *appdata, char *name) {
1208 project_t *project = g_new0(project_t, 1);
1209
1210 /* link to map state if a map already exists */
1211 if(appdata->map) {
1212 printf("Project: Using map state\n");
1213 project->map_state = appdata->map->state;
1214 } else {
1215 printf("Project: Creating new map_state\n");
1216 project->map_state = map_state_new();
1217 }
1218
1219 map_state_reset(project->map_state);
1220 project->map_state->refcount++;
1221
1222 /* build project path */
1223 project->path = g_strdup_printf("%s%s/",
1224 appdata->settings->base_path, name);
1225 project->name = g_strdup(name);
1226
1227 char *project_file = g_strdup_printf("%s%s.proj", project->path, name);
1228
1229 printf("project file = %s\n", project_file);
1230 if(!g_file_test(project_file, G_FILE_TEST_IS_REGULAR)) {
1231 printf("requested project file doesn't exist\n");
1232 project_free(project);
1233 g_free(project_file);
1234 return FALSE;
1235 }
1236
1237 if(!project_read(appdata, project_file, project)) {
1238 printf("error reading project file\n");
1239 project_free(project);
1240 g_free(project_file);
1241 return FALSE;
1242 }
1243
1244 g_free(project_file);
1245
1246 /* --------- project structure ok: load its OSM file --------- */
1247 appdata->project = project;
1248
1249 printf("project_open: loading osm %s\n", project->osm);
1250 appdata->osm = osm_parse(project->path, project->osm);
1251 if(!appdata->osm) {
1252 printf("OSM parsing failed\n");
1253 return FALSE;
1254 }
1255
1256 printf("parsing ok\n");
1257
1258 return TRUE;
1259 }
1260
1261 gboolean project_close(appdata_t *appdata) {
1262 if(!appdata->project) return FALSE;
1263
1264 printf("closing current project\n");
1265
1266 /* redraw the entire map by destroying all map items and redrawing them */
1267 if(appdata->osm)
1268 diff_save(appdata->project, appdata->osm);
1269
1270 /* Save track and turn off the handler callback */
1271 track_save(appdata->project, appdata->track.track);
1272 track_clear(appdata, appdata->track.track);
1273 appdata->track.track = NULL;
1274
1275 map_clear(appdata, MAP_LAYER_ALL);
1276
1277 if(appdata->osm) {
1278 osm_free(&appdata->icon, appdata->osm);
1279 appdata->osm = NULL;
1280 }
1281
1282 /* remember in settings that no project is open */
1283 if(appdata->settings->project)
1284 {
1285 g_free(appdata->settings->project);
1286 appdata->settings->project = NULL;
1287 }
1288
1289 /* update project file on disk */
1290 project_save(GTK_WIDGET(appdata->window), appdata->project);
1291
1292 project_free(appdata->project);
1293 appdata->project = NULL;
1294
1295 return TRUE;
1296 }
1297
1298 #define _PROJECT_LOAD_BUF_SIZ 64
1299
1300 gboolean project_load(appdata_t *appdata, char *name) {
1301 char *proj_name = NULL;
1302
1303 if(!name) {
1304 /* make user select a project */
1305 proj_name = project_select(appdata);
1306 if(!proj_name) {
1307 printf("no project selected\n");
1308 return FALSE;
1309 }
1310 }
1311 else {
1312 proj_name = g_strdup(name);
1313 }
1314
1315 char banner_txt[_PROJECT_LOAD_BUF_SIZ];
1316 memset(banner_txt, 0, _PROJECT_LOAD_BUF_SIZ);
1317
1318 snprintf(banner_txt, _PROJECT_LOAD_BUF_SIZ, _("Loading %s"), proj_name);
1319 banner_busy_start(appdata, TRUE, banner_txt);
1320
1321 /* close current project */
1322 banner_busy_tick();
1323
1324 if(appdata->project)
1325 project_close(appdata);
1326
1327 /* open project itself */
1328 banner_busy_tick();
1329
1330 if(!project_open(appdata, proj_name)) {
1331 printf("error opening requested project\n");
1332
1333 if(appdata->project) {
1334 project_free(appdata->project);
1335 appdata->project = NULL;
1336 }
1337
1338 if(appdata->osm) {
1339 osm_free(&appdata->icon, appdata->osm);
1340 appdata->osm = NULL;
1341 }
1342
1343 snprintf(banner_txt, _PROJECT_LOAD_BUF_SIZ,
1344 _("Error opening %s"), proj_name);
1345 banner_busy_stop(appdata);
1346 banner_show_info(appdata, banner_txt);
1347
1348 g_free(proj_name);
1349 return FALSE;
1350 }
1351
1352 if(!appdata->window) {
1353 g_free(proj_name);
1354 return FALSE;
1355 }
1356
1357 /* check if OSM data is valid */
1358 banner_busy_tick();
1359 if(!osm_sanity_check(GTK_WIDGET(appdata->window), appdata->osm)) {
1360 printf("project/osm sanity checks failed, unloading project\n");
1361
1362 if(appdata->project) {
1363 project_free(appdata->project);
1364 appdata->project = NULL;
1365 }
1366
1367 if(appdata->osm) {
1368 osm_free(&appdata->icon, appdata->osm);
1369 appdata->osm = NULL;
1370 }
1371
1372 snprintf(banner_txt, _PROJECT_LOAD_BUF_SIZ,
1373 _("Error opening %s"), proj_name);
1374 banner_busy_stop(appdata);
1375 banner_show_info(appdata, banner_txt);
1376
1377 g_free(proj_name);
1378 return FALSE;
1379 }
1380
1381 /* load diff possibly preset */
1382 banner_busy_tick();
1383 if(!appdata->window) goto fail;
1384
1385 diff_restore(appdata, appdata->project, appdata->osm);
1386
1387 /* prepare colors etc, draw data and adjust scroll/zoom settings */
1388 banner_busy_tick();
1389 if(!appdata->window) goto fail;
1390
1391 map_init(appdata);
1392
1393 /* restore a track */
1394 banner_busy_tick();
1395 if(!appdata->window) goto fail;
1396
1397 appdata->track.track = track_restore(appdata, appdata->project);
1398 if(appdata->track.track)
1399 map_track_draw(appdata->map, appdata->track.track);
1400
1401 /* finally load a background if present */
1402 banner_busy_tick();
1403 if(!appdata->window) goto fail;
1404 wms_load(appdata);
1405
1406 /* save the name of the project for the perferences */
1407 if(appdata->settings->project)
1408 g_free(appdata->settings->project);
1409 appdata->settings->project = g_strdup(appdata->project->name);
1410
1411 banner_busy_stop(appdata);
1412
1413 #if 0
1414 snprintf(banner_txt, _PROJECT_LOAD_BUF_SIZ, _("Loaded %s"), proj_name);
1415 banner_show_info(appdata, banner_txt);
1416 #endif
1417
1418 statusbar_set(appdata, NULL, 0);
1419
1420 g_free(proj_name);
1421
1422 return TRUE;
1423
1424 fail:
1425 printf("project loading interrupted by user\n");
1426
1427 if(appdata->project) {
1428 project_free(appdata->project);
1429 appdata->project = NULL;
1430 }
1431
1432 if(appdata->osm) {
1433 osm_free(&appdata->icon, appdata->osm);
1434 appdata->osm = NULL;
1435 }
1436
1437 g_free(proj_name);
1438
1439 return FALSE;
1440 }
1441
1442 /* ------------------- project setup wizard ----------------- */
1443
1444 struct wizard_s;
1445
1446 typedef struct wizard_page_s {
1447 const gchar *title;
1448 GtkWidget* (*setup)(struct wizard_page_s *page);
1449 void (*update)(struct wizard_page_s *page);
1450 GtkAssistantPageType type;
1451 gboolean complete;
1452 /* everything before here is initialized statically */
1453
1454 struct wizard_s *wizard;
1455 GtkWidget *widget;
1456 gint index;
1457
1458 union {
1459 struct {
1460 GtkWidget *check[4];
1461 GtkWidget *label[4];
1462 } source_selection;
1463
1464 } state;
1465
1466 } wizard_page_t;
1467
1468 typedef struct wizard_s {
1469 gboolean running;
1470
1471 int page_num;
1472 wizard_page_t *page;
1473 appdata_t *appdata;
1474 guint handler_id;
1475 GtkWidget *assistant;
1476 } wizard_t;
1477
1478
1479 static gint on_assistant_destroy(GtkWidget *widget, wizard_t *wizard) {
1480 printf("destroy callback\n");
1481 wizard->running = FALSE;
1482 return FALSE;
1483 }
1484
1485 static void on_assistant_cancel(GtkWidget *widget, wizard_t *wizard) {
1486 printf("cancel callback\n");
1487 wizard->running = FALSE;
1488 }
1489
1490 static void on_assistant_close(GtkWidget *widget, wizard_t *wizard) {
1491 printf("close callback\n");
1492 wizard->running = FALSE;
1493 }
1494
1495 static GtkWidget *wizard_text(const char *text) {
1496 GtkTextBuffer *buffer = gtk_text_buffer_new(NULL);
1497 gtk_text_buffer_set_text(buffer, text, -1);
1498
1499 #ifndef USE_HILDON_TEXT_VIEW
1500 GtkWidget *view = gtk_text_view_new_with_buffer(buffer);
1501 #else
1502 GtkWidget *view = hildon_text_view_new();
1503 hildon_text_view_set_buffer(HILDON_TEXT_VIEW(view), buffer);
1504 #endif
1505
1506 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD);
1507 gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
1508 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(view), 2 );
1509 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(view), 2 );
1510 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE );
1511
1512 return view;
1513 }
1514
1515 /* ---------------- page 1: intro ----------------- */
1516 static GtkWidget *wizard_create_intro_page(wizard_page_t *page) {
1517 static const char *text =
1518 "This wizard will guide you through the setup of a new project.\n\n"
1519 "An osm2go project covers a certain area of the world as seen "
1520 "by openstreetmap.org. The wizard will help you downloading "
1521 "the data describing that area and will enable you to make changes "
1522 "to it using osm2go.";
1523
1524 return wizard_text(text);
1525 }
1526
1527 /* ---------------- page 2: source selection ----------------- */
1528 static gboolean widget_get_sensitive(GtkWidget *widget) {
1529 GValue is_sensitive= { 0, };
1530 g_value_init(&is_sensitive, G_TYPE_BOOLEAN);
1531 g_object_get_property(G_OBJECT(widget), "sensitive", &is_sensitive);
1532 return g_value_get_boolean(&is_sensitive);
1533 }
1534
1535 static void wizard_update_source_selection_page(wizard_page_t *page) {
1536
1537 gboolean gps_on = page->wizard->appdata &&
1538 page->wizard->appdata->settings &&
1539 page->wizard->appdata->settings->enable_gps;
1540 gboolean gps_fix = gps_on && gps_get_pos(page->wizard->appdata, NULL, NULL);
1541
1542 gtk_widget_set_sensitive(page->state.source_selection.check[0], gps_fix);
1543 if(gps_fix)
1544 gtk_label_set_text(GTK_LABEL(page->state.source_selection.label[0]),
1545 "(GPS has a valid position)");
1546 else if(gps_on)
1547 gtk_label_set_text(GTK_LABEL(page->state.source_selection.label[0]),
1548 "(GPS has no valid position)");
1549 else
1550 gtk_label_set_text(GTK_LABEL(page->state.source_selection.label[0]),
1551 "(GPS is disabled)");
1552
1553 #ifndef USE_HILDON
1554 gtk_widget_set_sensitive(page->state.source_selection.check[1], FALSE);
1555 gtk_label_set_text(GTK_LABEL(page->state.source_selection.label[1]),
1556 "(Maemo Mapper not available)");
1557
1558 #endif
1559
1560 /* check if the user selected something that is actually selectable */
1561 /* only allow him to continue then */
1562 gboolean sel_ok = FALSE;
1563 int i;
1564 for(i=0;i<4;i++) {
1565 if(gtk_toggle_button_get_active(
1566 GTK_TOGGLE_BUTTON(page->state.source_selection.check[i])))
1567 sel_ok = widget_get_sensitive(page->state.source_selection.check[i]);
1568 }
1569
1570 /* set page to "completed" if a valid entry is selected */
1571 gtk_assistant_set_page_complete(
1572 GTK_ASSISTANT(page->wizard->assistant), page->widget, sel_ok);
1573 }
1574
1575 /* the user has changed the selected source, update dialog */
1576 static void on_wizard_source_selection_toggled(GtkToggleButton *togglebutton,
1577 gpointer user_data) {
1578 if(gtk_toggle_button_get_active(togglebutton))
1579 wizard_update_source_selection_page((wizard_page_t*)user_data);
1580 }
1581
1582 static GtkWidget *wizard_create_source_selection_page(wizard_page_t *page) {
1583 GtkWidget *vbox = gtk_vbox_new(FALSE, 0);
1584
1585 gtk_box_pack_start_defaults(GTK_BOX(vbox),
1586 wizard_text("Please choose how to determine the area you "
1587 "are planning to work on."));
1588
1589 GtkWidget *hbox = gtk_hbox_new(FALSE, 0);
1590 GtkWidget *vbox2 = gtk_vbox_new(FALSE, 0);
1591
1592 /* add selection buttons */
1593 int i;
1594 for(i=0;i<4;i++) {
1595 static const char *labels[] = {
1596 "Select from map",
1597 "Use current GPS position",
1598 "Get from Maemo Mapper",
1599 "Specify area manually"
1600 };
1601
1602 page->state.source_selection.check[i] =
1603 gtk_radio_button_new_with_label_from_widget(
1604 i?GTK_RADIO_BUTTON(page->state.source_selection.check[0]):NULL,
1605 _(labels[i]));
1606 g_signal_connect(G_OBJECT(page->state.source_selection.check[i]),
1607 "toggled", G_CALLBACK(on_wizard_source_selection_toggled), page);
1608 gtk_box_pack_start(GTK_BOX(vbox2), page->state.source_selection.check[i],
1609 TRUE, TRUE, 2);
1610 page->state.source_selection.label[i] = gtk_label_new("");
1611 gtk_box_pack_start(GTK_BOX(vbox2), page->state.source_selection.label[i],
1612 TRUE, TRUE, 2);
1613 }
1614
1615 gtk_box_pack_start(GTK_BOX(hbox), vbox2, TRUE, FALSE, 0);
1616 gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
1617 return vbox;
1618 }
1619
1620 /* this is called once a second while the wizard is running and can be used */
1621 /* to update pages etc */
1622 static gboolean wizard_update(gpointer data) {
1623 wizard_t *wizard = (wizard_t*)data;
1624 gint page = gtk_assistant_get_current_page(GTK_ASSISTANT(wizard->assistant));
1625
1626 if(wizard->page[page].update)
1627 ; // wizard->page[page].update(&wizard->page[page]);
1628 else
1629 printf("nothing to animate on page %d\n", page);
1630
1631 return TRUE;
1632 }
1633
1634 void project_wizard(appdata_t *appdata) {
1635 wizard_page_t page[] = {
1636 { "Introduction", wizard_create_intro_page, NULL,
1637 GTK_ASSISTANT_PAGE_INTRO, TRUE},
1638 { "Area source selection", wizard_create_source_selection_page,
1639 wizard_update_source_selection_page,
1640 GTK_ASSISTANT_PAGE_CONTENT, FALSE},
1641 { "Click the Check Button", NULL, NULL,
1642 GTK_ASSISTANT_PAGE_CONTENT, FALSE},
1643 { "Click the Button", NULL, NULL,
1644 GTK_ASSISTANT_PAGE_PROGRESS, FALSE},
1645 { "Confirmation", NULL, NULL,
1646 GTK_ASSISTANT_PAGE_CONFIRM, TRUE},
1647 };
1648
1649 wizard_t wizard = {
1650 TRUE,
1651
1652 /* the pages themselves */
1653 sizeof(page) / sizeof(wizard_page_t), page,
1654 appdata, 0, NULL
1655 };
1656
1657 wizard.assistant = gtk_assistant_new();
1658 gtk_widget_set_size_request(wizard.assistant, 450, 300);
1659
1660 /* Add five pages to the GtkAssistant dialog. */
1661 int i;
1662 for (i = 0; i < wizard.page_num; i++) {
1663 wizard.page[i].wizard = &wizard;
1664
1665 if(wizard.page[i].setup)
1666 wizard.page[i].widget =
1667 wizard.page[i].setup(&wizard.page[i]);
1668 else {
1669 char *str = g_strdup_printf("Page %d", i);
1670 wizard.page[i].widget = gtk_label_new(str);
1671 g_free(str);
1672 }
1673
1674 page[i].index = gtk_assistant_append_page(GTK_ASSISTANT(wizard.assistant),
1675 wizard.page[i].widget);
1676
1677 gtk_assistant_set_page_title(GTK_ASSISTANT(wizard.assistant),
1678 wizard.page[i].widget, wizard.page[i].title);
1679 gtk_assistant_set_page_type(GTK_ASSISTANT(wizard.assistant),
1680 wizard.page[i].widget, wizard.page[i].type);
1681
1682 /* Set the introduction and conclusion pages as complete so they can be
1683 * incremented or closed. */
1684 gtk_assistant_set_page_complete(GTK_ASSISTANT(wizard.assistant),
1685 wizard.page[i].widget, wizard.page[i].complete);
1686
1687 if(wizard.page[i].update)
1688 wizard.page[i].update(&wizard.page[i]);
1689 }
1690
1691 /* install handler for timed updates */
1692 wizard.handler_id = gtk_timeout_add(1000, wizard_update, &wizard);
1693
1694 /* make it a modal subdialog of the main window */
1695 gtk_window_set_modal(GTK_WINDOW(wizard.assistant), TRUE);
1696 gtk_window_set_transient_for(GTK_WINDOW(wizard.assistant),
1697 GTK_WINDOW(appdata->window));
1698
1699 gtk_widget_show_all(wizard.assistant);
1700
1701 g_signal_connect(G_OBJECT(wizard.assistant), "destroy",
1702 G_CALLBACK(on_assistant_destroy), &wizard);
1703
1704 g_signal_connect(G_OBJECT(wizard.assistant), "cancel",
1705 G_CALLBACK(on_assistant_cancel), &wizard);
1706
1707 g_signal_connect(G_OBJECT(wizard.assistant), "close",
1708 G_CALLBACK(on_assistant_close), &wizard);
1709
1710 do {
1711 if(gtk_events_pending())
1712 gtk_main_iteration();
1713 else
1714 usleep(1000);
1715
1716 } while(wizard.running);
1717
1718 gtk_timeout_remove(wizard.handler_id);
1719
1720 gtk_widget_destroy(wizard.assistant);
1721 }
1722
1723
1724 // vim:et:ts=8:sw=2:sts=2:ai