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