Contents of /trunk/src/project.c

Parent Directory Parent Directory | Revision Log Revision Log


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