]> git.itanic.dy.fi Git - maemo-mapper/blob - src/maps.c
Enabled GDBM FAST mode.
[maemo-mapper] / src / maps.c
1 /*
2  * Copyright (C) 2006, 2007 John Costigan.
3  *
4  * POI and GPS-Info code originally written by Cezary Jackiewicz.
5  *
6  * Default map data provided by http://www.openstreetmap.org/
7  *
8  * This file is part of Maemo Mapper.
9  *
10  * Maemo Mapper is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation, either version 3 of the License, or
13  * (at your option) any later version.
14  *
15  * Maemo Mapper is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Maemo Mapper.  If not, see <http://www.gnu.org/licenses/>.
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #    include "config.h"
26 #endif
27
28 #define _GNU_SOURCE
29
30 #include <stdlib.h>
31 #include <string.h>
32 #include <math.h>
33 #include <glib/gstdio.h>
34 #include <fcntl.h>
35 #include <locale.h>
36
37 #ifndef LEGACY
38 #    include <hildon/hildon-help.h>
39 #    include <hildon/hildon-note.h>
40 #    include <hildon/hildon-file-chooser-dialog.h>
41 #    include <hildon/hildon-number-editor.h>
42 #    include <hildon/hildon-banner.h>
43 #else
44 #    include <osso-helplib.h>
45 #    include <hildon-widgets/hildon-note.h>
46 #    include <hildon-widgets/hildon-file-chooser-dialog.h>
47 #    include <hildon-widgets/hildon-number-editor.h>
48 #    include <hildon-widgets/hildon-banner.h>
49 #    include <hildon-widgets/hildon-input-mode-hint.h>
50 #endif
51
52
53 #include "types.h"
54 #include "data.h"
55 #include "defines.h"
56
57 #include "display.h"
58 #include "main.h"
59 #include "maps.h"
60 #include "menu.h"
61 #include "settings.h"
62 #include "util.h"
63
64
65 typedef struct _RepoManInfo RepoManInfo;
66 struct _RepoManInfo {
67     GtkWidget *dialog;
68     GtkWidget *notebook;
69     GtkWidget *cmb_repos;
70     GList *repo_edits;
71 };
72
73 typedef struct _RepoEditInfo RepoEditInfo;
74 struct _RepoEditInfo {
75     gchar *name;
76     GtkWidget *txt_url;
77     GtkWidget *txt_db_filename;
78     GtkWidget *num_dl_zoom_steps;
79     GtkWidget *num_view_zoom_steps;
80     GtkWidget *chk_double_size;
81     GtkWidget *chk_nextable;
82     GtkWidget *btn_browse;
83     GtkWidget *btn_compact;
84     GtkWidget *num_min_zoom;
85     GtkWidget *num_max_zoom;
86     BrowseInfo browse_info;
87 };
88
89 typedef struct _MapmanInfo MapmanInfo;
90 struct _MapmanInfo {
91     GtkWidget *dialog;
92     GtkWidget *notebook;
93     GtkWidget *tbl_area;
94
95     /* The "Setup" tab. */
96     GtkWidget *rad_download;
97     GtkWidget *rad_delete;
98     GtkWidget *chk_overwrite;
99     GtkWidget *rad_by_area;
100     GtkWidget *rad_by_route;
101     GtkWidget *num_route_radius;
102
103     /* The "Area" tab. */
104     GtkWidget *txt_topleft_lat;
105     GtkWidget *txt_topleft_lon;
106     GtkWidget *txt_botright_lat;
107     GtkWidget *txt_botright_lon;
108
109     /* The "Zoom" tab. */
110     GtkWidget *chk_zoom_levels[MAX_ZOOM + 1];
111 };
112
113 typedef struct _CompactInfo CompactInfo;
114 struct _CompactInfo {
115     GtkWidget *dialog;
116     GtkWidget *txt;
117     GtkWidget *banner;
118     const gchar *db_filename;
119     gchar *status_msg;
120 };
121
122 typedef struct _MapCacheKey MapCacheKey;
123 struct _MapCacheKey {
124     RepoData      *repo;
125     gint           zoom;
126     gint           tilex;
127     gint           tiley;
128 };
129
130 typedef struct _MapCacheEntry MapCacheEntry;
131 struct _MapCacheEntry {
132     MapCacheKey    key;
133     int            list;
134     guint          size;
135     guint          data_sz;
136     gchar         *data;
137     GdkPixbuf     *pixbuf;
138     MapCacheEntry *next;
139     MapCacheEntry *prev;
140 };
141
142 typedef struct _MapCacheList MapCacheList;
143 struct _MapCacheList {
144     MapCacheEntry *head;
145     MapCacheEntry *tail;
146     size_t         size;
147     size_t         data_sz;
148 };
149
150 typedef struct _MapCache MapCache;
151 struct _MapCache {
152     MapCacheList  lists[4];
153     size_t        cache_size;
154     size_t        p;
155     size_t        thits;
156     size_t        bhits;
157     size_t        misses;
158     GHashTable   *entries;
159 };
160
161 static MapCache _map_cache;
162
163
164 static guint
165 mapdb_get_data(RepoData *repo, gint zoom, gint tilex, gint tiley, gchar **data)
166 {
167     guint size;
168     vprintf("%s(%s, %d, %d, %d)\n", __PRETTY_FUNCTION__,
169             repo->name, zoom, tilex, tiley);
170     *data = NULL;
171     size = 0;
172
173     if(!repo->db)
174     {
175         /* There is no cache.  Return NULL. */
176         vprintf("%s(): return %u\n", __PRETTY_FUNCTION__,size);
177         return size;
178     }
179
180 #ifdef MAPDB_SQLITE
181     /* Attempt to retrieve map from database. */
182     if(SQLITE_OK == sqlite3_bind_int(repo->stmt_map_select, 1, zoom)
183     && SQLITE_OK == sqlite3_bind_int(repo->stmt_map_select, 2, tilex)
184     && SQLITE_OK == sqlite3_bind_int(repo->stmt_map_select, 3, tiley)
185     && SQLITE_ROW == sqlite3_step(repo->stmt_map_select))
186     {
187         const gchar *bytes = NULL;
188         size = sqlite3_column_bytes(repo->stmt_map_select, 0);
189
190         /* "Pixbufs" of size less than or equal to MAX_PIXBUF_DUP_SIZE are
191          * actually keys into the dups table. */
192         if(size <= MAX_PIXBUF_DUP_SIZE)
193         {
194             gint hash = sqlite3_column_int(repo->stmt_map_select, 0);
195             if(SQLITE_OK == sqlite3_bind_int(repo->stmt_dup_select, 1, hash)
196             && SQLITE_ROW == sqlite3_step(repo->stmt_dup_select))
197             {
198                 bytes = sqlite3_column_blob(repo->stmt_dup_select, 0);
199                 size = sqlite3_column_bytes(repo->stmt_dup_select, 0);
200             }
201             else
202             {
203                 /* Not there?  Delete the entry, then. */
204                 if(SQLITE_OK != sqlite3_bind_int(
205                             repo->stmt_map_delete, 1, zoom)
206                 || SQLITE_OK != sqlite3_bind_int(
207                     repo->stmt_map_delete, 2, tilex)
208                 || SQLITE_OK != sqlite3_bind_int(
209                     repo->stmt_map_delete, 3, tiley)
210                 || SQLITE_DONE != sqlite3_step(repo->stmt_map_delete))
211                 {
212                     printf("Error in stmt_map_delete: %s\n", 
213                                 sqlite3_errmsg(repo->db));
214                 }
215                 sqlite3_reset(repo->stmt_map_delete);
216
217                 /* We have no bytes to return to the caller. */
218                 bytes = NULL;
219                 size = 0;
220             }
221             /* Don't reset the statement yet - we need the blob. */
222         }
223         else
224         {
225             bytes = sqlite3_column_blob(repo->stmt_map_select, 0);
226         }
227         if(bytes)
228         {
229             *data = g_slice_alloc(size);
230             memcpy(*data, bytes, size);
231         }
232         if(size <= MAX_PIXBUF_DUP_SIZE)
233             sqlite3_reset(repo->stmt_dup_select);
234     }
235     sqlite3_reset(repo->stmt_map_select);
236 #else
237     {
238         datum d;
239         gint32 key[] = {
240             GINT32_TO_BE(zoom),
241             GINT32_TO_BE(tilex),
242             GINT32_TO_BE(tiley)
243         };
244         d.dptr = (gchar*)&key;
245         d.dsize = sizeof(key);
246         d = gdbm_fetch(repo->db, d);
247         if(d.dptr)
248         {
249             size = d.dsize;
250             *data = g_slice_alloc(size);
251             memcpy(*data, d.dptr, size);
252             free(d.dptr);
253         }
254     }
255 #endif
256
257     vprintf("%s(): return %u\n", __PRETTY_FUNCTION__, size);
258     return size;
259 }
260
261 static void map_cache_list_remove(MapCacheList *_list, MapCacheEntry *_entry)
262 {
263     _list->size -= _entry->size;
264     _list->data_sz -= _entry->data_sz;
265     *(_entry->prev != NULL?&_entry->prev->next:&_list->head) = _entry->next;
266     *(_entry->next != NULL?&_entry->next->prev:&_list->tail) = _entry->prev;
267 }
268
269 static void map_cache_list_prepend(MapCacheList *_list, int _li,
270  MapCacheEntry *_entry)
271 {
272     _entry->prev = NULL;
273     _entry->next = _list[_li].head;
274     *(_list[_li].head != NULL?&_list[_li].head->prev:&_list[_li].tail) = _entry;
275     _list[_li].head = _entry;
276     _list[_li].size += _entry->size;
277     _list[_li].data_sz += _entry->data_sz;
278     _entry->list = _li;
279 }
280
281 static guint map_cache_key_hash(gconstpointer _key){
282     const MapCacheKey *key;
283     key = (const MapCacheKey *)_key;
284     return g_direct_hash(key->repo)+g_int_hash(&key->zoom)+
285      g_int_hash(&key->tilex)+g_int_hash(&key->tiley);
286 }
287
288 static gboolean map_cache_key_equal(gconstpointer _v1, gconstpointer _v2){
289     const MapCacheKey *key1;
290     const MapCacheKey *key2;
291     key1 = (const MapCacheKey *)_v1;
292     key2 = (const MapCacheKey *)_v2;
293     return key1->tilex == key2->tilex && key1->tiley == key2->tiley &&
294      key1->zoom == key2->zoom && key1->repo == key2->repo;
295 }
296
297 static void map_cache_entry_make_pixbuf(MapCacheEntry *_entry){
298     if (_entry->data != NULL)
299     {
300         GError *error;
301         GdkPixbufLoader *loader;
302         error = NULL;
303         loader = gdk_pixbuf_loader_new();
304         gdk_pixbuf_loader_write(loader, _entry->data, _entry->data_sz, NULL);
305         gdk_pixbuf_loader_close(loader, &error);
306         if(!error)
307         {
308             _entry->pixbuf = g_object_ref(gdk_pixbuf_loader_get_pixbuf(loader));
309             _entry->size = _entry->data_sz+
310              gdk_pixbuf_get_rowstride(_entry->pixbuf)*
311              gdk_pixbuf_get_height(_entry->pixbuf);
312             g_object_unref(loader);
313             return;
314         }
315         g_object_unref(loader);
316         g_slice_free1(_entry->data_sz, _entry->data);
317         _entry->data = NULL;
318         _entry->data_sz = 0;
319     }
320     _entry->pixbuf = NULL;
321     _entry->size = _entry->data_sz;
322 }
323
324 static void map_cache_entry_free_pixbuf(MapCacheEntry *_entry){
325     if(_entry->pixbuf!=NULL)
326     {
327         g_object_unref(_entry->pixbuf);
328         _entry->pixbuf = NULL;
329     }
330 }
331
332 static void map_cache_entry_free(MapCacheEntry *_entry){
333     if(_entry->list >= 0)
334         map_cache_list_remove(_map_cache.lists+_entry->list, _entry);
335     map_cache_entry_free_pixbuf(_entry);
336     g_slice_free1(_entry->data_sz, _entry->data);
337     g_slice_free(MapCacheEntry, _entry);
338 }
339
340 static gboolean
341 map_cache_replace(size_t _size, gboolean _b2)
342 {
343     gboolean ret;
344     size_t total_size;
345     total_size = _map_cache.lists[0].size+_map_cache.lists[1].data_sz
346      +_map_cache.lists[2].size+_map_cache.lists[3].data_sz;
347     ret = FALSE;
348     while(total_size+_size > _map_cache.cache_size)
349     {
350         MapCacheEntry *entry;
351         int list;
352         if(_map_cache.lists[0].tail != NULL &&
353          (_map_cache.lists[0].size > _map_cache.p ||
354          (_b2 && _map_cache.lists[0].size == _map_cache.p)))
355             list = 0;
356         else
357             list = 2;
358         entry = _map_cache.lists[list].tail;
359         if(entry == NULL)
360             break;
361         map_cache_list_remove(_map_cache.lists+list, entry);
362         map_cache_list_prepend(_map_cache.lists, list+1, entry);
363         total_size -= entry->size - entry->data_sz;
364         ret = TRUE;
365         _b2 = FALSE;
366     }
367     return ret;
368 }
369
370 static void
371 map_cache_evict(size_t _size)
372 {
373     size_t total_size;
374     size_t max_size;
375     total_size = _map_cache.lists[0].size+_map_cache.lists[1].size
376      +_map_cache.lists[2].size+_map_cache.lists[3].size;
377     max_size = _map_cache.cache_size<<1;
378     for(;;)
379     {
380         if(_map_cache.lists[0].size+_map_cache.lists[1].size+_size >
381          _map_cache.cache_size)
382         {
383             if(_map_cache.lists[1].tail != NULL)
384             {
385                 g_hash_table_remove(_map_cache.entries,
386                  &_map_cache.lists[1].tail->key);
387                 map_cache_replace(_size, FALSE);
388             }
389             else if(_map_cache.lists[0].tail != NULL)
390             {
391                 g_hash_table_remove(_map_cache.entries,
392                  &_map_cache.lists[0].tail->key);
393             }
394             else break;
395         }
396         else if(total_size+_size > _map_cache.cache_size)
397         {
398             if(total_size+_size > max_size &&
399              _map_cache.lists[3].tail != NULL)
400             {
401                 g_hash_table_remove(_map_cache.entries,
402                  &_map_cache.lists[3].tail->key);
403                 map_cache_replace(_size, FALSE);
404             }
405             else if(!map_cache_replace(_size, FALSE))
406                 break;
407         }
408         else break;
409         total_size = _map_cache.lists[0].size+_map_cache.lists[1].size
410          +_map_cache.lists[2].size+_map_cache.lists[3].size;
411     }
412 }
413
414 static GdkPixbuf *
415 map_cache_get(RepoData *repo, gint zoom, gint tilex, gint tiley)
416 {
417     MapCacheKey key;
418     MapCacheEntry *entry;
419     key.repo = repo;
420     key.zoom = zoom;
421     key.tilex = tilex;
422     key.tiley = tiley;
423     entry = (MapCacheEntry *)g_hash_table_lookup(_map_cache.entries, &key);
424     if(entry != NULL)
425     {
426         map_cache_list_remove(_map_cache.lists+entry->list, entry);
427         if(entry->pixbuf == NULL)
428         {
429             size_t bsize;
430             size_t dp;
431             map_cache_entry_make_pixbuf(entry);
432             bsize = _map_cache.lists[entry->list].size+entry->size;
433             if(bsize < 1)
434                 bsize = 1;
435             dp = _map_cache.lists[entry->list^2].size/bsize;
436             if(dp < 1)
437                 dp = 1;
438             if(entry->list == 1)
439             {
440                 _map_cache.p += dp;
441                 if(_map_cache.p > _map_cache.cache_size)
442                     _map_cache.p = _map_cache.cache_size;
443                 map_cache_replace(entry->size, FALSE);
444             }
445             else
446             {
447                 if(dp > _map_cache.p)
448                     _map_cache.p = 0;
449                 else
450                     _map_cache.p -= dp;
451                 map_cache_replace(entry->size, TRUE);
452             }
453             _map_cache.bhits++;
454         }
455         else
456             _map_cache.thits++;
457         map_cache_list_prepend(_map_cache.lists, 2, entry);
458     }
459     else
460     {
461         gchar *data;
462         guint  data_sz;
463         data_sz = mapdb_get_data(repo, zoom, tilex, tiley, &data);
464         entry = g_slice_new(MapCacheEntry);
465         *&entry->key = *&key;
466         entry->data = data;
467         entry->data_sz = data_sz;
468         map_cache_entry_make_pixbuf(entry);
469         map_cache_evict(entry->size);
470         map_cache_list_prepend(_map_cache.lists, 0, entry);
471         g_hash_table_insert(_map_cache.entries, &entry->key, entry);
472         _map_cache.misses++;
473     }
474     if(entry->pixbuf != NULL)
475         g_object_ref(entry->pixbuf);
476     return entry->pixbuf;
477 }
478
479 static void
480 map_cache_update(RepoData *repo, gint zoom, gint tilex, gint tiley,
481  gchar *data,guint size)
482 {
483     MapCacheKey key;
484     MapCacheEntry *entry;
485     key.repo = repo;
486     key.zoom = zoom;
487     key.tilex = tilex;
488     key.tiley = tiley;
489     entry = (MapCacheEntry *)g_hash_table_lookup(_map_cache.entries, &key);
490     if(entry != NULL)
491     {
492         g_slice_free1(entry->data_sz, entry->data);
493         entry->data = g_slice_alloc(size);
494         memcpy(entry->data, data, size);
495         entry->data_sz = size;
496         if(entry->pixbuf != NULL)
497         {
498             map_cache_entry_free_pixbuf(entry);
499             map_cache_list_remove(_map_cache.lists+entry->list, entry);
500             map_cache_list_prepend(_map_cache.lists, entry->list+1, entry);
501         }
502     }
503 }
504
505 static void
506 map_cache_remove(RepoData *repo, gint zoom, gint tilex, gint tiley)
507 {
508     MapCacheKey key;
509     key.repo = repo;
510     key.zoom = zoom;
511     key.tilex = tilex;
512     key.tiley = tiley;
513     g_hash_table_remove(_map_cache.entries, &key);
514 }
515
516 void
517 map_cache_init(size_t cache_size)
518 {
519     g_mutex_lock(_mapdb_mutex);
520     if(_map_cache.entries == NULL)
521         _map_cache.entries = g_hash_table_new_full(map_cache_key_hash,
522          map_cache_key_equal, NULL, (GDestroyNotify)map_cache_entry_free);
523     _map_cache.cache_size = cache_size;
524     if(_map_cache.p > cache_size)
525         _map_cache.p = cache_size;
526     map_cache_evict(0);
527     g_mutex_unlock(_mapdb_mutex);
528 }
529
530 size_t
531 map_cache_resize(size_t cache_size)
532 {
533     size_t total_size;
534     g_mutex_lock(_mapdb_mutex);
535     _map_cache.cache_size = cache_size;
536     total_size = _map_cache.lists[0].size+_map_cache.lists[1].data_sz
537      +_map_cache.lists[2].size+_map_cache.lists[3].data_sz;
538     g_mutex_unlock(_mapdb_mutex);
539     return total_size;
540 }
541
542 void
543 map_cache_destroy(void)
544 {
545     g_mutex_lock(_mapdb_mutex);
546     if(_map_cache.entries != NULL)
547     {
548         g_hash_table_destroy(_map_cache.entries);
549         _map_cache.entries = NULL;
550         printf("thits: %u (%0.2f%%)  bhits: %u (%0.2f%%)  "
551          "misses: %u (%0.2f%%)\n",
552          _map_cache.thits, 100*_map_cache.thits/(double)(
553          _map_cache.thits+_map_cache.bhits+_map_cache.misses),
554          _map_cache.bhits, 100*_map_cache.bhits/(double)(
555          _map_cache.thits+_map_cache.bhits+_map_cache.misses),
556          _map_cache.misses, 100*_map_cache.misses/(double)(
557          _map_cache.thits+_map_cache.bhits+_map_cache.misses));
558     }
559     g_mutex_unlock(_mapdb_mutex);
560 }
561
562 gboolean
563 mapdb_exists(RepoData *repo, gint zoom, gint tilex, gint tiley)
564 {
565     gboolean exists;
566     vprintf("%s(%s, %d, %d, %d)\n", __PRETTY_FUNCTION__,
567             repo->name, zoom, tilex, tiley);
568
569     g_mutex_lock(_mapdb_mutex);
570
571     if(!repo->db)
572     {
573         /* There is no cache.  Return FALSE. */
574         g_mutex_unlock(_mapdb_mutex);
575         vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
576         return FALSE;
577     }
578
579     /* Search the cache first. */
580     {
581         MapCacheKey key;
582         MapCacheEntry *entry;
583         key.repo = repo;
584         key.zoom = zoom;
585         key.tilex = tilex;
586         key.tiley = tiley;
587         entry = (MapCacheEntry *)g_hash_table_lookup(_map_cache.entries, &key);
588         if(entry != NULL)
589         {
590             gboolean ret;
591             ret = entry->data != NULL;
592             g_mutex_unlock(_mapdb_mutex);
593             return ret;
594         }
595     }
596
597 #ifdef MAPDB_SQLITE
598     /* Attempt to retrieve map from database. */
599     if(SQLITE_OK == sqlite3_bind_int(repo->stmt_map_exists, 1, zoom)
600     && SQLITE_OK == sqlite3_bind_int(repo->stmt_map_exists, 2, tilex)
601     && SQLITE_OK == sqlite3_bind_int(repo->stmt_map_exists, 3, tiley)
602     && SQLITE_ROW == sqlite3_step(repo->stmt_map_exists)
603     && sqlite3_column_int(repo->stmt_map_exists, 0) > 0)
604     {
605         exists = TRUE;
606     }
607     else
608     {
609         exists = FALSE;
610     }
611     sqlite3_reset(repo->stmt_map_exists);
612 #else
613     {
614         datum d;
615         gint32 key[] = {
616             GINT32_TO_BE(zoom),
617             GINT32_TO_BE(tilex),
618             GINT32_TO_BE(tiley)
619         };
620         d.dptr = (gchar*)&key;
621         d.dsize = sizeof(key);
622         exists = gdbm_exists(repo->db, d);
623     }
624 #endif
625
626     g_mutex_unlock(_mapdb_mutex);
627
628     vprintf("%s(): return %d\n", __PRETTY_FUNCTION__, exists);
629     return exists;
630 }
631
632 GdkPixbuf*
633 mapdb_get(RepoData *repo, gint zoom, gint tilex, gint tiley)
634 {
635     GdkPixbuf *pixbuf;
636     vprintf("%s(%s, %d, %d, %d)\n", __PRETTY_FUNCTION__,
637             repo->name, zoom, tilex, tiley);
638     g_mutex_lock(_mapdb_mutex);
639     pixbuf = map_cache_get(repo, zoom, tilex, tiley);
640     g_mutex_unlock(_mapdb_mutex);
641     vprintf("%s(): return %p\n", __PRETTY_FUNCTION__, pixbuf);
642     return pixbuf;
643 }
644
645 #ifdef MAPDB_SQLITE
646 static gboolean
647 mapdb_checkdec(RepoData *repo, gint zoom, gint tilex, gint tiley)
648 {
649     gboolean success = TRUE;
650     vprintf("%s(%s, %d, %d, %d)\n", __PRETTY_FUNCTION__,
651             repo->name, zoom, tilex, tiley);
652
653     /* First, we have to check if the old map was a dup. */
654     if(SQLITE_OK == sqlite3_bind_int(repo->stmt_map_select, 1, zoom)
655     && SQLITE_OK == sqlite3_bind_int(repo->stmt_map_select, 2, tilex)
656     && SQLITE_OK == sqlite3_bind_int(repo->stmt_map_select, 3, tiley)
657     && SQLITE_ROW == sqlite3_step(repo->stmt_map_select)
658     && sqlite3_column_bytes(repo->stmt_map_select, 0)
659             <= MAX_PIXBUF_DUP_SIZE)
660     {
661         /* Old map was indeed a dup. Decrement the reference count. */
662         gint hash = sqlite3_column_int(repo->stmt_map_select, 0);
663         if(SQLITE_OK != sqlite3_bind_int(
664                     repo->stmt_dup_decrem, 1, hash)
665         || SQLITE_DONE != sqlite3_step(repo->stmt_dup_decrem)
666         || SQLITE_OK != sqlite3_bind_int(
667                     repo->stmt_dup_delete, 1, hash)
668         || SQLITE_DONE != sqlite3_step(repo->stmt_dup_delete))
669         {
670             success = FALSE;
671             printf("Error in stmt_dup_decrem: %s\n",
672                     sqlite3_errmsg(repo->db));
673         }
674         sqlite3_reset(repo->stmt_dup_delete);
675         sqlite3_reset(repo->stmt_dup_decrem);
676     }
677     sqlite3_reset(repo->stmt_map_select);
678
679     vprintf("%s(): return %d\n", __PRETTY_FUNCTION__, success);
680     return success;
681 }
682 #endif
683
684 static gboolean
685 mapdb_update(gboolean exists, RepoData *repo,
686         gint zoom, gint tilex, gint tiley, void *bytes, gint size)
687 {
688 #ifdef MAPDB_SQLITE
689     sqlite3_stmt *stmt;
690     gint hash = 0;
691 #endif
692     gint success = TRUE;
693     vprintf("%s(%s, %d, %d, %d)\n", __PRETTY_FUNCTION__,
694             repo->name, zoom, tilex, tiley);
695
696     g_mutex_lock(_mapdb_mutex);
697     map_cache_update(repo, zoom, tilex, tiley, bytes, size);
698
699     if(!repo->db)
700     {
701         /* There is no cache.  Return FALSE. */
702         g_mutex_unlock(_mapdb_mutex);
703         vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
704         return FALSE;
705     }
706
707 #ifdef MAPDB_SQLITE
708     /* At least try to open a transaction. */
709     sqlite3_step(repo->stmt_trans_begin);
710     sqlite3_reset(repo->stmt_trans_begin);
711
712     /* Pixbufs of size MAX_PIXBUF_DUP_SIZE or less are special.  They are
713      * probably PNGs of a single color (like blue for water or beige for empty
714      * land).  To reduce redundancy in the database, we will store them in a
715      * separate table and, in the maps table, only refer to them. */
716     if(size <= MAX_PIXBUF_DUP_SIZE)
717     {
718         /* Duplicate pixbuf. */
719         if(exists)
720         {
721             /* First, check if we need to remove a count from the dups table.*/
722             mapdb_checkdec(repo, zoom, tilex, tiley);
723         }
724         if(success)
725         {
726             /* Compute hash of the bytes. */
727             gchar *cur = bytes, *end = bytes + size;
728             hash = *cur;
729             while(cur < end)
730                 hash = (hash << 5) - hash + *(++cur);
731
732             /* Check if dup already exists. */
733             if(SQLITE_OK == sqlite3_bind_int(repo->stmt_dup_exists, 1, hash)
734             && SQLITE_ROW == sqlite3_step(repo->stmt_dup_exists)
735             && sqlite3_column_int(repo->stmt_dup_exists, 0) > 0)
736             {
737                 /* Dup already exists - increment existing entry. */
738                 if(SQLITE_OK != sqlite3_bind_int(repo->stmt_dup_increm,1, hash)
739                 || SQLITE_DONE != sqlite3_step(repo->stmt_dup_increm))
740                 {
741                     success = FALSE;
742                     printf("Error in stmt_dup_increm: %s\n",
743                             sqlite3_errmsg(repo->db));
744                 }
745                 sqlite3_reset(repo->stmt_dup_increm);
746             }
747             else
748             {
749                 /* Dup doesn't exist - add new entry. */
750                 if(SQLITE_OK != sqlite3_bind_int(repo->stmt_dup_insert,1, hash)
751                 || SQLITE_OK != sqlite3_bind_blob(repo->stmt_dup_insert,
752                     2, bytes, size, NULL)
753                 || SQLITE_DONE != sqlite3_step(repo->stmt_dup_insert))
754                 {
755                     success = FALSE;
756                     printf("Error in stmt_dup_insert: %s\n",
757                             sqlite3_errmsg(repo->db));
758                 }
759                 sqlite3_reset(repo->stmt_dup_insert);
760             }
761             sqlite3_reset(repo->stmt_dup_exists);
762         }
763         /* Now, if successful so far, we fall through the end of this if
764          * statement and insert the hash as the blob.  Setting bytes to NULL
765          * is the signal to do this. */
766         bytes = NULL;
767     }
768
769     if(success)
770     {
771         stmt = exists ? repo->stmt_map_update : repo->stmt_map_insert;
772
773         /* Attempt to insert map from database. */
774         if(SQLITE_OK != (bytes ? sqlite3_bind_blob(stmt, 1, bytes, size, NULL)
775                     : sqlite3_bind_int(stmt, 1, hash))
776         || SQLITE_OK != sqlite3_bind_int(stmt, 2, zoom)
777         || SQLITE_OK != sqlite3_bind_int(stmt, 3, tilex)
778         || SQLITE_OK != sqlite3_bind_int(stmt, 4, tiley)
779         || SQLITE_DONE != sqlite3_step(stmt))
780         {
781             success = FALSE;
782             printf("Error in mapdb_update: %s\n", sqlite3_errmsg(repo->db));
783         }
784         sqlite3_reset(stmt);
785     }
786
787     if(success)
788     {
789         sqlite3_step(repo->stmt_trans_commit);
790         sqlite3_reset(repo->stmt_trans_commit);
791     }
792     else
793     {
794         sqlite3_step(repo->stmt_trans_rollback);
795         sqlite3_reset(repo->stmt_trans_rollback);
796     }
797
798 #else
799     {
800         datum dkey, dcon;
801         gint32 key[] = {
802             GINT32_TO_BE(zoom),
803             GINT32_TO_BE(tilex),
804             GINT32_TO_BE(tiley)
805         };
806         dkey.dptr = (gchar*)&key;
807         dkey.dsize = sizeof(key);
808         dcon.dptr = bytes;
809         dcon.dsize = size;
810         success = !gdbm_store(repo->db, dkey, dcon, GDBM_REPLACE);
811     }
812 #endif
813     g_mutex_unlock(_mapdb_mutex);
814
815     vprintf("%s(): return %d\n", __PRETTY_FUNCTION__, success);
816     return success;
817 }
818
819 static gboolean
820 mapdb_delete(RepoData *repo, gint zoom, gint tilex, gint tiley)
821 {
822     gint success = FALSE;
823     vprintf("%s(%s, %d, %d, %d)\n", __PRETTY_FUNCTION__,
824             repo->name, zoom, tilex, tiley);
825
826     g_mutex_lock(_mapdb_mutex);
827     map_cache_remove(repo, zoom, tilex, tiley);
828
829     if(!repo->db)
830     {
831         /* There is no cache.  Return FALSE. */
832         g_mutex_unlock(_mapdb_mutex);
833         vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
834         return FALSE;
835     }
836
837 #ifdef MAPDB_SQLITE
838     /* At least try to open a transaction. */
839     sqlite3_step(repo->stmt_trans_begin);
840     sqlite3_reset(repo->stmt_trans_begin);
841
842     /* First, check if we need to remove a count from the dups table. */
843     /* Then, attempt to delete map from database. */
844     if(!mapdb_checkdec(repo, zoom, tilex, tiley)
845     || SQLITE_OK != sqlite3_bind_int(repo->stmt_map_delete, 1, zoom)
846     || SQLITE_OK != sqlite3_bind_int(repo->stmt_map_delete, 2, tilex)
847     || SQLITE_OK != sqlite3_bind_int(repo->stmt_map_delete, 3, tiley)
848     || SQLITE_DONE != sqlite3_step(repo->stmt_map_delete))
849     {
850         success = FALSE;
851         printf("Error in stmt_map_delete: %s\n", 
852                     sqlite3_errmsg(repo->db));
853     }
854     sqlite3_reset(repo->stmt_map_delete);
855
856     if(success)
857     {
858         sqlite3_step(repo->stmt_trans_commit);
859         sqlite3_reset(repo->stmt_trans_commit);
860     }
861     else
862     {
863         sqlite3_step(repo->stmt_trans_rollback);
864         sqlite3_reset(repo->stmt_trans_rollback);
865     }
866 #else
867     {
868         datum d;
869         gint32 key[] = {
870             GINT32_TO_BE(zoom),
871             GINT32_TO_BE(tilex),
872             GINT32_TO_BE(tiley)
873         };
874         d.dptr = (gchar*)&key;
875         d.dsize = sizeof(key);
876         success = !gdbm_delete(repo->db, d);
877     }
878 #endif
879     g_mutex_unlock(_mapdb_mutex);
880
881     vprintf("%s(): return %d\n", __PRETTY_FUNCTION__, success);
882     return success;
883 }
884
885 void
886 set_repo_type(RepoData *repo)
887 {
888     printf("%s(%s)\n", __PRETTY_FUNCTION__, repo->url);
889
890     if(repo->url && *repo->url)
891     {
892         gchar *url = g_utf8_strdown(repo->url, -1);
893
894         /* Determine type of repository. */
895         if(strstr(url, "service=wms"))
896             repo->type = REPOTYPE_WMS;
897         else if(strstr(url, "%s"))
898             repo->type = REPOTYPE_QUAD_QRST;
899         else if(strstr(url, "%0d"))
900             repo->type = REPOTYPE_XYZ_INV;
901         else if(strstr(url, "%-d"))
902             repo->type = REPOTYPE_XYZ_SIGNED;
903         else if(strstr(url, "%0s"))
904             repo->type = REPOTYPE_QUAD_ZERO;
905         else
906             repo->type = REPOTYPE_XYZ;
907
908         g_free(url);
909     }
910     else
911         repo->type = REPOTYPE_NONE;
912
913     vprintf("%s(): return\n", __PRETTY_FUNCTION__);
914 }
915
916 /* Returns the directory containing the given database filename, or NULL
917  * if the database file could not be created. */
918 static gboolean
919 repo_make_db(RepoData *rd)
920 {
921     printf("%s(%s)\n", __PRETTY_FUNCTION__, rd->db_filename);
922     gchar *db_dirname;
923     gint fd;
924
925     db_dirname = g_path_get_dirname(rd->db_filename);
926     
927     /* Check if db_filename is a directory and ask to upgrade. */
928     if(g_file_test(rd->db_filename, G_FILE_TEST_IS_DIR))
929     {
930         gchar buffer[BUFFER_SIZE];
931         gchar *new_name = g_strdup_printf("%s.db", rd->db_filename);
932         g_free(rd->db_filename);
933         rd->db_filename = new_name;
934
935         snprintf(buffer, sizeof(buffer), "%s",
936                 _("The current repository is in a legacy format and will "
937                     "be converted.  You should delete your old maps if you "
938                     "no longer plan to use them."));
939         popup_error(_window, buffer);
940     }
941
942     if(g_mkdir_with_parents(db_dirname, 0755))
943     {
944         g_free(db_dirname);
945         return FALSE;
946     }
947     g_free(db_dirname);
948
949     if(!g_file_test(rd->db_filename, G_FILE_TEST_EXISTS))
950     {
951         fd = g_creat(rd->db_filename, 0644);
952         if(fd == -1)
953         {
954             g_free(db_dirname);
955             return FALSE;
956         }
957         close(fd);
958     }
959
960     vprintf("%s(): return %d\n", __PRETTY_FUNCTION__,
961            g_file_test(rd->db_filename, G_FILE_TEST_EXISTS));
962     return g_file_test(rd->db_filename, G_FILE_TEST_EXISTS);
963 }
964
965 gboolean
966 repo_set_curr(RepoData *rd)
967 {
968     printf("%s()\n", __PRETTY_FUNCTION__);
969     if(!rd->db_filename || !*rd->db_filename
970             || repo_make_db(rd))
971     {
972         if(_curr_repo)
973         {
974             if(_curr_repo->db)
975             {
976                 g_mutex_lock(_mapdb_mutex);
977 #ifdef MAPDB_SQLITE
978                 sqlite3_close(_curr_repo->db);
979 #else
980                 gdbm_close(_curr_repo->db);
981 #endif
982                 _curr_repo->db = NULL;
983                 g_mutex_unlock(_mapdb_mutex);
984             }
985         }
986
987         /* Set the current repository! */
988         _curr_repo = rd;
989
990         /* Set up the database. */
991         if(_curr_repo->db_filename && *_curr_repo->db_filename)
992         {
993
994 #ifdef MAPDB_SQLITE
995             if(SQLITE_OK != (sqlite3_open(_curr_repo->db_filename,
996                             &(_curr_repo->db)))
997             /* Open worked. Now create tables, failing if they already exist.*/
998             || (sqlite3_exec(_curr_repo->db,
999                         "create table maps ("
1000                         "zoom integer, "
1001                         "tilex integer, "
1002                         "tiley integer, "
1003                         "pixbuf blob, "
1004                         "primary key (zoom, tilex, tiley))"
1005                         ";"
1006                         "create table dups ("
1007                         "hash integer primary key, "
1008                         "uses integer, "
1009                         "pixbuf blob)",
1010                         NULL, NULL, NULL), FALSE) /* !! Comma operator !! */
1011                 /* Prepare select map statement. */
1012              || SQLITE_OK != sqlite3_prepare(_curr_repo->db,
1013                         "select pixbuf from maps "
1014                         "where zoom = ? and tilex = ? and tiley = ?",
1015                         -1, &_curr_repo->stmt_map_select, NULL)
1016                 /* Prepare exists map statement. */
1017              || SQLITE_OK != sqlite3_prepare(_curr_repo->db,
1018                         "select count(*) from maps "
1019                         "where zoom = ? and tilex = ? and tiley = ?",
1020                         -1, &_curr_repo->stmt_map_exists, NULL)
1021                 /* Prepare insert map statement. */
1022              || SQLITE_OK != sqlite3_prepare(_curr_repo->db,
1023                         "insert into maps (pixbuf, zoom, tilex, tiley)"
1024                         " values (?, ?, ?, ?)",
1025                         -1, &_curr_repo->stmt_map_insert, NULL)
1026                 /* Prepare update map statement. */
1027              || SQLITE_OK != sqlite3_prepare(_curr_repo->db,
1028                         "update maps set pixbuf = ? "
1029                         "where zoom = ? and tilex = ? and tiley = ?",
1030                         -1, &_curr_repo->stmt_map_update, NULL)
1031                 /* Prepare delete map statement. */
1032              || SQLITE_OK != sqlite3_prepare(_curr_repo->db,
1033                         "delete from maps "
1034                         "where zoom = ? and tilex = ? and tiley = ?",
1035                         -1, &_curr_repo->stmt_map_delete, NULL)
1036
1037                 /* Prepare select-by-map dup statement. */
1038                 /* Prepare select-by-hash dup statement. */
1039              || SQLITE_OK != sqlite3_prepare(_curr_repo->db,
1040                         "select pixbuf from dups "
1041                         "where hash = ?",
1042                         -1, &_curr_repo->stmt_dup_select, NULL)
1043                 /* Prepare exists map statement. */
1044              || SQLITE_OK != sqlite3_prepare(_curr_repo->db,
1045                         "select count(*) from dups "
1046                         "where hash = ?",
1047                         -1, &_curr_repo->stmt_dup_exists, NULL)
1048                 /* Prepare insert dup statement. */
1049              || SQLITE_OK != sqlite3_prepare(_curr_repo->db,
1050                         "insert into dups (hash, pixbuf, uses) "
1051                         "values (?, ?, 1)",
1052                         -1, &_curr_repo->stmt_dup_insert, NULL)
1053                 /* Prepare increment dup statement. */
1054              || SQLITE_OK != sqlite3_prepare(_curr_repo->db,
1055                         "update dups "
1056                         "set uses = uses + 1 "
1057                         "where hash = ?",
1058                         -1, &_curr_repo->stmt_dup_increm, NULL)
1059                 /* Prepare decrement dup statement. */
1060              || SQLITE_OK != sqlite3_prepare(_curr_repo->db,
1061                         "update dups "
1062                         "set uses = uses - 1 "
1063                         "where hash = ? ",
1064                         -1, &_curr_repo->stmt_dup_decrem, NULL)
1065                 /* Prepare delete dup statement. */
1066              || SQLITE_OK != sqlite3_prepare(_curr_repo->db,
1067                         "delete from dups "
1068                         "where hash = ? and uses <= 0",
1069                         -1, &_curr_repo->stmt_dup_delete, NULL)
1070
1071                 /* Prepare begin-transaction statement. */
1072              || SQLITE_OK != sqlite3_prepare(_curr_repo->db,
1073                      "begin transaction",
1074                         -1, &_curr_repo->stmt_trans_begin, NULL)
1075              || SQLITE_OK != sqlite3_prepare(_curr_repo->db,
1076                      "commit transaction",
1077                         -1, &_curr_repo->stmt_trans_commit, NULL)
1078              || SQLITE_OK != sqlite3_prepare(_curr_repo->db,
1079                      "rollback transaction", -1,
1080                      &_curr_repo->stmt_trans_rollback, NULL))
1081             {
1082                 gchar buffer[BUFFER_SIZE];
1083                 snprintf(buffer, sizeof(buffer), "%s: %s\n%s",
1084                         _("Failed to open map database for repository"),
1085                         sqlite3_errmsg(_curr_repo->db),
1086                         _("Downloaded maps will not be cached."));
1087                 sqlite3_close(_curr_repo->db);
1088                 _curr_repo->db = NULL;
1089                 popup_error(_window, buffer);
1090             }
1091 #else
1092             _curr_repo->db = gdbm_open(_curr_repo->db_filename,
1093                     0, GDBM_WRCREAT | GDBM_FAST, 0644, NULL);
1094             if(!_curr_repo->db)
1095             {
1096                 gchar buffer[BUFFER_SIZE];
1097                 snprintf(buffer, sizeof(buffer), "%s\n%s",
1098                         _("Failed to open map database for repository"),
1099                         _("Downloaded maps will not be cached."));
1100                 _curr_repo->db = NULL;
1101                 popup_error(_window, buffer);
1102             }
1103 #endif
1104         }
1105         else
1106         {
1107             _curr_repo->db = NULL;
1108         }
1109         vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
1110         return TRUE;
1111     }
1112     else
1113     {
1114         gchar buffer[BUFFER_SIZE];
1115         snprintf(buffer, sizeof(buffer), "%s: %s",
1116                 _("Unable to create map database for repository"), rd->name);
1117         popup_error(_window, buffer);
1118         _curr_repo = rd;
1119         vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
1120         return FALSE;
1121     }
1122 }
1123
1124 /**
1125  * Given a wms uri pattern, compute the coordinate transformation and
1126  * trimming.
1127  * 'proj' is used for the conversion
1128  */
1129 static gchar*
1130 map_convert_wms_to_wms(gint tilex, gint tiley, gint zoomlevel, gchar* uri)
1131 {
1132     gint system_retcode;
1133     gchar cmd[BUFFER_SIZE], srs[BUFFER_SIZE];
1134     gchar *ret = NULL;
1135     FILE* in;
1136     gdouble lon1, lat1, lon2, lat2;
1137
1138     gchar *widthstr   = strcasestr(uri,"WIDTH=");
1139     gchar *heightstr  = strcasestr(uri,"HEIGHT=");
1140     gchar *srsstr     = strcasestr(uri,"SRS=EPSG");
1141     gchar *srsstre    = strchr(srsstr,'&');
1142     vprintf("%s()\n", __PRETTY_FUNCTION__);
1143
1144     /* missing: test if found */
1145     strcpy(srs,"epsg");
1146     strncpy(srs+4,srsstr+8,256);
1147     /* missing: test srsstre-srsstr < 526 */
1148     srs[srsstre-srsstr-4] = 0;
1149     /* convert to lower, as WMC is EPSG and cs2cs is epsg */
1150
1151     gint dwidth  = widthstr ? atoi(widthstr+6) - TILE_SIZE_PIXELS : 0;
1152     gint dheight = heightstr ? atoi(heightstr+7) - TILE_SIZE_PIXELS : 0;
1153
1154     unit2latlon(tile2zunit(tilex,zoomlevel)
1155             - pixel2zunit(dwidth/2,zoomlevel),
1156             tile2zunit(tiley+1,zoomlevel)
1157             + pixel2zunit((dheight+1)/2,zoomlevel),
1158             lat1, lon1);
1159
1160     unit2latlon(tile2zunit(tilex+1,zoomlevel)
1161             + pixel2zunit((dwidth+1)/2,zoomlevel),
1162             tile2zunit(tiley,zoomlevel)
1163             - pixel2zunit(dheight/2,zoomlevel),
1164             lat2, lon2);
1165
1166     setlocale(LC_NUMERIC, "C");
1167
1168     snprintf(cmd, sizeof(cmd),
1169             "(echo \"%.6f %.6f\"; echo \"%.6f %.6f\") | "
1170             "/usr/bin/cs2cs +proj=longlat +datum=WGS84 +to +init=%s -f %%.6f "
1171             " > /tmp/tmpcs2cs ",
1172             lon1, lat1, lon2, lat2, srs);
1173     vprintf("Running command: %s\n", cmd);
1174     system_retcode = system(cmd);
1175
1176     if(system_retcode)
1177         g_printerr("cs2cs returned error code %d\n",
1178                 WEXITSTATUS(system_retcode));
1179     else if(!(in = g_fopen("/tmp/tmpcs2cs","r")))
1180         g_printerr("Cannot open results of conversion\n");
1181     else if(5 != fscanf(in,"%lf %lf %s %lf %lf",
1182                 &lon1, &lat1, cmd, &lon2, &lat2))
1183     {
1184         g_printerr("Wrong conversion\n");
1185         fclose(in);
1186     }
1187     else
1188     {
1189         fclose(in);
1190         ret = g_strdup_printf(uri, lon1, lat1, lon2, lat2);
1191     }
1192
1193     setlocale(LC_NUMERIC, "");
1194
1195     vprintf("%s(): return %s\n", __PRETTY_FUNCTION__, ret);
1196     return ret;
1197 }
1198
1199
1200 /**
1201  * Given the xyz coordinates of our map coordinate system, write the qrst
1202  * quadtree coordinates to buffer.
1203  */
1204 static void
1205 map_convert_coords_to_quadtree_string(gint x, gint y, gint zoomlevel,
1206                                       gchar *buffer, const gchar initial,
1207                                       const gchar *const quadrant)
1208 {
1209     gchar *ptr = buffer;
1210     gint n;
1211     vprintf("%s()\n", __PRETTY_FUNCTION__);
1212
1213     if (initial)
1214         *ptr++ = initial;
1215
1216     for(n = MAX_ZOOM - zoomlevel; n >= 0; n--)
1217     {
1218         gint xbit = (x >> n) & 1;
1219         gint ybit = (y >> n) & 1;
1220         *ptr++ = quadrant[xbit + 2 * ybit];
1221     }
1222     *ptr++ = '\0';
1223     vprintf("%s(): return\n", __PRETTY_FUNCTION__);
1224 }
1225
1226 /**
1227  * Construct the URL that we should fetch, based on the current URI format.
1228  * This method works differently depending on if a "%s" string is present in
1229  * the URI format, since that would indicate a quadtree-based map coordinate
1230  * system.
1231  */
1232 static gchar*
1233 map_construct_url(RepoData *repo, gint zoom, gint tilex, gint tiley)
1234 {
1235     gchar *retval;
1236     vprintf("%s(%p, %d, %d, %d)\n", __PRETTY_FUNCTION__,
1237             repo, zoom, tilex, tiley);
1238     switch(repo->type)
1239     {
1240         case REPOTYPE_XYZ:
1241             retval = g_strdup_printf(repo->url,
1242                     tilex, tiley,  zoom - (MAX_ZOOM - 16));
1243             break;
1244
1245         case REPOTYPE_XYZ_INV:
1246             retval = g_strdup_printf(repo->url,
1247                     MAX_ZOOM + 1 - zoom, tilex, tiley);
1248             break;
1249
1250         case REPOTYPE_XYZ_SIGNED:
1251             retval = g_strdup_printf(repo->url,
1252                     tilex,
1253                     (1 << (MAX_ZOOM - zoom)) - tiley - 1,
1254                     zoom - (MAX_ZOOM - 17));
1255             break;
1256
1257         case REPOTYPE_QUAD_QRST:
1258         {
1259             gchar location[MAX_ZOOM + 2];
1260             map_convert_coords_to_quadtree_string(
1261                     tilex, tiley, zoom, location, 't', "qrts");
1262             retval = g_strdup_printf(repo->url, location);
1263             break;
1264         }
1265
1266         case REPOTYPE_QUAD_ZERO:
1267         {
1268             /* This is a zero-based quadtree URI. */
1269             gchar location[MAX_ZOOM + 2];
1270             map_convert_coords_to_quadtree_string(
1271                     tilex, tiley, zoom, location, '\0', "0123");
1272             retval = g_strdup_printf(repo->url, location);
1273             break;
1274         }
1275
1276         case REPOTYPE_WMS:
1277             retval = map_convert_wms_to_wms(tilex, tiley, zoom, repo->url);
1278             break;
1279
1280         default:
1281             retval = g_strdup(repo->url);
1282             break;
1283     }
1284     vprintf("%s(): return \"%s\"\n", __PRETTY_FUNCTION__, retval);
1285     return retval;
1286 }
1287
1288 static gboolean
1289 mapdb_initiate_update_banner_idle()
1290 {
1291     if(!_download_banner && _num_downloads != _curr_download)
1292     {
1293         _download_banner = hildon_banner_show_progress(
1294                 _window, NULL, _("Processing Maps"));
1295         /* If we're not connected, then hide the banner immediately.  It will
1296          * be unhidden if/when we're connected. */
1297         if(!_conic_is_connected)
1298             gtk_widget_hide(_download_banner);
1299     }
1300     return FALSE;
1301 }
1302
1303 /**
1304  * Initiate a download of the given xyz coordinates using the given buffer
1305  * as the URL.  If the map already exists on disk, or if we are already
1306  * downloading the map, then this method does nothing.
1307  */
1308 gboolean
1309 mapdb_initiate_update(RepoData *repo, gint zoom, gint tilex, gint tiley,
1310         gint update_type, gint batch_id, gint priority,
1311         ThreadLatch *refresh_latch)
1312 {
1313     MapUpdateTask *mut;
1314     MapUpdateTask *old_mut;
1315     gboolean is_replacing = FALSE;
1316     vprintf("%s(%s, %d, %d, %d, %d)\n", __PRETTY_FUNCTION__,
1317             repo->name, zoom, tilex, tiley, update_type);
1318
1319     mut = g_slice_new(MapUpdateTask);
1320     if(!mut)
1321     {
1322         /* Could not allocate memory. */
1323         g_printerr("Out of memory in allocation of update task #%d\n",
1324                 _num_downloads + 1);
1325         return FALSE;
1326     }
1327     mut->zoom = zoom;
1328     mut->tilex = tilex;
1329     mut->tiley = tiley;
1330     mut->update_type = update_type;
1331
1332     /* Lock the mutex if this is an auto-update. */
1333     if(update_type == MAP_UPDATE_AUTO)
1334         g_mutex_lock(_mut_priority_mutex);
1335     if(NULL != (old_mut = g_hash_table_lookup(_mut_exists_table, mut)))
1336     {
1337         /* Check if new mut is in a newer batch that the old mut.
1338          * We use vfs_result to indicate a MUT that is already in the process
1339          * of being downloaded. */
1340         if(old_mut->batch_id < batch_id && old_mut->vfs_result < 0)
1341         {
1342             /* It is, so remove the old one so we can re-add this one. */
1343             g_hash_table_remove(_mut_exists_table, old_mut);
1344             g_tree_remove(_mut_priority_tree, old_mut);
1345             g_slice_free(MapUpdateTask, old_mut);
1346             is_replacing = TRUE;
1347         }
1348         else
1349         {
1350             /* It's not, so just ignore it. */
1351             if(update_type == MAP_UPDATE_AUTO)
1352                 g_mutex_unlock(_mut_priority_mutex);
1353             g_slice_free(MapUpdateTask, mut);
1354             vprintf("%s(): return FALSE (1)\n", __PRETTY_FUNCTION__);
1355             return FALSE;
1356         }
1357     }
1358
1359     g_hash_table_insert(_mut_exists_table, mut, mut);
1360
1361     mut->repo = repo;
1362     mut->refresh_latch = refresh_latch;
1363     mut->priority = priority;
1364     mut->batch_id = batch_id;
1365     mut->pixbuf = NULL;
1366     mut->vfs_result = -1;
1367
1368     g_tree_insert(_mut_priority_tree, mut, mut);
1369
1370     /* Unlock the mutex if this is an auto-update. */
1371     if(update_type == MAP_UPDATE_AUTO)
1372         g_mutex_unlock(_mut_priority_mutex);
1373
1374     if(!is_replacing)
1375     {
1376         /* Increment download count and (possibly) display banner. */
1377         if(++_num_downloads == 20 && !_download_banner)
1378             g_idle_add((GSourceFunc)mapdb_initiate_update_banner_idle, NULL);
1379
1380         /* This doesn't need to be thread-safe.  Extras in the pool don't
1381          * really make a difference. */
1382         if(g_thread_pool_get_num_threads(_mut_thread_pool)
1383                 < g_thread_pool_get_max_threads(_mut_thread_pool))
1384             g_thread_pool_push(_mut_thread_pool, (gpointer)1, NULL);
1385     }
1386
1387     vprintf("%s(): return FALSE (2)\n", __PRETTY_FUNCTION__);
1388     return FALSE;
1389 }
1390
1391 static gboolean
1392 get_next_mut(gpointer key, gpointer value, MapUpdateTask **data)
1393 {
1394     *data = key;
1395     return TRUE;
1396 }
1397
1398 gboolean
1399 thread_proc_mut()
1400 {
1401     printf("%s()\n", __PRETTY_FUNCTION__);
1402
1403     /* Make sure things are inititalized. */
1404     gnome_vfs_init();
1405
1406     while(conic_ensure_connected())
1407     {
1408         gint retries;
1409         gboolean refresh_sent = FALSE;
1410         MapUpdateTask *mut = NULL;
1411
1412         /* Get the next MUT from the mut tree. */
1413         g_mutex_lock(_mut_priority_mutex);
1414         g_tree_foreach(_mut_priority_tree, (GTraverseFunc)get_next_mut, &mut);
1415         if(!mut)
1416         {
1417             /* No more MUTs to process.  Return. */
1418             g_mutex_unlock(_mut_priority_mutex);
1419             return FALSE;
1420         }
1421         /* Mark this MUT as "in-progress". */
1422         mut->vfs_result = GNOME_VFS_NUM_ERRORS;
1423         g_tree_remove(_mut_priority_tree, mut);
1424         g_mutex_unlock(_mut_priority_mutex);
1425
1426         printf("%s(%s, %d, %d, %d)\n", __PRETTY_FUNCTION__,
1427                 mut->repo->name, mut->zoom, mut->tilex, mut->tiley);
1428
1429         if(mut->repo != _curr_repo)
1430         {
1431             /* Do nothing, except report that there is no error. */
1432             mut->vfs_result = GNOME_VFS_OK;
1433         }
1434         else if(mut->update_type == MAP_UPDATE_DELETE)
1435         {
1436             /* Easy - just delete the entry from the database.  We don't care
1437              * about failures (sorry). */
1438             if(mut->repo->db)
1439                 mapdb_delete(mut->repo, mut->zoom, mut->tilex, mut->tiley);
1440
1441             /* Report that there is no error. */
1442             mut->vfs_result = GNOME_VFS_OK;
1443         }
1444         else for(retries = INITIAL_DOWNLOAD_RETRIES; retries > 0; --retries)
1445         {
1446             gboolean exists = FALSE;
1447             gchar *src_url;
1448             gchar *bytes;
1449             gint size;
1450             GdkPixbufLoader *loader;
1451             RepoData *repo;
1452             gint zoom, tilex, tiley;
1453             GError *error = NULL;
1454
1455 #ifdef MAPDB_SQLITE
1456             /* First check for existence. */
1457             exists = mut->repo->db
1458                 ? mapdb_exists(mut->repo, mut->zoom,
1459                         mut->tilex, mut->tiley)
1460                 : FALSE;
1461             if(exists && mut->update_type == MAP_UPDATE_ADD)
1462             {
1463                 /* Map already exists, and we're not going to overwrite. */
1464                 /* Report that there is no error. */
1465                 mut->vfs_result = GNOME_VFS_OK;
1466                 break;
1467             }
1468 #else
1469             /* First check for existence. */
1470             if(mut->update_type == MAP_UPDATE_ADD)
1471             {
1472                 /* We don't want to overwrite, so check for existence. */
1473                 /* Map already exists, and we're not going to overwrite. */
1474                 if(mapdb_exists(mut->repo, mut->zoom,
1475                             mut->tilex,mut->tiley))
1476                 {
1477                     /* Report that there is no error. */
1478                     mut->vfs_result = GNOME_VFS_OK;
1479                     break;
1480                 }
1481             }
1482 #endif
1483
1484             /* First, construct the URL from which we will get the data. */
1485             src_url = map_construct_url(mut->repo, mut->zoom,
1486                     mut->tilex, mut->tiley);
1487
1488             /* Now, attempt to read the entire contents of the URL. */
1489             mut->vfs_result = gnome_vfs_read_entire_file(
1490                     src_url, &size, &bytes);
1491             g_free(src_url);
1492             if(mut->vfs_result != GNOME_VFS_OK || !bytes)
1493             {
1494                 /* Try again. */
1495                 printf("Error reading URL: %s\n",
1496                         gnome_vfs_result_to_string(mut->vfs_result));
1497                 g_free(bytes);
1498                 continue;
1499             }
1500             /* usleep(100000); DEBUG */
1501
1502             /* Attempt to parse the bytes into a pixbuf. */
1503             loader = gdk_pixbuf_loader_new();
1504             gdk_pixbuf_loader_write(loader, bytes, size, NULL);
1505             gdk_pixbuf_loader_close(loader, &error);
1506             if(error || (NULL == (mut->pixbuf = g_object_ref(
1507                         gdk_pixbuf_loader_get_pixbuf(loader)))))
1508             {
1509                 mut->vfs_result = GNOME_VFS_NUM_ERRORS;
1510                 if(mut->pixbuf)
1511                     g_object_unref(mut->pixbuf);
1512                 mut->pixbuf = NULL;
1513                 g_free(bytes);
1514                 g_object_unref(loader);
1515                 printf("Error parsing pixbuf: %s\n",
1516                         error ? error->message : "?");
1517                 continue;
1518             }
1519             g_object_unref(loader);
1520
1521             /* Copy database-relevant mut data before we release it. */
1522             repo = mut->repo;
1523             zoom = mut->zoom;
1524             tilex = mut->tilex;
1525             tiley = mut->tiley;
1526
1527             /* Pass the mut to the GTK thread for redrawing, but only if a
1528              * redraw isn't already in the pipeline. */
1529             if(mut->refresh_latch)
1530             {
1531                 /* Wait until the latch is open. */
1532                 g_mutex_lock(mut->refresh_latch->mutex);
1533                 while(!mut->refresh_latch->is_open)
1534                 {
1535                     g_cond_wait(mut->refresh_latch->cond,
1536                             mut->refresh_latch->mutex);
1537                 }
1538                 /* Latch is open.  Decrement the number of waiters and
1539                  * check if we're the last waiter to run. */
1540                 if(mut->refresh_latch->is_done_adding_tasks)
1541                 {
1542                     if(++mut->refresh_latch->num_done
1543                                 == mut->refresh_latch->num_tasks)
1544                     {
1545                         /* Last waiter.  Free the latch resources. */
1546                         g_mutex_unlock(mut->refresh_latch->mutex);
1547                         g_cond_free(mut->refresh_latch->cond);
1548                         g_mutex_free(mut->refresh_latch->mutex);
1549                         g_slice_free(ThreadLatch, mut->refresh_latch);
1550                         mut->refresh_latch = NULL;
1551                     }
1552                     else
1553                     {
1554                         /* Not the last waiter. Signal the next waiter.*/
1555                         g_cond_signal(mut->refresh_latch->cond);
1556                         g_mutex_unlock(mut->refresh_latch->mutex);
1557                     }
1558                 }
1559                 else
1560                     g_mutex_unlock(mut->refresh_latch->mutex);
1561             }
1562
1563             g_idle_add_full(G_PRIORITY_HIGH_IDLE,
1564                     (GSourceFunc)map_download_refresh_idle, mut, NULL);
1565             refresh_sent = TRUE;
1566
1567             /* DO NOT USE mut FROM THIS POINT ON. */
1568
1569             /* Also attempt to add to the database. */
1570             mapdb_update(exists, repo, zoom,
1571                     tilex, tiley, bytes, size);
1572
1573             /* Success! */
1574             g_free(bytes);
1575             break;
1576         }
1577
1578         if(!refresh_sent)
1579             g_idle_add_full(G_PRIORITY_HIGH_IDLE,
1580                     (GSourceFunc)map_download_refresh_idle, mut, NULL);
1581     }
1582
1583     vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
1584     return FALSE;
1585 }
1586
1587 guint
1588 mut_exists_hashfunc(const MapUpdateTask *a)
1589 {
1590     gint sum = a->zoom + a->tilex + a->tiley + a->update_type;
1591     return g_int_hash(&sum);
1592 }
1593
1594 gboolean
1595 mut_exists_equalfunc(const MapUpdateTask *a, const MapUpdateTask *b)
1596 {
1597     return (a->tilex == b->tilex
1598             && a->tiley == b->tiley
1599             && a->zoom == b->zoom
1600             && a->update_type == b->update_type);
1601 }
1602
1603 gint
1604 mut_priority_comparefunc(const MapUpdateTask *a, const MapUpdateTask *b)
1605 {
1606     /* The update_type enum is sorted in order of ascending priority. */
1607     gint diff = (b->update_type - a->update_type);
1608     if(diff)
1609         return diff;
1610     diff = (b->batch_id - a->batch_id); /* More recent ones first. */
1611     if(diff)
1612         return diff;
1613     diff = (a->priority - b->priority); /* Lower priority numbers first. */
1614     if(diff)
1615         return diff;
1616
1617     /* At this point, we don't care, so just pick arbitrarily. */
1618     diff = (a->tilex - b->tilex);
1619     if(diff)
1620         return diff;
1621     diff = (a->tiley - b->tiley);
1622     if(diff)
1623         return diff;
1624     return (a->zoom - b->zoom);
1625 }
1626
1627 static gboolean
1628 repoman_dialog_select(GtkWidget *widget, RepoManInfo *rmi)
1629 {
1630     printf("%s()\n", __PRETTY_FUNCTION__);
1631     gint curr_index = gtk_combo_box_get_active(GTK_COMBO_BOX(rmi->cmb_repos));
1632     gtk_notebook_set_current_page(GTK_NOTEBOOK(rmi->notebook), curr_index);
1633     vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
1634     return TRUE;
1635 }
1636
1637 static gboolean
1638 repoman_dialog_browse(GtkWidget *widget, BrowseInfo *browse_info)
1639 {
1640     GtkWidget *dialog;
1641     gchar *basename;
1642     printf("%s()\n", __PRETTY_FUNCTION__);
1643
1644     dialog = GTK_WIDGET(
1645             hildon_file_chooser_dialog_new(GTK_WINDOW(browse_info->dialog),
1646             GTK_FILE_CHOOSER_ACTION_SAVE));
1647
1648     gtk_file_chooser_set_uri(GTK_FILE_CHOOSER(dialog),
1649             gtk_entry_get_text(GTK_ENTRY(browse_info->txt)));
1650
1651     /* Work around a bug in HildonFileChooserDialog. */
1652     basename = g_path_get_basename(
1653             gtk_entry_get_text(GTK_ENTRY(browse_info->txt)));
1654     g_object_set(G_OBJECT(dialog), "autonaming", FALSE, NULL);
1655     gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), basename);
1656
1657     if(GTK_RESPONSE_OK == gtk_dialog_run(GTK_DIALOG(dialog)))
1658     {
1659         gchar *filename = gtk_file_chooser_get_filename(
1660                 GTK_FILE_CHOOSER(dialog));
1661         gtk_entry_set_text(GTK_ENTRY(browse_info->txt), filename);
1662         g_free(filename);
1663     }
1664
1665     gtk_widget_destroy(dialog);
1666
1667     vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
1668     return TRUE;
1669 }
1670
1671 static gboolean
1672 repoman_compact_complete_idle(CompactInfo *ci)
1673 {
1674     printf("%s()\n", __PRETTY_FUNCTION__);
1675
1676     gtk_widget_destroy(GTK_WIDGET(ci->banner));
1677     popup_error(ci->dialog, ci->status_msg);
1678     gtk_widget_destroy(ci->dialog);
1679     g_free(ci);
1680
1681     vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
1682     return FALSE;
1683 }
1684
1685 static void
1686 thread_repoman_compact(CompactInfo *ci)
1687 {
1688     GDBM_FILE db;
1689     printf("%s()\n", __PRETTY_FUNCTION__);
1690
1691     if(!(db = gdbm_open((gchar*)ci->db_filename, 0, GDBM_WRITER | GDBM_FAST,
1692                     0644, NULL)))
1693         ci->status_msg = _("Failed to open map database for compacting.");
1694     else
1695     {
1696         if(gdbm_reorganize(db))
1697             ci->status_msg = _("An error occurred while trying to "
1698                         "compact the database.");
1699         else
1700             ci->status_msg = _("Successfully compacted database.");
1701         gdbm_close(db);
1702     }
1703
1704     g_idle_add((GSourceFunc)repoman_compact_complete_idle, ci);
1705
1706     vprintf("%s(): return\n", __PRETTY_FUNCTION__);
1707 }
1708
1709 static void
1710 repoman_dialog_compact(GtkWidget *widget, BrowseInfo *browse_info)
1711 {
1712     CompactInfo *ci;
1713     GtkWidget *sw;
1714     printf("%s()\n", __PRETTY_FUNCTION__);
1715
1716     ci = g_new0(CompactInfo, 1);
1717
1718     ci->dialog = gtk_dialog_new_with_buttons(_("Compact Database"),
1719             GTK_WINDOW(browse_info->dialog), GTK_DIALOG_MODAL,
1720             GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1721             GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1722             NULL);
1723
1724     sw = gtk_scrolled_window_new (NULL, NULL);
1725     gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
1726             GTK_SHADOW_ETCHED_IN);
1727     gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1728             GTK_POLICY_NEVER,
1729             GTK_POLICY_ALWAYS);
1730     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(ci->dialog)->vbox),
1731             sw, TRUE, TRUE, 0);
1732
1733     gtk_container_add(GTK_CONTAINER(sw), ci->txt = gtk_text_view_new());
1734     gtk_text_view_set_editable(GTK_TEXT_VIEW(ci->txt), FALSE);
1735     gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(ci->txt), FALSE);
1736     gtk_text_buffer_set_text(
1737             gtk_text_view_get_buffer(GTK_TEXT_VIEW(ci->txt)),
1738             _("Generally, deleted maps create an empty space in the "
1739                 "database that is later reused when downloading new maps.  "
1740                 "Compacting the database reorganizes it such that all "
1741                 "that blank space is eliminated.  This is the only way "
1742                 "that the size of the database can decrease.\n"
1743                 "This reorganization requires creating a new file and "
1744                 "inserting all the maps in the old database file into the "
1745                 "new file. The new file is then renamed to the same name "
1746                 "as the old file and dbf is updated to contain all the "
1747                 "correct information about the new file.  Note that this "
1748                 "can require free space on disk of an amount up to the size "
1749                 "of the map database.\n"
1750                 "This process may take several minutes, especially if "
1751                 "your map database is large.  As a rough estimate, you can "
1752                 "expect to wait approximately 2-5 seconds per megabyte of "
1753                 "map data (34-85 minutes per gigabyte).  There is no progress "
1754                 "indicator, although you can watch the new file grow in any "
1755                 "file manager.  Do not attempt to close Maemo Mapper while "
1756                 "the compacting operation is in progress."),
1757             -1);
1758     {
1759         GtkTextIter iter;
1760         gtk_text_buffer_get_iter_at_offset(
1761                 gtk_text_view_get_buffer(GTK_TEXT_VIEW(ci->txt)),
1762                 &iter, 0);
1763         gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(ci->txt),
1764                 &iter, 0.0, FALSE, 0, 0);
1765     }
1766
1767     gtk_widget_set_size_request(GTK_WIDGET(sw), 600, 200);
1768     gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(ci->txt), GTK_WRAP_WORD);
1769
1770     gtk_widget_show_all(ci->dialog);
1771
1772     if(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(ci->dialog)))
1773     {
1774         gtk_widget_set_sensitive(GTK_DIALOG(ci->dialog)->action_area, FALSE);
1775         ci->db_filename = gtk_entry_get_text(GTK_ENTRY(browse_info->txt));
1776         ci->banner = hildon_banner_show_animation(ci->dialog, NULL,
1777                 _("Compacting database..."));
1778
1779         g_thread_create((GThreadFunc)thread_repoman_compact, ci, FALSE, NULL);
1780     }
1781     else
1782     {
1783         gtk_widget_destroy(ci->dialog);
1784     }
1785
1786     vprintf("%s(): return\n", __PRETTY_FUNCTION__);
1787 }
1788
1789 static gboolean
1790 repoman_dialog_rename(GtkWidget *widget, RepoManInfo *rmi)
1791 {
1792     static GtkWidget *hbox = NULL;
1793     static GtkWidget *label = NULL;
1794     static GtkWidget *txt_name = NULL;
1795     static GtkWidget *dialog = NULL;
1796     printf("%s()\n", __PRETTY_FUNCTION__);
1797
1798     if(dialog == NULL)
1799     {
1800         dialog = gtk_dialog_new_with_buttons(_("New Name"),
1801                 GTK_WINDOW(rmi->dialog), GTK_DIALOG_MODAL,
1802                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1803                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1804                 NULL);
1805
1806         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
1807                 hbox = gtk_hbox_new(FALSE, 4), FALSE, FALSE, 4);
1808
1809         gtk_box_pack_start(GTK_BOX(hbox),
1810                 label = gtk_label_new(_("Name")),
1811                 FALSE, FALSE, 0);
1812         gtk_box_pack_start(GTK_BOX(hbox),
1813                 txt_name = gtk_entry_new(),
1814                 TRUE, TRUE, 0);
1815     }
1816
1817     {
1818         gint active = gtk_combo_box_get_active(GTK_COMBO_BOX(rmi->cmb_repos));
1819         RepoEditInfo *rei = g_list_nth_data(rmi->repo_edits, active);
1820         gtk_entry_set_text(GTK_ENTRY(txt_name), rei->name);
1821     }
1822
1823     gtk_widget_show_all(dialog);
1824
1825     while(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog)))
1826     {
1827         gint active = gtk_combo_box_get_active(GTK_COMBO_BOX(rmi->cmb_repos));
1828         RepoEditInfo *rei = g_list_nth_data(rmi->repo_edits, active);
1829         g_free(rei->name);
1830         rei->name = g_strdup(gtk_entry_get_text(GTK_ENTRY(txt_name)));
1831         gtk_combo_box_insert_text(GTK_COMBO_BOX(rmi->cmb_repos),
1832                 active, g_strdup(rei->name));
1833         gtk_combo_box_set_active(GTK_COMBO_BOX(rmi->cmb_repos), active);
1834         gtk_combo_box_remove_text(GTK_COMBO_BOX(rmi->cmb_repos), active + 1);
1835         break;
1836     }
1837
1838     gtk_widget_hide(dialog);
1839
1840     vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
1841     return TRUE;
1842 }
1843
1844 static void
1845 repoman_delete(RepoManInfo *rmi, gint index)
1846 {
1847     gtk_combo_box_remove_text(GTK_COMBO_BOX(rmi->cmb_repos), index);
1848     gtk_notebook_remove_page(GTK_NOTEBOOK(rmi->notebook), index);
1849     rmi->repo_edits = g_list_remove_link(
1850             rmi->repo_edits,
1851             g_list_nth(rmi->repo_edits, index));
1852 }
1853
1854 static gboolean
1855 repoman_dialog_delete(GtkWidget *widget, RepoManInfo *rmi, gint index)
1856 {
1857     gchar buffer[100];
1858     GtkWidget *confirm;
1859     printf("%s()\n", __PRETTY_FUNCTION__);
1860
1861     if(gtk_tree_model_iter_n_children(GTK_TREE_MODEL(
1862                     gtk_combo_box_get_model(GTK_COMBO_BOX(rmi->cmb_repos))),
1863                                 NULL) <= 1)
1864     {
1865         popup_error(rmi->dialog,
1866                 _("Cannot delete the last repository - there must be at"
1867                 " lease one repository."));
1868         vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
1869         return TRUE;
1870     }
1871
1872     snprintf(buffer, sizeof(buffer), "%s:\n%s\n",
1873             _("Confirm delete of repository"),
1874             gtk_combo_box_get_active_text(GTK_COMBO_BOX(rmi->cmb_repos)));
1875
1876     confirm = hildon_note_new_confirmation(GTK_WINDOW(rmi->dialog),buffer);
1877
1878     if(GTK_RESPONSE_OK == gtk_dialog_run(GTK_DIALOG(confirm)))
1879     {
1880         gint active = gtk_combo_box_get_active(GTK_COMBO_BOX(rmi->cmb_repos));
1881         repoman_delete(rmi, active);
1882         gtk_combo_box_set_active(GTK_COMBO_BOX(rmi->cmb_repos),
1883                 MAX(0, index - 1));
1884     }
1885
1886     gtk_widget_destroy(confirm);
1887
1888     return TRUE;
1889 }
1890
1891 static RepoEditInfo*
1892 repoman_dialog_add_repo(RepoManInfo *rmi, gchar *name)
1893 {
1894     GtkWidget *vbox;
1895     GtkWidget *table;
1896     GtkWidget *label;
1897     GtkWidget *hbox;
1898     RepoEditInfo *rei = g_new(RepoEditInfo, 1);
1899     printf("%s(%s)\n", __PRETTY_FUNCTION__, name);
1900
1901     rei->name = name;
1902
1903     /* Maps page. */
1904     gtk_notebook_append_page(GTK_NOTEBOOK(rmi->notebook),
1905             vbox = gtk_vbox_new(FALSE, 4),
1906             gtk_label_new(name));
1907
1908     /* Prevent destruction of notebook page, because the destruction causes
1909      * a seg fault (!?!?) */
1910     gtk_object_ref(GTK_OBJECT(vbox));
1911
1912     gtk_box_pack_start(GTK_BOX(vbox),
1913             table = gtk_table_new(2, 2, FALSE),
1914             FALSE, FALSE, 0);
1915     /* Map download URI. */
1916     gtk_table_attach(GTK_TABLE(table),
1917             label = gtk_label_new(_("URL Format")),
1918             0, 1, 0, 1, GTK_FILL, 0, 2, 0);
1919     gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
1920     gtk_table_attach(GTK_TABLE(table),
1921             rei->txt_url = gtk_entry_new(),
1922             1, 2, 0, 1, GTK_EXPAND | GTK_FILL, 0, 2, 0);
1923
1924     /* Map Directory. */
1925     gtk_table_attach(GTK_TABLE(table),
1926             label = gtk_label_new(_("Cache DB")),
1927             0, 1, 1, 2, GTK_FILL, 0, 2, 0);
1928     gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
1929     gtk_table_attach(GTK_TABLE(table),
1930             hbox = gtk_hbox_new(FALSE, 4),
1931             1, 2, 1, 2, GTK_EXPAND | GTK_FILL, 0, 2, 0);
1932     gtk_box_pack_start(GTK_BOX(hbox),
1933             rei->txt_db_filename = gtk_entry_new(),
1934             TRUE, TRUE, 0);
1935     gtk_box_pack_start(GTK_BOX(hbox),
1936             rei->btn_browse = gtk_button_new_with_label(_("Browse...")),
1937             FALSE, FALSE, 0);
1938     gtk_box_pack_start(GTK_BOX(hbox),
1939             rei->btn_compact = gtk_button_new_with_label(_("Compact...")),
1940             FALSE, FALSE, 0);
1941
1942     /* Initialize cache dir */
1943     {
1944         gchar buffer[BUFFER_SIZE];
1945         snprintf(buffer, sizeof(buffer), "%s.db", name);
1946         gchar *db_base = gnome_vfs_expand_initial_tilde(
1947                 REPO_DEFAULT_CACHE_BASE);
1948         gchar *db_filename = gnome_vfs_uri_make_full_from_relative(
1949                 db_base, buffer);
1950         gtk_entry_set_text(GTK_ENTRY(rei->txt_db_filename), db_filename);
1951         g_free(db_filename);
1952         g_free(db_base);
1953     }
1954
1955     gtk_box_pack_start(GTK_BOX(vbox),
1956             table = gtk_table_new(3, 2, FALSE),
1957             FALSE, FALSE, 0);
1958
1959     /* Download Zoom Steps. */
1960     gtk_table_attach(GTK_TABLE(table),
1961             label = gtk_label_new(_("Download Zoom Steps")),
1962             0, 1, 0, 1, GTK_FILL, 0, 2, 0);
1963     gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
1964     gtk_table_attach(GTK_TABLE(table),
1965             label = gtk_alignment_new(0.f, 0.5f, 0.f, 0.f),
1966             1, 2, 0, 1, GTK_FILL, 0, 2, 0);
1967     gtk_container_add(GTK_CONTAINER(label),
1968             rei->num_dl_zoom_steps = hildon_controlbar_new());
1969     hildon_controlbar_set_range(
1970             HILDON_CONTROLBAR(rei->num_dl_zoom_steps), 1, 4);
1971     hildon_controlbar_set_value(HILDON_CONTROLBAR(rei->num_dl_zoom_steps),
1972             REPO_DEFAULT_DL_ZOOM_STEPS);
1973     force_min_visible_bars(HILDON_CONTROLBAR(rei->num_dl_zoom_steps), 1);
1974
1975     /* Download Zoom Steps. */
1976     gtk_table_attach(GTK_TABLE(table),
1977             label = gtk_label_new(_("View Zoom Steps")),
1978             0, 1, 1, 2, GTK_FILL, 0, 2, 0);
1979     gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
1980     gtk_table_attach(GTK_TABLE(table),
1981             label = gtk_alignment_new(0.f, 0.5f, 0.f, 0.f),
1982             1, 2, 1, 2, GTK_FILL, 0, 2, 0);
1983     gtk_container_add(GTK_CONTAINER(label),
1984             rei->num_view_zoom_steps = hildon_controlbar_new());
1985     hildon_controlbar_set_range(
1986             HILDON_CONTROLBAR(rei->num_view_zoom_steps), 1, 4);
1987     hildon_controlbar_set_value(HILDON_CONTROLBAR(rei->num_view_zoom_steps),
1988             REPO_DEFAULT_VIEW_ZOOM_STEPS);
1989     force_min_visible_bars(HILDON_CONTROLBAR(rei->num_view_zoom_steps), 1);
1990
1991     gtk_table_attach(GTK_TABLE(table),
1992             label = gtk_vseparator_new(),
1993             2, 3, 0, 2, GTK_FILL, GTK_FILL, 4, 0);
1994
1995     /* Double-size. */
1996     gtk_table_attach(GTK_TABLE(table),
1997             rei->chk_double_size = gtk_check_button_new_with_label(
1998                 _("Double Pixels")),
1999             3, 4, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
2000     gtk_toggle_button_set_active(
2001             GTK_TOGGLE_BUTTON(rei->chk_double_size), FALSE);
2002
2003     /* Next-able */
2004     gtk_table_attach(GTK_TABLE(table),
2005             rei->chk_nextable = gtk_check_button_new_with_label(
2006                 _("Next-able")),
2007             3, 4, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
2008     gtk_toggle_button_set_active(
2009             GTK_TOGGLE_BUTTON(rei->chk_nextable), TRUE);
2010
2011     /* Downloadable Zoom Levels. */
2012     gtk_table_attach(GTK_TABLE(table),
2013             label = gtk_label_new(_("Downloadable Zooms:")),
2014             0, 1, 2, 3, GTK_FILL, 0, 2, 0);
2015     gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
2016     gtk_table_attach(GTK_TABLE(table),
2017             label = gtk_alignment_new(0.f, 0.5f, 0.f, 0.f),
2018             1, 4, 2, 3, GTK_FILL, 0, 2, 0);
2019     gtk_container_add(GTK_CONTAINER(label),
2020             hbox = gtk_hbox_new(FALSE, 4));
2021     gtk_box_pack_start(GTK_BOX(hbox),
2022             label = gtk_label_new(_("Min.")),
2023             TRUE, TRUE, 0);
2024     gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
2025     gtk_box_pack_start(GTK_BOX(hbox),
2026             rei->num_min_zoom = hildon_number_editor_new(MIN_ZOOM, MAX_ZOOM),
2027             FALSE, FALSE, 0);
2028     hildon_number_editor_set_value(HILDON_NUMBER_EDITOR(rei->num_min_zoom), 4);
2029     gtk_box_pack_start(GTK_BOX(hbox),
2030             label = gtk_label_new(""),
2031             TRUE, TRUE, 4);
2032     gtk_box_pack_start(GTK_BOX(hbox),
2033             label = gtk_label_new(_("Max.")),
2034             TRUE, TRUE, 0);
2035     gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
2036     gtk_box_pack_start(GTK_BOX(hbox),
2037             rei->num_max_zoom = hildon_number_editor_new(MIN_ZOOM, MAX_ZOOM),
2038             FALSE, FALSE, 0);
2039     hildon_number_editor_set_value(HILDON_NUMBER_EDITOR(rei->num_max_zoom),20);
2040
2041     rmi->repo_edits = g_list_append(rmi->repo_edits, rei);
2042
2043     /* Connect signals. */
2044     rei->browse_info.dialog = rmi->dialog;
2045     rei->browse_info.txt = rei->txt_db_filename;
2046     g_signal_connect(G_OBJECT(rei->btn_browse), "clicked",
2047                       G_CALLBACK(repoman_dialog_browse),
2048                       &rei->browse_info);
2049     g_signal_connect(G_OBJECT(rei->btn_compact), "clicked",
2050                       G_CALLBACK(repoman_dialog_compact),
2051                       &rei->browse_info);
2052
2053     gtk_widget_show_all(vbox);
2054
2055     gtk_combo_box_append_text(GTK_COMBO_BOX(rmi->cmb_repos), name);
2056     gtk_combo_box_set_active(GTK_COMBO_BOX(rmi->cmb_repos),
2057             gtk_tree_model_iter_n_children(GTK_TREE_MODEL(
2058                     gtk_combo_box_get_model(GTK_COMBO_BOX(rmi->cmb_repos))),
2059                 NULL) - 1);
2060
2061     vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
2062     return rei;
2063 }
2064
2065 static gboolean
2066 repoman_dialog_new(GtkWidget *widget, RepoManInfo *rmi)
2067 {
2068     static GtkWidget *hbox = NULL;
2069     static GtkWidget *label = NULL;
2070     static GtkWidget *txt_name = NULL;
2071     static GtkWidget *dialog = NULL;
2072     printf("%s()\n", __PRETTY_FUNCTION__);
2073
2074     if(dialog == NULL)
2075     {
2076         dialog = gtk_dialog_new_with_buttons(_("New Repository"),
2077                 GTK_WINDOW(rmi->dialog), GTK_DIALOG_MODAL,
2078                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
2079                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
2080                 NULL);
2081
2082         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
2083                 hbox = gtk_hbox_new(FALSE, 4), FALSE, FALSE, 4);
2084
2085         gtk_box_pack_start(GTK_BOX(hbox),
2086                 label = gtk_label_new(_("Name")),
2087                 FALSE, FALSE, 0);
2088         gtk_box_pack_start(GTK_BOX(hbox),
2089                 txt_name = gtk_entry_new(),
2090                 TRUE, TRUE, 0);
2091     }
2092
2093     gtk_entry_set_text(GTK_ENTRY(txt_name), "");
2094
2095     gtk_widget_show_all(dialog);
2096
2097     while(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog)))
2098     {
2099         repoman_dialog_add_repo(rmi,
2100                 g_strdup(gtk_entry_get_text(GTK_ENTRY(txt_name))));
2101         break;
2102     }
2103
2104     gtk_widget_hide(dialog);
2105
2106     vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
2107     return TRUE;
2108 }
2109
2110 static gboolean
2111 repoman_reset(GtkWidget *widget, RepoManInfo *rmi)
2112 {
2113     GtkWidget *confirm;
2114     printf("%s()\n", __PRETTY_FUNCTION__);
2115
2116     confirm = hildon_note_new_confirmation(GTK_WINDOW(rmi->dialog),
2117             _("Replace all repositories with the default repository?"));
2118
2119     if(GTK_RESPONSE_OK == gtk_dialog_run(GTK_DIALOG(confirm)))
2120     {
2121         /* First, delete all existing repositories. */
2122         while(rmi->repo_edits)
2123             repoman_delete(rmi, 0);
2124
2125         /* Now, add the default repository. */
2126         repoman_dialog_add_repo(rmi, REPO_DEFAULT_NAME);
2127         gtk_entry_set_text(
2128                 GTK_ENTRY(((RepoEditInfo*)rmi->repo_edits->data)->txt_url),
2129                 REPO_DEFAULT_MAP_URI);
2130
2131         gtk_combo_box_set_active(GTK_COMBO_BOX(rmi->cmb_repos), 0);
2132     }
2133     gtk_widget_destroy(confirm);
2134
2135     vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
2136     return TRUE;
2137 }
2138
2139 static gboolean
2140 repoman_download(GtkWidget *widget, RepoManInfo *rmi)
2141 {
2142     GtkWidget *confirm;
2143     printf("%s()\n", __PRETTY_FUNCTION__);
2144
2145     confirm = hildon_note_new_confirmation(
2146             GTK_WINDOW(rmi->dialog),
2147             _("Maemo Mapper will now download and add a list of "
2148                 "possibly-duplicate repositories from the internet.  "
2149                 "Continue?"));
2150
2151     if(GTK_RESPONSE_OK == gtk_dialog_run(GTK_DIALOG(confirm)))
2152     {
2153         gchar *bytes;
2154         gchar *head;
2155         gchar *tail;
2156         gint size;
2157         GnomeVFSResult vfs_result;
2158         printf("%s()\n", __PRETTY_FUNCTION__);
2159
2160         /* Get repo config file from www.gnuite.com. */
2161         if(GNOME_VFS_OK != (vfs_result = gnome_vfs_read_entire_file(
2162                     "http://www.gnuite.com/nokia770/maemo-mapper/repos.txt",
2163                     &size, &bytes)))
2164         {
2165             popup_error(rmi->dialog,
2166                     _("An error occurred while retrieving the repositories.  "
2167                         "The web service may be temporarily down."));
2168             g_printerr("Error while download repositories: %s\n",
2169                     gnome_vfs_result_to_string(vfs_result));
2170         }
2171         /* Parse each line as a reposotory. */
2172         else
2173         {
2174             for(head = bytes; head && *head; head = tail)
2175             {
2176                 gchar buffer[BUFFER_SIZE];
2177                 RepoData *rd;
2178                 RepoEditInfo *rei;
2179                 tail = strchr(head, '\n');
2180                 *tail++ = '\0';
2181
2182                 rd = settings_parse_repo(head);
2183                 snprintf(buffer, sizeof(buffer), "%s.db", rd->db_filename);
2184                 rei = repoman_dialog_add_repo(
2185                         rmi, g_strdup(rd->name));
2186                 /* Initialize fields with data from the RepoData object. */
2187                 gtk_entry_set_text(GTK_ENTRY(rei->txt_url), rd->url);
2188                 gtk_entry_set_text(GTK_ENTRY(rei->txt_db_filename), buffer);
2189                 hildon_controlbar_set_value(
2190                         HILDON_CONTROLBAR(rei->num_dl_zoom_steps),
2191                         rd->dl_zoom_steps);
2192                 hildon_controlbar_set_value(
2193                         HILDON_CONTROLBAR(rei->num_view_zoom_steps),
2194                         rd->view_zoom_steps);
2195                 gtk_toggle_button_set_active(
2196                         GTK_TOGGLE_BUTTON(rei->chk_double_size),
2197                         rd->double_size);
2198                 gtk_toggle_button_set_active(
2199                         GTK_TOGGLE_BUTTON(rei->chk_nextable),
2200                         rd->nextable);
2201                 hildon_number_editor_set_value(
2202                         HILDON_NUMBER_EDITOR(rei->num_min_zoom),
2203                         rd->min_zoom);
2204                 hildon_number_editor_set_value(
2205                         HILDON_NUMBER_EDITOR(rei->num_max_zoom),
2206                         rd->max_zoom);
2207             }
2208             g_free(bytes);
2209         }
2210     }
2211     gtk_widget_destroy(confirm);
2212
2213     vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
2214     return TRUE;
2215 }
2216
2217 gboolean
2218 repoman_dialog()
2219 {
2220     static RepoManInfo rmi;
2221     static GtkWidget *dialog = NULL;
2222     static GtkWidget *hbox = NULL;
2223     static GtkWidget *btn_rename = NULL;
2224     static GtkWidget *btn_delete = NULL;
2225     static GtkWidget *btn_new = NULL;
2226     static GtkWidget *btn_reset = NULL;
2227     static GtkWidget *btn_download = NULL;
2228     gint i, curr_repo_index = 0;
2229     GList *curr;
2230     printf("%s()\n", __PRETTY_FUNCTION__);
2231
2232     if(dialog == NULL)
2233     {
2234         rmi.dialog = dialog = gtk_dialog_new_with_buttons(
2235                 _("Manage Repositories"),
2236                 GTK_WINDOW(_window), GTK_DIALOG_MODAL,
2237                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
2238                 NULL);
2239
2240         /* Enable the help button. */
2241 #ifndef LEGACY
2242         hildon_help_dialog_help_enable(
2243 #else
2244         ossohelp_dialog_help_enable(
2245 #endif
2246                 GTK_DIALOG(dialog), HELP_ID_REPOMAN, _osso);
2247
2248         /* Reset button. */
2249         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area),
2250                 btn_reset = gtk_button_new_with_label(_("Reset...")));
2251         g_signal_connect(G_OBJECT(btn_reset), "clicked",
2252                           G_CALLBACK(repoman_reset), &rmi);
2253
2254         /* Download button. */
2255         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area),
2256                 btn_download = gtk_button_new_with_label(_("Download...")));
2257         g_signal_connect(G_OBJECT(btn_download), "clicked",
2258                           G_CALLBACK(repoman_download), &rmi);
2259
2260         /* Cancel button. */
2261         gtk_dialog_add_button(GTK_DIALOG(dialog),
2262                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT);
2263
2264         hbox = gtk_hbox_new(FALSE, 4);
2265
2266         gtk_box_pack_start(GTK_BOX(hbox),
2267                 rmi.cmb_repos = gtk_combo_box_new_text(), TRUE, TRUE, 4);
2268
2269         gtk_box_pack_start(GTK_BOX(hbox),
2270                 gtk_vseparator_new(), FALSE, FALSE, 4);
2271         gtk_box_pack_start(GTK_BOX(hbox),
2272                 btn_rename = gtk_button_new_with_label(_("Rename...")),
2273                 FALSE, FALSE, 4);
2274         gtk_box_pack_start(GTK_BOX(hbox),
2275                 btn_delete = gtk_button_new_with_label(_("Delete...")),
2276                 FALSE, FALSE, 4);
2277         gtk_box_pack_start(GTK_BOX(hbox),
2278                 btn_new = gtk_button_new_with_label(_("New...")),
2279                 FALSE, FALSE, 4);
2280
2281         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
2282                 hbox, FALSE, FALSE, 4);
2283
2284         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
2285                 gtk_hseparator_new(), TRUE, TRUE, 4);
2286         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
2287                 rmi.notebook = gtk_notebook_new(), TRUE, TRUE, 4);
2288
2289         gtk_notebook_set_show_tabs(GTK_NOTEBOOK(rmi.notebook), FALSE);
2290         gtk_notebook_set_show_border(GTK_NOTEBOOK(rmi.notebook), FALSE);
2291
2292         rmi.repo_edits = NULL;
2293
2294         /* Connect signals. */
2295         g_signal_connect(G_OBJECT(btn_rename), "clicked",
2296                 G_CALLBACK(repoman_dialog_rename), &rmi);
2297         g_signal_connect(G_OBJECT(btn_delete), "clicked",
2298                 G_CALLBACK(repoman_dialog_delete), &rmi);
2299         g_signal_connect(G_OBJECT(btn_new), "clicked",
2300                 G_CALLBACK(repoman_dialog_new), &rmi);
2301         g_signal_connect(G_OBJECT(rmi.cmb_repos), "changed",
2302                 G_CALLBACK(repoman_dialog_select), &rmi);
2303     }
2304
2305     /* Populate combo box and pages in notebook. */
2306     for(i = 0, curr = _repo_list; curr; curr = curr->next, i++)
2307     {
2308         RepoData *rd = (RepoData*)curr->data;
2309         RepoEditInfo *rei = repoman_dialog_add_repo(&rmi, g_strdup(rd->name));
2310
2311         /* Initialize fields with data from the RepoData object. */
2312         gtk_entry_set_text(GTK_ENTRY(rei->txt_url), rd->url);
2313         gtk_entry_set_text(GTK_ENTRY(rei->txt_db_filename),
2314                 rd->db_filename);
2315         hildon_controlbar_set_value(
2316                 HILDON_CONTROLBAR(rei->num_dl_zoom_steps),
2317                 rd->dl_zoom_steps);
2318         hildon_controlbar_set_value(
2319                 HILDON_CONTROLBAR(rei->num_view_zoom_steps),
2320                 rd->view_zoom_steps);
2321         gtk_toggle_button_set_active(
2322                 GTK_TOGGLE_BUTTON(rei->chk_double_size),
2323                 rd->double_size);
2324         gtk_toggle_button_set_active(
2325                 GTK_TOGGLE_BUTTON(rei->chk_nextable),
2326                 rd->nextable);
2327         hildon_number_editor_set_value(
2328                 HILDON_NUMBER_EDITOR(rei->num_min_zoom),
2329                 rd->min_zoom);
2330         hildon_number_editor_set_value(
2331                 HILDON_NUMBER_EDITOR(rei->num_max_zoom),
2332                 rd->max_zoom);
2333         if(rd == _curr_repo)
2334             curr_repo_index = i;
2335     }
2336
2337     gtk_combo_box_set_active(GTK_COMBO_BOX(rmi.cmb_repos), curr_repo_index);
2338     gtk_notebook_set_current_page(GTK_NOTEBOOK(rmi.notebook), curr_repo_index);
2339
2340     gtk_widget_show_all(dialog);
2341
2342     while(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog)))
2343     {
2344         /* Iterate through repos and verify each. */
2345         gboolean verified = TRUE;
2346         gint i;
2347         GList *curr;
2348         gchar *old_curr_repo_name = _curr_repo->name;
2349
2350         for(i = 0, curr = rmi.repo_edits; curr; curr = curr->next, i++)
2351         {
2352             /* Check the ranges for the min and max zoom levels. */
2353             RepoEditInfo *rei = curr->data;
2354             if(hildon_number_editor_get_value(
2355                         HILDON_NUMBER_EDITOR(rei->num_max_zoom))
2356                  < hildon_number_editor_get_value(
2357                         HILDON_NUMBER_EDITOR(rei->num_min_zoom)))
2358             {
2359                 verified = FALSE;
2360                 break;
2361             }
2362         }
2363         if(!verified)
2364         {
2365             gtk_combo_box_set_active(GTK_COMBO_BOX(rmi.cmb_repos), i);
2366             popup_error(dialog,
2367                     _("Minimum Downloadable Zoom must be less than "
2368                         "Maximum Downloadable Zoom."));
2369             continue;
2370         }
2371
2372         /* We're good to replace.  Remove old _repo_list menu items. */
2373         menu_maps_remove_repos();
2374         /* But keep the repo list in memory, in case downloads are using it. */
2375         _repo_list = NULL;
2376
2377         /* Write new _repo_list. */
2378         curr_repo_index = gtk_combo_box_get_active(
2379                 GTK_COMBO_BOX(rmi.cmb_repos));
2380         _curr_repo = NULL;
2381         for(i = 0, curr = rmi.repo_edits; curr; curr = curr->next, i++)
2382         {
2383             RepoEditInfo *rei = curr->data;
2384             RepoData *rd = g_new(RepoData, 1);
2385             rd->name = g_strdup(rei->name);
2386             rd->url = g_strdup(gtk_entry_get_text(GTK_ENTRY(rei->txt_url)));
2387             rd->db_filename = gnome_vfs_expand_initial_tilde(
2388                     gtk_entry_get_text(GTK_ENTRY(rei->txt_db_filename)));
2389             rd->dl_zoom_steps = hildon_controlbar_get_value(
2390                     HILDON_CONTROLBAR(rei->num_dl_zoom_steps));
2391             rd->view_zoom_steps = hildon_controlbar_get_value(
2392                     HILDON_CONTROLBAR(rei->num_view_zoom_steps));
2393             rd->double_size = gtk_toggle_button_get_active(
2394                     GTK_TOGGLE_BUTTON(rei->chk_double_size));
2395             rd->nextable = gtk_toggle_button_get_active(
2396                     GTK_TOGGLE_BUTTON(rei->chk_nextable));
2397             rd->min_zoom = hildon_number_editor_get_value(
2398                     HILDON_NUMBER_EDITOR(rei->num_min_zoom));
2399             rd->max_zoom = hildon_number_editor_get_value(
2400                     HILDON_NUMBER_EDITOR(rei->num_max_zoom));
2401             set_repo_type(rd);
2402
2403             _repo_list = g_list_append(_repo_list, rd);
2404
2405             if(!_curr_repo && !strcmp(old_curr_repo_name, rd->name))
2406                 repo_set_curr(rd);
2407             else if(i == curr_repo_index)
2408                 repo_set_curr(rd);
2409         }
2410         if(!_curr_repo)
2411             repo_set_curr((RepoData*)g_list_first(_repo_list)->data);
2412         menu_maps_add_repos();
2413
2414         settings_save();
2415         break;
2416     }
2417
2418     gtk_widget_hide(dialog);
2419
2420     /* Clear out the notebook entries. */
2421     while(rmi.repo_edits)
2422         repoman_delete(&rmi, 0);
2423
2424     map_set_zoom(_zoom); /* make sure we're at an appropriate zoom level. */
2425
2426     vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
2427     return TRUE;
2428 }
2429
2430 static gboolean
2431 mapman_by_area(gdouble start_lat, gdouble start_lon,
2432         gdouble end_lat, gdouble end_lon, MapmanInfo *mapman_info,
2433         MapUpdateType update_type,
2434         gint download_batch_id)
2435 {
2436     gint start_unitx, start_unity, end_unitx, end_unity;
2437     gint num_maps = 0;
2438     gint z;
2439     gchar buffer[80];
2440     GtkWidget *confirm;
2441     printf("%s(%f, %f, %f, %f)\n", __PRETTY_FUNCTION__, start_lat, start_lon,
2442             end_lat, end_lon);
2443
2444     latlon2unit(start_lat, start_lon, start_unitx, start_unity);
2445     latlon2unit(end_lat, end_lon, end_unitx, end_unity);
2446
2447     /* Swap if they specified flipped lats or lons. */
2448     if(start_unitx > end_unitx)
2449     {
2450         gint swap = start_unitx;
2451         start_unitx = end_unitx;
2452         end_unitx = swap;
2453     }
2454     if(start_unity > end_unity)
2455     {
2456         gint swap = start_unity;
2457         start_unity = end_unity;
2458         end_unity = swap;
2459     }
2460
2461     /* First, get the number of maps to download. */
2462     for(z = 0; z <= MAX_ZOOM; ++z)
2463     {
2464         if(gtk_toggle_button_get_active(
2465                     GTK_TOGGLE_BUTTON(mapman_info->chk_zoom_levels[z])))
2466         {
2467             gint start_tilex, start_tiley, end_tilex, end_tiley;
2468             start_tilex = unit2ztile(start_unitx, z);
2469             start_tiley = unit2ztile(start_unity, z);
2470             end_tilex = unit2ztile(end_unitx, z);
2471             end_tiley = unit2ztile(end_unity, z);
2472             num_maps += (end_tilex - start_tilex + 1)
2473                 * (end_tiley - start_tiley + 1);
2474         }
2475     }
2476
2477     if(update_type == MAP_UPDATE_DELETE)
2478     {
2479         snprintf(buffer, sizeof(buffer), "%s %d %s", _("Confirm DELETION of"),
2480                 num_maps, _("maps "));
2481     }
2482     else
2483     {
2484         snprintf(buffer, sizeof(buffer),
2485                 "%s %d %s\n(%s %.2f MB)\n", _("Confirm download of"),
2486                 num_maps, _("maps"), _("up to about"),
2487                 num_maps * (strstr(_curr_repo->url, "%s") ? 18e-3 : 6e-3));
2488     }
2489     confirm = hildon_note_new_confirmation(
2490             GTK_WINDOW(mapman_info->dialog), buffer);
2491
2492     if(GTK_RESPONSE_OK != gtk_dialog_run(GTK_DIALOG(confirm)))
2493     {
2494         gtk_widget_destroy(confirm);
2495         vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
2496         return FALSE;
2497     }
2498
2499     g_mutex_lock(_mut_priority_mutex);
2500     for(z = 0; z <= MAX_ZOOM; ++z)
2501     {
2502         if(gtk_toggle_button_get_active(
2503                     GTK_TOGGLE_BUTTON(mapman_info->chk_zoom_levels[z])))
2504         {
2505             gint start_tilex, start_tiley, end_tilex, end_tiley;
2506             gint tilex, tiley;
2507             start_tilex = unit2ztile(start_unitx, z);
2508             start_tiley = unit2ztile(start_unity, z);
2509             end_tilex = unit2ztile(end_unitx, z);
2510             end_tiley = unit2ztile(end_unity, z);
2511             for(tiley = start_tiley; tiley <= end_tiley; tiley++)
2512             {
2513                 for(tilex = start_tilex; tilex <= end_tilex; tilex++)
2514                 {
2515                     /* Make sure this tile is even possible. */
2516                     if((unsigned)tilex < unit2ztile(WORLD_SIZE_UNITS, z)
2517                       && (unsigned)tiley < unit2ztile(WORLD_SIZE_UNITS, z))
2518                     {
2519                         mapdb_initiate_update(_curr_repo, z, tilex, tiley,
2520                                 update_type, download_batch_id,
2521                                 (abs(tilex - unit2tile(_next_center.unitx))
2522                                  + abs(tiley - unit2tile(_next_center.unity))),
2523                                 NULL);
2524                     }
2525                 }
2526             }
2527         }
2528     }
2529     g_mutex_unlock(_mut_priority_mutex);
2530
2531     gtk_widget_destroy(confirm);
2532     vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
2533     return TRUE;
2534 }
2535
2536 static gboolean
2537 mapman_by_route(MapmanInfo *mapman_info, MapUpdateType update_type,
2538         gint download_batch_id)
2539 {
2540     GtkWidget *confirm;
2541     gint prev_tilex, prev_tiley, num_maps = 0, z;
2542     Point *curr;
2543     gchar buffer[80];
2544     gint radius = hildon_number_editor_get_value(
2545             HILDON_NUMBER_EDITOR(mapman_info->num_route_radius));
2546     printf("%s()\n", __PRETTY_FUNCTION__);
2547
2548     /* First, get the number of maps to download. */
2549     for(z = 0; z <= MAX_ZOOM; ++z)
2550     {
2551         if(gtk_toggle_button_get_active(
2552                     GTK_TOGGLE_BUTTON(mapman_info->chk_zoom_levels[z])))
2553         {
2554             prev_tilex = 0;
2555             prev_tiley = 0;
2556             for(curr = _route.head - 1; curr++ != _route.tail; )
2557             {
2558                 if(curr->unity)
2559                 {
2560                     gint tilex = unit2ztile(curr->unitx, z);
2561                     gint tiley = unit2ztile(curr->unity, z);
2562                     if(tilex != prev_tilex || tiley != prev_tiley)
2563                     {
2564                         if(prev_tiley)
2565                             num_maps += (abs((gint)tilex - prev_tilex) + 1)
2566                                 * (abs((gint)tiley - prev_tiley) + 1) - 1;
2567                         prev_tilex = tilex;
2568                         prev_tiley = tiley;
2569                     }
2570                 }
2571             }
2572         }
2573     }
2574     num_maps *= 0.625 * pow(radius + 1, 1.85);
2575
2576     if(update_type == MAP_UPDATE_DELETE)
2577     {
2578         snprintf(buffer, sizeof(buffer), "%s %s %d %s",
2579                 _("Confirm DELETION of"), _("about"),
2580                 num_maps, _("maps "));
2581     }
2582     else
2583     {
2584         snprintf(buffer, sizeof(buffer),
2585                 "%s %s %d %s\n(%s %.2f MB)\n", _("Confirm download of"),
2586                 _("about"),
2587                 num_maps, _("maps"), _("up to about"),
2588                 num_maps * (strstr(_curr_repo->url, "%s") ? 18e-3 : 6e-3));
2589     }
2590     confirm = hildon_note_new_confirmation(
2591             GTK_WINDOW(mapman_info->dialog), buffer);
2592
2593     if(GTK_RESPONSE_OK != gtk_dialog_run(GTK_DIALOG(confirm)))
2594     {
2595         gtk_widget_destroy(confirm);
2596         vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
2597         return FALSE;
2598     }
2599
2600     /* Now, do the actual download. */
2601     g_mutex_lock(_mut_priority_mutex);
2602     for(z = 0; z <= MAX_ZOOM; ++z)
2603     {
2604         if(gtk_toggle_button_get_active(
2605                     GTK_TOGGLE_BUTTON(mapman_info->chk_zoom_levels[z])))
2606         {
2607             prev_tilex = 0;
2608             prev_tiley = 0;
2609             for(curr = _route.head - 1; curr++ != _route.tail; )
2610             {
2611                 if(curr->unity)
2612                 {
2613                     gint tilex = unit2ztile(curr->unitx, z);
2614                     gint tiley = unit2ztile(curr->unity, z);
2615                     if(tilex != prev_tilex || tiley != prev_tiley)
2616                     {
2617                         gint minx, miny, maxx, maxy, x, y;
2618                         if(prev_tiley != 0)
2619                         {
2620                             minx = MIN(tilex, prev_tilex) - radius;
2621                             miny = MIN(tiley, prev_tiley) - radius;
2622                             maxx = MAX(tilex, prev_tilex) + radius;
2623                             maxy = MAX(tiley, prev_tiley) + radius;
2624                         }
2625                         else
2626                         {
2627                             minx = tilex - radius;
2628                             miny = tiley - radius;
2629                             maxx = tilex + radius;
2630                             maxy = tiley + radius;
2631                         }
2632                         for(x = minx; x <= maxx; x++)
2633                         {
2634                             for(y = miny; y <= maxy; y++)
2635                             {
2636                                 /* Make sure this tile is even possible. */
2637                                 if((unsigned)tilex
2638                                         < unit2ztile(WORLD_SIZE_UNITS, z)
2639                                   && (unsigned)tiley
2640                                         < unit2ztile(WORLD_SIZE_UNITS, z))
2641                                 {
2642                                     mapdb_initiate_update(_curr_repo, z, x, y,
2643                                         update_type, download_batch_id,
2644                                         (abs(tilex - unit2tile(
2645                                                  _next_center.unitx))
2646                                          + abs(tiley - unit2tile(
2647                                                  _next_center.unity))),
2648                                         NULL);
2649                                 }
2650                             }
2651                         }
2652                         prev_tilex = tilex;
2653                         prev_tiley = tiley;
2654                     }
2655                 }
2656             }
2657         }
2658     }
2659     g_mutex_unlock(_mut_priority_mutex);
2660     _route_dl_radius = radius;
2661     gtk_widget_destroy(confirm);
2662     vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
2663     return TRUE;
2664 }
2665
2666 static void
2667 mapman_clear(GtkWidget *widget, MapmanInfo *mapman_info)
2668 {
2669     gint z;
2670     printf("%s()\n", __PRETTY_FUNCTION__);
2671     if(gtk_notebook_get_current_page(GTK_NOTEBOOK(mapman_info->notebook)))
2672         /* This is the second page (the "Zoom" page) - clear the checks. */
2673         for(z = 0; z <= MAX_ZOOM; ++z)
2674             gtk_toggle_button_set_active(
2675                     GTK_TOGGLE_BUTTON(mapman_info->chk_zoom_levels[z]), FALSE);
2676     else
2677     {
2678         /* This is the first page (the "Area" page) - clear the text fields. */
2679         gtk_entry_set_text(GTK_ENTRY(mapman_info->txt_topleft_lat), "");
2680         gtk_entry_set_text(GTK_ENTRY(mapman_info->txt_topleft_lon), "");
2681         gtk_entry_set_text(GTK_ENTRY(mapman_info->txt_botright_lat), "");
2682         gtk_entry_set_text(GTK_ENTRY(mapman_info->txt_botright_lon), "");
2683     }
2684     vprintf("%s(): return\n", __PRETTY_FUNCTION__);
2685 }
2686
2687 void mapman_update_state(GtkWidget *widget, MapmanInfo *mapman_info)
2688 {
2689     printf("%s()\n", __PRETTY_FUNCTION__);
2690     gtk_widget_set_sensitive( mapman_info->chk_overwrite,
2691             gtk_toggle_button_get_active(
2692                 GTK_TOGGLE_BUTTON(mapman_info->rad_download)));
2693
2694     if(gtk_toggle_button_get_active(
2695                 GTK_TOGGLE_BUTTON(mapman_info->rad_by_area)))
2696         gtk_widget_show(mapman_info->tbl_area);
2697     else if(gtk_notebook_get_n_pages(GTK_NOTEBOOK(mapman_info->notebook)) == 3)
2698         gtk_widget_hide(mapman_info->tbl_area);
2699
2700     gtk_widget_set_sensitive(mapman_info->num_route_radius,
2701             gtk_toggle_button_get_active(
2702                 GTK_TOGGLE_BUTTON(mapman_info->rad_by_route)));
2703     vprintf("%s(): return\n", __PRETTY_FUNCTION__);
2704 }
2705
2706 gboolean
2707 mapman_dialog()
2708 {
2709     static GtkWidget *dialog = NULL;
2710     static GtkWidget *vbox = NULL;
2711     static GtkWidget *hbox = NULL;
2712     static GtkWidget *table = NULL;
2713     static GtkWidget *label = NULL;
2714     static GtkWidget *button = NULL;
2715     static GtkWidget *lbl_gps_lat = NULL;
2716     static GtkWidget *lbl_gps_lon = NULL;
2717     static GtkWidget *lbl_center_lat = NULL;
2718     static GtkWidget *lbl_center_lon = NULL;
2719     static MapmanInfo mapman_info;
2720     gchar buffer[80];
2721     gdouble lat, lon;
2722     gint z;
2723     printf("%s()\n", __PRETTY_FUNCTION__);
2724
2725     if(!_curr_repo->db)
2726     {
2727         popup_error(_window, "To manage maps, you must set a valid repository "
2728                 "database filename in the \"Manage Repositories\" dialog.");
2729         vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
2730         return TRUE;
2731     }
2732
2733     if(dialog == NULL)
2734     {
2735         mapman_info.dialog = dialog = gtk_dialog_new_with_buttons(
2736                 _("Manage Maps"),
2737                 GTK_WINDOW(_window), GTK_DIALOG_MODAL,
2738                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
2739                 NULL);
2740
2741         /* Enable the help button. */
2742 #ifndef LEGACY
2743         hildon_help_dialog_help_enable(
2744 #else
2745         ossohelp_dialog_help_enable(
2746 #endif
2747                 GTK_DIALOG(mapman_info.dialog), HELP_ID_MAPMAN, _osso);
2748
2749         /* Clear button. */
2750         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area),
2751                 button = gtk_button_new_with_label(_("Clear")));
2752         g_signal_connect(G_OBJECT(button), "clicked",
2753                           G_CALLBACK(mapman_clear), &mapman_info);
2754
2755         /* Cancel button. */
2756         gtk_dialog_add_button(GTK_DIALOG(dialog),
2757                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT);
2758
2759         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
2760                 mapman_info.notebook = gtk_notebook_new(), TRUE, TRUE, 0);
2761
2762         /* Setup page. */
2763         gtk_notebook_append_page(GTK_NOTEBOOK(mapman_info.notebook),
2764                 vbox = gtk_vbox_new(FALSE, 2),
2765                 label = gtk_label_new(_("Setup")));
2766         gtk_notebook_set_tab_label_packing(
2767                 GTK_NOTEBOOK(mapman_info.notebook), vbox,
2768                 FALSE, FALSE, GTK_PACK_START);
2769
2770         gtk_box_pack_start(GTK_BOX(vbox),
2771                 hbox = gtk_hbox_new(FALSE, 4),
2772                 FALSE, FALSE, 0);
2773         gtk_box_pack_start(GTK_BOX(hbox),
2774                 mapman_info.rad_download = gtk_radio_button_new_with_label(
2775                     NULL,_("Download Maps")),
2776                 FALSE, FALSE, 0);
2777         gtk_box_pack_start(GTK_BOX(hbox),
2778                 label = gtk_alignment_new(0.f, 0.5f, 0.f, 0.f),
2779                 FALSE, FALSE, 0);
2780         gtk_container_add(GTK_CONTAINER(label),
2781                 mapman_info.chk_overwrite
2782                         = gtk_check_button_new_with_label(_("Overwrite"))),
2783
2784         gtk_box_pack_start(GTK_BOX(vbox),
2785                 mapman_info.rad_delete
2786                         = gtk_radio_button_new_with_label_from_widget(
2787                             GTK_RADIO_BUTTON(mapman_info.rad_download),
2788                             _("Delete Maps")),
2789                 FALSE, FALSE, 0);
2790
2791         gtk_box_pack_start(GTK_BOX(vbox),
2792                 gtk_hseparator_new(),
2793                 FALSE, FALSE, 0);
2794
2795         gtk_box_pack_start(GTK_BOX(vbox),
2796                 mapman_info.rad_by_area
2797                         = gtk_radio_button_new_with_label(NULL,
2798                             _("By Area (see tab)")),
2799                 FALSE, FALSE, 0);
2800         gtk_box_pack_start(GTK_BOX(vbox),
2801                 hbox = gtk_hbox_new(FALSE, 4),
2802                 FALSE, FALSE, 0);
2803         gtk_box_pack_start(GTK_BOX(hbox),
2804                 mapman_info.rad_by_route
2805                         = gtk_radio_button_new_with_label_from_widget(
2806                             GTK_RADIO_BUTTON(mapman_info.rad_by_area),
2807                             _("Along Route - Radius (tiles):")),
2808                 FALSE, FALSE, 0);
2809         gtk_box_pack_start(GTK_BOX(hbox),
2810                 mapman_info.num_route_radius = hildon_number_editor_new(0,100),
2811                 FALSE, FALSE, 0);
2812         hildon_number_editor_set_value(
2813                 HILDON_NUMBER_EDITOR(mapman_info.num_route_radius),
2814                 _route_dl_radius);
2815
2816
2817         /* Zoom page. */
2818         gtk_notebook_append_page(GTK_NOTEBOOK(mapman_info.notebook),
2819                 table = gtk_table_new(5, 5, FALSE),
2820                 label = gtk_label_new(_("Zoom")));
2821         gtk_notebook_set_tab_label_packing(
2822                 GTK_NOTEBOOK(mapman_info.notebook), table,
2823                 FALSE, FALSE, GTK_PACK_START);
2824         gtk_table_attach(GTK_TABLE(table),
2825                 label = gtk_label_new(
2826                     _("Zoom Levels to Download: (0 = most detail)")),
2827                 0, 4, 0, 1, GTK_FILL, 0, 4, 0);
2828         gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
2829         snprintf(buffer, sizeof(buffer), "%d", 0);
2830         gtk_table_attach(GTK_TABLE(table),
2831                 mapman_info.chk_zoom_levels[0]
2832                         = gtk_check_button_new_with_label(buffer),
2833                 4, 5 , 0, 1, GTK_FILL, 0, 0, 0);
2834         for(z = 0; z < MAX_ZOOM; ++z)
2835         {
2836             snprintf(buffer, sizeof(buffer), "%d", z + 1);
2837             gtk_table_attach(GTK_TABLE(table),
2838                     mapman_info.chk_zoom_levels[z + 1]
2839                             = gtk_check_button_new_with_label(buffer),
2840                     z / 4, z / 4 + 1, z % 4 + 1, z % 4 + 2,
2841                     GTK_FILL, 0, 0, 0);
2842         }
2843
2844         /* Area page. */
2845         gtk_notebook_append_page(GTK_NOTEBOOK(mapman_info.notebook),
2846             mapman_info.tbl_area = gtk_table_new(5, 3, FALSE),
2847             label = gtk_label_new(_("Area")));
2848
2849         /* Label Columns. */
2850         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
2851                 label = gtk_label_new(_("Latitude")),
2852                 1, 2, 0, 1, GTK_FILL, 0, 4, 0);
2853         gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
2854         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
2855                 label = gtk_label_new(_("Longitude")),
2856                 2, 3, 0, 1, GTK_FILL, 0, 4, 0);
2857         gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
2858
2859         /* GPS. */
2860         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
2861                 label = gtk_label_new(_("GPS Location")),
2862                 0, 1, 1, 2, GTK_FILL, 0, 4, 0);
2863         gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
2864         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
2865                 lbl_gps_lat = gtk_label_new(""),
2866                 1, 2, 1, 2, GTK_FILL, 0, 4, 0);
2867         gtk_label_set_selectable(GTK_LABEL(lbl_gps_lat), TRUE);
2868         gtk_misc_set_alignment(GTK_MISC(lbl_gps_lat), 1.f, 0.5f);
2869         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
2870                 lbl_gps_lon = gtk_label_new(""),
2871                 2, 3, 1, 2, GTK_FILL, 0, 4, 0);
2872         gtk_label_set_selectable(GTK_LABEL(lbl_gps_lon), TRUE);
2873         gtk_misc_set_alignment(GTK_MISC(lbl_gps_lon), 1.f, 0.5f);
2874
2875         /* Center. */
2876         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
2877                 label = gtk_label_new(_("View Center")),
2878                 0, 1, 2, 3, GTK_FILL, 0, 4, 0);
2879         gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
2880         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
2881                 lbl_center_lat = gtk_label_new(""),
2882                 1, 2, 2, 3, GTK_FILL, 0, 4, 0);
2883         gtk_label_set_selectable(GTK_LABEL(lbl_center_lat), TRUE);
2884         gtk_misc_set_alignment(GTK_MISC(lbl_center_lat), 1.f, 0.5f);
2885         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
2886                 lbl_center_lon = gtk_label_new(""),
2887                 2, 3, 2, 3, GTK_FILL, 0, 4, 0);
2888         gtk_label_set_selectable(GTK_LABEL(lbl_center_lon), TRUE);
2889         gtk_misc_set_alignment(GTK_MISC(lbl_center_lon), 1.f, 0.5f);
2890
2891         /* default values for Top Left and Bottom Right are defined by the
2892          * rectangle of the current and the previous Center */
2893
2894         /* Top Left. */
2895         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
2896                 label = gtk_label_new(_("Top-Left")),
2897                 0, 1, 3, 4, GTK_FILL, 0, 4, 0);
2898         gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
2899         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
2900                 mapman_info.txt_topleft_lat = gtk_entry_new(),
2901                 1, 2, 3, 4, GTK_FILL, 0, 4, 0);
2902         gtk_entry_set_width_chars(GTK_ENTRY(mapman_info.txt_topleft_lat), 12);
2903         gtk_entry_set_alignment(GTK_ENTRY(mapman_info.txt_topleft_lat), 1.f);
2904         g_object_set(G_OBJECT(mapman_info.txt_topleft_lat),
2905 #ifndef LEGACY
2906                 "hildon-input-mode",
2907                 HILDON_GTK_INPUT_MODE_FULL, NULL);
2908 #else
2909                 HILDON_INPUT_MODE_HINT,
2910                 HILDON_INPUT_MODE_HINT_ALPHANUMERICSPECIAL, NULL);
2911         g_object_set(G_OBJECT(mapman_info.txt_topleft_lat),
2912                 HILDON_AUTOCAP,
2913                 FALSE, NULL);
2914 #endif
2915         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
2916                 mapman_info.txt_topleft_lon = gtk_entry_new(),
2917                 2, 3, 3, 4, GTK_FILL, 0, 4, 0);
2918         gtk_entry_set_width_chars(GTK_ENTRY(mapman_info.txt_topleft_lon), 12);
2919         gtk_entry_set_alignment(GTK_ENTRY(mapman_info.txt_topleft_lon), 1.f);
2920         g_object_set(G_OBJECT(mapman_info.txt_topleft_lon),
2921 #ifndef LEGACY
2922                 "hildon-input-mode",
2923                 HILDON_GTK_INPUT_MODE_FULL, NULL);
2924 #else
2925                 HILDON_INPUT_MODE_HINT,
2926                 HILDON_INPUT_MODE_HINT_ALPHANUMERICSPECIAL, NULL);
2927         g_object_set(G_OBJECT(mapman_info.txt_topleft_lon),
2928                 HILDON_AUTOCAP,
2929                 FALSE, NULL);
2930 #endif
2931
2932         /* Bottom Right. */
2933         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
2934                 label = gtk_label_new(_("Bottom-Right")),
2935                 0, 1, 4, 5, GTK_FILL, 0, 4, 0);
2936         gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
2937         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
2938                 mapman_info.txt_botright_lat = gtk_entry_new(),
2939                 1, 2, 4, 5, GTK_FILL, 0, 4, 0);
2940         gtk_entry_set_width_chars(GTK_ENTRY(mapman_info.txt_botright_lat), 12);
2941         gtk_entry_set_alignment(GTK_ENTRY(mapman_info.txt_botright_lat), 1.f);
2942         g_object_set(G_OBJECT(mapman_info.txt_botright_lat),
2943 #ifndef LEGACY
2944                 "hildon-input-mode",
2945                 HILDON_GTK_INPUT_MODE_FULL, NULL);
2946 #else
2947                 HILDON_INPUT_MODE_HINT,
2948                 HILDON_INPUT_MODE_HINT_ALPHANUMERICSPECIAL, NULL);
2949         g_object_set(G_OBJECT(mapman_info.txt_botright_lat),
2950                 HILDON_AUTOCAP,
2951                 FALSE, NULL);
2952 #endif
2953         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
2954                 mapman_info.txt_botright_lon = gtk_entry_new(),
2955                 2, 3, 4, 5, GTK_FILL, 0, 4, 0);
2956         gtk_entry_set_width_chars(GTK_ENTRY(mapman_info.txt_botright_lat), 12);
2957         gtk_entry_set_alignment(GTK_ENTRY(mapman_info.txt_botright_lon), 1.f);
2958         g_object_set(G_OBJECT(mapman_info.txt_botright_lon),
2959 #ifndef LEGACY
2960                 "hildon-input-mode",
2961                 HILDON_GTK_INPUT_MODE_FULL, NULL);
2962 #else
2963                 HILDON_INPUT_MODE_HINT,
2964                 HILDON_INPUT_MODE_HINT_ALPHANUMERICSPECIAL, NULL);
2965         g_object_set(G_OBJECT(mapman_info.txt_botright_lon),
2966                 HILDON_AUTOCAP,
2967                 FALSE, NULL);
2968 #endif
2969
2970         /* Default action is to download by area. */
2971         gtk_toggle_button_set_active(
2972                 GTK_TOGGLE_BUTTON(mapman_info.rad_by_area), TRUE);
2973
2974         g_signal_connect(G_OBJECT(mapman_info.rad_download), "clicked",
2975                           G_CALLBACK(mapman_update_state), &mapman_info);
2976         g_signal_connect(G_OBJECT(mapman_info.rad_delete), "clicked",
2977                           G_CALLBACK(mapman_update_state), &mapman_info);
2978         g_signal_connect(G_OBJECT(mapman_info.rad_by_area), "clicked",
2979                           G_CALLBACK(mapman_update_state), &mapman_info);
2980         g_signal_connect(G_OBJECT(mapman_info.rad_by_route), "clicked",
2981                           G_CALLBACK(mapman_update_state), &mapman_info);
2982     }
2983
2984     /* Initialize fields.  Do no use g_ascii_formatd; these strings will be
2985      * output (and parsed) as locale-dependent. */
2986
2987     gtk_widget_set_sensitive(mapman_info.rad_by_route,
2988             _route.head != _route.tail);
2989
2990     lat_format(_gps.lat, buffer);
2991     gtk_label_set_text(GTK_LABEL(lbl_gps_lat), buffer);
2992     lon_format(_gps.lon, buffer);
2993     gtk_label_set_text(GTK_LABEL(lbl_gps_lon), buffer);
2994
2995     unit2latlon(_center.unitx, _center.unity, lat, lon);
2996     lat_format(lat, buffer);
2997     gtk_label_set_text(GTK_LABEL(lbl_center_lat), buffer);
2998     lon_format(lon, buffer);
2999     gtk_label_set_text(GTK_LABEL(lbl_center_lon), buffer);
3000
3001     /* Initialize to the bounds of the screen. */
3002     unit2latlon(
3003             _center.unitx - pixel2unit(MAX(_view_width_pixels,
3004                     _view_height_pixels) / 2),
3005             _center.unity - pixel2unit(MAX(_view_width_pixels,
3006                     _view_height_pixels) / 2), lat, lon);
3007     BOUND(lat, -90.f, 90.f);
3008     BOUND(lon, -180.f, 180.f);
3009     lat_format(lat, buffer);
3010     gtk_entry_set_text(GTK_ENTRY(mapman_info.txt_topleft_lat), buffer);
3011     lon_format(lon, buffer);
3012     gtk_entry_set_text(GTK_ENTRY(mapman_info.txt_topleft_lon), buffer);
3013
3014     unit2latlon(
3015             _center.unitx + pixel2unit(MAX(_view_width_pixels,
3016                     _view_height_pixels) / 2),
3017             _center.unity + pixel2unit(MAX(_view_width_pixels,
3018                     _view_height_pixels) / 2), lat, lon);
3019     BOUND(lat, -90.f, 90.f);
3020     BOUND(lon, -180.f, 180.f);
3021     lat_format(lat, buffer);
3022     gtk_entry_set_text(GTK_ENTRY(mapman_info.txt_botright_lat), buffer);
3023     lon_format(lon, buffer);
3024     gtk_entry_set_text(GTK_ENTRY(mapman_info.txt_botright_lon), buffer);
3025
3026     /* Initialize zoom levels. */
3027     {
3028         gint i;
3029         for(i = 0; i <= MAX_ZOOM; i++)
3030         {
3031             gtk_toggle_button_set_active(
3032                     GTK_TOGGLE_BUTTON(mapman_info.chk_zoom_levels[i]), FALSE);
3033         }
3034     }
3035     gtk_toggle_button_set_active(
3036             GTK_TOGGLE_BUTTON(mapman_info.chk_zoom_levels[
3037                 _zoom + (_curr_repo->double_size ? 1 : 0)]), TRUE);
3038
3039     gtk_widget_show_all(dialog);
3040
3041     mapman_update_state(NULL, &mapman_info);
3042
3043     if(_curr_repo->type != REPOTYPE_NONE)
3044     {
3045         gtk_widget_set_sensitive(mapman_info.rad_download, TRUE);
3046     }
3047     else
3048     {
3049         gtk_widget_set_sensitive(mapman_info.rad_download, FALSE);
3050         popup_error(dialog,
3051                 _("NOTE: You must set a Map URI in the current repository in "
3052                     "order to download maps."));
3053     }
3054
3055     while(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog)))
3056     {
3057         MapUpdateType update_type;
3058         static gint8 download_batch_id = INT8_MIN;
3059
3060         if(gtk_toggle_button_get_active(
3061                     GTK_TOGGLE_BUTTON(mapman_info.rad_delete)))
3062             update_type = MAP_UPDATE_DELETE;
3063         else if(gtk_toggle_button_get_active(
3064                 GTK_TOGGLE_BUTTON(mapman_info.chk_overwrite)))
3065             update_type = MAP_UPDATE_OVERWRITE;
3066         else
3067             update_type = MAP_UPDATE_ADD;
3068
3069         ++download_batch_id;
3070         if(gtk_toggle_button_get_active(
3071                     GTK_TOGGLE_BUTTON(mapman_info.rad_by_route)))
3072         {
3073             if(mapman_by_route(&mapman_info, update_type, download_batch_id))
3074                 break;
3075         }
3076         else
3077         {
3078             const gchar *text;
3079             gchar *error_check;
3080             gdouble start_lat, start_lon, end_lat, end_lon;
3081
3082             text = gtk_entry_get_text(GTK_ENTRY(mapman_info.txt_topleft_lat));
3083             start_lat = strdmstod(text, &error_check);
3084             if(text == error_check || start_lat < -90. || start_lat > 90.) {
3085                 popup_error(dialog, _("Invalid Top-Left Latitude"));
3086                 continue;
3087             }
3088
3089             text = gtk_entry_get_text(GTK_ENTRY(mapman_info.txt_topleft_lon));
3090             start_lon = strdmstod(text, &error_check);
3091             if(text == error_check || start_lon < -180. || start_lon>180.) {
3092                 popup_error(dialog, _("Invalid Top-Left Longitude"));
3093                 continue;
3094             }
3095
3096             text = gtk_entry_get_text(GTK_ENTRY(mapman_info.txt_botright_lat));
3097             end_lat = strdmstod(text, &error_check);
3098             if(text == error_check || end_lat < -90. || end_lat > 90.) {
3099                 popup_error(dialog, _("Invalid Bottom-Right Latitude"));
3100                 continue;
3101             }
3102
3103             text = gtk_entry_get_text(GTK_ENTRY(mapman_info.txt_botright_lon));
3104             end_lon = strdmstod(text, &error_check);
3105             if(text == error_check || end_lon < -180. || end_lon > 180.) {
3106                 popup_error(dialog,_("Invalid Bottom-Right Longitude"));
3107                 continue;
3108             }
3109
3110             if(mapman_by_area(start_lat, start_lon, end_lat, end_lon,
3111                         &mapman_info, update_type, download_batch_id))
3112                 break;
3113         }
3114     }
3115
3116     gtk_widget_hide(dialog);
3117
3118     vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
3119     return TRUE;
3120 }
3121