Contents of /trunk/src/project.c

Parent Directory Parent Directory | Revision Log Revision Log


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