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