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