]> git.itanic.dy.fi Git - maemo-mapper/commitdiff
Initial import of the maemo-mapper project.
authorgnuite <gnuite@gmail.com>
Mon, 22 May 2006 03:46:42 +0000 (03:46 +0000)
committergnuite <gnuite@gmail.com>
Mon, 22 May 2006 03:46:42 +0000 (03:46 +0000)
git-svn-id: svn+ssh://garage/var/lib/gforge/svnroot/maemo-mapper/trunk@1 6c538b50-5814-0410-93ad-8bdf4c0149d1

15 files changed:
Makefile.am [new file with mode: 0644]
autogen.sh [new file with mode: 0755]
com.nokia.maemo_mapper.service [new file with mode: 0644]
configure.ac [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/maemo-mapper.links [new file with mode: 0644]
debian/rules [new file with mode: 0755]
maemo-mapper.c [new file with mode: 0644]
maemo-mapper.desktop [new file with mode: 0644]
pkg_maemo-mapper.26.png [new file with mode: 0644]
pkg_maemo-mapper.40.png [new file with mode: 0644]
pkg_maemo-mapper.64.png [new file with mode: 0644]

diff --git a/Makefile.am b/Makefile.am
new file mode 100644 (file)
index 0000000..a6e16e0
--- /dev/null
@@ -0,0 +1,60 @@
+# The icons
+
+icondir = $(datadir)/icons/hicolor
+
+install-data-local:
+       i=pkg_maemo-mapper; \
+       $(mkinstalldirs) $(DESTDIR)$(icondir)/26x26/hildon; \
+       $(mkinstalldirs) $(DESTDIR)$(icondir)/40x40/hildon; \
+       $(mkinstalldirs) $(DESTDIR)$(icondir)/scalable/hildon; \
+       $(INSTALL_DATA) $(srcdir)/$$i.26.png $(DESTDIR)$(icondir)/26x26/hildon/$$i.png; \
+       $(INSTALL_DATA) $(srcdir)/$$i.40.png $(DESTDIR)$(icondir)/40x40/hildon/$$i.png; \
+       $(INSTALL_DATA) $(srcdir)/$$i.64.png $(DESTDIR)$(icondir)/scalable/hildon/$$i.png;
+
+uninstall-local:
+       i=pkg_maemo-mapper; \
+       rm $(DESTDIR)$(icondir)/26x26/hildon/$$i.png; \
+       rm $(DESTDIR)$(icondir)/40x40/hildon/$$i.png; \
+       rm $(DESTDIR)$(icondir)/scalable/hildon/$$i.png;
+
+# The program
+
+bin_PROGRAMS = maemo-mapper
+
+maemo_mapper_SOURCES = maemo-mapper.c
+maemo_mapper_CFLAGS = $(GTK_CFLAGS) $(OSSO_CFLAGS) $(HILDON_CFLAGS) $(GNOME_VFS_CFLAGS) $(GCONF_CFLAGS) $(LIBXML2_CFLAGS)
+maemo_mapper_LDADD = $(GTK_LIBS) $(OSSO_LIBS) $(HILDON_LIBS) $(GNOME_VFS_LIBS) $(GCONF_LIBS) $(LIBXML2_LIBS) -lbluetooth
+
+desktopdir = $(datadir)/applications/hildon
+desktop_DATA = maemo-mapper.desktop
+
+servicedir = $(libdir)/dbus-1.0/services
+service_DATA = com.nokia.maemo_mapper.service
+
+# The shared library
+
+# The control panel applet
+
+# The status bar plugin
+
+# The Home plugin
+
+# The locale
+
+# The gstreamer plugin
+
+# The backup/restore configuration
+
+EXTRA_DIST =                                   \
+        autogen.sh                             \
+        debian/changelog                       \
+        debian/compat                          \
+        debian/copyright                       \
+        debian/control                         \
+        debian/rules                           \
+        debian/maemo-mapper.links              \
+        maemo-mapper.desktop                   \
+        com.nokia.maemo_mapper.service \
+        pkg_maemo-mapper.26.png                \
+        pkg_maemo-mapper.40.png                \
+        pkg_maemo-mapper.64.png
diff --git a/autogen.sh b/autogen.sh
new file mode 100755 (executable)
index 0000000..d6b2ae6
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+set -x
+aclocal-1.7
+autoconf
+libtoolize
+automake-1.7 --add-missing --foreign
diff --git a/com.nokia.maemo_mapper.service b/com.nokia.maemo_mapper.service
new file mode 100644 (file)
index 0000000..1454b7e
--- /dev/null
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=com.nokia.maemo_mapper
+Exec=/var/lib/install/usr/bin/maemo-mapper
diff --git a/configure.ac b/configure.ac
new file mode 100644 (file)
index 0000000..cc972be
--- /dev/null
@@ -0,0 +1,33 @@
+AC_INIT(Makefile.am)
+AM_INIT_AUTOMAKE(maemo-mapper, 0.1)
+
+AC_PROG_CPP
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+
+PKG_CHECK_MODULES(GTK, gtk+-2.0)
+AC_SUBST(GTK_LIBS)
+AC_SUBST(GTK_CFLAGS)
+
+PKG_CHECK_MODULES(OSSO, libosso >= 0.8.4)
+AC_SUBST(OSSO_LIBS)
+AC_SUBST(OSSO_CFLAGS)
+
+PKG_CHECK_MODULES(HILDON, hildon-libs >= 0.9.50)
+AC_SUBST(HILDON_LIBS)
+AC_SUBST(HILDON_CFLAGS)
+
+PKG_CHECK_MODULES(GNOME_VFS, gnome-vfs-2.0 >= 2.8.4.11 gnome-vfs-module-2.0 >= 2.8.4.11)
+AC_SUBST(GNOME_VFS_LIBS)
+AC_SUBST(GNOME_VFS_CFLAGS)
+
+PKG_CHECK_MODULES(GCONF, gconf-2.0 >= 2.6.2)
+AC_SUBST(GCONF_LIBS)
+AC_SUBST(GCONF_CFLAGS)
+
+PKG_CHECK_MODULES(LIBXML2, libxml-2.0 >= 2.6.16)
+AC_SUBST(LIBXML2_LIBS)
+AC_SUBST(LIBXML2_CFLAGS)
+
+
+AC_OUTPUT([ Makefile ])
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..968a590
--- /dev/null
@@ -0,0 +1,6 @@
+maemo-mapper (0.1) unstable; urgency=low
+
+  * Initial release.
+
+ -- John Costigan <gnuite@gmail.com>  Sat, 22 Apr 2006 17:40:00 -0400
+
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..b8626c4
--- /dev/null
@@ -0,0 +1 @@
+4
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..5d97503
--- /dev/null
@@ -0,0 +1,13 @@
+Source: maemo-mapper
+Section: gnome
+Priority: optional
+Maintainer: John Costigan <gnuite@gmail.com>
+Build-Depends: debhelper (>= 4.0.0)
+Standards-Version: 3.6.0
+
+Package: maemo-mapper
+Architecture: any
+Depends: maemo
+Description: A mapping package for the maemo platform.
+ It contains a complete application for displaying maps and annotating them
+ with information from a GPS receiver.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..768fa4e
--- /dev/null
@@ -0,0 +1,8 @@
+Copyright (c) 2006 John Costigan
+
+maemo-mapper is licensed under the terms of the GNU GPL.
+
+By using this software you agree that you understand that using Maemo Mapper in
+Online Mode with a commercial map repository may be considered a violation of
+copyright law and that John Costigan cannot be held responsible for any of your
+actions related thereto.
diff --git a/debian/maemo-mapper.links b/debian/maemo-mapper.links
new file mode 100644 (file)
index 0000000..52f3861
--- /dev/null
@@ -0,0 +1 @@
+/var/lib/install/usr/share/applications/hildon/maemo-mapper.desktop /etc/others-menu/extra_applications/0100_maemo-mapper.desktop
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..dee7c4b
--- /dev/null
@@ -0,0 +1,100 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# GNU copyright 1997 to 1999 by Joey Hess.
+
+# Uncomment this to turn on verbose mode.
+export DH_VERBOSE=1
+
+
+
+
+CFLAGS = -Wall -g
+
+ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))
+       CFLAGS += -O0
+else
+       CFLAGS += -O2
+endif
+ifeq (,$(findstring nostrip,$(DEB_BUILD_OPTIONS)))
+       INSTALL_PROGRAM += -s
+endif
+ifneq (,$(findstring debug,$(DEB_BUILD_OPTIONS)))
+       CFLAGS += -g -O0 -DDEBUG
+endif
+
+configure: configure-stamp
+configure-stamp:
+       dh_testdir
+       # Add here commands to configure the package.
+       CFLAGS="$(CFLAGS)" ./configure --prefix=/usr --disable-static
+       touch configure-stamp
+
+
+build: build-stamp
+
+build-stamp: configure-stamp 
+       dh_testdir
+
+       # Add here commands to compile the package.
+       $(MAKE)
+
+       touch build-stamp
+
+clean:
+       dh_testdir
+       dh_testroot
+       rm -f build-stamp configure-stamp
+
+       # Add here commands to clean up after the build process.
+       -$(MAKE) clean
+
+       dh_clean 
+
+install: build
+       dh_testdir
+       dh_testroot
+       dh_clean -k 
+       dh_installdirs
+
+       # Add here commands to install the package
+       $(MAKE) install DESTDIR=$(CURDIR)/debian/maemo-mapper
+
+
+# Build architecture-independent files here.
+binary-indep: build install
+# We have nothing to do by default.
+
+# Build architecture-dependent files here.
+binary-arch: build install
+       dh_testdir
+       dh_testroot
+#      dh_installchangelogs 
+#      dh_installdocs
+#      dh_installexamples
+#      dh_install
+#      dh_installmenu
+#      dh_installdebconf       
+#      dh_installlogrotate
+#      dh_installemacsen
+#      dh_installpam
+#      dh_installmime
+#      dh_installinit
+#      dh_installcron
+#      dh_installinfo
+#      dh_installman
+       dh_link
+       dh_strip
+       dh_compress
+       dh_fixperms
+#      dh_perl
+#      dh_python
+#      dh_makeshlibs
+       dh_installdeb
+#      dh_shlibdeps
+       dh_gencontrol
+       dh_md5sums
+       dh_builddeb
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install configure
diff --git a/maemo-mapper.c b/maemo-mapper.c
new file mode 100644 (file)
index 0000000..329097d
--- /dev/null
@@ -0,0 +1,4746 @@
+/*
+ * This file is part of maemo-mapper
+ *
+ * Copyright (C) 2006 John Costigan
+ *
+ *
+ * This software is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or(at your option)any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#define _GNU_SOURCE
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <stddef.h>
+#include <math.h>
+#include <errno.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <libosso.h>
+#include <hildon-widgets/hildon-app.h>
+#include <hildon-widgets/hildon-controlbar.h>
+#include <hildon-widgets/hildon-note.h>
+#include <hildon-widgets/hildon-file-chooser-dialog.h>
+#include <hildon-widgets/hildon-number-editor.h>
+#include <hildon-widgets/gtk-infoprint.h>
+#include <libgnomevfs/gnome-vfs.h>
+#include <gconf/gconf-client.h>
+#include <libxml/parser.h>
+
+/* BELOW: for getting input from the GPS receiver */
+#include <sys/socket.h>
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/rfcomm.h>
+
+/****************************************************************************
+ * BELOW: DEFINES ***********************************************************
+ ****************************************************************************/
+
+#ifndef DEBUG
+#define printf(...)
+#endif
+
+/* Set the below if to determine whether to get verbose output. */
+#if 0
+#define vprintf printf
+#else
+#define vprintf(...)
+#endif
+
+#define BOUND(x, a, b) { \
+    if((x) < (a)) \
+        (x) = (a); \
+    else if((x) > (b)) \
+        (x) = (b); \
+}
+
+#define PI   (3.14159265358979323846f)
+
+#define MAX_ZOOM 17
+
+#define TILE_SIZE_PIXELS (256)
+#define TILE_SIZE_P2 (8)
+#define TILE_PIXBUF_STRIDE (768)
+#define BUF_WIDTH_TILES (4)
+#define BUF_HEIGHT_TILES (3)
+#define BUF_WIDTH_PIXELS (1024)
+#define BUF_HEIGHT_PIXELS (768)
+
+#define ARRAY_CHUNK_SIZE (1024)
+
+#define WORLD_SIZE_UNITS (1 << 26)
+
+#define tile2grid(tile) ((tile) << 3)
+#define grid2tile(grid) ((grid) >> 3)
+#define tile2pixel(tile) ((tile) << 8)
+#define pixel2tile(pixel) ((pixel) >> 8)
+#define tile2unit(tile) ((tile) << (8 + _zoom))
+#define unit2tile(unit) ((unit) >> (8 + _zoom))
+#define tile2zunit(tile, zoom) ((tile) << (8 + zoom))
+#define unit2ztile(unit, zoom) ((unit) >> (8 + zoom))
+
+#define grid2pixel(grid) ((grid) << 5)
+#define pixel2grid(pixel) ((pixel) >> 5)
+#define grid2unit(grid) ((grid) << (5 + _zoom))
+#define unit2grid(unit) ((unit) >> (5 + _zoom))
+
+#define pixel2unit(pixel) ((pixel) << _zoom)
+#define unit2pixel(pixel) ((pixel) >> _zoom)
+
+#define unit2bufx(unit) (unit2pixel(unit) - tile2pixel(_base_tilex))
+#define bufx2unit(x) (pixel2unit(x) + tile2unit(_base_tilex))
+#define unit2bufy(unit) (unit2pixel(unit) - tile2pixel(_base_tiley))
+#define bufy2unit(y) (pixel2unit(y) + tile2unit(_base_tiley))
+
+#define unit2x(unit) (unit2pixel(unit) - tile2pixel(_base_tilex) - _offsetx)
+#define x2unit(x) (pixel2unit(x + _offsetx) + tile2unit(_base_tilex))
+#define unit2y(unit) (unit2pixel(unit) - tile2pixel(_base_tiley) - _offsety)
+#define y2unit(y) (pixel2unit(y + _offsety) + tile2unit(_base_tiley))
+
+#define leadx2unit() (_pos.unitx + (_lead_ratio) * pixel2unit(_vel_offsetx))
+#define leady2unit() (_pos.unity + (0.6f*_lead_ratio)*pixel2unit(_vel_offsety))
+
+#define tile2punit(tile, zoom) ((((tile) << 1) + 1) << (7 + zoom))
+#define unit2ptile(unit) ((unit) >> (7 + _zoom))
+
+#define GCONF_KEY_PREFIX "/apps/maemo-mapper"
+#define GCONF_KEY_RCVR_MAC GCONF_KEY_PREFIX"/receiver_mac"
+#define GCONF_KEY_RCVR_CHAN GCONF_KEY_PREFIX"/receiver_channel"
+#define GCONF_KEY_MAP_URI_FORMAT GCONF_KEY_PREFIX"/map_uri_format"
+#define GCONF_KEY_MAP_ZOOM_STEPS GCONF_KEY_PREFIX"/map_zoom_steps"
+#define GCONF_KEY_MAP_DIR_NAME GCONF_KEY_PREFIX"/map_cache_dir"
+#define GCONF_KEY_AUTO_DOWNLOAD GCONF_KEY_PREFIX"/auto_download"
+#define GCONF_KEY_CENTER_SENSITIVITY GCONF_KEY_PREFIX"/center_sensitivity"
+#define GCONF_KEY_DRAW_LINE_WIDTH GCONF_KEY_PREFIX"/draw_line_width"
+#define GCONF_KEY_ENABLE_VOICE GCONF_KEY_PREFIX"/enable_voice"
+#define GCONF_KEY_ALWAYS_KEEP_ON \
+                                   GCONF_KEY_PREFIX"/always_keep_on"
+#define GCONF_KEY_AUTOCENTER_MODE GCONF_KEY_PREFIX"/autocenter_mode"
+#define GCONF_KEY_LEAD_AMOUNT GCONF_KEY_PREFIX"/lead_amount"
+#define GCONF_KEY_LAT GCONF_KEY_PREFIX"/last_latitude"
+#define GCONF_KEY_LON GCONF_KEY_PREFIX"/last_longitude"
+#define GCONF_KEY_ZOOM GCONF_KEY_PREFIX"/zoom"
+#define GCONF_KEY_ROUTEDIR GCONF_KEY_PREFIX"/route_directory"
+#define GCONF_KEY_TRACKFILE GCONF_KEY_PREFIX"/track_file"
+#define GCONF_KEY_SHOWTRACKS GCONF_KEY_PREFIX"/show_tracks"
+#define GCONF_KEY_SHOWROUTES GCONF_KEY_PREFIX"/show_routes"
+#define GCONF_KEY_SHOWVELVEC GCONF_KEY_PREFIX"/show_velocity_vector"
+#define GCONF_KEY_ENABLE_GPS GCONF_KEY_PREFIX"/enable_gps"
+#define GCONF_KEY_ROUTE_LOCATIONS GCONF_KEY_PREFIX"/route_locations"
+
+
+#define MACRO_RECALC_CENTER(center_unitx, center_unity) { \
+    switch(_center_mode) \
+    { \
+        case CENTER_LEAD: \
+            center_unitx = leadx2unit(); \
+            center_unity = leady2unit(); \
+            break; \
+        case CENTER_LATLON: \
+            center_unitx = _pos.unitx; \
+            center_unity = _pos.unity; \
+            break; \
+        default: \
+             center_unitx = _center.unitx; \
+             center_unity = _center.unity; \
+            ; \
+    } \
+};
+
+#define MERCATOR_SPAN (-6.28318377773622f)
+#define MERCATOR_TOP (3.14159188886811f)
+#define latlon2unit(lat, lon, unitx, unity) { \
+    gfloat tmp; \
+    unitx = (lon + 180.f) * (WORLD_SIZE_UNITS / 360.f) + 0.5f; \
+    tmp = sinf(lat * (PI / 180.f)); \
+    unity = 0.5f + (WORLD_SIZE_UNITS / MERCATOR_SPAN) \
+        * (logf((1.f + tmp) / (1.f - tmp)) * 0.5f - MERCATOR_TOP); \
+}
+
+#define unit2latlon(unitx, unity, lat, lon) { \
+    (lon) = ((unitx) * (360.f / WORLD_SIZE_UNITS)) - 180.f; \
+    (lat) = (360.f * (atanf(expf(((unity) \
+                                  * (MERCATOR_SPAN / WORLD_SIZE_UNITS)) \
+                     + MERCATOR_TOP)))) * (1.f / PI) - 90.f; \
+}
+
+#define MACRO_RECALC_OFFSET() { \
+    _offsetx = grid2pixel( \
+            unit2grid(_center.unitx) \
+            - _screen_grids_halfwidth \
+            - tile2grid(_base_tilex)); \
+    _offsety = grid2pixel( \
+            unit2grid(_center.unity) \
+            - _screen_grids_halfheight \
+            - tile2grid(_base_tiley)); \
+}
+
+#define MACRO_RECALC_FOCUS_BASE() { \
+    _focus.unitx = x2unit(_screen_width_pixels * _center_ratio / 20); \
+    _focus.unity = y2unit(_screen_height_pixels * _center_ratio / 20); \
+}
+
+#define MACRO_RECALC_FOCUS_SIZE() { \
+    _focus_unitwidth = pixel2unit( \
+            (10 - _center_ratio) * _screen_width_pixels / 10); \
+    _focus_unitheight = pixel2unit( \
+            (10 - _center_ratio) * _screen_height_pixels / 10); \
+}
+
+#define MACRO_RECALC_CENTER_BOUNDS() { \
+  _min_center.unitx = pixel2unit(grid2pixel(_screen_grids_halfwidth)); \
+  _min_center.unity = pixel2unit(grid2pixel(_screen_grids_halfheight)); \
+  _max_center.unitx = WORLD_SIZE_UNITS-grid2unit(_screen_grids_halfwidth) - 1; \
+  _max_center.unity = WORLD_SIZE_UNITS-grid2unit(_screen_grids_halfheight)- 1; \
+}
+
+#define MACRO_INIT_TRACK(track) { \
+    (track).head = (track).tail = g_new(TrackPoint, ARRAY_CHUNK_SIZE); \
+    (track).tail->unitx = (track).tail->unity = 0;  \
+    (track).cap = (track).head + ARRAY_CHUNK_SIZE; \
+    (track).whead = g_new(WayPoint, ARRAY_CHUNK_SIZE); \
+    (track).wtail = (track).whead - 1; \
+    (track).wcap = (track).whead + ARRAY_CHUNK_SIZE; \
+}
+
+#define MACRO_CLEAR_TRACK(track) if((track).head) { \
+    WayPoint *curr; \
+    g_free((track).head); \
+    (track).head = (track).tail = (track).cap = NULL; \
+    for(curr = (track).whead - 1; curr++ != (track).wtail; ) \
+        g_free(curr->desc); \
+    g_free((track).whead); \
+    (track).whead = (track).wtail = (track).wcap = NULL; \
+}
+
+#define DISTANCE_ROUGH(point) \
+    (abs((gint)((point).unitx) - _pos.unitx) \
+     + abs((gint)((point).unity) - _pos.unity))
+
+#define MACRO_QUEUE_DRAW_AREA() \
+    gtk_widget_queue_draw_area( \
+            _map_widget, \
+            0, 0, \
+            _screen_width_pixels, \
+            _screen_height_pixels)
+
+#define KEEP_DISPLAY_ON() { \
+    /* note that the flag means keep on ONLY when fullscreen. */ \
+    if(_always_keep_on || _fullscreen) \
+        osso_display_state_on(_osso); \
+}
+
+#define TRACKS_MASK 0x00000001
+#define ROUTES_MASK 0x00000002
+
+/****************************************************************************
+ * ABOVE: DEFINES ***********************************************************
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ * BELOW: TYPEDEFS **********************************************************
+ ****************************************************************************/
+
+/** This enumerated type defines the possible connection states. */
+typedef enum
+{
+    /** The receiver is "off", meaning that either the bluetooth radio is
+     * off or the user has requested not to connect to the GPS receiver.
+     * No gtk_banner is visible */
+    RCVR_OFF,
+
+    /** The connection with the receiver is down.  A gtk_banner is visible with
+     * the text, "Connecting to GPS receiver" */
+    RCVR_DOWN,
+
+    /** The connection with the receiver is up, but a GPS fix is not available.
+     * A gtk_banner is visible with the text, "(Re-)Establishing GPS fix" */
+    RCVR_UP,
+
+    /** The connection with the receiver is up and a GPS fix is not available.
+     * No gtk_banner is visible */
+    GPS_FIXED
+} ConnState;
+
+/** Possible center modes.  The "WAS" modes imply no current center mode;
+ * they only hint at what the last center mode was, so that it can be
+ * recalled. */
+typedef enum
+{
+    CENTER_WAS_LATLON = -2,
+    CENTER_WAS_LEAD = -1,
+    CENTER_LEAD = 1,
+    CENTER_LATLON = 2
+} CenterMode;
+
+/** This enum defines the states of the SAX parsing state machine. */
+typedef enum
+{
+    START,
+    INSIDE_GPX,
+    INSIDE_TRACK,
+    INSIDE_TRACK_SEGMENT,
+    INSIDE_TRACK_POINT,
+    INSIDE_TRACK_POINT_DESC,
+    FINISH,
+    UNKNOWN,
+    ERROR,
+} SaxState;
+
+/** A general definition of a point in the Maemo Mapper unit system. */
+typedef struct _TrackPoint TrackPoint;
+struct _TrackPoint {
+    guint unitx;
+    guint unity;
+};
+
+/** A WayPoint, which is a TrackPoint with a description. */
+typedef struct _WayPoint WayPoint;
+struct _WayPoint {
+    TrackPoint *point;
+    gchar *desc;
+};
+
+/** A Track is a set of TrackPoints and WayPoints. */
+typedef struct _Track Track;
+struct _Track {
+    TrackPoint *head; /* points to first element in array; NULL if empty(?) */
+    TrackPoint *tail; /* points to last element in array */
+    TrackPoint *cap; /* points after last slot in array */
+    WayPoint *whead; /* points to first element in array; NULL if empty */
+    WayPoint *wtail; /* points to last element in array */
+    WayPoint *wcap; /* points after last slot in array */
+};
+
+/** Data used during the SAX parsing operation. */
+typedef struct _SaxData SaxData;
+struct _SaxData {
+    Track track;
+    SaxState state;
+    SaxState prev_state;
+    guint unknown_depth;
+    gboolean at_least_one_trkpt;
+    GString *chars;
+};
+
+/** Data used during the asynchronous progress update phase of automatic map
+ * downloading. */
+typedef struct _ProgressUpdateInfo ProgressUpdateInfo;
+struct _ProgressUpdateInfo
+{
+    GnomeVFSAsyncHandle *handle;
+    GList *src_list;
+    GList *dest_list;
+    guint tilex, tiley, zoom; /* for refresh */
+    guint hash;
+};
+
+/** Data used during the asynchronous automatic route downloading operation. */
+typedef struct _AutoRouteDownloadData AutoRouteDownloadData;
+struct _AutoRouteDownloadData {
+    gboolean enabled;
+    gboolean in_progress;
+    gchar *dest;
+    GnomeVFSAsyncHandle *handle;
+    gchar *bytes;
+    guint bytes_read;
+    guint bytes_maxsize;
+};
+
+
+/****************************************************************************
+ * ABOVE: TYPEDEFS **********************************************************
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ * BELOW: DATA **************************************************************
+ ****************************************************************************/
+
+/** The main GtkWindow of the application. */
+static GtkWindow *_window = NULL;
+
+/** The main GtkContainer of the application. */
+static HildonAppView *_main_view = NULL;
+
+/** The main OSSO context of the application. */
+static osso_context_t *_osso = NULL;
+
+/** The widget that provides the visual display of the map. */
+static GtkWidget *_map_widget = NULL;
+
+/** The backing pixmap of _map_widget. */
+static GdkPixmap *_map_pixmap = NULL;
+
+
+/** The file descriptor of our connection with the GPS receiver. */
+static gint _fd = -1;
+
+/** The GIOChannel through which communication with the GPS receiver is
+ * performed. */
+static GIOChannel *_channel = NULL;
+
+/** The Source ID of the connect watch. */
+static guint _connect_sid = 0;
+/** The Source ID of the error watch. */
+static guint _error_sid = 0;
+/** The Source ID of the input watch. */
+static guint _input_sid = 0;
+/** The Source ID of the "Connect Later" idle. */
+static guint _clater_sid = 0;
+
+
+/** GPS data. */
+static gfloat _pos_lat = 0.f;
+static gfloat _pos_lon = 0.f;
+static TrackPoint _pos = {0, 0};
+
+static guint _speed = 0;
+static guint _heading = 0;
+static gint _vel_offsetx = 0;
+static gint _vel_offsety = 0;
+
+
+/** The current connection state. */
+static ConnState _conn_state = RCVR_OFF;
+
+
+/** VARIABLES FOR MAINTAINING STATE OF THE CURRENT VIEW. */
+
+/** The "zoom" level defines the resolution of a pixel, from 0 to MAX_ZOOM.
+ * Each pixel in the current view is exactly (1 << _zoom) "units" wide. */
+static guint _zoom = 3; /* zoom level, from 0 to MAX_ZOOM */
+static TrackPoint _center = {-1, -1}; /* current center location, X */
+
+/** The "base tile" is the upper-left tile in the pixmap. */
+static guint _base_tilex = -5;
+static guint _base_tiley = -5;
+
+/** The "offset" defines the upper-left corner of the visible portion of the
+ * 1024x768 pixmap. */
+static guint _offsetx = -1;
+static guint _offsety = -1;
+
+
+/** CACHED SCREEN INFORMATION THAT IS DEPENDENT ON THE CURRENT VIEW. */
+static guint _screen_grids_halfwidth = 0;
+static guint _screen_grids_halfheight = 0;
+static guint _screen_width_pixels = 0;
+static guint _screen_height_pixels = 0;
+static TrackPoint _focus = {-1, -1};
+static guint _focus_unitwidth = 0;
+static guint _focus_unitheight = 0;
+static TrackPoint _min_center = {-1, -1};
+static TrackPoint _max_center = {-1, -1};
+
+
+/** VARIABLES FOR ACCESSING THE LOCATION/BOUNDS OF THE CURRENT MARK. */
+static gint _mark_x1 = -1;
+static gint _mark_x2 = -1;
+static gint _mark_y1 = -1;
+static gint _mark_y2 = -1;
+static gint _mark_minx = -1;
+static gint _mark_miny = -1;
+static gint _mark_width = -1;
+static gint _mark_height = -1;
+
+/** The current track and route. */
+static Track _track;
+static Track _route;
+
+/** Data for tracking waypoints for the purpose of announcement. */
+/* _near_point is the route point to which we are closest. */
+static TrackPoint *_near_point = NULL;
+static guint _near_point_dist_rough = -1;
+/* _next_way is what we currently interpret to be the next waypoint. */
+static WayPoint *_next_way = NULL;
+static guint _next_way_dist_rough = -1;
+static gchar *_last_spoken_phrase = NULL;
+/* _next_point is the route point immediately following _next_way. */
+static TrackPoint *_next_point = NULL;
+static guint _next_point_dist_rough = -1;
+
+
+/** THE GtkGC OBJECTS USED FOR DRAWING. */
+static GdkGC *_mark_current_gc = NULL;
+static GdkGC *_mark_old_gc = NULL;
+static GdkGC *_vel_current_gc = NULL;
+static GdkGC *_vel_old_gc = NULL;
+static GdkGC *_track_gc = NULL;
+static GdkGC *_track_way_gc = NULL;
+static GdkGC *_route_gc = NULL;
+static GdkGC *_route_way_gc = NULL;
+static GdkGC *_next_way_gc = NULL;
+
+/** MENU ITEMS. */
+static GtkWidget *_menu_route_download_item = NULL;
+static GtkWidget *_menu_route_open_item = NULL;
+static GtkWidget *_menu_route_save_item = NULL;
+static GtkWidget *_menu_route_reset_item = NULL;
+static GtkWidget *_menu_route_clear_item = NULL;
+static GtkWidget *_menu_track_open_item = NULL;
+static GtkWidget *_menu_track_save_item = NULL;
+static GtkWidget *_menu_track_mark_way_item = NULL;
+static GtkWidget *_menu_track_clear_item = NULL;
+static GtkWidget *_menu_maps_dlroute_item = NULL;
+static GtkWidget *_menu_maps_dlarea_item = NULL;
+static GtkWidget *_menu_auto_download_item = NULL;
+static GtkWidget *_menu_show_tracks_item = NULL;
+static GtkWidget *_menu_show_routes_item = NULL;
+static GtkWidget *_menu_show_velvec_item = NULL;
+static GtkWidget *_menu_ac_latlon_item = NULL;
+static GtkWidget *_menu_ac_lead_item = NULL;
+static GtkWidget *_menu_ac_none_item = NULL;
+static GtkWidget *_menu_fullscreen_item = NULL;
+static GtkWidget *_menu_enable_gps_item = NULL;
+static GtkWidget *_menu_settings_item = NULL;
+static GtkWidget *_menu_close_item = NULL;
+
+/** DOWNLOAD PROGRESS. */
+static guint _num_downloads = 0;
+static guint _curr_download = 0;
+static GHashTable *_downloads_hash = NULL;
+
+/** CONFIGURATION INFORMATION. */
+static struct sockaddr_rc _rcvr_addr = { 0 };
+static gchar _rcvr_mac[18] = "";
+static gchar *_map_dir_name = NULL;
+static gchar *_autoroute_file_name = NULL;
+static gchar *_map_uri_format = NULL;
+static gchar *_route_dir_uri = NULL;
+static gchar *_track_file_uri = NULL;
+static CenterMode _center_mode = CENTER_LEAD;
+static gboolean _fullscreen = FALSE;
+static gboolean _enable_gps = FALSE;
+static gint _show_tracks = 0;
+static gboolean _show_velvec = TRUE;
+static gboolean _auto_download = FALSE;
+static guint _zoom_steps = 2;
+static guint _lead_ratio = 5;
+static guint _center_ratio = 7;
+static guint _draw_line_width = 5;
+static gboolean _enable_voice = TRUE;
+static gboolean _always_keep_on = FALSE;
+static GSList *_loc_list;
+static GtkListStore *_loc_model;
+
+/** The singleton auto-route-download data. */
+static AutoRouteDownloadData _autoroute_data;
+
+/****************************************************************************
+ * ABOVE: DATA **************************************************************
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ * BELOW: CALLBACK DECLARATIONS *********************************************
+ ****************************************************************************/
+
+static gint
+dbus_cb_default(const gchar *interface, const gchar *method,
+        GArray *arguments, gpointer data, osso_rpc_t *retval);
+static void
+osso_cb_hw_state(osso_hw_state_t *state, gpointer data);
+static gboolean
+window_cb_key_press(GtkWidget* widget, GdkEventKey *event);
+
+static gboolean
+map_cb_configure(GtkWidget *widget, GdkEventConfigure *event);
+static gboolean
+map_cb_expose(GtkWidget *widget, GdkEventExpose *event);
+static gboolean
+map_cb_button_release(GtkWidget *widget, GdkEventButton *event);
+
+static gboolean
+channel_cb_error(GIOChannel *src, GIOCondition condition, gpointer data);
+static gboolean
+channel_cb_connect(GIOChannel *src, GIOCondition condition, gpointer data);
+static gboolean
+channel_cb_input(GIOChannel *src, GIOCondition condition, gpointer data);
+
+static gboolean
+menu_cb_route_download(GtkAction *action);
+static gboolean
+menu_cb_route_open(GtkAction *action);
+static gboolean
+menu_cb_route_save(GtkAction *action);
+static gboolean
+menu_cb_route_reset(GtkAction *action);
+static gboolean
+menu_cb_route_clear(GtkAction *action);
+
+static gboolean
+menu_cb_track_open(GtkAction *action);
+static gboolean
+menu_cb_track_save(GtkAction *action);
+static gboolean
+menu_cb_track_mark_way(GtkAction *action);
+static gboolean
+menu_cb_track_clear(GtkAction *action);
+
+static gboolean
+menu_cb_show_tracks(GtkAction *action);
+static gboolean
+menu_cb_show_routes(GtkAction *action);
+static gboolean
+menu_cb_show_velvec(GtkAction *action);
+
+static gboolean
+menu_cb_maps_dlroute(GtkAction *action);
+static gboolean
+menu_cb_maps_dlarea(GtkAction *action);
+static gboolean
+menu_cb_auto_download(GtkAction *action);
+
+static gboolean
+menu_cb_ac_latlon(GtkAction *action);
+static gboolean
+menu_cb_ac_lead(GtkAction *action);
+static gboolean
+menu_cb_ac_none(GtkAction *action);
+
+static gboolean
+menu_cb_fullscreen(GtkAction *action);
+static gboolean
+menu_cb_enable_gps(GtkAction *action);
+
+static gboolean
+menu_cb_settings(GtkAction *action);
+
+static gint
+map_download_cb_async(GnomeVFSAsyncHandle *handle,
+        GnomeVFSXferProgressInfo *info, ProgressUpdateInfo*pui);
+
+static gboolean
+auto_route_dl_idle_refresh();
+static gboolean
+auto_route_dl_idle();
+
+/****************************************************************************
+ * ABOVE: CALLBACK DECLARATIONS *********************************************
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ * BELOW: ROUTINES **********************************************************
+ ****************************************************************************/
+
+/**
+ * Pop up a modal dialog box with simple error information in it.
+ */
+static void
+popup_error(const gchar *error)
+{
+    GtkWidget *dialog;
+    printf("%s(\"%s\")\n", __PRETTY_FUNCTION__, error);
+
+    dialog = hildon_note_new_information(_window, error);
+
+    gtk_dialog_run(GTK_DIALOG(dialog));
+    gtk_widget_destroy(dialog);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+/**
+ * Set the connection state.  This function controls all connection-related
+ * banners.
+ */
+static void
+set_conn_state(ConnState new_conn_state)
+{
+    printf("%s(%d)\n", __PRETTY_FUNCTION__, new_conn_state);
+
+    switch(_conn_state)
+    {
+        case RCVR_DOWN:
+        case RCVR_UP:
+            gtk_banner_close(_window);
+        default: ; /* to quell warning */
+    }
+    switch(_conn_state = new_conn_state)
+    {
+        case RCVR_DOWN:
+            gtk_banner_show_animation(_window, "Searching for GPS receiver");
+            break;
+        case RCVR_UP:
+            gtk_banner_show_bar(_window, "Establishing GPS fix");
+            break;
+        default: ; /* to quell warning */
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+/**
+ * Updates _near_point, _next_way, and _next_point.  If quick is FALSE (as
+ * it is when this function is called from route_find_nearest_point), then
+ * the entire list (starting from _near_point) is searched.  Otherwise, we
+ * stop searching when we find a point that is farther away.
+ */
+static gboolean
+route_update_nears(gboolean quick)
+{
+    gboolean ret = FALSE;
+    TrackPoint *curr, *near;
+    WayPoint *wcurr, *wnext;
+    guint near_dist_rough;
+    printf("%s(%d)\n", __PRETTY_FUNCTION__, quick);
+    
+    /* If we have waypoints (_next_way != NULL), then determine the "next
+     * waypoint", which is defined as the waypoint after the nearest point,
+     * UNLESS we've passed that waypoint, in which case the waypoint after
+     * that waypoint becomes the "next" waypoint. */
+    if(_next_way)
+    {
+        /* First, set near_dist_rough with the new distance from _near_point. */
+        near = _near_point;
+        near_dist_rough = DISTANCE_ROUGH(*near);
+
+        /* Now, search _route for a closer point.  If quick is TRUE, then we'll
+         * only search forward, only as long as we keep finding closer points.
+         */
+        for(curr = _near_point; curr++ != _route.tail; )
+            if(curr->unity)
+            {
+                guint dist_rough = DISTANCE_ROUGH(*curr);
+                if(dist_rough <= near_dist_rough)
+                {
+                    near = curr;
+                    near_dist_rough = dist_rough;
+                }
+                else if(quick)
+                    break;
+            }
+
+        /* Update _near_point. */
+        _near_point = near;
+        _near_point_dist_rough = near_dist_rough;
+
+        for(wnext = wcurr = _next_way; wcurr != _route.wtail; wcurr++)
+        {
+            if(wcurr->point < near
+            /* Okay, this else if expression warrants explanation.  If the
+             * nearest track point happens to be a waypoint, then we want to
+             * check if we have "passed" that waypoint.  To check this, we
+             * test the distance from _pos to the waypoint and from _pos to
+             * _next_point, and if the former is increasing and the latter is
+             * decreasing, then we have passed the waypoint, and thus we
+             * should skip it.  Note that if there is no _next_point, then
+             * there is no next waypoint, so we do not skip it in that case. */
+                || (wcurr->point == near && quick
+                    && (_next_point
+                     && (DISTANCE_ROUGH(*near) > _next_way_dist_rough
+                      && DISTANCE_ROUGH(*_next_point) < _next_point_dist_rough))))
+                wnext = wcurr + 1;
+            else
+                break;
+        }
+
+        if(wnext == _route.wtail && (wnext->point < near
+                || (wnext->point == near && quick
+                    && (!_next_point
+                     || (DISTANCE_ROUGH(*near) > _next_way_dist_rough
+                      && DISTANCE_ROUGH(*_next_point) < _next_point_dist_rough)))))
+        {
+            printf("Setting _next_way to NULL\n");
+            _next_way = NULL;
+            _next_point = NULL;
+            _next_way_dist_rough = -1;
+            _next_point_dist_rough = -1;
+            ret = TRUE;
+        }
+        /* Only update _next_way (and consequently _next_point) if _next_way is
+         * different, and record that fact for return. */
+        else
+        {
+            if(_next_way != wnext)
+            {
+                _next_way = wnext;
+                if(wnext->point == _route.tail
+                        || (_next_point = wnext->point + 1)->unity == 0)
+                    _next_point = NULL;
+                ret = TRUE;
+            }
+            _next_way_dist_rough = DISTANCE_ROUGH(*wnext->point);
+            if(_next_point)
+                _next_point_dist_rough = DISTANCE_ROUGH(*_next_point);
+        }
+    }
+
+    vprintf("%s(): return %d\n", __PRETTY_FUNCTION__, ret);
+    return ret;
+}
+
+/**
+ * Reset the _near_point data by searching the entire route for the nearest
+ * route point and waypoint.
+ */
+static void
+route_find_nearest_point()
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    /* Initialize _near_point. */
+    _near_point = _route.head;
+    while(_near_point->unity == 0 && _near_point != _route.tail)
+        _near_point++;
+
+    /* Initialize _next_way. */
+    if(_route.wtail == _route.whead - 1
+            || (_autoroute_data.enabled && _route.wtail == _route.whead))
+        _next_way = NULL;
+    else
+        /* we have at least one waypoint. */
+        _next_way = (_autoroute_data.enabled ? _route.whead + 1 : _route.whead);
+    _next_way_dist_rough = -1;
+
+    /* Initialize _next_point. */
+    if(!_next_way || _next_way->point == _route.tail
+            || (_next_point = _next_way->point + 1)->unity == 0)
+        _next_point = NULL;
+
+    route_update_nears(FALSE);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+/**
+ * Render a single track line to _map_pixmap.  If either point on the line
+ * is a break (defined as unity == 0), a circle is drawn at the other point.
+ * IT IS AN ERROR FOR BOTH POINTS TO INDICATE A BREAK.
+ */
+static void
+map_render_track_line(GdkGC *gc_norm, GdkGC *gc_way,
+        guint unitx1, guint unity1, guint unitx2, guint unity2)
+{
+    vprintf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(!unity1)
+    {
+        guint x2, y2;
+        x2 = unit2bufx(unitx2);
+        y2 = unit2bufy(unity2);
+        /* make sure this circle will be visible */
+        if((x2 < BUF_WIDTH_PIXELS)
+                && (y2 < BUF_HEIGHT_PIXELS))
+            gdk_draw_arc(_map_pixmap, gc_way,
+                    FALSE, /* FALSE: not filled */
+                    x2 - _draw_line_width,
+                    y2 - _draw_line_width,
+                    2 * _draw_line_width,
+                    2 * _draw_line_width,
+                    0, /* start at 0 degrees */
+                    360 * 64);
+    }
+    else if(!unity2)
+    {
+        guint x1, y1;
+        x1 = unit2bufx(unitx1);
+        y1 = unit2bufy(unity1);
+        /* make sure this circle will be visible */
+        if((x1 < BUF_WIDTH_PIXELS)
+                && ((unsigned)y1 < BUF_HEIGHT_PIXELS))
+            gdk_draw_arc(_map_pixmap, gc_way,
+                    FALSE, /* FALSE: not filled */
+                    x1 - _draw_line_width,
+                    y1 - _draw_line_width,
+                    2 * _draw_line_width,
+                    2 * _draw_line_width,
+                    0, /* start at 0 degrees */
+                    360 * 64);
+    }
+    else
+    {
+        guint x1, y1, x2, y2;
+        x1 = unit2bufx(unitx1);
+        y1 = unit2bufy(unity1);
+        x2 = unit2bufx(unitx2);
+        y2 = unit2bufy(unity2);
+        /* make sure this line could possibly be visible */
+        if((x1 < BUF_WIDTH_PIXELS
+                    || x2 < BUF_WIDTH_PIXELS)
+                && (y1 < BUF_HEIGHT_PIXELS
+                    || (y2 < BUF_HEIGHT_PIXELS)))
+            gdk_draw_line(_map_pixmap, gc_norm, x1, y1, x2, y2);
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+/**
+ * Render all track data onto the _map_pixmap.  Note that this does not
+ * clear the pixmap of previous track data (use map_force_redraw() for
+ * that), and also note that this method does not queue any redraws, so it
+ * is up to the caller to decide which part of the track really needs to be
+ * redrawn.
+ */
+static void
+map_render_track(Track *track, GdkGC *line_gc, GdkGC *way_gc)
+{
+    TrackPoint *curr;
+    WayPoint *wcurr;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    for(curr = track->head, wcurr = track->whead; curr != track->tail; curr++)
+    {
+        if(wcurr <= track->wtail && curr == wcurr->point)
+        {
+            guint x1 = unit2bufx(wcurr->point->unitx);
+            guint y1 = unit2bufy(wcurr->point->unity);
+            if((x1 < BUF_WIDTH_PIXELS)
+                    && (y1 < BUF_HEIGHT_PIXELS))
+            {
+                gdk_draw_arc(_map_pixmap,
+                        (wcurr == _next_way ? _next_way_gc : way_gc),
+                        FALSE, /* FALSE: not filled */
+                        x1 - _draw_line_width,
+                        y1 - _draw_line_width,
+                        2 * _draw_line_width,
+                        2 * _draw_line_width,
+                        0, /* start at 0 degrees */
+                        360 * 64);
+            }
+            wcurr++;
+            if(curr[1].unity == 0)
+                continue;
+        }
+        map_render_track_line(line_gc, way_gc,
+                curr->unitx, curr->unity, curr[1].unitx, curr[1].unity);
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+static void
+map_render_tracks()
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if((_show_tracks & ROUTES_MASK) && _route.head)
+    {
+        map_render_track(&_route, _route_gc, _route_way_gc);
+        if(_next_way == NULL)
+        {
+            guint x1 = unit2bufx(_route.tail[-1].unitx);
+            guint y1 = unit2bufy(_route.tail[-1].unity);
+            if((x1 < BUF_WIDTH_PIXELS)
+                    && (y1 < BUF_HEIGHT_PIXELS))
+            {
+                gdk_draw_arc(_map_pixmap,
+                        _next_way_gc,
+                        FALSE, /* FALSE: not filled */
+                        x1 - _draw_line_width,
+                        y1 - _draw_line_width,
+                        2 * _draw_line_width,
+                        2 * _draw_line_width,
+                        0, /* start at 0 degrees */
+                        360 * 64);
+            }
+        }
+    }
+    if(_show_tracks & TRACKS_MASK)
+        map_render_track(&_track, _track_gc, _track_way_gc);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+static void
+track_resize(Track *track, guint size)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(track->head + size != track->cap)
+    {
+        TrackPoint *old_head = track->head;
+        WayPoint *curr;
+        track->head = g_renew(TrackPoint, old_head, size);
+        track->cap = track->head + size;
+        if(track->head != old_head)
+        {
+            track->tail = track->head + (track->tail - old_head);
+
+            /* adjust all of the waypoints. */
+            for(curr = track->whead - 1; curr++ != track->wtail; )
+                curr->point = track->head + (curr->point - old_head);
+        }
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+static void
+track_wresize(Track *track, guint wsize)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(track->whead + wsize != track->wcap)
+    {
+        WayPoint *old_whead = track->whead;
+        track->whead = g_renew(WayPoint, old_whead, wsize);
+        track->wtail = track->whead + (track->wtail - old_whead);
+        track->wcap = track->whead + wsize;
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+#define MACRO_TRACK_INCREMENT_TAIL(track) { \
+    if(++(track).tail == (track).cap) \
+        track_resize(&(track), (track).cap - (track).head + ARRAY_CHUNK_SIZE); \
+}
+
+#define MACRO_TRACK_INCREMENT_WTAIL(track) { \
+    if(++(track).wtail == (track).wcap) \
+        track_wresize(&(track), \
+                (track).wcap - (track).whead + ARRAY_CHUNK_SIZE); \
+}
+
+/**
+ * Add a point to the _track list.  This function is slightly overloaded,
+ * since it is what houses the check for "have we moved
+ * significantly": it also initiates the re-calculation of the _near_point
+ * data, as well as calling osso_display_state_on() when we have the focus.
+ */
+static void
+track_add(guint unitx, guint unity, gboolean newly_fixed)
+{
+    printf("%s(%u, %u)\n", __PRETTY_FUNCTION__, unitx, unity);
+
+    if(abs((gint)unitx - _track.tail->unitx) > _draw_line_width
+            || abs((gint)unity - _track.tail->unity) > _draw_line_width)
+    {
+        if(unity != 0 && _route.head
+                && (newly_fixed ? (route_find_nearest_point(), TRUE)
+                                : route_update_nears(TRUE)))
+        {
+            map_render_tracks();
+            MACRO_QUEUE_DRAW_AREA();
+        }
+        if(_show_tracks & TRACKS_MASK)
+        {
+            /* instead of calling map_render_tracks(), we'll draw the new line
+             * ourselves and call gtk_widget_queue_draw_area() */
+            gint tx1, ty1, tx2, ty2;
+            map_render_track_line(_track_gc, _track_way_gc,
+                    _track.tail->unitx, _track.tail->unity, unitx, unity);
+            if(unity != 0 && _track.tail->unity != 0)
+            {
+                tx1 = unit2x(_track.tail->unitx);
+                ty1 = unit2y(_track.tail->unity);
+                tx2 = unit2x(unitx);
+                ty2 = unit2y(unity);
+                gtk_widget_queue_draw_area(_map_widget,
+                        MIN(tx1, tx2) - _draw_line_width,
+                        MIN(ty1, ty2) - _draw_line_width,
+                        abs(tx1 - tx2) + (2 * _draw_line_width),
+                        abs(ty1 - ty2) + (2 * _draw_line_width));
+            }
+        }
+        MACRO_TRACK_INCREMENT_TAIL(_track);
+
+        _track.tail->unitx = unitx;
+        _track.tail->unity = unity;
+
+        if(_autoroute_data.enabled && !_autoroute_data.in_progress
+                && _near_point_dist_rough > 400)
+        {
+            _autoroute_data.in_progress = TRUE;
+            g_idle_add((GSourceFunc)auto_route_dl_idle, NULL);
+        }
+
+        /* Keep the display on if we have the focus. */
+        KEEP_DISPLAY_ON();
+    }
+
+    /* Check if we should announce upcoming waypoints. */
+    if(unity && _next_way_dist_rough < 400 + (_speed << 5)) /* << 5 == * 32 */
+    {
+        if(_enable_voice && strcmp(_next_way->desc, _last_spoken_phrase))
+        {
+            gchar buffer[1024];
+            sprintf(buffer, "flite -t \"Approaching Waypoint. %s\" &",
+                    _next_way->desc);
+            printf("system(%s)\n", buffer);
+            system(buffer);
+            g_free(_last_spoken_phrase);
+            _last_spoken_phrase = g_strdup(_next_way->desc);
+        }
+        gtk_infoprint(_window, _next_way->desc);
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+/**
+ * Disconnect from the receiver.  This method cleans up any and everything
+ * that might be associated with the receiver.
+ */
+static void
+rcvr_disconnect()
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    /* remove watches */
+    if(_clater_sid)
+    {
+        g_source_remove(_clater_sid);
+        _clater_sid = 0;
+    }
+    if(_error_sid)
+    {
+        g_source_remove(_error_sid);
+        _error_sid = 0;
+    }
+    if(_connect_sid)
+    {
+        g_source_remove(_connect_sid);
+        _connect_sid = 0;
+    }
+    if(_input_sid)
+    {
+        g_source_remove(_input_sid);
+        _input_sid = 0;
+    }
+
+    /* Destroy the GIOChannel object. */
+    if(_channel)
+    {
+        g_io_channel_shutdown(_channel, FALSE, NULL);
+        g_io_channel_unref(_channel);
+        _channel = NULL;
+    }
+
+    /* Close the file descriptor. */
+    if(_fd != -1)
+    {
+        close(_fd);
+        _fd = -1;
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+static void rcvr_connect_later(); /* Forward declaration. */
+
+/**
+ * Connect to the receiver.
+ * This method assumes that _fd is -1 and _channel is NULL.  If unsure, call
+ * rcvr_disconnect() first.
+ */
+static gboolean
+rcvr_connect_now()
+{
+    printf("%s(%d)\n", __PRETTY_FUNCTION__, _conn_state);
+
+    if(_conn_state == RCVR_DOWN) {
+#ifndef DEBUG
+        /* Create the file descriptor. */
+        _fd = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
+        _channel = g_io_channel_unix_new(_fd);
+        g_io_channel_set_flags(_channel, G_IO_FLAG_NONBLOCK, NULL);
+        _error_sid = g_io_add_watch_full(_channel, G_PRIORITY_HIGH_IDLE,
+                G_IO_ERR | G_IO_HUP, channel_cb_error, NULL, NULL);
+        _connect_sid = g_io_add_watch_full(_channel, G_PRIORITY_HIGH_IDLE,
+                G_IO_OUT, channel_cb_connect, NULL, NULL);
+        if(connect(_fd, (struct sockaddr*)&_rcvr_addr, sizeof(_rcvr_addr))
+                && errno != EAGAIN)
+        {
+            rcvr_disconnect();
+            rcvr_connect_later();
+        }
+#else
+        set_conn_state(GPS_FIXED);
+#endif
+    }
+
+    _clater_sid = 0;
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return FALSE;
+}
+
+/**
+ * Place a request to connect about 1 second after the function is called.
+ */
+static void
+rcvr_connect_later()
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    _clater_sid = g_timeout_add(1000, (GSourceFunc)rcvr_connect_now, NULL);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+/**
+ * Convert the float lat/lon/speed/heading data into integer units.
+ */
+static void
+integerize_data()
+{
+    gfloat tmp;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    latlon2unit(_pos_lat, _pos_lon, _pos.unitx, _pos.unity);
+
+    tmp = (_heading * (1.f / 180.f)) * PI;
+    _vel_offsetx = (gint)(floorf(_speed * sinf(tmp) + 0.5f));
+    _vel_offsety = -(gint)(floorf(_speed * cosf(tmp) + 0.5f));
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+/**
+ * Scan for all bluetooth devices.  This method can take a few seconds,
+ * during which the UI will freeze.
+ */
+static gboolean
+scan_bluetooth(GtkWidget *txt_rcvr_mac)
+{
+    /* do an hci_inquiry for our boy. */
+    char buffer[18];
+    inquiry_info ii;
+    inquiry_info *pii = &ii;
+    gint devid = hci_get_route(NULL);
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(hci_inquiry(devid, 4, 1, NULL, &pii, IREQ_CACHE_FLUSH) > 0)
+    {
+        ba2str(&ii.bdaddr, buffer);
+        gtk_banner_close(_window);
+        gtk_entry_set_text(GTK_ENTRY(txt_rcvr_mac), buffer);
+
+        vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
+        return FALSE;
+    }
+
+    vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+/**
+ * Update all GtkGC objects to reflect the current _draw_line_width.
+ */
+#define UPDATE_GC(gc) \
+    gdk_gc_set_line_attributes(gc, \
+            _draw_line_width, GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND);
+static void
+update_gcs()
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    UPDATE_GC(_mark_current_gc);
+    UPDATE_GC(_mark_old_gc);
+    UPDATE_GC(_vel_current_gc);
+    UPDATE_GC(_vel_old_gc);
+    UPDATE_GC(_track_gc);
+    UPDATE_GC(_track_way_gc);
+    UPDATE_GC(_route_gc);
+    UPDATE_GC(_route_way_gc);
+    UPDATE_GC(_next_way_gc);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+/**
+ * Change the map cache directory and update dependent variables.
+ */
+static void
+config_set_map_dir_name(gchar *new_map_dir_name)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(_map_dir_name)
+        g_free(_map_dir_name);
+    _map_dir_name = new_map_dir_name;
+    {
+        gchar buffer[1024];
+        sprintf(buffer, "%s/.autoroute", _map_dir_name);
+        if(_autoroute_file_name)
+            g_free(_autoroute_file_name);
+        _autoroute_file_name = g_strdup(buffer);
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+/**
+ * Bring up the Settings dialog.  Return TRUE if and only if the recever
+ * information has changed (MAC or channel).
+ */
+static gboolean
+settings_dialog()
+{
+    GtkWidget *dialog;
+    GtkWidget *notebook;
+    GtkWidget *table;
+    GtkWidget *label;
+    GtkWidget *txt_rcvr_mac;
+    GtkWidget *num_rcvr_chan;
+    GtkWidget *num_zoom_steps;
+    GtkWidget *num_center_ratio;
+    GtkWidget *num_lead_ratio;
+    GtkWidget *num_draw_line_width;
+    GtkWidget *chk_enable_voice;
+    GtkWidget *chk_always_keep_on;
+    GtkWidget *txt_map_uri_format;
+    GtkWidget *txt_map_dir_name;
+    gboolean rcvr_changed = FALSE;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    dialog = gtk_dialog_new_with_buttons("Maemo Mapper Settings",
+            _window, GTK_DIALOG_MODAL,
+            GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+            GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+            NULL);
+
+    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
+            notebook = gtk_notebook_new(), TRUE, TRUE, 0);
+    
+    /* Receiver page */
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+            table = gtk_table_new(2, 3, FALSE),
+            label = gtk_label_new("GPS"));
+
+    /* Receiver MAC Address */
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new("MAC"),
+            0, 1, 0, 1, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+    gtk_table_attach(GTK_TABLE(table),
+            txt_rcvr_mac = gtk_entry_new_with_max_length(17),
+            1, 2, 0, 1, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+    if(*_rcvr_mac)
+        gtk_entry_set_text(GTK_ENTRY(txt_rcvr_mac), _rcvr_mac);
+    else
+    {
+        gtk_banner_show_animation(_window,
+                "Scanning for bluetooth devices");
+        gtk_idle_add((GSourceFunc)scan_bluetooth, txt_rcvr_mac);
+    }
+
+    /* Receiver Channel */
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new("Channel"),
+            0, 1, 1, 2, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+    gtk_table_attach(GTK_TABLE(table),
+            num_rcvr_chan = hildon_number_editor_new(0, 255),
+            1, 2, 1, 2, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+    hildon_number_editor_set_value(HILDON_NUMBER_EDITOR(num_rcvr_chan),
+            _rcvr_addr.rc_channel);
+
+    /* Note! */
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new(
+                "Note: \"Channel\" refers to the device side!"),
+            0, 2, 2, 3, GTK_FILL, 0, 2, 4);
+    gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.5f, 0.5f);
+
+
+    /* Maps page */
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+            table = gtk_table_new(2, 3, FALSE),
+            label = gtk_label_new("Maps"));
+
+    /* Map download URI */
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new("URI Prefix"),
+            0, 1, 0, 1, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+    gtk_table_attach(GTK_TABLE(table),
+            txt_map_uri_format = gtk_entry_new(),
+            1, 2, 0, 1, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+    gtk_entry_set_width_chars(GTK_ENTRY(txt_map_uri_format), 20);
+    if(_map_uri_format)
+        gtk_entry_set_text(GTK_ENTRY(txt_map_uri_format), _map_uri_format);
+
+    /* Zoom Steps */
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new("Zoom Steps"),
+            0, 1, 1, 2, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+    gtk_table_attach(GTK_TABLE(table),
+            num_zoom_steps = hildon_controlbar_new(),
+            1, 2, 1, 2, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+    hildon_controlbar_set_range(HILDON_CONTROLBAR(num_zoom_steps), 1, 4);
+    hildon_controlbar_set_value(HILDON_CONTROLBAR(num_zoom_steps), _zoom_steps);
+
+    /* Map Directory */
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new("Cache Dir."),
+            0, 1, 2, 3, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+    gtk_table_attach(GTK_TABLE(table),
+            txt_map_dir_name = gtk_entry_new(),
+            1, 2, 2, 3, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+    gtk_entry_set_width_chars(GTK_ENTRY(txt_map_dir_name), 20);
+    if(_map_dir_name)
+        gtk_entry_set_text(GTK_ENTRY(txt_map_dir_name), _map_dir_name);
+
+
+    /* Auto-Center page */
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+            table = gtk_table_new(2, 2, FALSE),
+            label = gtk_label_new("Auto-Center"));
+
+    /* Auto-Center Sensitivity */
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new("Sensitivity"),
+            0, 1, 0, 1, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+    gtk_table_attach(GTK_TABLE(table),
+            num_center_ratio = hildon_controlbar_new(),
+            1, 2, 0, 1, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+    hildon_controlbar_set_range(HILDON_CONTROLBAR(num_center_ratio), 1, 10);
+    hildon_controlbar_set_value(HILDON_CONTROLBAR(num_center_ratio),
+            _center_ratio);
+
+    /* Lead Amount */
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new("Lead Amount"),
+            0, 1, 1, 2, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+    gtk_table_attach(GTK_TABLE(table),
+            num_lead_ratio = hildon_controlbar_new(),
+            1, 2, 1, 2, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+    hildon_controlbar_set_range(HILDON_CONTROLBAR(num_lead_ratio), 1, 10);
+    hildon_controlbar_set_value(HILDON_CONTROLBAR(num_lead_ratio),
+            _lead_ratio);
+
+    /* Misc. page */
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+            table = gtk_table_new(2, 2, FALSE),
+            label = gtk_label_new("Misc."));
+
+    /* Line Width */
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new("Line Width"),
+            0, 1, 0, 1, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+    gtk_table_attach(GTK_TABLE(table),
+            num_draw_line_width = hildon_controlbar_new(),
+            1, 2, 0, 1, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+    hildon_controlbar_set_range(HILDON_CONTROLBAR(num_draw_line_width), 1, 20);
+    hildon_controlbar_set_value(HILDON_CONTROLBAR(num_draw_line_width),
+            _draw_line_width);
+
+    /* Enable Voice */
+    gtk_table_attach(GTK_TABLE(table),
+            chk_enable_voice = gtk_check_button_new_with_label(
+                "Enable Voice Synthesis (requires flite)"),
+            0, 2, 1, 2, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(chk_enable_voice),
+            _enable_voice);
+
+    /* Keep Display On Only When Fullscreen */
+    gtk_table_attach(GTK_TABLE(table),
+            chk_always_keep_on = gtk_check_button_new_with_label(
+                "Keep Display On Only in Fullscreen Mode"),
+            0, 2, 2, 3, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(chk_always_keep_on),
+            !_always_keep_on);
+
+
+    gtk_widget_show_all(dialog);
+
+    if(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog)))
+    {
+        if(17 == strlen(gtk_entry_get_text(GTK_ENTRY(txt_rcvr_mac)))
+              && strcmp(_rcvr_mac, gtk_entry_get_text(GTK_ENTRY(txt_rcvr_mac))))
+        {
+            g_strlcpy(_rcvr_mac, gtk_entry_get_text(GTK_ENTRY(txt_rcvr_mac)),
+                    sizeof(_rcvr_mac));
+            str2ba(_rcvr_mac, &_rcvr_addr.rc_bdaddr);
+            rcvr_changed = TRUE;
+        }
+
+        if(_rcvr_addr.rc_channel != hildon_number_editor_get_value(
+                    HILDON_NUMBER_EDITOR(num_rcvr_chan)))
+        {
+            _rcvr_addr.rc_channel = hildon_number_editor_get_value(
+                    HILDON_NUMBER_EDITOR(num_rcvr_chan));
+            rcvr_changed = TRUE;
+        }
+
+        if(_map_uri_format)
+            g_free(_map_uri_format);
+        if(strlen(gtk_entry_get_text(GTK_ENTRY(txt_map_uri_format))))
+            _map_uri_format = g_strdup(gtk_entry_get_text(
+                        GTK_ENTRY(txt_map_uri_format)));
+        else
+            _map_uri_format = NULL;
+
+        _zoom_steps = hildon_controlbar_get_value(
+                HILDON_CONTROLBAR(num_zoom_steps));
+
+        config_set_map_dir_name(gnome_vfs_expand_initial_tilde(
+                gtk_entry_get_text(GTK_ENTRY(txt_map_dir_name))));
+
+        _center_ratio = hildon_controlbar_get_value(
+                HILDON_CONTROLBAR(num_center_ratio));
+
+        _lead_ratio = hildon_controlbar_get_value(
+                HILDON_CONTROLBAR(num_lead_ratio));
+
+        _draw_line_width = hildon_controlbar_get_value(
+                HILDON_CONTROLBAR(num_draw_line_width));
+
+        _enable_voice = gtk_toggle_button_get_active(
+                GTK_TOGGLE_BUTTON(chk_enable_voice));
+
+        _always_keep_on = !gtk_toggle_button_get_active(
+                GTK_TOGGLE_BUTTON(chk_always_keep_on));
+        update_gcs();
+    }
+    gtk_widget_destroy(dialog);
+
+    vprintf("%s(): return %d\n", __PRETTY_FUNCTION__, rcvr_changed);
+    return rcvr_changed;
+}
+
+/**
+ * Save all configuration data to GCONF.
+ */
+static void
+config_save()
+{
+    GConfClient *gconf_client = gconf_client_get_default();
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(!gconf_client)
+    {
+        fprintf(stderr, "Failed to initialize GConf.  Aborting.\n");
+        return;
+    }
+
+    /* Save Receiver MAC from GConf. */
+    if(_rcvr_mac)
+        gconf_client_set_string(gconf_client,
+                GCONF_KEY_RCVR_MAC, _rcvr_mac, NULL);
+
+    /* Save Receiver Channel to GConf. */
+    gconf_client_set_int(gconf_client,
+            GCONF_KEY_RCVR_CHAN, _rcvr_addr.rc_channel, NULL);
+
+    /* Save Map Download URI Format. */
+    if(_map_uri_format)
+        gconf_client_set_string(gconf_client,
+                GCONF_KEY_MAP_URI_FORMAT, _map_uri_format, NULL);
+
+    /* Save Map Download Zoom Steps. */
+    gconf_client_set_int(gconf_client,
+            GCONF_KEY_MAP_ZOOM_STEPS, _zoom_steps, NULL);
+
+    /* Save Map Cache Directory. */
+    if(_map_dir_name)
+        gconf_client_set_string(gconf_client,
+            GCONF_KEY_MAP_DIR_NAME, _map_dir_name, NULL);
+
+    /* Save Auto-Download. */
+    gconf_client_set_bool(gconf_client,
+            GCONF_KEY_AUTO_DOWNLOAD, _auto_download, NULL);
+
+    /* Save Auto-Center Sensitivity. */
+    gconf_client_set_int(gconf_client,
+            GCONF_KEY_CENTER_SENSITIVITY, _center_ratio, NULL);
+
+    /* Save Auto-Center Lead Amount. */
+    gconf_client_set_int(gconf_client,
+            GCONF_KEY_LEAD_AMOUNT, _lead_ratio, NULL);
+
+    /* Save Draw Line Width. */
+    gconf_client_set_int(gconf_client,
+            GCONF_KEY_DRAW_LINE_WIDTH, _draw_line_width, NULL);
+
+    /* Save Enable Voice flag. */
+    gconf_client_set_bool(gconf_client,
+            GCONF_KEY_ENABLE_VOICE, _enable_voice, NULL);
+
+    /* Save Keep On When Fullscreen flag. */
+    gconf_client_set_bool(gconf_client,
+            GCONF_KEY_ALWAYS_KEEP_ON, _always_keep_on, NULL);
+
+    /* Save last saved latitude. */
+    gconf_client_set_float(gconf_client,
+            GCONF_KEY_LAT, _pos_lat, NULL);
+
+    /* Save last saved longitude. */
+    gconf_client_set_float(gconf_client,
+            GCONF_KEY_LON, _pos_lon, NULL);
+
+    /* Save last Zoom Level. */
+    gconf_client_set_int(gconf_client,
+            GCONF_KEY_ZOOM, _zoom, NULL);
+
+    /* Save Route Directory. */
+    if(_route_dir_uri)
+        gconf_client_set_string(gconf_client,
+                GCONF_KEY_ROUTEDIR, _route_dir_uri, NULL);
+
+    /* Save Last Track File. */
+    if(_track_file_uri)
+        gconf_client_set_string(gconf_client,
+                GCONF_KEY_TRACKFILE, _track_file_uri, NULL);
+
+    /* Save Auto-Center Mode. */
+    gconf_client_set_int(gconf_client,
+            GCONF_KEY_AUTOCENTER_MODE, _center_mode, NULL);
+
+    /* Save Show Tracks flag. */
+    gconf_client_set_bool(gconf_client,
+            GCONF_KEY_SHOWTRACKS, _show_tracks & TRACKS_MASK, NULL);
+
+    /* Save Show Routes flag. */
+    gconf_client_set_bool(gconf_client,
+            GCONF_KEY_SHOWROUTES, _show_tracks & ROUTES_MASK, NULL);
+
+    /* Save Show Velocity Vector flag. */
+    gconf_client_set_bool(gconf_client,
+            GCONF_KEY_SHOWVELVEC, _show_velvec, NULL);
+
+    /* Save Enable GPS flag. */
+    gconf_client_set_bool(gconf_client,
+            GCONF_KEY_ENABLE_GPS, _enable_gps, NULL);
+
+    /* Save Route Locations */
+    gconf_client_set_list(gconf_client,
+            GCONF_KEY_ROUTE_LOCATIONS, GCONF_VALUE_STRING, _loc_list, NULL);
+
+    g_object_unref(gconf_client);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+/**
+ * Initialize all configuration from GCONF.  This should not be called more
+ * than once during execution.
+ */
+static void
+config_init()
+{
+    GConfValue *value;
+    GConfClient *gconf_client = gconf_client_get_default();
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(!gconf_client)
+    {
+        fprintf(stderr, "Failed to initialize GConf.  Aborting.\n");
+        exit(1);
+    }
+
+    /* Get Receiver MAC from GConf.  Default is scanned via hci_inquiry. */
+    {
+        gchar *rcvr_mac = gconf_client_get_string(
+                gconf_client, GCONF_KEY_RCVR_MAC, NULL);
+        if(rcvr_mac)
+        {
+            g_strlcpy(_rcvr_mac, rcvr_mac, sizeof(_rcvr_mac));
+            g_free(rcvr_mac);
+            str2ba(_rcvr_mac, &_rcvr_addr.rc_bdaddr);
+        }
+    }
+
+    /* Get Receiver Channel from GConf.  Default is 1. */
+    _rcvr_addr.rc_family = AF_BLUETOOTH;
+    _rcvr_addr.rc_channel = gconf_client_get_int(gconf_client,
+            GCONF_KEY_RCVR_CHAN, NULL);
+    if(_rcvr_addr.rc_channel < 1)
+        _rcvr_addr.rc_channel = 1;
+
+    /* Get Map Download URI Format.  Default is NULL. */
+    _map_uri_format = gconf_client_get_string(gconf_client,
+            GCONF_KEY_MAP_URI_FORMAT, NULL);
+
+    /* Get Map Download Zoom Steps.  Default is 2. */
+    _zoom_steps = gconf_client_get_int(gconf_client,
+            GCONF_KEY_MAP_ZOOM_STEPS, NULL);
+    if(!_zoom_steps)
+        _zoom_steps = 2;
+
+    /* Get Map Cache Directory.  Default is "~/apps/maemo-mapper". */
+    {
+        gchar *tmp = gconf_client_get_string(gconf_client,
+                GCONF_KEY_MAP_DIR_NAME, NULL);
+        if(!tmp)
+            tmp = g_strdup("~/apps/maemo-mapper");
+        config_set_map_dir_name(gnome_vfs_expand_initial_tilde(tmp));
+        gnome_vfs_make_directory(_map_dir_name, 0775);
+        g_free(tmp);
+    }
+
+    /* Get Auto-Download.  Default is FALSE. */
+    _auto_download = gconf_client_get_bool(gconf_client,
+            GCONF_KEY_AUTO_DOWNLOAD, NULL);
+
+    /* Get Center Ratio - Default is 3 */
+    _center_ratio = gconf_client_get_int(gconf_client,
+            GCONF_KEY_CENTER_SENSITIVITY, NULL);
+    if(!_center_ratio)
+        _center_ratio = 7;
+
+    /* Get Lead Ratio - Default is 5 */
+    _lead_ratio = gconf_client_get_int(gconf_client,
+            GCONF_KEY_LEAD_AMOUNT, NULL);
+    if(!_lead_ratio)
+        _lead_ratio = 5;
+
+    /* Get Draw Line Width- Default is 5 */
+    _draw_line_width = gconf_client_get_int(gconf_client,
+            GCONF_KEY_DRAW_LINE_WIDTH, NULL);
+    if(!_draw_line_width)
+        _draw_line_width = 5;
+
+    /* Get Enable Voide flag.  Default is TRUE. */
+    value = gconf_client_get(gconf_client, GCONF_KEY_ENABLE_VOICE, NULL);
+    if(value)
+    {
+        _enable_voice = gconf_value_get_bool(value);
+        gconf_value_free(value);
+    }
+    else
+        _enable_voice = TRUE;
+
+    /* Get Always Keep On flag.  Default is FALSE. */
+    _always_keep_on = gconf_client_get_bool(gconf_client,
+            GCONF_KEY_ALWAYS_KEEP_ON, NULL);
+
+    /* Get last saved latitude.  Default is 0. */
+    _pos_lat = gconf_client_get_float(gconf_client, GCONF_KEY_LAT, NULL);
+
+    /* Get last saved longitude.  Default is somewhere in Midwest. */
+    value = gconf_client_get(gconf_client, GCONF_KEY_LON, NULL);
+    _pos_lon = gconf_client_get_float(gconf_client, GCONF_KEY_LON, NULL);
+
+    /* Get last Zoom Level.  Default is 16. */
+    value = gconf_client_get(gconf_client, GCONF_KEY_ZOOM, NULL);
+    if(value)
+    {
+        _zoom = gconf_value_get_int(value);
+        gconf_value_free(value);
+    }
+    else
+        _zoom = 16;
+
+    /* Speed and Heading are always initialized as 0. */
+    _speed = 0;
+    _heading = 0;
+
+    /* Get Route Directory.  Default is NULL. */
+    _route_dir_uri = gconf_client_get_string(gconf_client,
+            GCONF_KEY_ROUTEDIR, NULL);
+
+    /* Get Last Track File.  Default is NULL. */
+    _track_file_uri = gconf_client_get_string(gconf_client,
+            GCONF_KEY_TRACKFILE, NULL);
+
+    /* Get Auto-Center Mode.  Default is CENTER_LEAD. */
+    value = gconf_client_get(gconf_client, GCONF_KEY_AUTOCENTER_MODE, NULL);
+    if(value)
+    {
+        _center_mode = gconf_value_get_int(value);
+        gconf_value_free(value);
+    }
+    else
+        _center_mode = CENTER_LEAD;
+
+    /* Get Show Tracks flag.  Default is TRUE. */
+    value = gconf_client_get(gconf_client, GCONF_KEY_SHOWTRACKS, NULL);
+    if(value)
+    {
+        _show_tracks |= (gconf_value_get_bool(value) ? TRACKS_MASK : 0);
+        gconf_value_free(value);
+    }
+    else
+        _show_tracks |= TRACKS_MASK;
+
+    /* Get Show Routes flag.  Default is TRUE. */
+    value = gconf_client_get(gconf_client, GCONF_KEY_SHOWROUTES, NULL);
+    if(value)
+    {
+        _show_tracks |= (gconf_value_get_bool(value) ? ROUTES_MASK : 0);
+        gconf_value_free(value);
+    }
+    else
+        _show_tracks |= ROUTES_MASK;
+
+    /* Get Show Velocity Vector flag.  Default is TRUE. */
+    value = gconf_client_get(gconf_client, GCONF_KEY_SHOWVELVEC, NULL);
+    if(value)
+    {
+        _show_velvec = gconf_value_get_bool(value);
+        gconf_value_free(value);
+    }
+    else
+        _show_velvec = TRUE;
+
+    /* Get Enable GPS flag.  Default is TRUE. */
+    value = gconf_client_get(gconf_client, GCONF_KEY_ENABLE_GPS, NULL);
+    if(value)
+    {
+        _enable_gps = gconf_value_get_bool(value);
+        gconf_value_free(value);
+    }
+    else
+        _enable_gps = TRUE;
+
+    /* Initialize _conn_state based on _enable_gps. */
+    _conn_state = (_enable_gps ? RCVR_DOWN : RCVR_OFF);
+
+    /* Load the route locations. */
+    {
+        GSList *curr;
+        _loc_list = gconf_client_get_list(gconf_client,
+            GCONF_KEY_ROUTE_LOCATIONS, GCONF_VALUE_STRING, NULL);
+        _loc_model = gtk_list_store_new(1, G_TYPE_STRING);
+        for(curr = _loc_list; curr != NULL; curr = curr->next)
+        {
+            GtkTreeIter iter;
+            gtk_list_store_insert_with_values(_loc_model, &iter, INT_MAX,
+                    0, curr->data, -1);
+        }
+    }
+
+    g_object_unref(gconf_client);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+/**
+ * Create the menu items needed for the drop down menu.
+ */
+static void
+menu_init()
+{
+    /* Create needed handles */
+    GtkMenu *main_menu;
+    GtkWidget *submenu;
+    GtkWidget *menu_item;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    /* Get the menu of our view */
+    main_menu = hildon_appview_get_menu(_main_view);
+
+    /* Create the menu items */
+
+    /* The "Routes" submenu. */
+    gtk_menu_append(main_menu, menu_item
+            = gtk_menu_item_new_with_label("Route"));
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item),
+            submenu = gtk_menu_new());
+    gtk_menu_append(submenu, _menu_route_open_item
+            = gtk_menu_item_new_with_label("Open..."));
+    gtk_menu_append(submenu, _menu_route_download_item
+            = gtk_menu_item_new_with_label("Download..."));
+    gtk_menu_append(submenu, _menu_route_save_item
+            = gtk_menu_item_new_with_label("Save..."));
+    gtk_menu_append(submenu, _menu_route_reset_item
+        = gtk_menu_item_new_with_label("Reset"));
+    gtk_menu_append(submenu, _menu_route_clear_item
+        = gtk_menu_item_new_with_label("Clear"));
+
+    /* The "Track" submenu. */
+    gtk_menu_append(main_menu, menu_item
+            = gtk_menu_item_new_with_label("Track"));
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item),
+            submenu = gtk_menu_new());
+    gtk_menu_append(submenu, _menu_track_open_item
+            = gtk_menu_item_new_with_label("Open..."));
+    gtk_menu_append(submenu, _menu_track_save_item
+            = gtk_menu_item_new_with_label("Save..."));
+    gtk_menu_append(submenu, _menu_track_mark_way_item
+            = gtk_menu_item_new_with_label("Mark a Waypoint"));
+    gtk_menu_append(submenu, _menu_track_clear_item
+            = gtk_menu_item_new_with_label("Clear"));
+
+    gtk_menu_append(main_menu, menu_item
+            = gtk_menu_item_new_with_label("Maps"));
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item),
+            submenu = gtk_menu_new());
+    gtk_menu_append(submenu, _menu_maps_dlroute_item
+            = gtk_menu_item_new_with_label("Download Along Route"));
+    gtk_menu_append(submenu, _menu_maps_dlarea_item
+            = gtk_menu_item_new_with_label("Download Area..."));
+    gtk_menu_append(submenu, _menu_auto_download_item
+            = gtk_check_menu_item_new_with_label("Auto-Download"));
+    gtk_check_menu_item_set_active(
+            GTK_CHECK_MENU_ITEM(_menu_auto_download_item), _auto_download);
+
+    gtk_menu_append(main_menu, gtk_separator_menu_item_new());
+
+    gtk_menu_append(main_menu, menu_item
+            = gtk_menu_item_new_with_label("Show"));
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item),
+            submenu = gtk_menu_new());
+    gtk_menu_append(submenu, _menu_show_routes_item
+            = gtk_check_menu_item_new_with_label("Route"));
+    gtk_check_menu_item_set_active(
+            GTK_CHECK_MENU_ITEM(_menu_show_routes_item),
+            _show_tracks & ROUTES_MASK);
+    gtk_menu_append(submenu, _menu_show_tracks_item
+            = gtk_check_menu_item_new_with_label("Track"));
+    gtk_check_menu_item_set_active(
+            GTK_CHECK_MENU_ITEM(_menu_show_tracks_item),
+            _show_tracks & TRACKS_MASK);
+    gtk_menu_append(submenu, _menu_show_velvec_item
+            = gtk_check_menu_item_new_with_label("Velocity Vector"));
+    gtk_check_menu_item_set_active(
+            GTK_CHECK_MENU_ITEM(_menu_show_velvec_item), _show_velvec);
+
+    gtk_menu_append(main_menu, menu_item
+            = gtk_menu_item_new_with_label("Auto-Center"));
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item),
+            submenu = gtk_menu_new());
+    gtk_menu_append(submenu, _menu_ac_latlon_item
+            = gtk_radio_menu_item_new_with_label(NULL, "Lat/Lon"));
+    gtk_check_menu_item_set_active(
+            GTK_CHECK_MENU_ITEM(_menu_ac_latlon_item),
+            _center_mode == CENTER_LATLON);
+    gtk_menu_append(submenu, _menu_ac_lead_item
+            = gtk_radio_menu_item_new_with_label_from_widget(
+                GTK_RADIO_MENU_ITEM(_menu_ac_latlon_item), "Lead"));
+    gtk_check_menu_item_set_active(
+            GTK_CHECK_MENU_ITEM(_menu_ac_lead_item),
+            _center_mode == CENTER_LEAD);
+    gtk_menu_append(submenu, _menu_ac_none_item
+            = gtk_radio_menu_item_new_with_label_from_widget(
+                GTK_RADIO_MENU_ITEM(_menu_ac_latlon_item), "None"));
+    gtk_check_menu_item_set_active(
+            GTK_CHECK_MENU_ITEM(_menu_ac_none_item),
+            _center_mode < 0);
+
+    gtk_menu_append(main_menu, _menu_fullscreen_item
+            = gtk_check_menu_item_new_with_label("Full Screen"));
+    gtk_check_menu_item_set_active(
+            GTK_CHECK_MENU_ITEM(_menu_fullscreen_item), _fullscreen);
+
+    gtk_menu_append(main_menu, _menu_enable_gps_item
+            = gtk_check_menu_item_new_with_label("Enable GPS"));
+    gtk_check_menu_item_set_active(
+            GTK_CHECK_MENU_ITEM(_menu_enable_gps_item), _enable_gps);
+
+    gtk_menu_append(main_menu, gtk_separator_menu_item_new());
+
+    gtk_menu_append(main_menu, _menu_settings_item
+        = gtk_menu_item_new_with_label("Settings..."));
+
+    gtk_menu_append(main_menu, gtk_separator_menu_item_new());
+
+    gtk_menu_append(main_menu, _menu_close_item
+        = gtk_menu_item_new_with_label("Close"));
+
+    /* We need to show menu items */
+    gtk_widget_show_all(GTK_WIDGET(main_menu));
+
+    /* Connect the signals. */
+    g_signal_connect(G_OBJECT(_menu_route_open_item), "activate",
+                      G_CALLBACK(menu_cb_route_open), NULL);
+    g_signal_connect(G_OBJECT(_menu_route_download_item), "activate",
+                      G_CALLBACK(menu_cb_route_download), NULL);
+    g_signal_connect(G_OBJECT(_menu_route_save_item), "activate",
+                      G_CALLBACK(menu_cb_route_save), NULL);
+    g_signal_connect(G_OBJECT(_menu_route_reset_item), "activate",
+                      G_CALLBACK(menu_cb_route_reset), NULL);
+    g_signal_connect(G_OBJECT(_menu_route_clear_item), "activate",
+                      G_CALLBACK(menu_cb_route_clear), NULL);
+    g_signal_connect(G_OBJECT(_menu_track_open_item), "activate",
+                      G_CALLBACK(menu_cb_track_open), NULL);
+    g_signal_connect(G_OBJECT(_menu_track_save_item), "activate",
+                      G_CALLBACK(menu_cb_track_save), NULL);
+    g_signal_connect(G_OBJECT(_menu_track_mark_way_item), "activate",
+                      G_CALLBACK(menu_cb_track_mark_way), NULL);
+    g_signal_connect(G_OBJECT(_menu_track_clear_item), "activate",
+                      G_CALLBACK(menu_cb_track_clear), NULL);
+    g_signal_connect(G_OBJECT(_menu_show_tracks_item), "toggled",
+                      G_CALLBACK(menu_cb_show_tracks), NULL);
+    g_signal_connect(G_OBJECT(_menu_show_routes_item), "toggled",
+                      G_CALLBACK(menu_cb_show_routes), NULL);
+    g_signal_connect(G_OBJECT(_menu_show_velvec_item), "toggled",
+                      G_CALLBACK(menu_cb_show_velvec), NULL);
+    g_signal_connect(G_OBJECT(_menu_maps_dlroute_item), "activate",
+                      G_CALLBACK(menu_cb_maps_dlroute), NULL);
+    g_signal_connect(G_OBJECT(_menu_maps_dlarea_item), "activate",
+                      G_CALLBACK(menu_cb_maps_dlarea), NULL);
+    g_signal_connect(G_OBJECT(_menu_ac_latlon_item), "toggled",
+                      G_CALLBACK(menu_cb_ac_latlon), NULL);
+    g_signal_connect(G_OBJECT(_menu_ac_lead_item), "toggled",
+                      G_CALLBACK(menu_cb_ac_lead), NULL);
+    g_signal_connect(G_OBJECT(_menu_ac_none_item), "toggled",
+                      G_CALLBACK(menu_cb_ac_none), NULL);
+    g_signal_connect(G_OBJECT(_menu_fullscreen_item), "toggled",
+                      G_CALLBACK(menu_cb_fullscreen), NULL);
+    g_signal_connect(G_OBJECT(_menu_enable_gps_item), "toggled",
+                      G_CALLBACK(menu_cb_enable_gps), NULL);
+    g_signal_connect(G_OBJECT(_menu_auto_download_item), "toggled",
+                      G_CALLBACK(menu_cb_auto_download), NULL);
+    g_signal_connect(G_OBJECT(_menu_settings_item), "activate",
+                      G_CALLBACK(menu_cb_settings), NULL);
+    g_signal_connect(G_OBJECT(_menu_close_item), "activate",
+                      G_CALLBACK(gtk_main_quit), NULL);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+/**
+ * Call gtk_window_present() on Maemo Mapper.  This also checks the
+ * configuration and brings up the Settings dialog if the GPS Receiver is
+ * not set up, the first time it is called.
+ */
+static gboolean
+window_present()
+{
+    static gint been_here = 0;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(!been_here++)
+    {
+        /* Set connection state first, to avoid going into this if twice. */
+        if(*_rcvr_mac || !_enable_gps || settings_dialog())
+        {
+            gtk_widget_show_all(GTK_WIDGET(_main_view));
+
+            /* connect to receiver */
+            rcvr_connect_now();
+        }
+        else
+            gtk_main_quit();
+    }
+    gtk_window_present(_window);
+
+    /* re-enable any banners that might have been up. */
+    set_conn_state(_conn_state);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return FALSE;
+}
+
+static guint
+download_hashfunc(ProgressUpdateInfo *pui)
+{
+    vprintf("%s()\n", __PRETTY_FUNCTION__);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return pui->hash;
+}
+
+static gboolean
+download_equalfunc(
+        ProgressUpdateInfo *pui1, ProgressUpdateInfo *pui2)
+{
+    vprintf("%s()\n", __PRETTY_FUNCTION__);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return pui1->tilex == pui2->tilex && pui1->tiley == pui2->tiley
+        && pui1->zoom == pui2->zoom;
+}
+
+static void
+map_draw_mark()
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(_enable_gps)
+    {
+        gdk_draw_arc(
+                _map_widget->window,
+                _conn_state == GPS_FIXED ? _mark_current_gc : _mark_old_gc,
+                FALSE, /* not filled */
+                _mark_x1 - _draw_line_width, _mark_y1 - _draw_line_width,
+                2 * _draw_line_width, 2 * _draw_line_width,
+                0, 360 * 64);
+        gdk_draw_line(
+                _map_widget->window,
+                _conn_state == GPS_FIXED
+                    ? (_show_velvec ? _vel_current_gc : _mark_current_gc)
+                    : _vel_old_gc,
+                _mark_x1, _mark_y1, _mark_x2, _mark_y2);
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+static void map_set_mark()
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    _mark_x1 = unit2x(_pos.unitx);
+    _mark_y1 = unit2y(_pos.unity);
+    _mark_x2 = _mark_x1 + (_show_velvec ? _vel_offsetx : 0);
+    _mark_y2 = _mark_y1 + (_show_velvec ? _vel_offsety : 0);
+    _mark_minx = MIN(_mark_x1, _mark_x2) - (2 * _draw_line_width);
+    _mark_miny = MIN(_mark_y1, _mark_y2) - (2 * _draw_line_width);
+    _mark_width = abs(_mark_x1 - _mark_x2) + (4 * _draw_line_width);
+    _mark_height = abs(_mark_y1 - _mark_y2) + (4 * _draw_line_width);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+static void
+map_pixbuf_scale_inplace(guchar *pixels, guint ratio_p2,
+        guint src_x, guint src_y)
+{
+    guint dest_x = 0, dest_y = 0, dest_dim = TILE_SIZE_PIXELS;
+    vprintf("%s(%d, %d, %d)\n", __PRETTY_FUNCTION__, ratio_p2, src_x, src_y);
+
+    /* sweep through the entire dest area, copying as necessary, but
+     * DO NOT OVERWRITE THE SOURCE AREA.  We'll copy it afterward. */
+    do {
+        guint src_dim = dest_dim >> ratio_p2;
+        guint src_endx = src_x - dest_x + src_dim;
+        gint x, y;
+        for(y = dest_dim - 1; y >= 0; y--)
+        {
+            guint src_offset_y, dest_offset_y;
+            src_offset_y = (src_y + (y >> ratio_p2)) * TILE_PIXBUF_STRIDE;
+            dest_offset_y = (dest_y + y) * TILE_PIXBUF_STRIDE;
+            x = dest_dim - 1;
+            if((unsigned)(dest_y + y - src_y) < src_dim
+                    && (unsigned)(dest_x + x - src_x) < src_dim)
+                x -= src_dim;
+            for(; x >= 0; x--)
+            {
+                guint src_offset, dest_offset;
+                src_offset = src_offset_y + (src_x + (x >> ratio_p2)) * 3;
+                dest_offset = dest_offset_y + (dest_x + x) * 3;
+                pixels[dest_offset + 0] = pixels[src_offset + 0];
+                pixels[dest_offset + 1] = pixels[src_offset + 1];
+                pixels[dest_offset + 2] = pixels[src_offset + 2];
+                if((unsigned)(dest_y + y - src_y) < src_dim && x == src_endx)
+                    x -= src_dim;
+            }
+        }
+        /* Reuse src_dim and src_endx to store new src_x and src_y. */
+        src_dim = src_x + ((src_x - dest_x) >> ratio_p2);
+        src_endx = src_y + ((src_y - dest_y) >> ratio_p2);
+        dest_x = src_x;
+        dest_y = src_y;
+        src_x = src_dim;
+        src_y = src_endx;
+    }
+    while((dest_dim >>= ratio_p2) > 1);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+/** Free a ProgressUpdateInfo data structure that was allocated during the
+ * auto-map-download process. */
+static void
+progress_update_info_free(ProgressUpdateInfo *pui)
+{
+    vprintf("%s()\n", __PRETTY_FUNCTION__);
+
+    g_hash_table_remove(_downloads_hash, pui);
+    g_list_free(pui->src_list);
+    g_list_free(pui->dest_list);
+    g_free(pui);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+static gboolean
+map_initiate_download(gchar *buffer, guint tilex, guint tiley, guint zoom)
+{
+    GnomeVFSURI *src, *dest;
+    GList *src_list = NULL, *dest_list = NULL;
+    gint priority;
+    ProgressUpdateInfo *pui;
+    vprintf("%s(%u, %u, %u)\n", __PRETTY_FUNCTION__, tilex, tiley, zoom);
+
+    pui = g_new(ProgressUpdateInfo, 1);
+    pui->hash = tilex + (tiley << 12) + (zoom << 24);
+    if(g_hash_table_lookup(_downloads_hash, pui))
+    {
+        /* already downloading - return FALSE */
+        g_free(pui);
+        return FALSE;
+    }
+    dest = gnome_vfs_uri_new(buffer);
+    if(gnome_vfs_uri_exists(dest))
+    {
+        /* already downloaded - return FALSE */
+        gnome_vfs_uri_unref(dest);
+        g_free(pui);
+        return FALSE;
+    }
+    pui->tilex = tilex;
+    pui->tiley = tiley;
+    pui->zoom = zoom;
+    pui->src_list = src_list;
+    pui->dest_list = dest_list;
+
+    /* priority is based on proximity to _center.unitx - lower number means
+     * higher priority, so the further we are, the higher the number. */
+    priority = GNOME_VFS_PRIORITY_MIN
+        + unit2ptile(abs(tile2punit(tilex, zoom) - _center.unitx)
+                + abs(tile2punit(tiley, zoom) - _center.unity));
+    BOUND(priority, GNOME_VFS_PRIORITY_MIN, GNOME_VFS_PRIORITY_MAX);
+
+    sprintf(buffer, _map_uri_format, tilex, tiley, zoom - 1);
+    src = gnome_vfs_uri_new(buffer);
+    src_list = g_list_prepend(src_list, src);
+    dest_list = g_list_prepend(dest_list, dest);
+
+    if(GNOME_VFS_OK != gnome_vfs_async_xfer(
+                &pui->handle,
+                src_list, dest_list,
+                GNOME_VFS_XFER_USE_UNIQUE_NAMES, /* needed for dupe detect */
+                GNOME_VFS_XFER_ERROR_MODE_QUERY,
+                GNOME_VFS_XFER_OVERWRITE_MODE_QUERY,
+                priority,
+                (GnomeVFSAsyncXferProgressCallback)map_download_cb_async,
+                pui,
+                (GnomeVFSXferProgressCallback)gtk_true,
+                NULL))
+    {
+        progress_update_info_free(pui);
+        return FALSE;
+    }
+    g_hash_table_insert(_downloads_hash, pui, pui);
+    if(!_num_downloads++)
+        gtk_banner_show_bar(_window, "Downloading maps");
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static void
+map_render_tile(guint tilex, guint tiley, guint destx, guint desty,
+        gboolean fast_fail) {
+    gchar buffer[1024];
+    GdkPixbuf *pixbuf;
+    GError *error = NULL;
+    guint zoff, xoff = 0, yoff = 0;
+    vprintf("%s(%u, %u, %u, %u)\n", __PRETTY_FUNCTION__,
+            tilex, tiley, destx, desty);
+    sprintf(buffer, "%s/%u/%u/%u.jpg",
+        _map_dir_name, (_zoom - 1), tilex, tiley);
+    pixbuf = gdk_pixbuf_new_from_file(buffer, &error);
+
+    if(error)
+    {
+        pixbuf = NULL;
+        error = NULL;
+    }
+
+    if(!pixbuf && !fast_fail && _auto_download && _map_uri_format && _zoom
+            && !((_zoom - 1) % _zoom_steps))
+    {
+        map_initiate_download(buffer, tilex, tiley, _zoom);
+        fast_fail = TRUE;
+    }
+
+    for(zoff = 1; !pixbuf && (_zoom + zoff) <= MAX_ZOOM; zoff += 1)
+    {
+        /* attempt to blit a wider map */
+        sprintf(buffer, "%s/%u/%u/%u.jpg",
+                _map_dir_name, _zoom + zoff - 1,
+                (tilex >> zoff), (tiley >> zoff));
+        pixbuf = gdk_pixbuf_new_from_file(buffer, &error);
+        if(error)
+        {
+            pixbuf = NULL;
+            error = NULL;
+        }
+        if(pixbuf)
+        {
+            map_pixbuf_scale_inplace(gdk_pixbuf_get_pixels(pixbuf), zoff,
+                  (tilex - ((tilex >> zoff) << zoff)) << (TILE_SIZE_P2 - zoff),
+                  (tiley - ((tiley >> zoff) << zoff)) << (TILE_SIZE_P2 - zoff));
+        }
+        else
+        {
+            if(_auto_download && _map_uri_format
+                    && !((_zoom + zoff - 1) % _zoom_steps))
+            {
+                if(!fast_fail)
+                    map_initiate_download(buffer,
+                            tilex >> zoff, tiley >> zoff, _zoom + zoff);
+                fast_fail = TRUE;
+            }
+        }
+    }
+    if(!pixbuf)
+        pixbuf = gdk_pixbuf_new(
+                GDK_COLORSPACE_RGB,
+                FALSE, /* no alpha */
+                8, /* 8 bits per sample */
+                TILE_SIZE_PIXELS, TILE_SIZE_PIXELS);
+    if(pixbuf)
+    {
+        gdk_draw_pixbuf(
+                _map_pixmap,
+                _mark_current_gc,
+                pixbuf,
+                xoff, yoff,
+                destx, desty,
+                TILE_SIZE_PIXELS, TILE_SIZE_PIXELS,
+                GDK_RGB_DITHER_NONE, 0, 0);
+        g_object_unref(pixbuf);
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+void
+map_force_redraw()
+{
+    guint new_x, new_y;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    for(new_y = 0; new_y < BUF_HEIGHT_TILES; ++new_y)
+        for(new_x = 0; new_x < BUF_WIDTH_TILES; ++new_x)
+        {
+            map_render_tile(
+                    _base_tilex + new_x,
+                    _base_tiley + new_y,
+                    new_x * TILE_SIZE_PIXELS,
+                    new_y * TILE_SIZE_PIXELS,
+                    FALSE);
+        }
+    map_render_tracks();
+    MACRO_QUEUE_DRAW_AREA();
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+void
+map_set_zoom(guint new_zoom)
+{
+    printf("%s(%d)\n", __PRETTY_FUNCTION__, _zoom);
+
+    if(new_zoom > MAX_ZOOM || new_zoom == _zoom)
+        return;
+    _zoom = new_zoom;
+
+
+    /* if we're leading, update the center to reflect new zoom level */
+    MACRO_RECALC_CENTER(_center.unitx, _center.unity);
+
+    /* update center bounds to reflect new zoom level */
+    _min_center.unitx = pixel2unit(grid2pixel(_screen_grids_halfwidth));
+    _min_center.unity = pixel2unit(grid2pixel(_screen_grids_halfheight));
+    _max_center.unitx = WORLD_SIZE_UNITS-grid2unit(_screen_grids_halfwidth) - 1;
+    _max_center.unity = WORLD_SIZE_UNITS-grid2unit(_screen_grids_halfheight)- 1;
+
+    BOUND(_center.unitx, _min_center.unitx, _max_center.unitx);
+    BOUND(_center.unity, _min_center.unity, _max_center.unity);
+
+    _base_tilex = grid2tile((gint)pixel2grid(
+                (gint)unit2pixel((gint)_center.unitx))
+            - (gint)_screen_grids_halfwidth);
+    _base_tiley = grid2tile(pixel2grid(
+                unit2pixel(_center.unity))
+            - _screen_grids_halfheight);
+
+    /* new zoom level, so we can't reuse the old buffer's pixels */
+
+
+    /* update state variables */
+    MACRO_RECALC_OFFSET();
+    MACRO_RECALC_FOCUS_BASE();
+    MACRO_RECALC_FOCUS_SIZE();
+
+    map_set_mark();
+    map_force_redraw();
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+
+static void
+map_center_unit(guint new_center_unitx, guint new_center_unity)
+{
+    gint new_base_tilex, new_base_tiley;
+    guint new_x, new_y;
+    guint j, k, base_new_x, base_old_x, old_x, old_y, iox, ioy;
+    printf("%s(%d, %d)\n", __PRETTY_FUNCTION__,
+            new_center_unitx, new_center_unity);
+
+    /* assure that _center_unitx/y are bounded */
+    BOUND(new_center_unitx, _min_center.unitx, _max_center.unitx);
+    BOUND(new_center_unity, _min_center.unity, _max_center.unity);
+
+    _center.unitx = new_center_unitx;
+    _center.unity = new_center_unity;
+
+    new_base_tilex = grid2tile((gint)pixel2grid(
+                (gint)unit2pixel((gint)_center.unitx))
+            - (gint)_screen_grids_halfwidth);
+    new_base_tiley = grid2tile(pixel2grid(
+                unit2pixel(_center.unity))
+            - _screen_grids_halfheight);
+
+    /* same zoom level, so it's likely that we can reuse some of the old
+     * buffer's pixels */
+
+    if(new_base_tilex != _base_tilex
+            || new_base_tiley != _base_tiley)
+    {
+        /* if copying from old parts to new parts, we need to make sure we
+         * don't overwrite the old parts when copying, so set up new_x,
+         * new_y, old_x, old_y, iox, and ioy with that in mind. */
+        if(new_base_tiley < _base_tiley)
+        {
+            /* new is lower than old - start at bottom and go up */
+            new_y = BUF_HEIGHT_TILES - 1;
+            ioy = -1;
+        }
+        else
+        {
+            /* new is higher than old - start at top and go down */
+            new_y = 0;
+            ioy = 1;
+        }
+        if(new_base_tilex < _base_tilex)
+        {
+            /* new is righter than old - start at right and go left */
+            base_new_x = BUF_WIDTH_TILES - 1;
+            iox = -1;
+        }
+        else
+        {
+            /* new is lefter than old - start at left and go right */
+            base_new_x = 0;
+            iox = 1;
+        }
+
+        /* iterate over the y tile values */
+        old_y = new_y + new_base_tiley - _base_tiley;
+        base_old_x = base_new_x + new_base_tilex - _base_tilex;
+        _base_tilex = new_base_tilex;
+        _base_tiley = new_base_tiley;
+        for(j = 0; j < BUF_HEIGHT_TILES; ++j, new_y += ioy, old_y += ioy)
+        {
+            new_x = base_new_x;
+            old_x = base_old_x;
+            /* iterate over the x tile values */
+            for(k = 0; k < BUF_WIDTH_TILES; ++k, new_x += iox, old_x += iox)
+            {
+                /* can we get this grid block from the old buffer? */
+                if(old_x >= 0 && old_x < BUF_WIDTH_TILES
+                        && old_y >= 0 && old_y < BUF_HEIGHT_TILES)
+                {
+                    /* copy from old buffer to new buffer */
+                    gdk_draw_drawable(
+                            _map_pixmap,
+                            _mark_current_gc,
+                            _map_pixmap,
+                            old_x * TILE_SIZE_PIXELS,
+                            old_y * TILE_SIZE_PIXELS,
+                            new_x * TILE_SIZE_PIXELS,
+                            new_y * TILE_SIZE_PIXELS,
+                            TILE_SIZE_PIXELS, TILE_SIZE_PIXELS);
+                }
+                else
+                {
+                    map_render_tile(
+                            new_base_tilex + new_x,
+                            new_base_tiley + new_y,
+                            new_x * TILE_SIZE_PIXELS,
+                            new_y * TILE_SIZE_PIXELS,
+                            FALSE);
+                }
+            }
+        }
+        map_render_tracks();
+    }
+
+    MACRO_RECALC_OFFSET();
+    MACRO_RECALC_FOCUS_BASE();
+
+    map_set_mark();
+    MACRO_QUEUE_DRAW_AREA();
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+void
+map_pan(gint delta_gridx, gint delta_gridy)
+{
+    printf("%s(%d, %d)\n", __PRETTY_FUNCTION__, delta_gridx, delta_gridy);
+
+    if(_center_mode > 0)
+        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(
+                    _menu_ac_none_item), TRUE);
+    map_center_unit(
+            _center.unitx + grid2unit(delta_gridx),
+            _center.unity + grid2unit(delta_gridy));
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+static void
+map_move_mark()
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    /* just queue the old and new draw areas */
+    gtk_widget_queue_draw_area(_map_widget,
+            _mark_minx,
+            _mark_miny,
+            _mark_width,
+            _mark_height);
+    map_set_mark();
+    gtk_widget_queue_draw_area(_map_widget,
+            _mark_minx,
+            _mark_miny,
+            _mark_width,
+            _mark_height);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+static void
+refresh_mark()
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    guint new_center_unitx;
+    guint new_center_unity;
+
+    MACRO_RECALC_CENTER(new_center_unitx, new_center_unity);
+
+    if((new_center_unitx - _focus.unitx) < _focus_unitwidth
+            && (new_center_unity - _focus.unity) < _focus_unitheight)
+        /* we're not changing the view - just move the mark */
+        map_move_mark();
+    else
+        map_center_unit(new_center_unitx, new_center_unity);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+#define MACRO_SET_UNKNOWN() { \
+    data->prev_state = data->state; \
+    data->state = UNKNOWN; \
+    data->unknown_depth = 1; \
+}
+
+static void
+gpx_start_element(SaxData *data, const xmlChar *name, const xmlChar **attrs)
+{
+    vprintf("%s(%s)\n", __PRETTY_FUNCTION__, name);
+
+    switch(data->state)
+    {
+        case ERROR:
+            break;
+        case START:
+            if(!strcmp((gchar*)name, "gpx"))
+                data->state = INSIDE_GPX;
+            else
+                MACRO_SET_UNKNOWN();
+            break;
+        case INSIDE_GPX:
+            if(!strcmp((gchar*)name, "trk"))
+                data->state = INSIDE_TRACK;
+            else
+                MACRO_SET_UNKNOWN();
+            break;
+        case INSIDE_TRACK:
+            if(!strcmp((gchar*)name, "trkseg"))
+            {
+                data->state = INSIDE_TRACK_SEGMENT;
+                data->at_least_one_trkpt = FALSE;
+            }
+            else
+                MACRO_SET_UNKNOWN();
+            break;
+        case INSIDE_TRACK_SEGMENT:
+            if(!strcmp((gchar*)name, "trkpt"))
+            {
+                const xmlChar **curr_attr;
+                gchar *error_check;
+                gfloat lat = 0.f, lon = 0.f;
+                gboolean has_lat, has_lon;
+                has_lat = FALSE;
+                has_lon = FALSE;
+                for(curr_attr = attrs; *curr_attr != NULL; )
+                {
+                    const gchar *attr_name = *curr_attr++;
+                    const gchar *attr_val = *curr_attr++;
+                    if(!strcmp(attr_name, "lat"))
+                    {
+                        lat = strtof(attr_val, &error_check);
+                        if(error_check != attr_val)
+                            has_lat = TRUE;
+                    }
+                    else if(!strcmp(attr_name, "lon"))
+                    {
+                        lon = strtof(attr_val, &error_check);
+                        if(error_check != attr_val)
+                            has_lon = TRUE;
+                    }
+                }
+                if(has_lat && has_lon)
+                {
+                    MACRO_TRACK_INCREMENT_TAIL(data->track);
+                    latlon2unit(lat, lon, data->track.tail->unitx,
+                            data->track.tail->unity);
+                    data->state = INSIDE_TRACK_POINT;
+                }
+                else
+                    data->state = ERROR;
+            }
+            else
+                MACRO_SET_UNKNOWN();
+            break;
+        case INSIDE_TRACK_POINT:
+            if(!strcmp((gchar*)name, "desc"))
+                data->state = INSIDE_TRACK_POINT_DESC;
+            else
+                MACRO_SET_UNKNOWN();
+            break;
+        case UNKNOWN:
+            data->unknown_depth++;
+            break;
+        default:
+            ;
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+static void
+gpx_end_element(SaxData *data, const xmlChar *name)
+{
+    vprintf("%s(%s)\n", __PRETTY_FUNCTION__, name);
+
+    switch(data->state)
+    {
+        case ERROR:
+            break;
+        case START:
+            data->state = ERROR;
+            break;
+        case INSIDE_GPX:
+            if(!strcmp((gchar*)name, "gpx"))
+                data->state = FINISH;
+            else
+                data->state = ERROR;
+            break;
+        case INSIDE_TRACK:
+            if(!strcmp((gchar*)name, "trk"))
+                data->state = INSIDE_GPX;
+            else
+                data->state = ERROR;
+            break;
+        case INSIDE_TRACK_SEGMENT:
+            if(!strcmp((gchar*)name, "trkseg"))
+            {
+                if(data->at_least_one_trkpt)
+                {
+                    MACRO_TRACK_INCREMENT_TAIL(data->track);
+                    data->track.tail->unitx = data->track.tail->unity = 0;
+                }
+                data->state = INSIDE_TRACK;
+            }
+            else
+                data->state = ERROR;
+            break;
+        case INSIDE_TRACK_POINT:
+            if(!strcmp((gchar*)name, "trkpt"))
+            {
+                data->state = INSIDE_TRACK_SEGMENT;
+                data->at_least_one_trkpt = TRUE;
+            }
+            else
+                data->state = ERROR;
+            break;
+        case INSIDE_TRACK_POINT_DESC:
+            if(!strcmp((gchar*)name, "desc"))
+            {
+                MACRO_TRACK_INCREMENT_WTAIL(data->track);
+                data->track.wtail->point = data->track.tail;
+                data->track.wtail->desc = g_string_free(data->chars, FALSE);
+                data->chars = g_string_new("");
+                data->state = INSIDE_TRACK_POINT;
+            }
+            else
+                data->state = ERROR;
+            break;
+        case UNKNOWN:
+            if(!--data->unknown_depth)
+                data->state = data->prev_state;
+            else
+                data->state = ERROR;
+            break;
+        default:
+            ;
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+static void
+gpx_chars(SaxData *data, const xmlChar *ch, int len)
+{
+    guint i;
+    vprintf("%s()\n", __PRETTY_FUNCTION__);
+
+    switch(data->state)
+    {
+        case ERROR:
+            break;
+        case INSIDE_TRACK_POINT_DESC:
+            for (i = 0; i < len; i++)
+                data->chars = g_string_append_c(data->chars, ch[i]);
+            vprintf("%s\n", data->chars->str);
+            break;
+        default:
+            ;
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+static xmlEntityPtr
+gpx_get_entity(SaxData *data, const xmlChar *name)
+{
+    vprintf("%s()\n", __PRETTY_FUNCTION__);
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return xmlGetPredefinedEntity(name);
+}
+
+static void
+gpx_error(SaxData *data, const char *msg, ...)
+{
+    vprintf("%s()\n", __PRETTY_FUNCTION__);
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    data->state = ERROR;
+}
+
+static gboolean
+parse_gpx(gchar *buffer, gint size, Track *toreplace, gint policy_old,
+        gint extra_bins)
+{
+    SaxData data;
+    xmlSAXHandler sax_handler;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    MACRO_INIT_TRACK(data.track);
+    data.state = START;
+    data.chars = g_string_new("");
+
+    memset(&sax_handler, 0, sizeof(sax_handler));
+    sax_handler.characters = (charactersSAXFunc)gpx_chars;
+    sax_handler.startElement = (startElementSAXFunc)gpx_start_element;
+    sax_handler.endElement = (endElementSAXFunc)gpx_end_element;
+    sax_handler.entityDecl = (entityDeclSAXFunc)gpx_get_entity;
+    sax_handler.warning = (warningSAXFunc)gpx_error;
+    sax_handler.error = (errorSAXFunc)gpx_error;
+    sax_handler.fatalError = (fatalErrorSAXFunc)gpx_error;
+
+    xmlSAXUserParseMemory(&sax_handler, &data, buffer, size);
+    g_string_free(data.chars, TRUE);
+
+    if(data.state != FINISH)
+    {
+        vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
+        return FALSE;
+    }
+    
+    /* Successful parsing - replace given Track structure. */
+    if(policy_old && toreplace->head)
+    {
+        TrackPoint *src_first;
+        Track *src, *dest;
+
+        if(policy_old > 0)
+        {
+            /* append to current track. */
+            src = &data.track;
+            dest = toreplace;
+        }
+        else
+        {
+            /* prepend to current track. */
+            src = toreplace;
+            dest = &data.track;
+        }
+
+        /* Find src_first non-zero point. */
+        for(src_first = src->head - 1; src_first++ != src->tail; )
+            if(src_first->unity != 0)
+                break;
+
+        /* Append track points from src to dest */
+        if(src->tail >= src_first)
+        {
+            WayPoint *curr;
+            guint num_dest_points = dest->tail - dest->head + 1;
+            guint num_src_points = src->tail - src_first + 1;
+
+            /* Adjust dest->tail to be able to fit src track data
+             * plus room for more track data. */
+            track_resize(dest, num_dest_points + num_src_points + extra_bins);
+
+            memcpy(dest->tail + 1, src_first,
+                    num_src_points * sizeof(TrackPoint));
+
+            dest->tail += num_src_points;
+
+            /* Append waypoints from src to dest-> */
+            track_wresize(dest, (dest->wtail - dest->whead)
+                    + (src->wtail - src->whead) + 2 + extra_bins);
+            for(curr = src->whead - 1; curr++ != src->wtail; )
+            {
+                (++(dest->wtail))->point = dest->head + num_dest_points
+                    + (curr->point - src_first);
+                dest->wtail->desc = curr->desc;
+            }
+
+        }
+
+        /* kill old track - don't use MACRO_CLEAR_TRACK(), because that
+         * would free the string desc's that we moved to data.track */
+        g_free(src->head);
+        g_free(src->whead);
+        if(policy_old < 0)
+            *toreplace = *dest;
+    }
+    else
+    {
+        MACRO_CLEAR_TRACK(*toreplace);
+        /* overwrite with data.track */
+        *toreplace = data.track;
+        track_resize(toreplace,
+                toreplace->tail - toreplace->head + 1 + extra_bins);
+    }
+
+    vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+/**
+ * This is a multi-purpose function for allowing the user to select a file
+ * for either reading or writing.  If chooser_action is
+ * GTK_FILE_CHOOSER_ACTION_OPEN, then bytes_out and size_out must be
+ * non-NULL.  If chooser_action is GTK_FILE_CHOOSER_ACTION_SAVE, then
+ * handle_out must be non-NULL.  Either dir or file (or both) can be NULL.
+ * This function returns TRUE if a file was successfully opened.
+ */
+static gboolean
+open_file(gchar **bytes_out, GnomeVFSHandle **handle_out, gint *size_out,
+        gchar **dir, gchar **file, GtkFileChooserAction chooser_action)
+{
+    GtkWidget *dialog;
+    gboolean success = FALSE;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    dialog = hildon_file_chooser_dialog_new(_window, chooser_action);
+
+    if(dir && *dir)
+        gtk_file_chooser_set_current_folder_uri(
+                GTK_FILE_CHOOSER(dialog), *dir);
+    if(file && *file)
+    {
+        GValue val;
+        gtk_file_chooser_set_uri(
+                GTK_FILE_CHOOSER(dialog), *file);
+        /* Work around a bug in HildonFileChooserDialog. */
+        memset(&val, 0, sizeof(val));
+        g_value_init(&val, G_TYPE_BOOLEAN);
+        g_value_set_boolean(&val, FALSE);
+        g_object_set_property(G_OBJECT(dialog), "autonaming", &val);
+        gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog),
+                strrchr(*file, '/') + 1);
+    }
+
+    gtk_widget_show_all(GTK_WIDGET(dialog));
+
+    while(!success && gtk_dialog_run(GTK_DIALOG(dialog))==GTK_RESPONSE_OK)
+    {
+        gchar *file_uri_str;
+        GnomeVFSResult vfs_result;
+
+        /* Get the selected filename. */
+        file_uri_str = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dialog));
+
+        if((chooser_action == GTK_FILE_CHOOSER_ACTION_OPEN
+                && (GNOME_VFS_OK != (vfs_result = gnome_vfs_read_entire_file(
+                        file_uri_str, size_out, bytes_out))))
+                || (chooser_action == GTK_FILE_CHOOSER_ACTION_SAVE
+                    && GNOME_VFS_OK != (vfs_result = gnome_vfs_create(
+                            handle_out, file_uri_str,
+                            GNOME_VFS_OPEN_WRITE, FALSE, 0664))))
+        {
+            char buffer[1024];
+            sprintf(buffer, "Failed to open file for %s.\n%s",
+                    chooser_action == GTK_FILE_CHOOSER_ACTION_OPEN
+                    ? "reading" : "writing",
+                    gnome_vfs_result_to_string(vfs_result));
+            popup_error(buffer);
+        }
+        else
+            success = TRUE;
+
+        g_free(file_uri_str);
+    }
+
+    if(success)
+    {
+        /* Success! */
+        if(dir)
+        {
+            if(*dir)
+                g_free(*dir);
+            *dir = gtk_file_chooser_get_current_folder_uri(
+                    GTK_FILE_CHOOSER(dialog));
+        }
+
+        /* If desired, save the file for later. */
+        if(file)
+        {
+            if(*file)
+                g_free(*file);
+            *file = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dialog));
+        }
+    }
+
+    gtk_widget_destroy(dialog);
+
+    vprintf("%s(): return %d\n", __PRETTY_FUNCTION__, success);
+    return success;
+}
+
+static gboolean
+auto_route_dl_idle_refresh()
+{
+    /* make sure we're still supposed to do work */
+    vprintf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(!_autoroute_data.enabled)
+        _autoroute_data.handle = NULL;
+    else
+    {
+        if(parse_gpx(_autoroute_data.bytes, _autoroute_data.bytes_read,
+                    &_route, 0, 0))
+        {
+            /* Find the nearest route point, if we're connected. */
+            route_find_nearest_point();
+
+            map_force_redraw();
+        }
+    }
+
+    _autoroute_data.in_progress = FALSE;
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return FALSE;
+}
+
+static void
+auto_route_dl_cb_read(GnomeVFSAsyncHandle *handle,
+        GnomeVFSResult result,
+        gpointer bytes,
+        GnomeVFSFileSize bytes_requested,
+        GnomeVFSFileSize bytes_read)
+{
+    vprintf("%s(%s)\n", __PRETTY_FUNCTION__,gnome_vfs_result_to_string(result));
+
+    _autoroute_data.bytes_read += bytes_read;
+    if(result == GNOME_VFS_OK)
+    {
+        /* Expand bytes and continue reading. */
+        if(_autoroute_data.bytes_read * 2 > _autoroute_data.bytes_maxsize)
+        {
+            _autoroute_data.bytes = g_renew(gchar, _autoroute_data.bytes,
+                    2 * _autoroute_data.bytes_read);
+            _autoroute_data.bytes_maxsize = 2 * _autoroute_data.bytes_read;
+        }
+        gnome_vfs_async_read(_autoroute_data.handle,
+                _autoroute_data.bytes + _autoroute_data.bytes_read,
+                _autoroute_data.bytes_maxsize - _autoroute_data.bytes_read,
+                (GnomeVFSAsyncReadCallback)auto_route_dl_cb_read, NULL);
+    }
+    else
+        g_idle_add((GSourceFunc)auto_route_dl_idle_refresh, NULL);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+
+static void
+auto_route_dl_cb_open(GnomeVFSAsyncHandle *handle, GnomeVFSResult result)
+{
+    vprintf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(result == GNOME_VFS_OK)
+        gnome_vfs_async_read(_autoroute_data.handle,
+                _autoroute_data.bytes,
+                _autoroute_data.bytes_maxsize,
+                (GnomeVFSAsyncReadCallback)auto_route_dl_cb_read, NULL);
+    else
+    {
+        _autoroute_data.in_progress = FALSE;
+        _autoroute_data.handle = NULL;
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+static gboolean
+auto_route_dl_idle()
+{
+    gchar buffer[1024];
+    vprintf("%s(%f, %f, %s)\n", __PRETTY_FUNCTION__,
+            _pos_lat, _pos_lon, _autoroute_data.dest);
+
+    sprintf(buffer,"http://www.gnuite.com/cgi-bin/gpx.cgi?saddr=%f,%f&daddr=%s",
+            _pos_lat, _pos_lon, _autoroute_data.dest);
+    printf("Downloading %s\n", buffer);
+    _autoroute_data.bytes_read = 0;
+
+    gnome_vfs_async_open(&_autoroute_data.handle,
+            buffer, GNOME_VFS_OPEN_READ,
+            GNOME_VFS_PRIORITY_DEFAULT,
+            (GnomeVFSAsyncOpenCallback)auto_route_dl_cb_open, NULL);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return FALSE;
+}
+
+
+static void
+maemo_mapper_destroy(void)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    config_save();
+    rcvr_disconnect();
+    /* _window and widgets have already been destroyed. */
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+static void
+maemo_mapper_init(gint argc, gchar **argv)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    config_init();
+
+    /* Block for HildonApp, HildonAppView variables. */
+    {
+        HildonApp *window;
+        /* Initialize _window. */
+        window = HILDON_APP(hildon_app_new());
+        hildon_app_set_title(window, "Maemo Mapper");
+
+        /* Initialize _main_view. */
+        _main_view = HILDON_APPVIEW(hildon_appview_new("Maemo Mapper"));
+        hildon_app_set_appview(window, _main_view);
+
+        _window = GTK_WINDOW(window);
+    }
+
+    /* Create and add widgets and supporting data. */
+    _map_widget = gtk_drawing_area_new();
+    gtk_container_add(GTK_CONTAINER(_main_view), _map_widget);
+
+    gtk_widget_realize(_map_widget);
+
+    _map_pixmap = gdk_pixmap_new(
+                _map_widget->window,
+                BUF_WIDTH_PIXELS, BUF_HEIGHT_PIXELS,
+                -1); /* -1: use bit depth of widget->window */
+
+    /* Connect signals. */
+    g_signal_connect(G_OBJECT(_window), "destroy",
+            G_CALLBACK(gtk_main_quit), NULL);
+
+    g_signal_connect(G_OBJECT(_window), "key_press_event",
+            G_CALLBACK(window_cb_key_press), NULL);
+
+    g_signal_connect(G_OBJECT(_map_widget), "configure_event",
+            G_CALLBACK(map_cb_configure), NULL);
+
+    g_signal_connect(G_OBJECT(_map_widget), "expose_event",
+            G_CALLBACK(map_cb_expose), NULL);
+
+    g_signal_connect(G_OBJECT(_map_widget), "button_release_event",
+            G_CALLBACK(map_cb_button_release), NULL);
+
+    gtk_widget_add_events(_map_widget,
+            GDK_EXPOSURE_MASK
+            | GDK_BUTTON_PRESS_MASK
+            | GDK_BUTTON_RELEASE_MASK);
+
+    osso_hw_set_event_cb(_osso, NULL, osso_cb_hw_state, NULL);
+
+    gnome_vfs_async_set_job_limit(24);
+
+    /* Initialize data. */
+
+    memset(&_track, 0, sizeof(_track));
+    memset(&_route, 0, sizeof(_route));
+    _last_spoken_phrase = g_strdup("");
+
+    /* Set up track array. */
+    MACRO_INIT_TRACK(_track);
+
+    _downloads_hash = g_hash_table_new((GHashFunc)download_hashfunc,
+            (GEqualFunc)download_equalfunc);
+    memset(&_autoroute_data, 0, sizeof(_autoroute_data));
+
+    integerize_data();
+
+    _center.unitx = leadx2unit();
+    _center.unity = leady2unit();
+
+    /* Initialize our line styles. */
+    {
+        GdkColor red = { 0, 0xffff, 0, 0 };
+        GdkColor dark_red = { 0, 0xbfff, 0, 0 };
+        GdkColor green = { 0, 0, 0xdfff, 0 };
+        GdkColor dark_green = { 0, 0, 0xbfff, 0 };
+        GdkColor darker_green = { 0, 0, 0x9fff, 0 };
+        GdkColor blue = { 0, 0x5fff, 0x5fff, 0xffff };
+        GdkColor dark_blue = { 0, 0, 0, 0xffff };
+        GdkColor gray = { 0, 0x7fff, 0x7fff, 0x7fff };
+
+        gdk_color_alloc(gtk_widget_get_colormap(_map_widget), &red);
+        gdk_color_alloc(gtk_widget_get_colormap(_map_widget), &dark_red);
+        gdk_color_alloc(gtk_widget_get_colormap(_map_widget), &green);
+        gdk_color_alloc(gtk_widget_get_colormap(_map_widget), &dark_green);
+        gdk_color_alloc(gtk_widget_get_colormap(_map_widget), &darker_green);
+        gdk_color_alloc(gtk_widget_get_colormap(_map_widget), &blue);
+        gdk_color_alloc(gtk_widget_get_colormap(_map_widget), &dark_blue);
+        gdk_color_alloc(gtk_widget_get_colormap(_map_widget), &gray);
+
+        /* _mark_current_gc is used to draw the mark when data is current */
+        _mark_current_gc = gdk_gc_new(_map_pixmap);
+        gdk_gc_set_foreground(_mark_current_gc, &dark_blue);
+
+        /* _mark_old_gc is used to draw the mark when data is old */
+        _mark_old_gc = gdk_gc_new(_map_pixmap);
+        gdk_gc_copy(_mark_old_gc, _mark_current_gc);
+        gdk_gc_set_foreground(_mark_old_gc, &gray);
+
+        /* _vel_current_gc is used to draw the vel_current line */
+        _vel_current_gc = gdk_gc_new(_map_pixmap);
+        gdk_gc_copy(_vel_current_gc, _mark_current_gc);
+        gdk_gc_set_foreground(_vel_current_gc, &blue);
+
+        /* _vel_current_gc is used to draw the vel mark when data is old */
+        _vel_old_gc = gdk_gc_new(_map_pixmap);
+        gdk_gc_copy(_vel_old_gc, _mark_old_gc);
+
+        /* _track_gc is used to draw the track line */
+        _track_gc = gdk_gc_new(_map_pixmap);
+        gdk_gc_copy(_track_gc, _mark_current_gc);
+        gdk_gc_set_foreground(_track_gc, &red);
+
+        /* _track_way_gc is used to draw the track_way line */
+        _track_way_gc = gdk_gc_new(_map_pixmap);
+        gdk_gc_copy(_track_way_gc, _mark_current_gc);
+        gdk_gc_set_foreground(_track_way_gc, &dark_red);
+
+        /* _route_gc is used to draw the route line */
+        _route_gc = gdk_gc_new(_map_pixmap);
+        gdk_gc_copy(_route_gc, _mark_current_gc);
+        gdk_gc_set_foreground(_route_gc, &green);
+
+        /* _way_gc is used to draw the waypoint dots */
+        _route_way_gc = gdk_gc_new(_map_pixmap);
+        gdk_gc_copy(_route_way_gc, _mark_current_gc);
+        gdk_gc_set_foreground(_route_way_gc, &dark_green);
+
+        /* _next_way_gc is used to draw the next_way labels */
+        _next_way_gc = gdk_gc_new(_map_pixmap);
+        gdk_gc_copy(_next_way_gc, _mark_current_gc);
+        gdk_gc_set_foreground(_next_way_gc, &darker_green);
+
+        update_gcs();
+    }
+
+    menu_init();
+
+    /* If present, attempt to load the file specified on the command line. */
+    if(argc > 1)
+    {
+        GnomeVFSResult vfs_result;
+        gint size;
+        gchar *buffer;
+        gchar *file_uri;
+
+        /* Get the selected filename. */
+        file_uri = gnome_vfs_make_uri_from_shell_arg(argv[1]);
+
+        if(GNOME_VFS_OK != (vfs_result = gnome_vfs_read_entire_file(
+                        file_uri, &size, &buffer)))
+        {
+            char buffer[1024];
+            sprintf(buffer, "Failed to open file for reading.\n%s",
+                    gnome_vfs_result_to_string(vfs_result));
+            popup_error(buffer);
+        }
+        else
+        {
+            /* if auto is enabled, append the route, otherwise replace it. */
+            if(parse_gpx(buffer, size, &_route, 0, 0))
+                gtk_infoprint(_window, "Route Opened");
+            else
+                popup_error("Error parsing GPX file.");
+            g_free(buffer);
+        }
+        g_free(file_uri);
+    }
+
+    gtk_idle_add((GSourceFunc)window_present, NULL);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+static void
+cancel_autoroute()
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(_autoroute_data.enabled)
+    {
+        _autoroute_data.enabled = FALSE;
+        if(_autoroute_data.dest)
+        {
+            g_free(_autoroute_data.dest);
+            _autoroute_data.dest = NULL;
+        }
+        if(_autoroute_data.handle)
+        {
+            gnome_vfs_async_cancel(_autoroute_data.handle);
+            _autoroute_data.handle = NULL;
+        }
+        if(_autoroute_data.bytes)
+        {
+            g_free(_autoroute_data.bytes);
+            _autoroute_data.bytes = NULL;
+        }
+        _autoroute_data.in_progress = FALSE;
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+/****************************************************************************
+ * ABOVE: ROUTINES **********************************************************
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ * BELOW: MAIN **************************************************************
+ ****************************************************************************/
+
+gint
+main(gint argc, gchar *argv[])
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    g_thread_init(NULL);
+
+    /* Initialize _osso. */
+    _osso = osso_initialize("maemo_mapper", VERSION, TRUE, NULL);
+    if(!_osso)
+    {
+        fprintf(stderr, "osso_initialize failed.\n");
+        return 1;
+    }
+
+    gtk_init(&argc, &argv);
+
+    /* Init gconf */
+    g_type_init();
+    gconf_init(argc, argv, NULL);
+
+    gnome_vfs_init();
+
+    maemo_mapper_init(argc, argv);
+
+    if(OSSO_OK != osso_rpc_set_default_cb_f(_osso, dbus_cb_default, NULL))
+    {
+        fprintf(stderr, "osso_rpc_set_default_cb_f failed.\n");
+        return 1;
+    }
+
+    gtk_main();
+
+    maemo_mapper_destroy();
+
+    osso_deinitialize(_osso);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return 0;
+}
+
+/****************************************************************************
+ * ABOVE: MAIN **************************************************************
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ * BELOW: CALLBACKS *********************************************************
+ ****************************************************************************/
+
+static gint
+dbus_cb_default(const gchar *interface, const gchar *method,
+        GArray *arguments, gpointer data, osso_rpc_t *retval)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(!strcmp(method, "top_application"))
+        gtk_idle_add((GSourceFunc)window_present, NULL);
+
+    retval->type = DBUS_TYPE_INVALID;
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return OSSO_OK;
+}
+
+static void
+osso_cb_hw_state(osso_hw_state_t *state, gpointer data)
+{
+    static gboolean _must_save_data = FALSE;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(state->system_inactivity_ind)
+    {
+        if(_must_save_data)
+            _must_save_data = FALSE;
+        else if(_conn_state > RCVR_OFF)
+        {
+            set_conn_state(RCVR_OFF);
+            rcvr_disconnect();
+            track_add(0, 0, FALSE);
+            /* pretend the autoroute is in progress to avoid download. */
+            if(_autoroute_data.enabled)
+                _autoroute_data.in_progress = TRUE;
+        }
+    }
+    else if(state->save_unsaved_data_ind)
+    {
+        config_save();
+        _must_save_data = TRUE;
+    }
+    else if(_conn_state == RCVR_OFF && _enable_gps)
+    {
+        set_conn_state(RCVR_DOWN);
+        rcvr_connect_later();
+        if(_autoroute_data.enabled)
+            _autoroute_data.in_progress = TRUE;
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+static gboolean
+window_cb_key_press(GtkWidget* widget, GdkEventKey *event)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    switch (event->keyval)
+    {
+        case GDK_Up:
+            map_pan(0, -2);
+            return TRUE;
+
+        case GDK_Down:
+            map_pan(0, 2);
+            return TRUE;
+
+        case GDK_Left:
+            map_pan(-2, 0);
+            return TRUE;
+
+        case GDK_Right:
+            map_pan(2, 0);
+            return TRUE;
+
+        case GDK_Return:
+            switch(_center_mode)
+            {
+                case CENTER_LATLON:
+                case CENTER_WAS_LEAD:
+                    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(
+                                _menu_ac_lead_item), TRUE);
+                    break;
+                case CENTER_LEAD:
+                case CENTER_WAS_LATLON:
+                    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(
+                                _menu_ac_latlon_item), TRUE);
+                    break;
+            }
+            return TRUE;
+
+        case GDK_F6: /* the fullscreen button */
+            gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(
+                        _menu_fullscreen_item), !_fullscreen);
+            return TRUE;
+
+        case GDK_F7: /* the zoom-in button */
+            map_set_zoom(_zoom - 1);
+            return TRUE;
+
+        case GDK_F8: /* the zoom-out button */
+            map_set_zoom(_zoom + 1);
+            return TRUE;
+
+        case GDK_Escape:
+            {
+                switch(_show_tracks)
+                {
+                    case 0:
+                        /* Nothing shown, nothing saved; just set both. */
+                        _show_tracks = TRACKS_MASK | ROUTES_MASK;
+                        break;
+                    case TRACKS_MASK << 16:
+                    case ROUTES_MASK << 16:
+                    case (ROUTES_MASK | TRACKS_MASK) << 16:
+                        /* Something was saved and nothing changed since.
+                         * Restore saved. */
+                        _show_tracks = _show_tracks >> 16;
+                        break;
+                    default:
+                        /* There is no history, or they changed something since
+                         * the last historical change. Save and clear. */
+                        _show_tracks = _show_tracks << 16;
+                }
+
+                gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(
+                            _menu_show_routes_item),
+                        _show_tracks & ROUTES_MASK);
+
+                gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(
+                            _menu_show_tracks_item),
+                        _show_tracks & TRACKS_MASK);
+            }
+            return TRUE;
+    }
+    return FALSE;
+}
+
+static gboolean
+map_cb_configure(GtkWidget *widget, GdkEventConfigure *event)
+{
+    printf("%s(%d, %d)\n", __PRETTY_FUNCTION__,
+            _map_widget->allocation.width, _map_widget->allocation.height);
+
+    _screen_width_pixels = _map_widget->allocation.width;
+    _screen_height_pixels = _map_widget->allocation.height;
+    _screen_grids_halfwidth = pixel2grid(_screen_width_pixels) / 2;
+    _screen_grids_halfheight = pixel2grid(_screen_height_pixels) / 2;
+
+    MACRO_RECALC_FOCUS_BASE();
+    MACRO_RECALC_FOCUS_SIZE();
+
+    _min_center.unitx = pixel2unit(grid2pixel(_screen_grids_halfwidth));
+    _min_center.unity = pixel2unit(grid2pixel(_screen_grids_halfheight));
+    _max_center.unitx = WORLD_SIZE_UNITS-grid2unit(_screen_grids_halfwidth) - 1;
+    _max_center.unity = WORLD_SIZE_UNITS-grid2unit(_screen_grids_halfheight)- 1;
+
+    map_center_unit(_center.unitx, _center.unity);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+map_cb_expose(GtkWidget *widget, GdkEventExpose *event)
+{
+    printf("%s(%d, %d, %d, %d)\n", __PRETTY_FUNCTION__,
+            event->area.x, event->area.y,
+            event->area.width, event->area.height);
+
+    gdk_draw_drawable(
+            _map_widget->window,
+            _mark_current_gc,
+            _map_pixmap,
+            event->area.x + _offsetx, event->area.y + _offsety,
+            event->area.x, event->area.y,
+            event->area.width, event->area.height);
+    map_draw_mark();
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+map_cb_button_release(GtkWidget *widget, GdkEventButton *event)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+#ifdef DEBUG
+    if(event->button != 1)
+    {
+        _pos.unitx = x2unit((gint)(event->x + 0.5));
+        _pos.unity = y2unit((gint)(event->y + 0.5));
+        unit2latlon(_pos.unitx, _pos.unity, _pos_lat, _pos_lon);
+        _speed = 20;
+        integerize_data();
+        track_add(_pos.unitx, _pos.unity, FALSE);
+        refresh_mark();
+    }
+    else
+#endif
+    {
+        if(_center_mode > 0)
+            gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(
+                        _menu_ac_none_item), TRUE);
+        map_center_unit(
+                x2unit((gint)(event->x + 0.5)),
+                y2unit((gint)(event->y + 0.5)));
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+channel_cb_error(GIOChannel *src, GIOCondition condition, gpointer data)
+{
+    printf("%s(%d)\n", __PRETTY_FUNCTION__, condition);
+
+    /* An error has occurred - re-connect(). */
+    if(_conn_state != GPS_FIXED)
+        gtk_banner_close(_window);
+
+    rcvr_disconnect();
+    track_add(0, 0, FALSE);
+
+    if(_conn_state > RCVR_OFF)
+    {
+        set_conn_state(RCVR_DOWN);
+        rcvr_connect_now();
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return FALSE;
+}
+
+static gboolean
+channel_cb_connect(GIOChannel *src, GIOCondition condition, gpointer data)
+{
+    int error, size = sizeof(error);
+    printf("%s(%d)\n", __PRETTY_FUNCTION__, condition);
+
+    if(getsockopt(_fd, SOL_SOCKET, SO_ERROR, &error, &size) || error)
+    {
+        printf("%s(): Error connecting to receiver; retrying...\n",
+                __PRETTY_FUNCTION__);
+        /* try again */
+        rcvr_disconnect();
+        rcvr_connect_later();
+    }
+    else
+    {
+        printf("%s(): Connected to receiver!\n",
+                __PRETTY_FUNCTION__);
+        set_conn_state(RCVR_UP);
+        _input_sid = g_io_add_watch_full(_channel, G_PRIORITY_HIGH_IDLE,
+                G_IO_IN | G_IO_PRI, channel_cb_input, NULL, NULL);
+    }
+
+    _connect_sid = 0;
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return FALSE;
+}
+
+static void
+channel_parse_rmc(gchar *sentence)
+{
+    /* Recommended Minimum Navigation Information C
+     *  1) UTC Time
+     *  2) Status, V=Navigation receiver warning A=Valid
+     *  3) Latitude
+     *  4) N or S
+     *  5) Longitude
+     *  6) E or W
+     *  7) Speed over ground, knots
+     *  8) Track made good, degrees true
+     *  9) Date, ddmmyy
+     * 10) Magnetic Variation, degrees
+     * 11) E or W
+     * 12) FAA mode indicator (NMEA 2.3 and later)
+     * 13) Checksum
+     */
+    gchar *token, *dpoint;
+    gboolean newly_fixed = FALSE;
+    vprintf("%s(): %s", __PRETTY_FUNCTION__, sentence);
+
+#define DELIM ","
+
+    /* skip time */
+    token = strsep(&sentence, DELIM);
+
+    token = strsep(&sentence, DELIM);
+    /* token is now Status */
+    if(token[0] != 'A')
+    {
+        /* data is invalid - not enough satellites? */
+        if(_conn_state != RCVR_UP)
+        {
+            set_conn_state(RCVR_UP);
+            track_add(0, 0, FALSE);
+        }
+    }
+    else
+    {
+        /* data is valid */
+        if(_conn_state != GPS_FIXED)
+        {
+            newly_fixed = TRUE;
+            set_conn_state(GPS_FIXED);
+        }
+    }
+
+    /* parse the latitude */
+    token = strsep(&sentence, DELIM);
+    dpoint = strchr(token, '.');
+    _pos_lat = atof(dpoint - 2) * (1.f / 60.f);
+    dpoint[-2] = '\0';
+    _pos_lat += atoi(token);
+
+    /* parse N or S */
+    token = strsep(&sentence, DELIM);
+    if(token[0] == 'S')
+        _pos_lat = -_pos_lat;
+
+    /* parse the longitude */
+    token = strsep(&sentence, DELIM);
+    dpoint = strchr(token, '.');
+    _pos_lon = atof(dpoint - 2) * (1.f / 60.f);
+    dpoint[-2] = '\0';
+    _pos_lon += atoi(token);
+
+    /* parse E or W */
+    token = strsep(&sentence, DELIM);
+    if(token[0] == 'W')
+        _pos_lon = -_pos_lon;
+
+    /* parse speed over ground, knots */
+    token = strsep(&sentence, DELIM);
+    _speed = (atof(token) + 0.5);
+
+    /* parse heading, degrees from true north */
+    token = strsep(&sentence, DELIM);
+    _heading = (atof(token) + 0.5);
+
+    /* translate data into integers */
+    integerize_data();
+
+    if(_conn_state == GPS_FIXED)
+        track_add(_pos.unitx, _pos.unity, newly_fixed);
+
+    /* add new data to track */
+    refresh_mark();
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+static void
+channel_parse_gsv(gchar *sentence)
+{
+    /* Must be GSV - Satellites in view
+     *  1) total number of messages
+     *  2) message number
+     *  3) satellites in view
+     *  4) satellite number
+     *  5) elevation in degrees (0-90)
+     *  6) azimuth in degrees to true north (0-359)
+     *  7) SNR in dB (0-99)
+     *  more satellite infos like 4)-7)
+     *  n) checksum
+     */
+    gchar *token, *asterisk;
+    gint msgcnt, nummsgs, numsats;
+    static gint running_total = 0;
+    static gint num_sats_used = 0;
+    static gint satcnt = 0;
+    vprintf("%s(): %s", __PRETTY_FUNCTION__, sentence);
+
+    /* skip time */
+    token = strsep(&sentence, DELIM);
+    nummsgs = atoi(token);
+
+    /* parse message number */
+    token = strsep(&sentence, DELIM);
+    msgcnt = atoi(token);
+
+    /* parse number of satellites in view */
+    token = strsep(&sentence, DELIM);
+    numsats = atoi(token);
+
+    for(asterisk = NULL; satcnt < numsats && !asterisk; satcnt++)
+    {
+        /* skip satellite number OR checksum*/
+        token = strsep(&sentence, DELIM);
+        /* skip elevation in degrees (0-90) */
+        token = strsep(&sentence, DELIM);
+        /* skip azimuth in degrees to true north (0-359) */
+        token = strsep(&sentence, DELIM);
+
+        /* parse SNR */
+        token = strsep(&sentence, DELIM);
+        /* token may have include checksum - if so, then this is last sat */
+        if(*token)
+        {
+            asterisk = strchr(token, '*');
+            if(asterisk)
+                *asterisk = '\0';
+            running_total += atoi(token);
+            num_sats_used++;
+        }
+    }
+
+    if(satcnt == numsats || msgcnt == nummsgs)
+    {
+        gdouble fraction = running_total * sqrt(num_sats_used)
+            / num_sats_used / 100.0;
+        BOUND(fraction, 0.0, 1.0);
+        gtk_banner_set_fraction(_window, fraction);
+        satcnt = 0;
+        running_total = 0;
+        num_sats_used = 0;
+
+        /* keep awake while they watch the progress bar... */
+        KEEP_DISPLAY_ON();
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+static gboolean
+channel_cb_input(GIOChannel *src, GIOCondition condition, gpointer data)
+{
+    gchar *sentence;
+    vprintf("%s(%d)\n", __PRETTY_FUNCTION__, condition);
+    
+    while(G_IO_STATUS_NORMAL == g_io_channel_read_line(
+                _channel,
+                &sentence,
+                NULL,
+                NULL,
+                NULL) && sentence)
+    {
+        if(!strncmp(sentence + 3, "RMC", 3))
+            channel_parse_rmc(sentence + 7);
+        else if(_conn_state == RCVR_UP && !strncmp(sentence + 3, "GSV", 3))
+            channel_parse_gsv(sentence + 7);
+        g_free(sentence);
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+gps_toggled_from(GtkToggleButton *chk_gps,
+        GtkWidget *txt_from)
+{
+    gchar buffer[1024];
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    sprintf(buffer, "%f, %f", _pos_lat, _pos_lon);
+    gtk_widget_set_sensitive(txt_from, !gtk_toggle_button_get_active(chk_gps));
+    gtk_entry_set_text(GTK_ENTRY(txt_from), buffer);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+gps_toggled_auto(GtkToggleButton *chk_gps,
+        GtkWidget *chk_auto)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    gtk_widget_set_sensitive(chk_auto, gtk_toggle_button_get_active(chk_gps));
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+menu_cb_route_download(GtkAction *action)
+{
+    GtkWidget *dialog;
+    GtkWidget *table;
+    GtkWidget *label;
+    GtkWidget *chk_gps;
+    GtkWidget *chk_auto;
+    GtkWidget *txt_from;
+    GtkWidget *txt_to;
+    GtkWidget *hbox;
+    GtkEntryCompletion *from_comp;
+    GtkEntryCompletion *to_comp;
+    gchar *bytes = NULL;
+    gint size;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    dialog = gtk_dialog_new_with_buttons("Download Route",
+            _window, GTK_DIALOG_MODAL,
+            GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+            GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+            NULL);
+
+    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
+            table = gtk_table_new(2, 4, FALSE), TRUE, TRUE, 0);
+
+    from_comp = gtk_entry_completion_new();
+    gtk_entry_completion_set_model(from_comp, GTK_TREE_MODEL(_loc_model));
+    gtk_entry_completion_set_text_column(from_comp, 0);
+    to_comp = gtk_entry_completion_new();
+    gtk_entry_completion_set_model(to_comp, GTK_TREE_MODEL(_loc_model));
+    gtk_entry_completion_set_text_column(to_comp, 0);
+
+    /* Auto */
+    gtk_table_attach(GTK_TABLE(table),
+            hbox = gtk_hbox_new(FALSE, 6),
+            0, 2, 0, 1, 0, 0, 2, 4);
+    gtk_box_pack_start(GTK_BOX(hbox),
+            chk_gps = gtk_check_button_new_with_label(
+                "Use GPS Location"),
+            TRUE, TRUE, 0);
+    gtk_box_pack_start(GTK_BOX(hbox),
+            chk_auto = gtk_check_button_new_with_label(
+                "Auto-Update"),
+            TRUE, TRUE, 0);
+    gtk_widget_set_sensitive(chk_auto, FALSE);
+
+    /* Origin */
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new("Origin"),
+            0, 1, 1, 2, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+    gtk_table_attach(GTK_TABLE(table),
+            txt_from = gtk_entry_new(),
+            1, 2, 1, 2, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+    gtk_entry_set_completion(GTK_ENTRY(txt_from), from_comp);
+    gtk_entry_set_width_chars(GTK_ENTRY(txt_from), 25);
+
+    /* Destination */
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new("Destination"),
+            0, 1, 2, 3, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+    gtk_table_attach(GTK_TABLE(table),
+            txt_to = gtk_entry_new(),
+            1, 2, 2, 3, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+    gtk_entry_set_completion(GTK_ENTRY(txt_to), to_comp);
+    gtk_entry_set_width_chars(GTK_ENTRY(txt_to), 25);
+
+    g_signal_connect(G_OBJECT(chk_gps), "toggled",
+                      G_CALLBACK(gps_toggled_from), txt_from);
+    g_signal_connect(G_OBJECT(chk_gps), "toggled",
+                      G_CALLBACK(gps_toggled_auto), chk_auto);
+    gtk_widget_show_all(dialog);
+
+    while(!bytes && GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog)))
+    {
+        GnomeVFSResult vfs_result;
+        gchar buffer[1024];
+        const gchar *from, *to;
+        gchar *from_escaped, *to_escaped;
+
+        from = gtk_entry_get_text(GTK_ENTRY(txt_from));
+        if(!strlen(from))
+        {
+            popup_error("Please specify a start location.");
+            continue;
+        }
+
+        to = gtk_entry_get_text(GTK_ENTRY(txt_to));
+        if(!strlen(to))
+        {
+            popup_error("Please specify an end location.");
+            continue;
+        }
+
+        from_escaped = gnome_vfs_escape_string(from);
+        to_escaped = gnome_vfs_escape_string(to);
+        sprintf(buffer, "http://www.gnuite.com/cgi-bin/gpx.cgi?"
+                "saddr=%s&daddr=%s",
+                from_escaped, to_escaped);
+        g_free(from_escaped);
+        g_free(to_escaped);
+
+        if(GNOME_VFS_OK != (vfs_result = gnome_vfs_read_entire_file(
+                        buffer, &size, &bytes)))
+        {
+            char buffer[1024];
+            sprintf(buffer, "Failed to connect to GPX Directions server.\n%s",
+                    gnome_vfs_result_to_string(vfs_result));
+            popup_error(buffer);
+        }
+        else if(strncmp(bytes, "<?xml", strlen("<?xml")))
+        {
+            /* not an XML document - must be bad locations. */
+            popup_error("Could not generate directions. Make sure your "
+                    "locations are valid.");
+            g_free(bytes);
+            bytes = NULL;
+        }
+        else
+        {
+            /* if GPS is enabled, append the route, otherwise replace it. */
+            if(parse_gpx(bytes, size, &_route,
+                    (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(chk_gps))
+                        ? 0 : 1), 0))
+            {
+                GtkTreeIter iter;
+
+                /* Find the nearest route point, if we're connected. */
+                route_find_nearest_point();
+
+                /* Cancel any autoroute that might be occurring. */
+                cancel_autoroute();
+
+                map_force_redraw();
+
+                if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(chk_auto)))
+                {
+                    /* kick off a timeout to start the first update */
+                    _autoroute_data.dest = gnome_vfs_escape_string(to);
+                    _autoroute_data.enabled = TRUE;
+                    _autoroute_data.bytes_maxsize = 2 * size;
+                    _autoroute_data.bytes = g_new(gchar, 2 * size);
+                }
+
+                /* Save Origin in Route Locations list if not from GPS. */
+                if(!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(chk_gps))
+                    && !g_slist_find_custom(_loc_list, from,
+                                (GCompareFunc)strcmp))
+                {
+                    _loc_list = g_slist_prepend(_loc_list, g_strdup(from));
+                    gtk_list_store_insert_with_values(_loc_model, &iter,
+                            INT_MAX, 0, from, -1);
+                }
+
+                /* Save Destination in Route Locations list. */
+                if(!g_slist_find_custom(_loc_list, to,
+                            (GCompareFunc)strcmp))
+                {
+                    _loc_list = g_slist_prepend(_loc_list, g_strdup(to));
+                    gtk_list_store_insert_with_values(_loc_model, &iter,
+                            INT_MAX, 0, to, -1);
+                }
+
+                gtk_infoprint(_window, "Route Downloaded");
+            }
+            else
+                popup_error("Error parsing GPX file.");
+            g_free(bytes);
+        }
+    }
+    gtk_widget_destroy(dialog);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+menu_cb_route_open(GtkAction *action)
+{
+    gchar *buffer;
+    gint size;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(open_file(&buffer, NULL, &size, &_route_dir_uri, NULL,
+                    GTK_FILE_CHOOSER_ACTION_OPEN))
+    {
+        /* if auto is enabled, append the route, otherwise replace it. */
+        if(parse_gpx(buffer, size, &_route, _autoroute_data.enabled ? 0 : 1, 0))
+        {
+            cancel_autoroute();
+
+            /* Find the nearest route point, if we're connected. */
+            route_find_nearest_point();
+
+            map_force_redraw();
+            gtk_infoprint(_window, "Route Opened");
+        }
+        else
+            popup_error("Error parsing GPX file.");
+        g_free(buffer);
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+menu_cb_route_reset(GtkAction *action)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    route_find_nearest_point();
+    map_render_tracks();
+    MACRO_QUEUE_DRAW_AREA();
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+menu_cb_route_clear(GtkAction *action)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    _next_way_dist_rough = -1; /* to avoid announcement attempts */
+    cancel_autoroute();
+    MACRO_CLEAR_TRACK(_route);
+    map_force_redraw();
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+menu_cb_track_open(GtkAction *action)
+{
+    gchar *buffer;
+    gint size;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(open_file(&buffer, NULL, &size, NULL, &_track_file_uri,
+                    GTK_FILE_CHOOSER_ACTION_OPEN))
+    {
+        if(parse_gpx(buffer, size, &_track, -1, ARRAY_CHUNK_SIZE))
+        {
+            map_force_redraw();
+            gtk_infoprint(_window, "Track Opened");
+        }
+        else
+            popup_error("Error parsing GPX file.");
+        g_free(buffer);
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+#define WRITE_STRING(string) { \
+    GnomeVFSResult vfs_result; \
+    GnomeVFSFileSize size; \
+    if(GNOME_VFS_OK != (vfs_result = gnome_vfs_write( \
+                    handle, (string), strlen((string)), &size))) \
+    { \
+        char buffer[1024]; \
+        sprintf(buffer, "Error while writing to file:\n%s\n" \
+                        "File is incomplete.", \
+                gnome_vfs_result_to_string(vfs_result)); \
+        popup_error(buffer); \
+        return FALSE; \
+    } \
+}
+
+static gboolean
+write_gpx(GnomeVFSHandle *handle, Track *track)
+{
+    gboolean last_was_way = FALSE;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    /* write the header */
+    WRITE_STRING("<?xml version=\"1.0\"?>\n"
+                    "<gpx version=\"1.0\" creator=\"Maemo Mapper\" "
+                    "xmlns=\"http://www.topografix.com/GPX/1/0\">\n"
+                    "  <trk>\n"
+                    "    <trkseg>\n");
+
+    /* iterate through the track and write the points to the file */
+    {
+        TrackPoint *curr;
+        WayPoint *wcurr;
+        gboolean trkseg_break = FALSE;
+
+        /* Find first non-zero point. */
+        for(curr = track->head-1, wcurr = track->whead; curr++ != track->tail; )
+            if(curr->unity != 0)
+                break;
+            else if(curr == wcurr->point)
+                wcurr++;
+
+        /* curr points to first non-zero point. */
+        for(curr--; curr++ != track->tail; )
+        {
+            gfloat lat, lon;
+            if(curr->unity != 0)
+            {
+                char buffer[80];
+                if(trkseg_break)
+                {
+                    /* first trkpt of the segment - write trkseg header */
+                    WRITE_STRING("    </trkseg>\n");
+                    /* write trkseg header */
+                    WRITE_STRING("    <trkseg>\n");
+                    trkseg_break = FALSE;
+                }
+                unit2latlon(curr->unitx, curr->unity, lat, lon);
+                sprintf(buffer, "      <trkpt lat=\"%f\" lon=\"%f\"", lat, lon);
+                if(curr == wcurr->point)
+                {
+                    sprintf(buffer + strlen(buffer),
+                            "><desc>%s</desc></trkpt>\n", wcurr++->desc);
+                    last_was_way = TRUE;
+                }
+                else
+                {
+                    strcat(buffer, "/>\n");
+                    last_was_way = FALSE;
+                }
+                WRITE_STRING(buffer);
+            }
+            else
+                trkseg_break = TRUE;
+        }
+    }
+
+    /* write the footer */
+    WRITE_STRING("    </trkseg>\n  </trk>\n</gpx>\n");
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+menu_cb_track_save(GtkAction *action)
+{
+    GnomeVFSHandle *handle;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(open_file(NULL, &handle, NULL, NULL, &_track_file_uri,
+                    GTK_FILE_CHOOSER_ACTION_SAVE))
+    {
+        if(write_gpx(handle, &_track))
+            gtk_infoprint(_window, "Track Saved");
+        else
+            popup_error("Error writing GPX file.");
+        gnome_vfs_close(handle);
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+menu_cb_track_mark_way(GtkAction *action)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    MACRO_TRACK_INCREMENT_WTAIL(_track);
+    if(_track.tail->unity != 0)
+    {
+        guint x1, y1;
+        _track.wtail->point = _track.tail;
+        _track.wtail->desc = g_strdup("Track Waypoint");
+        
+        /** Instead of calling map_render_tracks(), we'll just add the waypoint
+         * ourselves. */
+        x1 = unit2bufx(_track.tail->unitx);
+        y1 = unit2bufy(_track.tail->unity);
+        /* make sure this circle will be visible */
+        if((x1 < BUF_WIDTH_PIXELS)
+                && ((unsigned)y1 < BUF_HEIGHT_PIXELS))
+            gdk_draw_arc(_map_pixmap, _track_way_gc,
+                    FALSE, /* FALSE: not filled */
+                    x1 - _draw_line_width,
+                    y1 - _draw_line_width,
+                    2 * _draw_line_width,
+                    2 * _draw_line_width,
+                    0, /* start at 0 degrees */
+                    360 * 64);
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+menu_cb_route_save(GtkAction *action)
+{
+    GnomeVFSHandle *handle;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(open_file(NULL, &handle, NULL, &_route_dir_uri, NULL,
+                    GTK_FILE_CHOOSER_ACTION_SAVE))
+    {
+        if(write_gpx(handle, &_route))
+            gtk_infoprint(_window, "Route Saved");
+        else
+            popup_error("Error writing GPX file.");
+        gnome_vfs_close(handle);
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+menu_cb_track_clear(GtkAction *action)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    _track.tail = _track.head;
+    _track.wtail = _track.whead;
+    map_force_redraw();
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+menu_cb_show_tracks(GtkAction *action)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    _show_tracks ^= TRACKS_MASK;
+    if(gtk_check_menu_item_get_active(
+                GTK_CHECK_MENU_ITEM(_menu_show_tracks_item)))
+    {
+        _show_tracks |= TRACKS_MASK;
+        map_render_tracks();
+        MACRO_QUEUE_DRAW_AREA();
+        gtk_infoprint(_window, "Tracks are now shown");
+    }
+    else
+    {
+        _show_tracks &= ~TRACKS_MASK;
+        map_force_redraw();
+        gtk_infoprint(_window, "Tracks are now hidden");
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+menu_cb_show_routes(GtkAction *action)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(gtk_check_menu_item_get_active(
+                GTK_CHECK_MENU_ITEM(_menu_show_routes_item)))
+    {
+        _show_tracks |= ROUTES_MASK;
+        map_render_tracks();
+        MACRO_QUEUE_DRAW_AREA();
+        gtk_infoprint(_window, "Routes are now shown");
+    }
+    else
+    {
+        _show_tracks &= ~ROUTES_MASK;
+        map_force_redraw();
+        gtk_infoprint(_window, "Routes are now hidden");
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+menu_cb_show_velvec(GtkAction *action)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    _show_velvec = gtk_check_menu_item_get_active(
+            GTK_CHECK_MENU_ITEM(_menu_show_velvec_item));
+    map_move_mark();
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+menu_cb_ac_lead(GtkAction *action)
+{
+    guint new_center_unitx, new_center_unity;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    _center_mode = CENTER_LEAD;
+    gtk_infoprint(_window, "Auto-Center Mode: Lead");
+    MACRO_RECALC_CENTER(new_center_unitx, new_center_unity);
+    map_center_unit(new_center_unitx, new_center_unity);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+menu_cb_ac_latlon(GtkAction *action)
+{
+    guint new_center_unitx, new_center_unity;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    _center_mode = CENTER_LATLON;
+    gtk_infoprint(_window, "Auto-Center Mode: Lat/Lon");
+    MACRO_RECALC_CENTER(new_center_unitx, new_center_unity);
+    map_center_unit(new_center_unitx, new_center_unity);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+menu_cb_ac_none(GtkAction *action)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    _center_mode = -_center_mode;
+    gtk_infoprint(_window, "Auto-Center Off");
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+menu_cb_maps_dlroute(GtkAction *action)
+{
+    guint last_tilex, last_tiley;
+    TrackPoint *curr;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(!_route.head)
+        return TRUE;
+
+    last_tilex = -1;
+    last_tiley = -1;
+    for(curr = _route.head - 1; ++curr != _route.tail; )
+    {
+        if(curr->unity)
+        {
+            guint tilex = unit2tile(curr->unitx);
+            guint tiley = unit2tile(curr->unity);
+            if(tilex != last_tilex || tiley != last_tiley)
+            {
+                gint x, y;
+                for(x = -2; x <= 2; x++)
+                    for(y = -1; y <= 1; y++)
+                    {
+                        gchar buffer[1024];
+                        sprintf(buffer, "%s/%u/%u/%u.jpg",
+                            _map_dir_name, (_zoom - 1), tilex, tiley);
+                        map_initiate_download(buffer, tilex+x, tiley+y, _zoom);
+                    }
+                last_tilex = tilex;
+                last_tiley = tilex;
+            }
+        }
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+menu_cb_maps_dlarea(GtkAction *action)
+{
+    GtkWidget *dialog;
+    GtkWidget *notebook;
+    GtkWidget *table;
+    GtkWidget *label;
+    GtkWidget *txt_topleft_lat;
+    GtkWidget *txt_topleft_lon;
+    GtkWidget *txt_botright_lat;
+    GtkWidget *txt_botright_lon;
+    gchar buffer[32];
+    gfloat lat, lon;
+    GtkWidget *chk_zoom_levels[MAX_ZOOM];
+    guint i;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    dialog = gtk_dialog_new_with_buttons("Download Maps by Area",
+            _window, GTK_DIALOG_MODAL,
+            GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+            GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+            NULL);
+
+    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
+            notebook = gtk_notebook_new(), TRUE, TRUE, 0);
+    
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+            table = gtk_table_new(2, 3, FALSE),
+            label = gtk_label_new("Area"));
+
+    /* Label Columns */
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new("Latitude"),
+            1, 2, 0, 1, GTK_FILL, 0, 4, 0);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new("Longitude"),
+            2, 3, 0, 1, GTK_FILL, 0, 4, 0);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+    /* GPS */
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new("GPS Location"),
+            0, 1, 1, 2, GTK_FILL, 0, 4, 0);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+    sprintf(buffer, "%f", _pos_lat);
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new(buffer),
+            1, 2, 1, 2, GTK_FILL, 0, 4, 0);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+    sprintf(buffer, "%f", _pos_lon);
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new(buffer),
+            2, 3, 1, 2, GTK_FILL, 0, 4, 0);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+    /* Center */
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new("View Center"),
+            0, 1, 2, 3, GTK_FILL, 0, 4, 0);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+    unit2latlon(_center.unitx, _center.unity, lat, lon);
+    sprintf(buffer, "%f", lat);
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new(buffer),
+            1, 2, 2, 3, GTK_FILL, 0, 4, 0);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+    sprintf(buffer, "%f", lon);
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new(buffer),
+            2, 3, 2, 3, GTK_FILL, 0, 4, 0);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+    /* Top Left */
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new("Top-Left"),
+            0, 1, 3, 4, GTK_FILL, 0, 4, 0);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+    gtk_table_attach(GTK_TABLE(table),
+            txt_topleft_lat = gtk_entry_new(),
+            1, 2, 3, 4, GTK_EXPAND | GTK_FILL, 0, 4, 0);
+    gtk_entry_set_alignment(GTK_ENTRY(txt_topleft_lat), 1.f);
+    gtk_entry_set_width_chars(GTK_ENTRY(txt_topleft_lat), 10);
+    gtk_table_attach(GTK_TABLE(table),
+            txt_topleft_lon = gtk_entry_new(),
+            2, 3, 3, 4, GTK_EXPAND | GTK_FILL, 0, 4, 0);
+    gtk_entry_set_alignment(GTK_ENTRY(txt_topleft_lon), 1.f);
+    gtk_entry_set_width_chars(GTK_ENTRY(txt_topleft_lon), 10);
+
+    /* Bottom Right */
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new("Bottom-Right"),
+            0, 1, 4, 5, GTK_FILL, 0, 4, 0);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+    gtk_table_attach(GTK_TABLE(table),
+            txt_botright_lat = gtk_entry_new(),
+            1, 2, 4, 5, GTK_EXPAND | GTK_FILL, 0, 4, 0);
+    gtk_entry_set_alignment(GTK_ENTRY(txt_botright_lat), 1.f);
+    gtk_entry_set_width_chars(GTK_ENTRY(txt_botright_lat), 10);
+    gtk_table_attach(GTK_TABLE(table),
+            txt_botright_lon = gtk_entry_new(),
+            2, 3, 4, 5, GTK_EXPAND | GTK_FILL, 0, 4, 0);
+    gtk_entry_set_alignment(GTK_ENTRY(txt_botright_lon), 1.f);
+    gtk_entry_set_width_chars(GTK_ENTRY(txt_botright_lon), 10);
+
+
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+            table = gtk_table_new(5, 5, FALSE),
+            label = gtk_label_new("Zoom"));
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new("Zoom Levels to Download:"),
+            0, 5, 0, 1, GTK_FILL, 0, 4, 0);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+    gtk_table_attach(GTK_TABLE(table),
+            label = gtk_label_new("(lower -> more detail)"),
+            0, 5, 1, 2, GTK_FILL, 0, 4, 0);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+    for(i = 0; i < MAX_ZOOM; i++)
+    {
+        sprintf(buffer, "%d", i);
+        gtk_table_attach(GTK_TABLE(table),
+                chk_zoom_levels[i] = gtk_check_button_new_with_label(buffer),
+                i % 6, i % 6 + 1, i / 6 + 2, i / 6 + 3,
+                GTK_EXPAND | GTK_FILL, 0, 4, 0);
+    }
+    gtk_toggle_button_set_active(
+            GTK_TOGGLE_BUTTON(chk_zoom_levels[_zoom - 1]), TRUE);
+
+    gtk_widget_show_all(dialog);
+
+    while(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog)))
+    {
+        const gchar *text;
+        gchar *error_check;
+        gfloat start_lat, start_lon, end_lat, end_lon;
+        guint start_unitx, start_unity, end_unitx, end_unity;
+
+        text = gtk_entry_get_text(GTK_ENTRY(txt_topleft_lat));
+        start_lat = strtof(text, &error_check);
+        if(text == error_check) {
+            popup_error("Invalid Top-Left Latitude");
+            continue;
+        }
+
+        text = gtk_entry_get_text(GTK_ENTRY(txt_topleft_lon));
+        start_lon = strtof(text, &error_check);
+        if(text == error_check) {
+            popup_error("Invalid Top-Left Longitude");
+            continue;
+        }
+
+        text = gtk_entry_get_text(GTK_ENTRY(txt_botright_lat));
+        end_lat = strtof(text, &error_check);
+        if(text == error_check) {
+            popup_error("Invalid Bottom-Right Latitude");
+            continue;
+        }
+
+        text = gtk_entry_get_text(GTK_ENTRY(txt_botright_lon));
+        end_lon = strtof(text, &error_check);
+        if(text == error_check) {
+            popup_error("Invalid Bottom-Right Longitude");
+            continue;
+        }
+
+        latlon2unit(start_lat, start_lon, start_unitx, start_unity);
+        latlon2unit(end_lat, end_lon, end_unitx, end_unity);
+        if(start_unitx > end_unitx)
+        {
+            guint swap = start_unitx;
+            start_unitx = end_unitx;
+            end_unitx = swap;
+        }
+        if(start_unity > end_unity)
+        {
+            guint swap = start_unity;
+            start_unity = end_unity;
+            end_unity = swap;
+        }
+        for(i = 0; i < MAX_ZOOM; i++)
+        {
+            if(gtk_toggle_button_get_active(
+                        GTK_TOGGLE_BUTTON(chk_zoom_levels[i])))
+            {
+                guint start_tilex, start_tiley, end_tilex, end_tiley;
+                guint tilex, tiley;
+                start_tilex = unit2ztile(start_unitx, i + 1);
+                start_tiley = unit2ztile(start_unity, i + 1);
+                end_tilex = unit2ztile(end_unitx, i + 1);
+                end_tiley = unit2ztile(end_unity, i + 1);
+                for(tiley = start_tiley; tiley <= end_tiley; tiley++)
+                    for(tilex = start_tilex; tilex <= end_tilex; tilex++)
+                    {
+                        gchar buffer[1024];
+                        sprintf(buffer, "%s/%u/%u/%u.jpg",
+                            _map_dir_name, i, tilex, tiley);
+                        map_initiate_download(buffer, tilex, tiley, i + 1);
+                    }
+            }
+        }
+        break;
+    }
+    gtk_widget_destroy(dialog);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+menu_cb_fullscreen(GtkAction *action)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    hildon_appview_set_fullscreen(_main_view,
+            _fullscreen = gtk_check_menu_item_get_active(
+                GTK_CHECK_MENU_ITEM(_menu_fullscreen_item)));
+
+    gtk_idle_add((GSourceFunc)window_present, NULL);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+menu_cb_enable_gps(GtkAction *action)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if((_enable_gps = gtk_check_menu_item_get_active(
+                GTK_CHECK_MENU_ITEM(_menu_enable_gps_item))))
+    {
+        set_conn_state(RCVR_DOWN);
+        rcvr_connect_now();
+    }
+    else
+    {
+        if(_conn_state > RCVR_OFF)
+            set_conn_state(RCVR_OFF);
+        rcvr_disconnect();
+        track_add(0, 0, FALSE);
+    }
+    map_move_mark();
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+menu_cb_auto_download(GtkAction *action)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if((_auto_download = gtk_check_menu_item_get_active(
+            GTK_CHECK_MENU_ITEM(_menu_auto_download_item))))
+        map_force_redraw();
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+menu_cb_settings(GtkAction *action)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(settings_dialog())
+    {
+        /* settings have changed - reconnect to receiver */
+        if(_conn_state > RCVR_OFF)
+        {
+            set_conn_state(RCVR_DOWN);
+            rcvr_disconnect();
+            rcvr_connect_now();
+        }
+    }
+    MACRO_RECALC_FOCUS_BASE();
+    MACRO_RECALC_FOCUS_SIZE();
+    map_force_redraw();
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean
+map_download_idle_refresh(ProgressUpdateInfo *pui)
+{
+    vprintf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(pui)
+    {
+        gint zoom_diff = pui->zoom - _zoom;
+        /* Only refresh at same or "lower" (more detailed) zoom level. */
+        if(zoom_diff >= 0)
+        {
+            /* If zoom has changed since we first put in the request for this
+             * tile, then we may have to update more than one tile. */
+            guint tilex, tiley, tilex_end, tiley_end;
+            for(tilex = pui->tilex << zoom_diff,
+                    tilex_end = tilex + (1 << zoom_diff);
+                    tilex < tilex_end; tilex++)
+                for(tiley = pui->tiley<<zoom_diff,
+                        tiley_end = tiley + (1 << zoom_diff);
+                        tiley < tiley_end; tiley++)
+                    if((tilex - _base_tilex) < 4 && (tiley - _base_tiley) < 3)
+                    {
+                        map_render_tile(
+                                tilex, tiley,
+                                ((tilex - _base_tilex) << TILE_SIZE_P2),
+                                ((tiley - _base_tiley) << TILE_SIZE_P2),
+                                TRUE);
+                        map_render_tracks();
+                        gtk_widget_queue_draw_area(
+                                _map_widget,
+                                ((tilex-_base_tilex)<<TILE_SIZE_P2) - _offsetx,
+                                ((tiley-_base_tiley)<<TILE_SIZE_P2) - _offsety,
+                                TILE_SIZE_PIXELS, TILE_SIZE_PIXELS);
+                    }
+        }
+        progress_update_info_free(pui);
+    }
+    if(++_curr_download == _num_downloads)
+    {
+        gtk_banner_close(_window);
+        _num_downloads = _curr_download = 0;
+    }
+    else
+        gtk_banner_set_fraction(_window,
+                _curr_download / (double)_num_downloads);
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return FALSE;
+}
+
+static gint
+map_download_cb_async(GnomeVFSAsyncHandle *handle,
+        GnomeVFSXferProgressInfo *info, ProgressUpdateInfo*pui)
+{
+    vprintf("%s(%p, %d, %d, %d, %s)\n", __PRETTY_FUNCTION__, pui->handle,
+            info->status, info->vfs_status, info->phase,
+            info->target_name ? info->target_name + 7 : info->target_name);
+
+    if(info->phase == GNOME_VFS_XFER_PHASE_COMPLETED)
+    {
+        /* Transfer is complete, successful or not - pui must be freed */
+        if(info->status == GNOME_VFS_XFER_PROGRESS_STATUS_OK
+                && info->vfs_status == GNOME_VFS_OK
+        /* Only refresh at same or "lower" (more detailed) zoom level */
+                && _zoom <= pui->zoom)
+            g_idle_add_full(G_PRIORITY_HIGH_IDLE,
+                    (GSourceFunc)map_download_idle_refresh, pui, NULL);
+        else
+        {
+            /* didn't download a map, but we should still update
+             * _curr_download */
+            g_idle_add_full(G_PRIORITY_HIGH_IDLE,
+                    (GSourceFunc)map_download_idle_refresh, NULL, NULL);
+            progress_update_info_free(pui);
+        }
+    }
+    else if(info->status != GNOME_VFS_XFER_PROGRESS_STATUS_OK)
+    {
+        /* Something failed - if dir is not found, we can fix that. */
+        if(info->vfs_status == GNOME_VFS_ERROR_NOT_FOUND)
+        {
+            /* Directory doesn't exist yet - create it, then we'll retry */
+            gchar buffer[1024];
+            sprintf(buffer, "%s/%u", _map_dir_name, (pui->zoom - 1));
+            gnome_vfs_make_directory(buffer, 0775);
+            sprintf(buffer, "%s/%u/%u", _map_dir_name,
+                    (pui->zoom - 1), pui->tilex);
+            gnome_vfs_make_directory(buffer, 0775);
+        }
+        else
+        {
+            /* Nothing we can do. Abort. */
+            return FALSE;
+            vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
+        }
+    }
+
+    vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+/****************************************************************************
+ * ABOVE: CALLBACKS *********************************************************
+ ****************************************************************************/
+
diff --git a/maemo-mapper.desktop b/maemo-mapper.desktop
new file mode 100644 (file)
index 0000000..568e298
--- /dev/null
@@ -0,0 +1,12 @@
+[Desktop Entry]
+Encoding=UTF-8
+Version=1.0
+Type=Application
+Name=Maemo Mapper
+Exec=/var/lib/install/usr/bin/maemo-mapper
+Icon=pkg_maemo-mapper
+X-Window-Icon=tn-bookmarks-link
+X-HildonDesk-ShowInToolbar=true
+X-Osso-Service=maemo_mapper
+X-Osso-Type=application/x-executable
+Terminal=false
diff --git a/pkg_maemo-mapper.26.png b/pkg_maemo-mapper.26.png
new file mode 100644 (file)
index 0000000..80ea758
Binary files /dev/null and b/pkg_maemo-mapper.26.png differ
diff --git a/pkg_maemo-mapper.40.png b/pkg_maemo-mapper.40.png
new file mode 100644 (file)
index 0000000..791947b
Binary files /dev/null and b/pkg_maemo-mapper.40.png differ
diff --git a/pkg_maemo-mapper.64.png b/pkg_maemo-mapper.64.png
new file mode 100644 (file)
index 0000000..6559378
Binary files /dev/null and b/pkg_maemo-mapper.64.png differ