Contents of /trunk/src/project.c

Parent Directory Parent Directory | Revision Log Revision Log


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