Contents of /trunk/src/project.c

Parent Directory Parent Directory | Revision Log Revision Log


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