Contents of /trunk/src/project.c

Parent Directory Parent Directory | Revision Log Revision Log


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