Index: src/types.h
===================================================================
--- src/types.h	(revision 12)
+++ src/types.h	(revision 13)
@@ -443,8 +443,16 @@
     GCond *cond;
 };
 
+typedef struct _Tile Tile;
+struct _Tile
+{
+    gint x, y;
+    gint8 z;
+};
+
 /** Data used during the asynchronous progress update phase of automatic map
  * downloading. */
+/* TODO: merge with Tile */
 typedef struct _MapUpdateTask MapUpdateTask;
 struct _MapUpdateTask
 {
Index: src/maps.c
===================================================================
--- src/maps.c	(revision 12)
+++ src/maps.c	(revision 13)
@@ -58,6 +58,7 @@
 #include "display.h"
 #include "main.h"
 #include "maps.h"
+#include "poi.h"
 #include "menu.h"
 #include "settings.h"
 #include "util.h"
@@ -119,6 +120,7 @@
     GtkWidget *dialog;
     GtkWidget *notebook;
     GtkWidget *tbl_area;
+    GtkWidget *tbl_pois;
 
     /* The "Setup" tab. */
     GtkWidget *rad_download;
@@ -126,6 +128,7 @@
     GtkWidget *chk_overwrite;
     GtkWidget *rad_by_area;
     GtkWidget *rad_by_route;
+    GtkWidget *rad_by_pois;
     GtkWidget *num_route_radius;
 
     /* The "Area" tab. */
@@ -190,6 +193,12 @@
 
 static MapCache _map_cache;
 
+typedef struct _MapmanData MapmanData;
+struct _MapmanData {
+    MapUpdateType update_type;
+    gint          download_batch_id;
+};
+
 const gchar* layer_timestamp_key = "tEXt::mm_ts";
 
 
@@ -2934,9 +2943,10 @@
 static gboolean
 mapman_by_area(gdouble start_lat, gdouble start_lon,
         gdouble end_lat, gdouble end_lon, MapmanInfo *mapman_info,
-        MapUpdateType update_type,
-        gint download_batch_id)
+        MapmanData *mapman_data)
 {
+    MapUpdateType update_type = mapman_data->update_type;
+    gint download_batch_id = mapman_data->download_batch_id;
     gint start_unitx, start_unity, end_unitx, end_unity;
     gint num_maps = 0;
     gint z;
@@ -3045,9 +3055,10 @@
 }
 
 static gboolean
-mapman_by_route(MapmanInfo *mapman_info, MapUpdateType update_type,
-        gint download_batch_id)
+mapman_by_route(MapmanInfo *mapman_info, MapmanData *mapman_data)
 {
+    MapUpdateType update_type = mapman_data->update_type;
+    gint download_batch_id = mapman_data->download_batch_id;
     GtkWidget *confirm;
     gint prev_tilex, prev_tiley, num_maps = 0, z;
     Point *curr;
@@ -3175,6 +3186,170 @@
 }
 
 static void
+poi_hash_destroy_key_val(gpointer data)
+{
+    g_free(data);
+}
+
+static void
+mapman_cat_poi_tiles_dl(gpointer key_raw, gpointer val_raw, gpointer data)
+{
+    Tile *tile = val_raw;
+    MapmanData *mm = data;
+
+    /* Make sure this tile is even possible. */
+    if((unsigned)tile->x < unit2ztile(WORLD_SIZE_UNITS, tile->z)
+            && (unsigned)tile->y < unit2ztile(WORLD_SIZE_UNITS, tile->z))
+    {
+        RepoData* rd = _curr_repo;
+        while(rd) {
+            if (rd == _curr_repo || (rd->layer_enabled && MAPDB_EXISTS(rd)))
+            {
+                mapdb_initiate_update(rd, tile->z, tile->x, tile->y,
+                        mm->update_type, mm->download_batch_id,
+                        (abs(tile->x - unit2tile(_next_center.unitx))
+                         + abs(tile->y - unit2tile(_next_center.unity))),
+                        NULL);
+            }
+            rd = rd->layers;
+        }
+    }
+}
+
+static gboolean
+mapman_by_pois(MapmanInfo *mapman_info, GtkListStore *poi_store,
+        MapmanData *mapman_data)
+{
+    gint num_maps;
+    GtkWidget *confirm;
+    gchar buffer[80];
+    GSList *list_iter;
+    GtkTreeIter tree_iter;
+    GHashTable *tiles;
+
+    vprintf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(poi_store), &tree_iter))
+        return FALSE;
+
+    tiles = g_hash_table_new_full(g_str_hash, g_str_equal,
+            poi_hash_destroy_key_val, poi_hash_destroy_key_val);
+
+    do
+    {
+        GSList *poi_list;
+        gchar key[256];
+        gint id;
+	gboolean selected;
+        gtk_tree_model_get(GTK_TREE_MODEL(poi_store), &tree_iter,
+                CAT_ID, &id,
+                CAT_ENABLED, &selected,
+                -1);
+
+        if(!selected)
+            continue;
+
+        poi_list = get_category_pois(id);
+        for(list_iter = poi_list; list_iter; list_iter = list_iter->next)
+        {
+            PoiInfo *poi = list_iter->data;
+            gint unit_start_x, unit_start_y, unit_end_x, unit_end_y;
+            int r;
+            int z;
+
+            /* TODO: radius (in metres) */
+            latlon2unit(poi->lat, poi->lon, unit_start_x, unit_start_y);
+            latlon2unit(poi->lat, poi->lon, unit_end_x, unit_end_y);
+
+            for(z = 0; z <= MAX_ZOOM; ++z)
+            {
+                gint start_tile_x, end_tile_x;
+                gint start_tile_y, end_tile_y;
+                gint x, y;
+
+                if(!gtk_toggle_button_get_active(
+                            GTK_TOGGLE_BUTTON(mapman_info->chk_zoom_levels[z])))
+                    continue;
+
+                start_tile_x = unit2ztile(unit_start_x, z);
+                end_tile_x = unit2ztile(unit_end_x, z);
+                start_tile_y = unit2ztile(unit_start_y, z);
+                end_tile_y = unit2ztile(unit_end_y, z);
+
+                if(start_tile_x > end_tile_x)
+                {
+                    gint t = start_tile_x;
+                    start_tile_x = end_tile_x;
+                    end_tile_x = t;
+                }
+                if(start_tile_y > end_tile_y)
+                {
+                    gint t = start_tile_y;
+                    start_tile_y = end_tile_y;
+                    end_tile_y = t;
+                }
+
+                for(y = start_tile_y; y <= end_tile_y; y++)
+                {
+                    for(x = start_tile_x; x <= end_tile_x; x++)
+                    {
+                        snprintf(key, 255, "%d,%d,%d", x, y, z);
+                        if(!g_hash_table_lookup(tiles, key))
+                        {
+                            Tile *tile = g_new0(Tile, 1);
+                            tile->x = x;
+                            tile->y = y;
+                            tile->z = z;
+                            g_hash_table_replace(tiles, g_strdup(key), tile);
+                        }
+                    }
+                }
+            }
+        }
+        for(list_iter = poi_list; list_iter; list_iter = list_iter->next)
+            g_free(list_iter->data);
+        if (poi_list)
+            g_slist_free(poi_list);
+    } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(poi_store), &tree_iter));
+
+    num_maps = g_hash_table_size(tiles);
+    /* TODO what is num_maps is zero */
+
+    if(mapman_data->update_type == MAP_UPDATE_DELETE)
+    {
+        snprintf(buffer, sizeof(buffer), "%s %d %s", _("Confirm DELETION of"),
+                num_maps, _("maps "));
+    }
+    else
+    {
+        snprintf(buffer, sizeof(buffer),
+                "%s %d %s\n(%s %.2f MB)\n", _("Confirm download of"),
+                num_maps, _("maps"), _("up to about"),
+                num_maps * (strstr(_curr_repo->url, "%s") ? 18e-3 : 6e-3));
+    }
+    confirm = hildon_note_new_confirmation(
+            GTK_WINDOW(mapman_info->dialog), buffer);
+
+    if(GTK_RESPONSE_OK != gtk_dialog_run(GTK_DIALOG(confirm)))
+    {
+        g_hash_table_destroy(tiles);
+        gtk_widget_destroy(confirm);
+        vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
+        return FALSE;
+    }
+
+    g_mutex_lock(_mut_priority_mutex);
+    g_hash_table_foreach(tiles, mapman_cat_poi_tiles_dl, mapman_data);
+    g_mutex_unlock(_mut_priority_mutex);
+
+    g_hash_table_destroy(tiles);
+    gtk_widget_destroy(confirm);
+    vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+
+static void
 mapman_clear(GtkWidget *widget, MapmanInfo *mapman_info)
 {
     gint z;
@@ -3202,18 +3377,82 @@
             gtk_toggle_button_get_active(
                 GTK_TOGGLE_BUTTON(mapman_info->rad_download)));
 
+    gtk_widget_hide(mapman_info->tbl_area);
+    gtk_widget_hide(mapman_info->tbl_pois);
+    gtk_widget_set_sensitive(mapman_info->num_route_radius,
+            gtk_toggle_button_get_active(
+                GTK_TOGGLE_BUTTON(mapman_info->rad_by_route)));
+
     if(gtk_toggle_button_get_active(
                 GTK_TOGGLE_BUTTON(mapman_info->rad_by_area)))
         gtk_widget_show(mapman_info->tbl_area);
-    else if(gtk_notebook_get_n_pages(GTK_NOTEBOOK(mapman_info->notebook)) == 3)
-        gtk_widget_hide(mapman_info->tbl_area);
+    else if(gtk_toggle_button_get_active(
+                GTK_TOGGLE_BUTTON(mapman_info->rad_by_pois)))
+        gtk_widget_show(mapman_info->tbl_pois);
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
 
-    gtk_widget_set_sensitive(mapman_info->num_route_radius,
-            gtk_toggle_button_get_active(
-                GTK_TOGGLE_BUTTON(mapman_info->rad_by_route)));
+static void
+category_toggled(GtkCellRendererToggle *cell, gchar *path, GtkListStore *data)
+{
+    GtkTreeIter iter;
+    gboolean cat_enabled;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    GtkTreeModel *model = GTK_TREE_MODEL(data);
+    if( !gtk_tree_model_get_iter_from_string(model, &iter, path) )
+        return;
+
+    gtk_tree_model_get(model, &iter,
+            CAT_ENABLED, &cat_enabled,
+            -1);
+
+    gtk_list_store_set(GTK_LIST_STORE(model), &iter,
+            CAT_ENABLED, cat_enabled ^ 1, -1);
+
     vprintf("%s(): return\n", __PRETTY_FUNCTION__);
 }
 
+static GtkWidget *
+mapman_create_poi_page(GtkListStore **store)
+{
+    GtkTreeViewColumn *column;
+    GtkWidget *tree_view;
+    GtkCellRenderer *renderer;
+    GtkWidget *sw;
+
+    *store = poi_create_poi_store();
+    tree_view = gtk_tree_view_new();
+
+    gtk_tree_selection_set_mode(
+            gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view)),
+            GTK_SELECTION_SINGLE);
+    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree_view), TRUE);
+
+    renderer = gtk_cell_renderer_toggle_new();
+    column = gtk_tree_view_column_new_with_attributes(
+            _("Enabled"), renderer, "active", CAT_ENABLED, NULL);
+    gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
+    g_signal_connect(renderer, "toggled",
+            G_CALLBACK(category_toggled), *store);
+
+    renderer = gtk_cell_renderer_text_new();
+    column = gtk_tree_view_column_new_with_attributes(
+            _("Label"), renderer, "text", CAT_LABEL, NULL);
+    gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
+
+    gtk_tree_view_set_model(GTK_TREE_VIEW(tree_view), GTK_TREE_MODEL(*store));
+    g_object_unref(*store);
+
+    sw = gtk_scrolled_window_new(NULL, NULL);
+    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
+                  GTK_POLICY_NEVER,
+                  GTK_POLICY_AUTOMATIC);
+    gtk_container_add(GTK_CONTAINER(sw), tree_view);
+
+    return sw;
+}
+
 gboolean
 mapman_dialog()
 {
@@ -3227,6 +3466,7 @@
     GtkWidget *lbl_gps_lon;
     GtkWidget *lbl_center_lat;
     GtkWidget *lbl_center_lon;
+    GtkListStore *poi_store;
     MapmanInfo mapman_info;
     
     gchar buffer[80];
@@ -3351,12 +3591,18 @@
                         _("By Area (see tab)")),
             FALSE, FALSE, 0);
     gtk_box_pack_start(GTK_BOX(vbox),
+            mapman_info.rad_by_pois
+            = gtk_radio_button_new_with_label_from_widget(
+                GTK_RADIO_BUTTON(mapman_info.rad_by_area),
+                _("Around POIs (see tab)")),
+            FALSE, FALSE, 0);
+    gtk_box_pack_start(GTK_BOX(vbox),
             hbox = gtk_hbox_new(FALSE, 4),
             FALSE, FALSE, 0);
     gtk_box_pack_start(GTK_BOX(hbox),
             mapman_info.rad_by_route
                     = gtk_radio_button_new_with_label_from_widget(
-                        GTK_RADIO_BUTTON(mapman_info.rad_by_area),
+                        GTK_RADIO_BUTTON(mapman_info.rad_by_pois),
                         _("Along Route - Radius (tiles):")),
             FALSE, FALSE, 0);
     gtk_box_pack_start(GTK_BOX(hbox),
@@ -3550,6 +3796,12 @@
 #endif
 
     }
+
+    /* POIs page. */
+    /* TODO: list categories for POI page on demand */
+    gtk_notebook_append_page(GTK_NOTEBOOK(mapman_info.notebook),
+            mapman_info.tbl_pois = mapman_create_poi_page(&poi_store),
+            label = gtk_label_new(_("POIs")));
     
     /* Default action is to download by area. */
     gtk_toggle_button_set_active(
@@ -3563,6 +3815,8 @@
                       G_CALLBACK(mapman_update_state), &mapman_info);
     g_signal_connect(G_OBJECT(mapman_info.rad_by_route), "clicked",
                       G_CALLBACK(mapman_update_state), &mapman_info);
+    g_signal_connect(G_OBJECT(mapman_info.rad_by_pois), "clicked",
+            G_CALLBACK(mapman_update_state), &mapman_info);
 
     /* Initialize fields.  Do no use g_ascii_formatd; these strings will be
      * output (and parsed) as locale-dependent. */
@@ -3634,25 +3888,32 @@
 
     while(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog)))
     {
-        MapUpdateType update_type;
         static gint8 download_batch_id = INT8_MIN;
+        MapmanData mapman_data;
 
         if(gtk_toggle_button_get_active(
                     GTK_TOGGLE_BUTTON(mapman_info.rad_delete)))
-            update_type = MAP_UPDATE_DELETE;
+            mapman_data.update_type = MAP_UPDATE_DELETE;
         else if(gtk_toggle_button_get_active(
                 GTK_TOGGLE_BUTTON(mapman_info.chk_overwrite)))
-            update_type = MAP_UPDATE_OVERWRITE;
+            mapman_data.update_type = MAP_UPDATE_OVERWRITE;
         else
-            update_type = MAP_UPDATE_ADD;
+            mapman_data.update_type = MAP_UPDATE_ADD;
 
         ++download_batch_id;
+        mapman_data.download_batch_id = download_batch_id;
         if(gtk_toggle_button_get_active(
                     GTK_TOGGLE_BUTTON(mapman_info.rad_by_route)))
         {
-            if(mapman_by_route(&mapman_info, update_type, download_batch_id))
+            if(mapman_by_route(&mapman_info, &mapman_data))
                 break;
         }
+        else if(gtk_toggle_button_get_active(
+                    GTK_TOGGLE_BUTTON(mapman_info.rad_by_pois)))
+        {
+            if(mapman_by_pois(&mapman_info, poi_store, &mapman_data))
+                break;
+        }
         else
         {
             const gchar *text_lon, *text_lat;
@@ -3680,7 +3941,7 @@
   
 
             if(mapman_by_area(start_lat, start_lon, end_lat, end_lon,
-                        &mapman_info, update_type, download_batch_id))
+                        &mapman_info, &mapman_data))
                 break;
         }
     }
Index: src/poi.c
===================================================================
--- src/poi.c	(revision 12)
+++ src/poi.c	(revision 13)
@@ -63,6 +63,7 @@
 static sqlite3_stmt *_stmt_browsecat_poi = NULL;
 static sqlite3_stmt *_stmt_select_poi = NULL;
 static sqlite3_stmt *_stmt_select_nearest_poi = NULL;
+static sqlite3_stmt *_stmt_select_cat_poi = NULL;
 static sqlite3_stmt *_stmt_insert_poi = NULL;
 static sqlite3_stmt *_stmt_update_poi = NULL;
 static sqlite3_stmt *_stmt_delete_poi = NULL;
@@ -269,6 +270,14 @@
                                  "+ ($LON - p.lon) * ($LON - p.lon)) limit 1",
                         -1, &_stmt_select_nearest_poi, NULL);
 
+        /* select lat/lon for all pois in category*/
+        sqlite3_prepare(_poi_db,
+                        "select p.lat, p.lon"
+                        " from poi p inner join category c"
+                        "   on p.cat_id = c.cat_id"
+                        " where c.cat_id = ?",
+                        -1, &_stmt_select_cat_poi, NULL);
+
         /* insert poi */
         sqlite3_prepare(_poi_db,
                             "insert into poi (lat, lon, label, desc, cat_id)"
@@ -383,6 +392,30 @@
     *id = poi->poi_id;
 }
 
+GSList *
+get_category_pois(gint cat)
+{
+    GSList *list = NULL;
+    printf("%s(%d, %d)\n", __PRETTY_FUNCTION__);
+
+    if(SQLITE_OK != sqlite3_bind_double(_stmt_select_cat_poi, 1, cat))
+    {
+        g_printerr("Failed to bind values for _stmt_select_cat_poi\n");
+        return NULL;
+    }
+
+    while(SQLITE_ROW == sqlite3_step(_stmt_select_cat_poi))
+    {
+        PoiInfo *poi = g_new0(PoiInfo, 1);
+        poi->lat = sqlite3_column_double(_stmt_select_cat_poi, 0);
+        poi->lon = sqlite3_column_double(_stmt_select_cat_poi, 1);
+        list = g_slist_append(list, poi);
+    }
+    sqlite3_reset(_stmt_select_cat_poi);
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+    return list;
+}
+
 gboolean
 select_poi(gint unitx, gint unity, PoiInfo *poi, gboolean quick)
 {
@@ -879,6 +912,37 @@
     return store;
 }
 
+GtkListStore *
+poi_create_poi_store()
+{
+    GtkTreeIter iter;
+    GtkListStore *store;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    store = gtk_list_store_new(CAT_NUM_COLUMNS,
+                               G_TYPE_UINT,
+                               G_TYPE_BOOLEAN,
+                               G_TYPE_STRING,
+                               G_TYPE_STRING,
+                               G_TYPE_UINT);
+
+    while(SQLITE_ROW == sqlite3_step(_stmt_selall_cat))
+    {
+        gtk_list_store_append(store, &iter);
+        gtk_list_store_set(store, &iter,
+                CAT_ID, sqlite3_column_int(_stmt_selall_cat, 0),
+                CAT_ENABLED, sqlite3_column_int(_stmt_selall_cat, 3),
+                CAT_LABEL, sqlite3_column_text(_stmt_selall_cat, 1),
+                CAT_DESC, sqlite3_column_text(_stmt_selall_cat, 2),
+                CAT_POI_CNT, sqlite3_column_int(_stmt_selall_cat, 4),
+                -1);
+    }
+    sqlite3_reset(_stmt_selall_cat);
+
+    vprintf("%s(): return %p\n", __PRETTY_FUNCTION__, store);
+    return store;
+}
+
 static gboolean
 category_add(GtkWidget *widget, PoiCategoryEditInfo *pcedit)
 {
Index: src/poi.h
===================================================================
--- src/poi.h	(revision 12)
+++ src/poi.h	(revision 13)
@@ -30,6 +30,8 @@
 
 gboolean select_poi(gint unitx, gint unity, PoiInfo *poi, gboolean quick);
 
+GSList *get_category_pois(gint cat);
+
 gboolean category_list_dialog(GtkWidget *parent);
 
 gboolean poi_dialog(GtkWidget *parent, PoiInfo *poi, gboolean is_add_dialog);
@@ -39,6 +41,8 @@
 
 gint poi_get_next_id();
 
+GtkListStore *poi_create_poi_store();
+
 void map_render_poi(void);
 
 void poi_destroy(void);
