Contents of /trunk/src/project.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 159 - (hide annotations)
Fri Apr 10 11:57:28 2009 UTC (15 years, 1 month ago) by harbaum
File MIME type: text/plain
File size: 36606 byte(s)
Fixed crash when loading broken project
1 harbaum 1 /*
2     * Copyright (C) 2008 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     #include "appdata.h"
21 achadwick 28 #include "banner.h"
22 harbaum 1
23     #include <sys/stat.h>
24    
25     #include <libxml/parser.h>
26     #include <libxml/tree.h>
27    
28     #if !defined(LIBXML_TREE_ENABLED) || !defined(LIBXML_OUTPUT_ENABLED)
29     #error "libxml doesn't support required tree or output"
30     #endif
31    
32     typedef struct {
33     // appdata_t *appdata;
34     project_t *project;
35     GtkWidget *dialog, *fsize, *diff_stat, *diff_remove;
36     GtkWidget *desc, *server;
37     GtkWidget *minlat, *minlon, *maxlat, *maxlon;
38     area_edit_t area_edit;
39     } project_context_t;
40    
41     /* ------------ project file io ------------- */
42    
43     static gboolean project_read(appdata_t *appdata,
44     char *project_file, project_t *project) {
45    
46     LIBXML_TEST_VERSION;
47    
48     xmlDoc *doc = NULL;
49     xmlNode *root_element = NULL;
50    
51     /* parse the file and get the DOM */
52     if((doc = xmlReadFile(project_file, NULL, 0)) == NULL) {
53     printf("error: could not parse file %s\n", project_file);
54     return FALSE;
55     }
56    
57     /* Get the root element node */
58     root_element = xmlDocGetRootElement(doc);
59    
60     xmlNode *cur_node = NULL;
61     for (cur_node = root_element; cur_node; cur_node = cur_node->next) {
62     if (cur_node->type == XML_ELEMENT_NODE) {
63     if(strcasecmp((char*)cur_node->name, "proj") == 0) {
64     char *str;
65    
66     if((str = (char*)xmlGetProp(cur_node, BAD_CAST "dirty"))) {
67     project->data_dirty = (strcasecmp(str, "true") == 0);
68     xmlFree(str);
69     } else
70     project->data_dirty = FALSE;
71    
72     xmlNode *node = cur_node->children;
73    
74     while(node != NULL) {
75     if(node->type == XML_ELEMENT_NODE) {
76    
77     if(strcasecmp((char*)node->name, "desc") == 0) {
78     str = (char*)xmlNodeListGetString(doc, node->children, 1);
79     project->desc = g_strdup(str);
80     printf("desc = %s\n", project->desc);
81     xmlFree(str);
82    
83     } else if(strcasecmp((char*)node->name, "server") == 0) {
84     str = (char*)xmlNodeListGetString(doc, node->children, 1);
85     project->server = g_strdup(str);
86     printf("server = %s\n", project->server);
87     xmlFree(str);
88    
89     } else if(project->map_state &&
90     strcasecmp((char*)node->name, "map") == 0) {
91     if((str = (char*)xmlGetProp(node, BAD_CAST "zoom"))) {
92     project->map_state->zoom = g_ascii_strtod(str, NULL);
93     xmlFree(str);
94     }
95     if((str = (char*)xmlGetProp(node, BAD_CAST "scroll-offset-x"))) {
96     project->map_state->scroll_offset.x = strtoul(str, NULL, 10);
97     xmlFree(str);
98     }
99     if((str = (char*)xmlGetProp(node, BAD_CAST "scroll-offset-y"))) {
100     project->map_state->scroll_offset.y = strtoul(str, NULL, 10);
101     xmlFree(str);
102     }
103    
104     } else if(strcasecmp((char*)node->name, "wms") == 0) {
105    
106     if((str = (char*)xmlGetProp(node, BAD_CAST "server"))) {
107     project->wms_server = g_strdup(str);
108     xmlFree(str);
109     }
110     if((str = (char*)xmlGetProp(node, BAD_CAST "path"))) {
111     project->wms_path = g_strdup(str);
112     xmlFree(str);
113     }
114     if((str = (char*)xmlGetProp(node, BAD_CAST "x-offset"))) {
115     project->wms_offset.x = strtoul(str, NULL, 10);
116     xmlFree(str);
117     }
118     if((str = (char*)xmlGetProp(node, BAD_CAST "y-offset"))) {
119     project->wms_offset.y = strtoul(str, NULL, 10);
120     xmlFree(str);
121     }
122    
123     } else if(strcasecmp((char*)node->name, "osm") == 0) {
124     str = (char*)xmlNodeListGetString(doc, node->children, 1);
125     project->osm = g_strdup(str);
126     printf("osm = %s\n", project->osm);
127     xmlFree(str);
128     } else if(strcasecmp((char*)node->name, "min") == 0) {
129     if((str = (char*)xmlGetProp(node, BAD_CAST "lat"))) {
130     project->min.lat = g_ascii_strtod(str, NULL);
131     xmlFree(str);
132     }
133     if((str = (char*)xmlGetProp(node, BAD_CAST "lon"))) {
134     project->min.lon = g_ascii_strtod(str, NULL);
135     xmlFree(str);
136     }
137    
138     } else if(strcasecmp((char*)node->name, "max") == 0) {
139     if((str = (char*)xmlGetProp(node, BAD_CAST "lat"))) {
140     project->max.lat = g_ascii_strtod(str, NULL);
141     xmlFree(str);
142     }
143     if((str = (char*)xmlGetProp(node, BAD_CAST "lon"))) {
144     project->max.lon = g_ascii_strtod(str, NULL);
145     xmlFree(str);
146     }
147     }
148     }
149     node = node->next;
150     }
151     }
152     }
153     }
154    
155     xmlFreeDoc(doc);
156     xmlCleanupParser();
157    
158     return TRUE;
159     }
160    
161     gboolean project_save(GtkWidget *parent, project_t *project) {
162     char str[32];
163     char *project_file = g_strdup_printf("%s%s.proj",
164     project->path, project->name);
165    
166     printf("saving project to %s\n", project_file);
167    
168     /* check if project path exists */
169     if(!g_file_test(project->path, G_FILE_TEST_IS_DIR)) {
170     /* make sure project base path exists */
171     if(g_mkdir_with_parents(project->path, S_IRWXU) != 0) {
172     errorf(GTK_WIDGET(parent),
173     _("Unable to create project path %s"), project->path);
174     return FALSE;
175     }
176     }
177    
178     LIBXML_TEST_VERSION;
179    
180     xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0");
181     xmlNodePtr node, root_node = xmlNewNode(NULL, BAD_CAST "proj");
182     xmlNewProp(root_node, BAD_CAST "name", BAD_CAST project->name);
183     if(project->data_dirty)
184     xmlNewProp(root_node, BAD_CAST "dirty", BAD_CAST "true");
185    
186     xmlDocSetRootElement(doc, root_node);
187    
188     node = xmlNewChild(root_node, NULL, BAD_CAST "server",
189     BAD_CAST project->server);
190    
191     xmlNewChild(root_node, NULL, BAD_CAST "desc", BAD_CAST project->desc);
192     xmlNewChild(root_node, NULL, BAD_CAST "osm", BAD_CAST project->osm);
193    
194     node = xmlNewChild(root_node, NULL, BAD_CAST "min", NULL);
195 harbaum 156 g_ascii_formatd(str, sizeof(str), LL_FORMAT, project->min.lat);
196 harbaum 1 xmlNewProp(node, BAD_CAST "lat", BAD_CAST str);
197 harbaum 156 g_ascii_formatd(str, sizeof(str), LL_FORMAT, project->min.lon);
198 harbaum 1 xmlNewProp(node, BAD_CAST "lon", BAD_CAST str);
199    
200     node = xmlNewChild(root_node, NULL, BAD_CAST "max", NULL);
201 harbaum 156 g_ascii_formatd(str, sizeof(str), LL_FORMAT, project->max.lat);
202 harbaum 1 xmlNewProp(node, BAD_CAST "lat", BAD_CAST str);
203 harbaum 156 g_ascii_formatd(str, sizeof(str), LL_FORMAT, project->max.lon);
204 harbaum 1 xmlNewProp(node, BAD_CAST "lon", BAD_CAST str);
205    
206     if(project->map_state) {
207     node = xmlNewChild(root_node, NULL, BAD_CAST "map", BAD_CAST NULL);
208 harbaum 156 g_ascii_formatd(str, sizeof(str), LL_FORMAT, project->map_state->zoom);
209 harbaum 1 xmlNewProp(node, BAD_CAST "zoom", BAD_CAST str);
210     snprintf(str, sizeof(str), "%d", project->map_state->scroll_offset.x);
211     xmlNewProp(node, BAD_CAST "scroll-offset-x", BAD_CAST str);
212     snprintf(str, sizeof(str), "%d", project->map_state->scroll_offset.y);
213     xmlNewProp(node, BAD_CAST "scroll-offset-y", BAD_CAST str);
214     }
215    
216     node = xmlNewChild(root_node, NULL, BAD_CAST "wms", NULL);
217 harbaum 14 if(project->wms_server)
218     xmlNewProp(node, BAD_CAST "server", BAD_CAST project->wms_server);
219     if(project->wms_path)
220     xmlNewProp(node, BAD_CAST "path", BAD_CAST project->wms_path);
221 harbaum 1 snprintf(str, sizeof(str), "%d", project->wms_offset.x);
222     xmlNewProp(node, BAD_CAST "x-offset", BAD_CAST str);
223     snprintf(str, sizeof(str), "%d", project->wms_offset.y);
224     xmlNewProp(node, BAD_CAST "y-offset", BAD_CAST str);
225    
226     xmlSaveFormatFileEnc(project_file, doc, "UTF-8", 1);
227     xmlFreeDoc(doc);
228     xmlCleanupParser();
229    
230     g_free(project_file);
231    
232     return TRUE;
233     }
234    
235     /* ------------ freeing projects --------------------- */
236    
237     void project_free(project_t *project) {
238     if(!project) return;
239    
240     if(project->name) g_free(project->name);
241     if(project->desc) g_free(project->desc);
242     if(project->server) g_free(project->server);
243    
244     if(project->wms_server) g_free(project->wms_server);
245     if(project->wms_path) g_free(project->wms_path);
246    
247     if(project->path) g_free(project->path);
248     if(project->osm) g_free(project->osm);
249    
250     map_state_free(project->map_state);
251    
252     g_free(project);
253     }
254    
255     /* ------------ project selection dialog ------------- */
256    
257     static char *project_fullname(settings_t *settings, const char *name) {
258     return g_strdup_printf("%s%s/%s.proj", settings->base_path, name, name);
259     }
260    
261     static gboolean project_exists(settings_t *settings, const char *name) {
262     gboolean ok = FALSE;
263     char *fulldir = g_strdup_printf("%s%s", settings->base_path, name);
264    
265     if(g_file_test(fulldir, G_FILE_TEST_IS_DIR)) {
266    
267     /* check for project file */
268     char *fullname = project_fullname(settings, name);
269    
270     if(g_file_test(fullname, G_FILE_TEST_IS_REGULAR))
271     ok = TRUE;
272    
273     g_free(fullname);
274     }
275     g_free(fulldir);
276    
277     return ok;
278     }
279    
280     static project_t *project_scan(appdata_t *appdata) {
281     project_t *projects = NULL, **current = &projects;
282    
283     /* scan for projects */
284     GDir *dir = g_dir_open(appdata->settings->base_path, 0, NULL);
285     const char *name = NULL;
286     do {
287     if((name = g_dir_read_name(dir))) {
288     if(project_exists(appdata->settings, name)) {
289     printf("found project %s\n", name);
290    
291     /* try to read project and append it to chain */
292     *current = g_new0(project_t, 1);
293     (*current)->name = g_strdup(name);
294     (*current)->path = g_strdup_printf("%s%s/",
295     appdata->settings->base_path, name);
296    
297     char *fullname = project_fullname(appdata->settings, name);
298     if(project_read(appdata, fullname, *current))
299     current = &((*current)->next);
300     else {
301     g_free(*current);
302     *current = NULL;
303     }
304     g_free(fullname);
305     }
306     }
307     } while(name);
308    
309     g_dir_close(dir);
310    
311     return projects;
312     }
313    
314     typedef struct {
315     project_t *project;
316 harbaum 146 GtkWidget *dialog, *list;
317 harbaum 1 settings_t *settings;
318     #ifdef USE_HILDON
319     dbus_mm_pos_t *mmpos;
320     osso_context_t *osso_context;
321     #endif
322     } select_context_t;
323    
324     enum {
325     PROJECT_COL_NAME = 0,
326 achadwick 45 PROJECT_COL_STATUS,
327 harbaum 1 PROJECT_COL_DESCRIPTION,
328     PROJECT_COL_DATA,
329     PROJECT_NUM_COLS
330     };
331    
332     static void view_selected(select_context_t *context, project_t *project) {
333 harbaum 146 list_button_enable(context->list, LIST_BUTTON_REMOVE, project != NULL);
334     list_button_enable(context->list, LIST_BUTTON_EDIT, project != NULL);
335 harbaum 1
336     /* check if the selected project also has a valid osm file */
337     gtk_dialog_set_response_sensitive(GTK_DIALOG(context->dialog),
338     GTK_RESPONSE_ACCEPT,
339     project && g_file_test(project->osm, G_FILE_TEST_IS_REGULAR));
340     }
341    
342     static gboolean
343     view_selection_func(GtkTreeSelection *selection, GtkTreeModel *model,
344     GtkTreePath *path, gboolean path_currently_selected,
345     gpointer userdata) {
346     select_context_t *context = (select_context_t*)userdata;
347     GtkTreeIter iter;
348    
349     if(gtk_tree_model_get_iter(model, &iter, path)) {
350     project_t *project = NULL;
351     gtk_tree_model_get(model, &iter, PROJECT_COL_DATA, &project, -1);
352     g_assert(gtk_tree_path_get_depth(path) == 1);
353    
354     view_selected(context, project);
355     }
356    
357     return TRUE; /* allow selection state to change */
358     }
359    
360     /* get the currently selected project in the list, NULL if none */
361 harbaum 146 static project_t *project_get_selected(GtkWidget *list) {
362 harbaum 1 project_t *project = NULL;
363     GtkTreeModel *model;
364     GtkTreeIter iter;
365    
366 harbaum 146 GtkTreeSelection *selection = list_get_selection(list);
367 harbaum 1 g_assert(gtk_tree_selection_get_selected(selection, &model, &iter));
368     gtk_tree_model_get(model, &iter, PROJECT_COL_DATA, &project, -1);
369    
370     return project;
371     }
372    
373     /* ------------------------- create a new project ---------------------- */
374    
375     /* returns true of str contains one of the characters in chars */
376     static gboolean strchrs(char *str, char *chars) {
377     while(*chars) {
378     char *p = str;
379     while(*p) {
380     if(*p == *chars)
381     return TRUE;
382    
383     p++;
384     }
385     chars++;
386     }
387     return FALSE;
388     }
389    
390     typedef struct {
391     GtkWidget *dialog;
392     settings_t *settings;
393     } name_callback_context_t;
394    
395     static void callback_modified_name(GtkWidget *widget, gpointer data) {
396     name_callback_context_t *context = (name_callback_context_t*)data;
397    
398     char *name = (char*)gtk_entry_get_text(GTK_ENTRY(widget));
399    
400     /* name must not contain some special chars */
401     gboolean ok = FALSE;
402    
403     /* check if there's a name */
404     if(name && strlen(name) > 0) {
405     /* check if it consists of valid characters */
406     if(!strchrs(name, "\\*?()\n\t\r")) {
407     /* check if such a project already exists */
408     if(!project_exists(context->settings, name))
409     ok = TRUE;
410     }
411     }
412    
413     gtk_dialog_set_response_sensitive(GTK_DIALOG(context->dialog),
414     GTK_RESPONSE_ACCEPT, ok);
415     }
416    
417    
418     gboolean project_delete(select_context_t *context, project_t *project) {
419    
420     /* remove entire directory from disk */
421     GDir *dir = g_dir_open(project->path, 0, NULL);
422     const char *name = NULL;
423     do {
424     if((name = g_dir_read_name(dir))) {
425     char *fullname = g_strdup_printf("%s/%s", project->path, name);
426     g_remove(fullname);
427     g_free(fullname);
428     }
429     } while(name);
430    
431     /* remove the projects directory */
432     g_remove(project->path);
433    
434     /* remove from view */
435     GtkTreeIter iter;
436 harbaum 146 GtkTreeModel *model = list_get_model(context->list);
437 harbaum 1 gboolean deleted = FALSE;
438     if(gtk_tree_model_get_iter_first(model, &iter)) {
439     do {
440     project_t *prj = NULL;
441     gtk_tree_model_get(model, &iter, PROJECT_COL_DATA, &prj, -1);
442     if(prj && (prj == project)) {
443     printf("found %s to remove\n", prj->name);
444     /* and remove from store */
445     gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
446     deleted = TRUE;
447     }
448     } while(!deleted && gtk_tree_model_iter_next(model, &iter));
449     }
450    
451     /* de-chain entry from project list */
452     project_t **project_list = &context->project;
453     while(*project_list) {
454     if(*project_list == project)
455     *project_list = (*project_list)->next;
456     else
457     project_list = &((*project_list)->next);
458     }
459    
460     /* free project structure */
461     project_free(project);
462    
463     /* disable edit/remove buttons */
464     view_selected(context, NULL);
465    
466     return TRUE;
467     }
468    
469     project_t *project_new(select_context_t *context) {
470     printf("creating project with default values\n");
471    
472     /* -------------- first choose a name for the project --------------- */
473     GtkWidget *dialog = gtk_dialog_new_with_buttons(_("Project name"),
474     GTK_WINDOW(context->dialog), GTK_DIALOG_MODAL,
475     GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
476     GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
477     NULL);
478    
479     GtkWidget *hbox = gtk_hbox_new(FALSE, 8);
480     gtk_box_pack_start_defaults(GTK_BOX(hbox), gtk_label_new(_("Name:")));
481    
482     name_callback_context_t name_context = { dialog, context->settings };
483     GtkWidget *entry = gtk_entry_new();
484     // gtk_entry_set_text(GTK_ENTRY(entry), "<enter name>");
485     gtk_box_pack_start_defaults(GTK_BOX(hbox), entry);
486     g_signal_connect(G_OBJECT(entry), "changed",
487     G_CALLBACK(callback_modified_name), &name_context);
488    
489     gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox);
490    
491     /* don't all user to click ok until something useful has been entered */
492     gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
493     GTK_RESPONSE_ACCEPT, FALSE);
494    
495     gtk_widget_show_all(dialog);
496     if(GTK_RESPONSE_ACCEPT != gtk_dialog_run(GTK_DIALOG(dialog))) {
497     gtk_widget_destroy(dialog);
498     return NULL;
499     }
500    
501     project_t *project = g_new0(project_t, 1);
502     project->name = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
503     gtk_widget_destroy(dialog);
504    
505    
506     project->path = g_strdup_printf("%s%s/",
507     context->settings->base_path, project->name);
508     project->desc = g_strdup(_("<project description>"));
509    
510     /* no data downloaded yet */
511     project->data_dirty = TRUE;
512    
513     /* use global server/access settings */
514     project->server = g_strdup(context->settings->server);
515    
516     /* build project osm file name */
517     project->osm = g_strdup_printf("%s%s.osm", project->path, project->name);
518    
519     /* around the castle in karlsruhe, germany ... */
520     project->min.lat = 49.005; project->min.lon = 8.3911;
521     project->max.lat = 49.023; project->max.lon = 8.4185;
522    
523     /* create project file on disk */
524     project_save(context->dialog, project);
525    
526     #ifdef USE_HILDON
527     if(!project_edit(context->dialog, project, context->mmpos,
528     context->osso_context))
529     #else
530     if(!project_edit(context->dialog, project))
531     #endif
532     {
533     printf("edit cancelled!!\n");
534    
535     project_delete(context, project);
536    
537     project = NULL;
538     }
539    
540 harbaum 14 /* enable/disable edit/remove buttons */
541     view_selected(context, project);
542    
543 harbaum 1 return project;
544     }
545    
546 achadwick 45 // predecs
547     void project_get_status_icon_stock_id(project_t *project, gchar **stock_id);
548    
549 harbaum 1 static void on_project_new(GtkButton *button, gpointer data) {
550     select_context_t *context = (select_context_t*)data;
551     project_t **project = &context->project;
552     *project = project_new(context);
553     if(*project) {
554    
555 harbaum 146 GtkTreeModel *model = list_get_model(context->list);
556 harbaum 1
557     GtkTreeIter iter;
558 achadwick 45 gchar *status_stock_id = NULL;
559     project_get_status_icon_stock_id(*project, &status_stock_id);
560 harbaum 1 gtk_list_store_append(GTK_LIST_STORE(model), &iter);
561     gtk_list_store_set(GTK_LIST_STORE(model), &iter,
562     PROJECT_COL_NAME, (*project)->name,
563 achadwick 45 PROJECT_COL_STATUS, status_stock_id,
564 harbaum 1 PROJECT_COL_DESCRIPTION, (*project)->desc,
565     PROJECT_COL_DATA, *project,
566     -1);
567    
568 harbaum 146 GtkTreeSelection *selection = list_get_selection(context->list);
569 harbaum 1 gtk_tree_selection_select_iter(selection, &iter);
570     }
571     }
572    
573     static void on_project_delete(GtkButton *button, gpointer data) {
574     select_context_t *context = (select_context_t*)data;
575 harbaum 146 project_t *project = project_get_selected(context->list);
576 harbaum 1
577     char *str = g_strdup_printf(_("Do you really want to delete the "
578     "project \"%s\"?"), project->name);
579     GtkWidget *dialog = gtk_message_dialog_new(
580     GTK_WINDOW(context->dialog),
581     GTK_DIALOG_DESTROY_WITH_PARENT,
582     GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, str);
583     g_free(str);
584    
585     gtk_window_set_title(GTK_WINDOW(dialog), _("Delete project?"));
586    
587     /* set the active flag again if the user answered "no" */
588     if(GTK_RESPONSE_NO == gtk_dialog_run(GTK_DIALOG(dialog))) {
589     gtk_widget_destroy(dialog);
590     return;
591     }
592    
593     gtk_widget_destroy(dialog);
594    
595     if(!project_delete(context, project))
596     printf("unable to delete project\n");
597     }
598    
599     static void on_project_edit(GtkButton *button, gpointer data) {
600     select_context_t *context = (select_context_t*)data;
601 harbaum 146 project_t *project = project_get_selected(context->list);
602 harbaum 1 g_assert(project);
603     #ifdef USE_HILDON
604     if(project_edit(context->dialog, project,
605     context->mmpos, context->osso_context))
606     #else
607     if(project_edit(context->dialog, project))
608     #endif
609     {
610     GtkTreeModel *model;
611     GtkTreeIter iter;
612    
613 achadwick 45 /* description etc. may have changed, so update list */
614 harbaum 146 GtkTreeSelection *selection = list_get_selection(context->list);
615 harbaum 1 g_assert(gtk_tree_selection_get_selected(selection, &model, &iter));
616    
617     // gtk_tree_model_get(model, &iter, PROJECT_COL_DATA, &project, -1);
618 achadwick 45 gchar *status_stock_id = NULL;
619     project_get_status_icon_stock_id(project, &status_stock_id);
620 harbaum 1 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
621     PROJECT_COL_NAME, project->name,
622 achadwick 45 PROJECT_COL_STATUS, status_stock_id,
623 harbaum 1 PROJECT_COL_DESCRIPTION, project->desc,
624     -1);
625    
626    
627     }
628 harbaum 14
629     /* enable/disable edit/remove buttons */
630     view_selected(context, project);
631 harbaum 1 }
632    
633 achadwick 45
634     gboolean project_osm_present(project_t *project) {
635     char *osm_name = g_strdup_printf("%s/%s.osm", project->path, project->name);
636     gboolean is_present = g_file_test(osm_name, G_FILE_TEST_EXISTS);
637     g_free(osm_name);
638     return is_present;
639     }
640    
641     void project_get_status_icon_stock_id(project_t *project, gchar **stock_id) {
642     *stock_id = (! project_osm_present(project)) ? GTK_STOCK_DIALOG_WARNING
643     : diff_present(project) ? GTK_STOCK_PROPERTIES
644     : GTK_STOCK_FILE;
645     // TODO: check for outdatedness too. Which icon to use?
646     }
647    
648 harbaum 1 static GtkWidget *project_list_widget(select_context_t *context) {
649 harbaum 148 context->list = list_new(LIST_HILDON_WITHOUT_HEADERS);
650 harbaum 1
651 harbaum 146 list_set_selection_function(context->list, view_selection_func, context);
652 harbaum 1
653 harbaum 146 list_set_columns(context->list,
654     _("Name"), PROJECT_COL_NAME, 0,
655     _("State"), PROJECT_COL_STATUS, LIST_FLAG_STOCK_ICON,
656 harbaum 148 _("Description"), PROJECT_COL_DESCRIPTION, LIST_FLAG_ELLIPSIZE,
657 harbaum 146 NULL);
658    
659 harbaum 1
660     /* build the store */
661     GtkListStore *store = gtk_list_store_new(PROJECT_NUM_COLS,
662 achadwick 45 G_TYPE_STRING, // name
663     G_TYPE_STRING, // status
664     G_TYPE_STRING, // desc
665     G_TYPE_POINTER); // data
666 harbaum 1
667     GtkTreeIter iter;
668     project_t *project = context->project;
669     while(project) {
670 achadwick 45 gchar *status_stock_id = NULL;
671     project_get_status_icon_stock_id(project, &status_stock_id);
672 harbaum 1 /* Append a row and fill in some data */
673     gtk_list_store_append(store, &iter);
674     gtk_list_store_set(store, &iter,
675     PROJECT_COL_NAME, project->name,
676 achadwick 45 PROJECT_COL_STATUS, status_stock_id,
677 harbaum 1 PROJECT_COL_DESCRIPTION, project->desc,
678     PROJECT_COL_DATA, project,
679     -1);
680     project = project->next;
681     }
682    
683 harbaum 146 list_set_store(context->list, store);
684 harbaum 1 g_object_unref(store);
685    
686 harbaum 146 list_set_static_buttons(context->list, G_CALLBACK(on_project_new),
687     G_CALLBACK(on_project_edit), G_CALLBACK(on_project_delete), context);
688 harbaum 1
689 harbaum 146 return context->list;
690 harbaum 1 }
691    
692     char *project_select(appdata_t *appdata) {
693     char *name = NULL;
694    
695     select_context_t *context = g_new0(select_context_t, 1);
696     #ifdef USE_HILDON
697     context->mmpos = &appdata->mmpos;
698     context->osso_context = appdata->osso_context;
699     #endif
700     context->settings = appdata->settings;
701     context->project = project_scan(appdata);
702    
703     /* create project selection dialog */
704     context->dialog = gtk_dialog_new_with_buttons(_("Project selection"),
705     GTK_WINDOW(appdata->window), GTK_DIALOG_MODAL,
706     GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
707     GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
708     NULL);
709    
710     #ifdef USE_HILDON
711     gtk_window_set_default_size(GTK_WINDOW(context->dialog), 500, 300);
712     #else
713     gtk_window_set_default_size(GTK_WINDOW(context->dialog), 400, 200);
714     #endif
715    
716     gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(context->dialog)->vbox),
717     project_list_widget(context));
718    
719     /* don't all user to click ok until something is selected */
720     gtk_dialog_set_response_sensitive(GTK_DIALOG(context->dialog),
721     GTK_RESPONSE_ACCEPT, FALSE);
722    
723     gtk_widget_show_all(context->dialog);
724     if(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(context->dialog)))
725 harbaum 146 name = g_strdup(project_get_selected(context->list)->name);
726 harbaum 1
727     gtk_widget_destroy(context->dialog);
728    
729     /* free all entries */
730     project_t *project = context->project;
731     while(project) {
732     project_t *next = project->next;
733     project_free(project);
734     project = next;
735     }
736    
737     g_free(context);
738    
739     return name;
740     }
741    
742     /* ---------------------------------------------------- */
743    
744     /* return file length or -1 on error */
745     static gsize file_length(char *name) {
746     GMappedFile *gmap = g_mapped_file_new(name, FALSE, NULL);
747     if(!gmap) return -1;
748     gsize size = g_mapped_file_get_length(gmap);
749     g_mapped_file_free(gmap);
750     return size;
751     }
752    
753     void project_filesize(project_context_t *context) {
754     char *str = NULL;
755    
756     printf("Checking size of %s\n", context->project->osm);
757    
758     if(!g_file_test(context->project->osm, G_FILE_TEST_IS_REGULAR)) {
759     GdkColor color;
760     gdk_color_parse("red", &color);
761     gtk_widget_modify_fg(context->fsize, GTK_STATE_NORMAL, &color);
762    
763     str = g_strdup(_("Not downloaded!"));
764     gtk_dialog_set_response_sensitive(GTK_DIALOG(context->dialog),
765     GTK_RESPONSE_ACCEPT, 0);
766     } else {
767     gtk_widget_modify_fg(context->fsize, GTK_STATE_NORMAL, NULL);
768    
769     if(!context->project->data_dirty)
770     str = g_strdup_printf(_("%d bytes present"),
771     file_length(context->project->osm));
772     else
773     str = g_strdup_printf(_("Outdated, please download!"));
774    
775     /* project also must not be dirty to proceed */
776     gtk_dialog_set_response_sensitive(GTK_DIALOG(context->dialog),
777     GTK_RESPONSE_ACCEPT, !context->project->data_dirty);
778     }
779    
780     if(str) {
781     gtk_label_set_text(GTK_LABEL(context->fsize), str);
782     g_free(str);
783     }
784     }
785    
786     void project_diffstat(project_context_t *context) {
787     char *str = NULL;
788    
789     if(diff_present(context->project))
790     str = g_strdup(_("present"));
791     else
792     str = g_strdup(_("not present"));
793    
794     gtk_label_set_text(GTK_LABEL(context->diff_stat), str);
795     g_free(str);
796     }
797    
798     static void project_update(project_context_t *context) {
799    
800     /* fetch values from dialog */
801     if(context->project->desc) g_free(context->project->desc);
802     context->project->desc = g_strdup(gtk_entry_get_text(
803     GTK_ENTRY(context->desc)));
804    
805     if(context->project->server) g_free(context->project->server);
806     context->project->server = g_strdup(gtk_entry_get_text(
807     GTK_ENTRY(context->server)));
808     }
809    
810     static void on_edit_clicked(GtkButton *button, gpointer data) {
811     project_context_t *context = (project_context_t*)data;
812    
813     if(area_edit(&context->area_edit)) {
814     printf("coordinates changed!!\n");
815    
816     pos_lon_label_set(context->minlat, context->project->min.lat);
817     pos_lon_label_set(context->minlon, context->project->min.lon);
818     pos_lon_label_set(context->maxlat, context->project->max.lat);
819     pos_lon_label_set(context->maxlon, context->project->max.lon);
820     }
821     }
822    
823     static void on_download_clicked(GtkButton *button, gpointer data) {
824     project_context_t *context = (project_context_t*)data;
825    
826     project_update(context);
827    
828     printf("download %s\n", context->project->osm);
829    
830     if(osm_download(context->dialog, context->project)) {
831     context->project->data_dirty = FALSE;
832     project_filesize(context);
833     } else
834     printf("download failed\n");
835     }
836    
837     static void on_diff_remove_clicked(GtkButton *button, gpointer data) {
838     project_context_t *context = (project_context_t*)data;
839    
840     printf("clicked diff remove\n");
841    
842     GtkWidget *dialog = gtk_message_dialog_new(
843     GTK_WINDOW(context->dialog),
844     GTK_DIALOG_DESTROY_WITH_PARENT,
845     GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
846     _("Do you really want to remove the diff file? This "
847     "will delete all changes you've made so far and which "
848     "you didn't upload yet."));
849    
850     gtk_window_set_title(GTK_WINDOW(dialog), _("Remove diff?"));
851    
852     /* set the active flag again if the user answered "no" */
853     if(GTK_RESPONSE_YES == gtk_dialog_run(GTK_DIALOG(dialog))) {
854     diff_remove(context->project);
855     project_diffstat(context);
856     gtk_widget_set_sensitive(context->diff_remove, FALSE);
857     }
858    
859     gtk_widget_destroy(dialog);
860     }
861    
862     gboolean project_edit(GtkWidget *parent, project_t *project POS_PARM) {
863     gboolean ok = FALSE;
864    
865     /* ------------ project edit dialog ------------- */
866    
867     project_context_t *context = g_new0(project_context_t, 1);
868     context->project = project;
869    
870     context->area_edit.min = &project->min;
871     context->area_edit.max = &project->max;
872     #ifdef USE_HILDON
873     context->area_edit.mmpos = mmpos;
874     context->area_edit.osso_context = osso_context;
875     #endif
876    
877     char *str = g_strdup_printf(_("Project - %s"), project->name);
878     context->area_edit.parent =
879     context->dialog = gtk_dialog_new_with_buttons(str,
880     GTK_WINDOW(parent), GTK_DIALOG_MODAL,
881     GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
882     GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
883     NULL);
884     g_free(str);
885    
886     #ifdef USE_HILDON
887     /* making the dialog a little wider makes it less "crowded" */
888     gtk_window_set_default_size(GTK_WINDOW(context->dialog), 640, 100);
889     #else
890     gtk_window_set_default_size(GTK_WINDOW(context->dialog), 400, 100);
891     #endif
892    
893     GtkWidget *download, *label;
894     GtkWidget *table = gtk_table_new(4, 6, FALSE); // x, y
895    
896     label = gtk_label_new(_("Description:"));
897     gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
898     gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 0, 1);
899     context->desc = gtk_entry_new();
900     gtk_entry_set_text(GTK_ENTRY(context->desc), project->desc);
901     gtk_table_attach_defaults(GTK_TABLE(table), context->desc, 1, 4, 0, 1);
902    
903     gtk_table_set_row_spacing(GTK_TABLE(table), 0, 4);
904    
905     label = gtk_label_new(_("Latitude"));
906     gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 1, 2);
907     label = gtk_label_new(_("Longitude"));
908     gtk_table_attach_defaults(GTK_TABLE(table), label, 2, 3, 1, 2);
909    
910     label = gtk_label_new(_("Min:"));
911     gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
912     gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 2, 3);
913     context->minlat = pos_lat_label_new(project->min.lat);
914     gtk_table_attach_defaults(GTK_TABLE(table), context->minlat, 1, 2, 2, 3);
915     context->minlon = pos_lon_label_new(project->min.lon);
916     gtk_table_attach_defaults(GTK_TABLE(table), context->minlon, 2, 3, 2, 3);
917    
918     label = gtk_label_new(_("Max:"));
919     gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
920     gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 3, 4);
921     context->maxlat = pos_lat_label_new(project->max.lat);
922     gtk_table_attach_defaults(GTK_TABLE(table), context->maxlat, 1, 2, 3, 4);
923     context->maxlon = pos_lon_label_new(project->max.lon);
924     gtk_table_attach_defaults(GTK_TABLE(table), context->maxlon, 2, 3, 3, 4);
925    
926     GtkWidget *edit = gtk_button_new_with_label(_("Edit..."));
927     gtk_signal_connect(GTK_OBJECT(edit), "clicked",
928     (GtkSignalFunc)on_edit_clicked, context);
929     gtk_table_attach(GTK_TABLE(table), edit, 3, 4, 2, 4,
930     GTK_EXPAND | GTK_FILL,0,0,0);
931    
932     gtk_table_set_col_spacing(GTK_TABLE(table), 0, 4);
933     gtk_table_set_row_spacing(GTK_TABLE(table), 3, 4);
934    
935     label = gtk_label_new(_("Server:"));
936     gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
937     gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 4, 5);
938     context->server = gtk_entry_new();
939     HILDON_ENTRY_NO_AUTOCAP(context->server);
940     gtk_entry_set_text(GTK_ENTRY(context->server), project->server);
941     gtk_table_attach_defaults(GTK_TABLE(table), context->server, 1, 4, 4, 5);
942    
943     label = gtk_label_new(_("OSM file:"));
944     gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
945     gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 5, 6);
946     GtkWidget *hbox = gtk_hbox_new(FALSE, 0);
947     context->fsize = gtk_label_new(_(""));
948     gtk_misc_set_alignment(GTK_MISC(context->fsize), 0.f, 0.5f);
949     project_filesize(context);
950     gtk_box_pack_start_defaults(GTK_BOX(hbox), context->fsize);
951     download = gtk_button_new_with_label(_("Download..."));
952     gtk_signal_connect(GTK_OBJECT(download), "clicked",
953     (GtkSignalFunc)on_download_clicked, context);
954     gtk_box_pack_start(GTK_BOX(hbox), download, FALSE, FALSE, 0);
955     gtk_table_attach_defaults(GTK_TABLE(table), hbox, 1, 4, 5, 6);
956    
957     label = gtk_label_new(_("Diff file:"));
958     gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
959     gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 6, 7);
960     hbox = gtk_hbox_new(FALSE, 0);
961     context->diff_stat = gtk_label_new(_(""));
962     gtk_misc_set_alignment(GTK_MISC(context->diff_stat), 0.f, 0.5f);
963     project_diffstat(context);
964     gtk_box_pack_start_defaults(GTK_BOX(hbox), context->diff_stat);
965     context->diff_remove = gtk_button_new_with_label(_("Remove..."));
966     if(!diff_present(project))
967     gtk_widget_set_sensitive(context->diff_remove, FALSE);
968     gtk_signal_connect(GTK_OBJECT(context->diff_remove), "clicked",
969     (GtkSignalFunc)on_diff_remove_clicked, context);
970     gtk_box_pack_start(GTK_BOX(hbox), context->diff_remove, FALSE, FALSE, 0);
971     gtk_table_attach_defaults(GTK_TABLE(table), hbox, 1, 4, 6, 7);
972    
973    
974     gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(context->dialog)->vbox),
975     table);
976     gtk_widget_show_all(context->dialog);
977    
978     if(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(context->dialog))) {
979     ok = TRUE;
980    
981     /* transfer values from edit dialog into project structure */
982     project_update(context);
983    
984     /* save project */
985     project_save(context->dialog, project);
986     }
987    
988     gtk_widget_destroy(context->dialog);
989     g_free(context);
990    
991     return ok;
992     }
993    
994     gboolean project_open(appdata_t *appdata, char *name) {
995     project_t *project = g_new0(project_t, 1);
996    
997     /* link to map state if a map already exists */
998     if(appdata->map) {
999     printf("Project: Using map state\n");
1000     project->map_state = appdata->map->state;
1001     } else {
1002     printf("Project: Creating new map_state\n");
1003     project->map_state = g_new0(map_state_t,1);
1004     }
1005     project->map_state->refcount++;
1006    
1007     /* build project path */
1008     project->path = g_strdup_printf("%s%s/",
1009     appdata->settings->base_path, name);
1010     project->name = g_strdup(name);
1011    
1012     char *project_file = g_strdup_printf("%s%s.proj", project->path, name);
1013    
1014     printf("project file = %s\n", project_file);
1015     if(!g_file_test(project_file, G_FILE_TEST_IS_REGULAR)) {
1016     printf("requested project file doesn't exist\n");
1017     project_free(project);
1018     g_free(project_file);
1019     return FALSE;
1020     }
1021    
1022     if(!project_read(appdata, project_file, project)) {
1023     printf("error reading project file\n");
1024     project_free(project);
1025     g_free(project_file);
1026     return FALSE;
1027     }
1028    
1029     g_free(project_file);
1030    
1031     /* --------- project structure ok: load its OSM file --------- */
1032     appdata->project = project;
1033    
1034 achadwick 28 printf("project_open: loading osm\n");
1035 harbaum 1 appdata->osm = osm_parse(project->osm);
1036 harbaum 4 if(!appdata->osm) return FALSE;
1037 harbaum 1
1038     printf("parsing ok\n");
1039    
1040     return TRUE;
1041     }
1042    
1043     gboolean project_close(appdata_t *appdata) {
1044     if(!appdata->project) return FALSE;
1045    
1046     printf("closing current project\n");
1047    
1048     /* redraw the entire map by destroying all map items and redrawing them */
1049     if(appdata->osm)
1050     diff_save(appdata->project, appdata->osm);
1051    
1052 achadwick 26 /* Save track and turn off the handler callback */
1053     track_save(appdata->project, appdata->track.track);
1054 harbaum 156 track_clear(appdata, appdata->track.track);
1055     appdata->track.track = NULL;
1056 achadwick 26
1057 harbaum 1 map_clear(appdata, MAP_LAYER_ALL);
1058    
1059     if(appdata->osm) {
1060     osm_free(&appdata->icon, appdata->osm);
1061     appdata->osm = NULL;
1062     }
1063    
1064     project_free(appdata->project);
1065     appdata->project = NULL;
1066    
1067     return TRUE;
1068     }
1069    
1070 achadwick 28 #define _PROJECT_LOAD_BUF_SIZ 64
1071    
1072 harbaum 1 gboolean project_load(appdata_t *appdata, char *name) {
1073     char *proj_name = NULL;
1074    
1075     if(!name) {
1076     /* make user select a project */
1077     proj_name = project_select(appdata);
1078     if(!proj_name) {
1079     printf("no project selected\n");
1080     return FALSE;
1081     }
1082 achadwick 28 }
1083     else {
1084 harbaum 1 proj_name = g_strdup(name);
1085 achadwick 28 }
1086 harbaum 1
1087 achadwick 28 char banner_txt[_PROJECT_LOAD_BUF_SIZ];
1088     memset(banner_txt, 0, _PROJECT_LOAD_BUF_SIZ);
1089    
1090 harbaum 29 snprintf(banner_txt, _PROJECT_LOAD_BUF_SIZ, _("Loading %s"), proj_name);
1091 achadwick 28 banner_busy_start(appdata, TRUE, banner_txt);
1092    
1093 harbaum 1 /* close current project */
1094 achadwick 28 banner_busy_tick();
1095 harbaum 1 if(appdata->project)
1096     project_close(appdata);
1097    
1098     /* open project itself */
1099 achadwick 28 banner_busy_tick();
1100 harbaum 1 if(!project_open(appdata, proj_name)) {
1101     printf("error opening requested project\n");
1102 harbaum 159
1103     if(appdata->project) {
1104     project_free(appdata->project);
1105     appdata->project = NULL;
1106     }
1107    
1108     if(appdata->osm) {
1109     osm_free(&appdata->icon, appdata->osm);
1110     appdata->osm = NULL;
1111     }
1112    
1113     snprintf(banner_txt, _PROJECT_LOAD_BUF_SIZ,
1114     _("Error opening %s"), proj_name);
1115 achadwick 28 banner_busy_stop(appdata);
1116     banner_show_info(appdata, banner_txt);
1117 harbaum 159
1118 harbaum 1 g_free(proj_name);
1119     return FALSE;
1120     }
1121    
1122     /* check if OSM data is valid */
1123 achadwick 28 banner_busy_tick();
1124 harbaum 1 if(!osm_sanity_check(GTK_WIDGET(appdata->window), appdata->osm)) {
1125     printf("project/osm sanity checks failed, unloading project\n");
1126 harbaum 159
1127     if(appdata->project) {
1128     project_free(appdata->project);
1129     appdata->project = NULL;
1130     }
1131    
1132     if(appdata->osm) {
1133     osm_free(&appdata->icon, appdata->osm);
1134     appdata->osm = NULL;
1135     }
1136    
1137     snprintf(banner_txt, _PROJECT_LOAD_BUF_SIZ,
1138     _("Error opening %s"), proj_name);
1139 achadwick 28 banner_busy_stop(appdata);
1140     banner_show_info(appdata, banner_txt);
1141 harbaum 159
1142 achadwick 28 g_free(proj_name);
1143 harbaum 1 return FALSE;
1144     }
1145    
1146     /* load diff possibly preset */
1147 achadwick 28 banner_busy_tick();
1148 harbaum 1 diff_restore(appdata, appdata->project, appdata->osm);
1149    
1150     /* prepare colors etc, draw data and adjust scroll/zoom settings */
1151 achadwick 28 banner_busy_tick();
1152 harbaum 1 map_init(appdata);
1153    
1154     /* restore a track */
1155 achadwick 28 banner_busy_tick();
1156 harbaum 1 appdata->track.track = track_restore(appdata, appdata->project);
1157     if(appdata->track.track)
1158     map_track_draw(appdata->map, appdata->track.track);
1159    
1160     /* finally load a background if present */
1161 achadwick 28 banner_busy_tick();
1162 harbaum 1 wms_load(appdata);
1163    
1164     /* save the name of the project for the perferences */
1165     if(appdata->settings->project)
1166     g_free(appdata->settings->project);
1167     appdata->settings->project = g_strdup(appdata->project->name);
1168    
1169 harbaum 29 snprintf(banner_txt, _PROJECT_LOAD_BUF_SIZ, _("Loaded %s"), proj_name);
1170 achadwick 28 banner_busy_stop(appdata);
1171     banner_show_info(appdata, banner_txt);
1172     statusbar_set(appdata, NULL, 0);
1173    
1174     g_free(proj_name);
1175 harbaum 1 return TRUE;
1176     }
1177 achadwick 28 // vim:et:ts=8:sw=2:sts=2:ai