[cairo-commit] roadster/src db.c, 1.21, 1.22 gui.c, 1.8, 1.9 gui.h, 1.1, 1.2 location.c, 1.5, 1.6 location.h, 1.3, 1.4 locationset.c, 1.10, 1.11 main.c, 1.20, 1.21 mainwindow.c, 1.36, 1.37 map.c, 1.38, 1.39 map.h, 1.16, 1.17 map_draw_cairo.c, 1.16, 1.17 map_draw_gdk.c, 1.13, 1.14 search_location.c, 1.10, 1.11 search_road.c, 1.18, 1.19 searchwindow.c, 1.17, 1.18 util.c, 1.5, 1.6 util.h, 1.7, 1.8

Ian McIntosh commit at pdx.freedesktop.org
Sat Apr 23 11:13:41 PDT 2005


Committed by: ian

Update of /cvs/cairo/roadster/src
In directory gabe:/tmp/cvs-serv2058/src

Modified Files:
	db.c gui.c gui.h location.c location.h locationset.c main.c 
	mainwindow.c map.c map.h map_draw_cairo.c map_draw_gdk.c 
	search_location.c search_road.c searchwindow.c util.c util.h 
Log Message:
	* src/gui.c: Add new generic way to load XML files.
	* src/location.c: Add loading of locations and their attributes.
	* src/map.c: Add tracking of selected POI.  Add hit-testing for POI info boxes.
	* src/mainwindow.c: Add response to clicks on POI info box and its various bits.
	* src/map_draw_cairo.c: Add rendering of POI info boxes.
	* src/map_draw_gdk.c: Minor changes to colors (shouldn't be hardcoded though!).
	* src/search_location.c: Add new POI search.  All words must be present in ANY attribute of a POI.
	* src/search_road.c: Cleanup.
	* src/searchwindow.c: Cleanup.
	* src/util.c: Add ability to open URIs.


Index: db.c
===================================================================
RCS file: /cvs/cairo/roadster/src/db.c,v
retrieving revision 1.21
retrieving revision 1.22
diff -u -d -r1.21 -r1.22
--- db.c	29 Mar 2005 09:16:20 -0000	1.21
+++ db.c	23 Apr 2005 18:13:39 -0000	1.22
@@ -39,6 +39,7 @@
 #include "mainwindow.h"
 #include "util.h"
 #include "layers.h"
+#include "location.h"
 #include "locationset.h"
 
 #ifdef USE_GNOME_VFS
@@ -81,13 +82,21 @@
 
 	gchar* apszServerOptions[] = {
 		"",	// program name -- unused
-		"--skip-innodb",	// don't bother with table types we don't use
+
+		// Unused server features
+		"--skip-innodb",
 		"--skip-bdb",
+
+		// query cache options
                 "--query-cache-type=1",		// enable query cache (for map tiles)
-                pszSetQueryCacheSize,	//
+                pszSetQueryCacheSize,
 
+		// fulltext index options
+		"--ft-min-word-len=1",		// don't miss any words, even 1-letter words (esp. numbers like "3")
+		"--ft-stopword-file=''",	// non-existant stopword file. we don't want ANY stopwords (words that are ignored)
+
+		// Misc options
 		pszKeyBufferSize,
-		"--ft-min-word-len=2",		// 2 chars should count as a word for fulltext indexes
 		pszSetDataDirCommand
 	};
 
@@ -607,6 +616,14 @@
 		" PRIMARY KEY (ID),"
 		" UNIQUE INDEX (Name));", NULL);
 
+	gchar* pszSQL = g_strdup_printf("INSERT INTO LocationAttributeName SET ID=%d, Name='name'", LOCATION_ATTRIBUTE_ID_NAME);
+	db_query(pszSQL, NULL);
+        g_free(pszSQL);
+
+	pszSQL = g_strdup_printf("INSERT INTO LocationAttributeName SET ID=%d, Name='address'", LOCATION_ATTRIBUTE_ID_ADDRESS);
+	db_query(pszSQL, NULL);
+        g_free(pszSQL);
+
 	// Location Attribute Value
 	db_query("CREATE TABLE IF NOT EXISTS LocationAttributeValue("
 		// a unique ID for the value

Index: gui.c
===================================================================
RCS file: /cvs/cairo/roadster/src/gui.c,v
retrieving revision 1.8
retrieving revision 1.9
diff -u -d -r1.8 -r1.9
--- gui.c	28 Mar 2005 18:49:50 -0000	1.8
+++ gui.c	23 Apr 2005 18:13:39 -0000	1.9
@@ -40,28 +40,40 @@
 
 void gui_init()
 {
+	GladeXML* pGladeXML = gui_load_xml(GLADE_FILE_NAME, NULL);
+	glade_xml_signal_autoconnect(pGladeXML);
+
+	// init all windows/dialogs
+	mainwindow_init(pGladeXML);	
+	searchwindow_init(pGladeXML);
+	gotowindow_init(pGladeXML);
+	importwindow_init(pGladeXML);
+	datasetwindow_init(pGladeXML);
+	welcomewindow_init(pGladeXML);
+}
+
+GladeXML* gui_load_xml(gchar* pszFileName, gchar* pszXMLTreeRoot)
+{
 	GladeXML *pGladeXML;
+	gchar* pszPath;
 
 	// Load glade UI definition file and connect to callback functions	
 	// try source directory first (good for development)
-	pGladeXML = glade_xml_new (PACKAGE_SOURCE_DIR"/data/roadster.glade", NULL, NULL);
+	pszPath = g_strdup_printf(PACKAGE_SOURCE_DIR"/data/%s", pszFileName);
+	pGladeXML = glade_xml_new(pszPath, pszXMLTreeRoot, NULL);
+	g_free(pszPath);
+
 	if(pGladeXML == NULL) {
-		pGladeXML = glade_xml_new (PACKAGE_DATA_DIR"/roadster.glade", NULL, NULL);
+		pszPath = g_strdup_printf(PACKAGE_DATA_DIR"/data/%s", pszFileName);
+		pGladeXML = glade_xml_new(pszPath, pszXMLTreeRoot, NULL);
+		g_free(pszPath);
 
 		if(pGladeXML == NULL) {
-			g_message("cannot find file roadster.glade\n");
+			g_message("cannot find glade file '%s'\n", pszFileName);
 			gtk_main_quit();
 		}
 	}
-	glade_xml_signal_autoconnect(pGladeXML);
-
-	// init all windows/dialogs
-	mainwindow_init(pGladeXML);	
-	searchwindow_init(pGladeXML);
-	gotowindow_init(pGladeXML);
-	importwindow_init(pGladeXML);
-	datasetwindow_init(pGladeXML);
-	welcomewindow_init(pGladeXML);
+	return pGladeXML;
 }
 
 void gui_run()

Index: gui.h
===================================================================
RCS file: /cvs/cairo/roadster/src/gui.h,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- gui.h	23 Feb 2005 17:43:50 -0000	1.1
+++ gui.h	23 Apr 2005 18:13:39 -0000	1.2
@@ -25,6 +25,9 @@
 #define _GUI_H_
 
 #include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#define GLADE_FILE_NAME	("roadster.glade")
 
 extern GtkWidget *g_pApplicationWidget;
 extern GtkWidget *g_pDisplaySettingsWidget;
@@ -41,34 +44,6 @@
 //
 void gui_init(void);
 void gui_run(void);
-extern gboolean gui_redraw_map_if_needed(void);
-extern void gui_set_tool_label(gchar* pMessage);
 extern void gui_exit(void);
-extern GtkWidget* gui_get_top_window(void);
-
-//~ void gui_show_goto_window();
-//~ void gui_hide_goto_window();
-
-//~ void gui_show_colors_window();
-//~ void gui_hide_colors_window();
-
-//~ void gui_show_about_dialog();
-//~ void gui_hide_about_window();
-
-//~ void gui_show_preferences_window();
-//~ void gui_hide_preferences_window();
-
-void cursor_init(void);
-//~ void gui_statusbar_update_zoomscale();
-//~ void gui_statusbar_update_position();
-//~ void gui_statusbar_set_position(gchar* pMessage);
-//~ void gui_statusbar_set_zoomscale(gchar* pMessage);
-
-//~ gboolean gui_get_toolbar_visible();
-//~ void gui_set_toolbar_visible(gboolean bVisible);
-
-//~ void gui_toggle_fullscreen();
-
-//~ void gui_zoomin();
-//~ void gui_zoomout();
+GladeXML* gui_load_xml(gchar* pszFileName, gchar* pszXMLTreeRoot);
 #endif

Index: location.c
===================================================================
RCS file: /cvs/cairo/roadster/src/location.c,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -d -r1.5 -r1.6
--- location.c	31 Mar 2005 08:29:53 -0000	1.5
+++ location.c	23 Apr 2005 18:13:39 -0000	1.6
@@ -25,6 +25,7 @@
 #include "main.h"
 #include "map.h"
 #include "location.h"
+#include "db.h"
 
 struct {
 	GMemChunk* m_pLocationChunkAllocator;
@@ -105,3 +106,79 @@
 	return TRUE;
 }
 
+gboolean location_load(gint nLocationID, mappoint_t* pReturnCoordinates, gint* pnReturnLocationSetID)
+{
+	db_resultset_t* pResultSet = NULL;
+	db_row_t aRow;
+
+	gchar* pszSQL = g_strdup_printf(
+		"SELECT LocationSetID, AsBinary(Coordinates)"
+		" FROM Location"
+		" WHERE ID=%d", nLocationID);
+
+	db_query(pszSQL, &pResultSet);
+	g_free(pszSQL);
+
+	g_return_val_if_fail(pResultSet, FALSE);
+	
+	aRow = db_fetch_row(pResultSet);
+
+	if(aRow) {
+		if(pnReturnLocationSetID != NULL) {
+			*pnReturnLocationSetID = atoi(aRow[0]);
+		}
+		if(pReturnCoordinates != NULL) {
+			db_parse_wkb_point(aRow[1], pReturnCoordinates);
+		}
+		db_free_result(pResultSet);
+		return TRUE;
+	}
+	else {
+		db_free_result(pResultSet);
+		return FALSE;
+	}
+}
+
+void location_load_attributes(gint nLocationID, GPtrArray* pAttributeArray)
+{
+	db_resultset_t* pResultSet = NULL;
+	db_row_t aRow;
+
+	g_assert(pAttributeArray->len == 0);
+
+	gchar* pszSQL = g_strdup_printf(
+		"SELECT LocationAttributeValue.ID, LocationAttributeName.Name, LocationAttributeValue.Value"
+		" FROM LocationAttributeValue"
+		" LEFT JOIN LocationAttributeName ON (LocationAttributeValue.AttributeNameID=LocationAttributeName.ID)"
+		" WHERE LocationAttributeValue.LocationID=%d",
+		nLocationID
+		);
+
+	db_query(pszSQL, &pResultSet);
+	g_free(pszSQL);
+
+	if(pResultSet) {
+		while((aRow = db_fetch_row(pResultSet))) {
+			locationattribute_t* pNew = g_new0(locationattribute_t, 1);
+
+			pNew->m_nValueID = atoi(aRow[0]);
+			pNew->m_pszName = g_strdup((aRow[1] == NULL) ? "" : aRow[1]);
+			pNew->m_pszValue = g_strdup((aRow[2] == NULL) ? "" : aRow[2]);
+
+			g_ptr_array_add(pAttributeArray, pNew);
+		}
+		db_free_result(pResultSet);
+	}
+}
+
+void location_free_attributes(GPtrArray* pAttributeArray)
+{
+	gint i;
+	for(i=(pAttributeArray->len-1) ; i>=0 ; i--) {
+		locationattribute_t* pAttribute = g_ptr_array_remove_index_fast(pAttributeArray, i);
+		g_free(pAttribute->m_pszName);
+		g_free(pAttribute->m_pszValue);
+		g_free(pAttribute);
+	}
+	g_assert(pAttributeArray->len == 0);
+}

Index: location.h
===================================================================
RCS file: /cvs/cairo/roadster/src/location.h,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -d -r1.3 -r1.4
--- location.h	31 Mar 2005 08:29:53 -0000	1.3
+++ location.h	23 Apr 2005 18:13:39 -0000	1.4
@@ -32,18 +32,26 @@
 #define LOCATION_ATTRIBUTE_ID_ADDRESS	(2)		// "address" must be #2 in the DB
 #define LOCATION_ATTRIBUTE_ID________	(3)		// "" must be #3 in the DB
 
-// a single location (eg. "Someday Cafe")
-typedef struct location {
+// a single location (eg. "Someday Cafe").  this is all that's needed for drawing lots of points (with mouse-over name).
+typedef struct {
 	gint m_nID;
 	gchar* m_pszName;
 	mappoint_t m_Coordinates;
 } location_t;
 
+typedef struct {
+	gchar* m_pszName;
+	gchar* m_pszValue;
+	gint m_nValueID;
+} locationattribute_t;
+
 void location_init();
 gboolean location_alloc(location_t** ppLocation);
 void location_free(location_t* pLocation);
 gboolean location_insert(gint nLocationSetID, mappoint_t* pPoint, gint* pnReturnID);
 gboolean location_insert_attribute(gint nLocationID, gint nAttributeID, const gchar* pszValue, gint* pnReturnID);
+gboolean location_load(gint nLocationID, mappoint_t* pReturnCoordinates, gint* pnReturnLocationSetID);
+void location_load_attributes(gint nLocationID, GPtrArray* pAttributeArray);
 
 G_END_DECLS
 

Index: locationset.c
===================================================================
RCS file: /cvs/cairo/roadster/src/locationset.c,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -d -r1.10 -r1.11
--- locationset.c	31 Mar 2005 08:29:53 -0000	1.10
+++ locationset.c	23 Apr 2005 18:13:39 -0000	1.11
@@ -47,7 +47,7 @@
 
 	// create memory allocator
 	g_LocationSet.m_pLocationSetChunkAllocator = g_mem_chunk_new("ROADSTER locationsets",
-			sizeof(locationset_t), 1000, G_ALLOC_AND_FREE);
+			sizeof(locationset_t), 20, G_ALLOC_AND_FREE);
 	g_return_if_fail(g_LocationSet.m_pLocationSetChunkAllocator != NULL);
 }
 

Index: main.c
===================================================================
RCS file: /cvs/cairo/roadster/src/main.c,v
retrieving revision 1.20
retrieving revision 1.21
diff -u -d -r1.20 -r1.21
--- main.c	1 Apr 2005 09:53:49 -0000	1.20
+++ main.c	23 Apr 2005 18:13:39 -0000	1.21
@@ -62,19 +62,19 @@
 	gint nNewLocationID;
 
 	mappoint_t pt;
-	pt.m_fLatitude = 41.55130;
-	pt.m_fLongitude = -70.61409;
+	pt.m_fLatitude = 42.37382;
+	pt.m_fLongitude = -71.10054;
 	nNewLocationID = 0;
 	location_insert(nNewLocationSetID, &pt, &nNewLocationID);
 	location_insert_attribute(nNewLocationID, LOCATION_ATTRIBUTE_ID_NAME, "1369 Coffee House", NULL);
 	location_insert_attribute(nNewLocationID, LOCATION_ATTRIBUTE_ID_ADDRESS, "1369 Cambridge Street\nCambridge, MA, 02141", NULL);
 
-	pt.m_fLatitude = 41.55120;
-	pt.m_fLongitude = -70.61409;
+	pt.m_fLatitude = 42.36650;
+	pt.m_fLongitude = -71.10554;
 	nNewLocationID = 0;
 	location_insert(nNewLocationSetID, &pt, &nNewLocationID);
-	location_insert_attribute(nNewLocationID, LOCATION_ATTRIBUTE_ID_NAME, "One Cup of Coffee", NULL);
-	location_insert_attribute(nNewLocationID, LOCATION_ATTRIBUTE_ID_ADDRESS, "29 Shore Street\nCambridge, MA, 02141", NULL);
+	location_insert_attribute(nNewLocationID, LOCATION_ATTRIBUTE_ID_NAME, "1369 Coffee House", NULL);
+	location_insert_attribute(nNewLocationID, LOCATION_ATTRIBUTE_ID_ADDRESS, "757 Massachusetts Avenue\nCambridge, MA 02139", NULL);
 */
 	prefs_read();
 

Index: mainwindow.c
===================================================================
RCS file: /cvs/cairo/roadster/src/mainwindow.c,v
retrieving revision 1.36
retrieving revision 1.37
diff -u -d -r1.36 -r1.37
--- mainwindow.c	1 Apr 2005 09:53:49 -0000	1.36
+++ mainwindow.c	23 Apr 2005 18:13:39 -0000	1.37
@@ -840,7 +840,7 @@
 	map_windowpoint_to_mappoint(g_MainWindow.m_pMap, &screenpoint, &mappoint);
 	
 	maphit_t* pHitStruct = NULL;
-	gboolean bLocationHit = (map_hit_test(g_MainWindow.m_pMap, &mappoint, &pHitStruct) && pHitStruct->m_eHitType == MAP_HITTYPE_LOCATION);
+	map_hit_test(g_MainWindow.m_pMap, &mappoint, &pHitStruct);
 	// hitstruct free'd far below
 
 	if(event->button == MOUSE_BUTTON_LEFT) {
@@ -885,8 +885,24 @@
 
 					mainwindow_add_history();
 				}
-				else if(bLocationHit) {
-					g_print("click on location ID #%d\n", pHitStruct->m_LocationHit.m_nLocationID);
+				else if(pHitStruct != NULL) {
+					if(pHitStruct->m_eHitType == MAP_HITTYPE_LOCATION) {
+						if(map_location_selection_add(g_MainWindow.m_pMap, pHitStruct->m_LocationHit.m_nLocationID)) {
+							mainwindow_draw_map(DRAWFLAG_ALL);
+						}
+					}
+					else if(pHitStruct->m_eHitType == MAP_HITTYPE_LOCATIONSELECTION_CLOSE) {
+						if(map_location_selection_remove(g_MainWindow.m_pMap, pHitStruct->m_LocationHit.m_nLocationID)) {
+							mainwindow_draw_map(DRAWFLAG_ALL);
+						}
+					}
+					else if(pHitStruct->m_eHitType == MAP_HITTYPE_LOCATIONSELECTION_EDIT) {
+						// edit POI
+						//g_MainWindow.m_pMap, pHitStruct->m_LocationHit.m_nLocationID
+					}
+					else if(pHitStruct->m_eHitType == MAP_HITTYPE_URL) {
+						util_open_uri(pHitStruct->m_URLHit.m_pszURL);
+					}
 				}
 			}
 
@@ -1044,14 +1060,29 @@
 
 					// also set mouse cursor to hand
 					nCursor = GDK_HAND2;
+					tooltip_show(g_MainWindow.m_pTooltip);
 				}
-				else {
+				else if(pHitStruct->m_eHitType == MAP_HITTYPE_ROAD) {
 					GdkColor clr = {0, ROAD_TOOLTIP_BG_COLOR};
 					tooltip_set_bg_color(g_MainWindow.m_pTooltip, &clr);
+					tooltip_show(g_MainWindow.m_pTooltip);
+				}
+				else if(pHitStruct->m_eHitType == MAP_HITTYPE_LOCATIONSELECTION) {
+					tooltip_hide(g_MainWindow.m_pTooltip);
+				}
+				else if(pHitStruct->m_eHitType == MAP_HITTYPE_LOCATIONSELECTION_CLOSE) {
+					nCursor = GDK_HAND2;
+					tooltip_hide(g_MainWindow.m_pTooltip);
+				}
+				else if(pHitStruct->m_eHitType == MAP_HITTYPE_LOCATIONSELECTION_EDIT) {
+					nCursor = GDK_HAND2;
+					tooltip_hide(g_MainWindow.m_pTooltip);
+				}
+				else if(pHitStruct->m_eHitType == MAP_HITTYPE_URL) {
+					nCursor = GDK_HAND2;
+					tooltip_hide(g_MainWindow.m_pTooltip);
 				}
 
-				tooltip_show(g_MainWindow.m_pTooltip);	// ensure it's visible
-				
 				map_hitstruct_free(g_MainWindow.m_pMap, pHitStruct);
 			}
 			else {

Index: map.c
===================================================================
RCS file: /cvs/cairo/roadster/src/map.c,v
retrieving revision 1.38
retrieving revision 1.39
diff -u -d -r1.38 -r1.39
--- map.c	1 Apr 2005 09:53:49 -0000	1.38
+++ map.c	23 Apr 2005 18:13:39 -0000	1.39
@@ -74,6 +74,7 @@
 static gboolean map_hit_test_layer_roads(GPtrArray* pPointStringsArray, gdouble fMaxDistance, mappoint_t* pHitPoint, maphit_t** ppReturnStruct);
 static gboolean map_hit_test_line(mappoint_t* pPoint1, mappoint_t* pPoint2, mappoint_t* pHitPoint, gdouble fMaxDistance, mappoint_t* pReturnClosestPoint, gdouble* pfReturnPercentAlongLine);
 static ESide map_side_test_line(mappoint_t* pPoint1, mappoint_t* pPoint2, mappoint_t* pClosestPointOnLine, mappoint_t* pHitPoint);
+static gboolean map_hit_test_locationselections(map_t* pMap, rendermetrics_t* pRenderMetrics, GPtrArray* pLocationSelectionsArray, mappoint_t* pHitPoint, maphit_t** ppReturnStruct);
 
 static gboolean map_hit_test_locationsets(map_t* pMap, rendermetrics_t* pRenderMetrics, mappoint_t* pHitPoint, maphit_t** ppReturnStruct);
 static gboolean map_hit_test_locations(map_t* pMap, rendermetrics_t* pRenderMetrics, GPtrArray* pLocationsArray, mappoint_t* pHitPoint, maphit_t** ppReturnStruct);
@@ -198,6 +199,10 @@
 		pMap->m_apLayerData[i] = pLayer;
 	}
 
+	// init POI selection
+	pMap->m_pLocationSelectionArray = g_ptr_array_new();
+	pMap->m_pLocationSelectionAllocator = g_free_list_new(sizeof(locationselection_t), 100);
+
 	// save it
 	*ppMap = pMap;
 	return TRUE;
@@ -249,7 +254,7 @@
 	map_data_load_tiles(pMap, &(pRenderMetrics->m_rWorldBoundingBox));
 	TIMER_END(loadtimer, "--- END ALL DB LOAD");
 
-	gint nRenderMode = RENDERMODE_FAST;
+	gint nRenderMode = RENDERMODE_FAST; //RENDERMODE_PRETTY; //;
 
 #ifdef SCENEMANAGER_DEBUG_TEST
         GdkRectangle rect = {200,200,100,100};
@@ -790,6 +795,12 @@
 {
 	if(pHitStruct == NULL) return;
 
+	// free type-specific stuff
+	if(pHitStruct->m_eHitType == MAP_HITTYPE_URL) {
+		g_free(pHitStruct->m_URLHit.m_pszURL);
+	}
+
+	// free common stuff
 	g_free(pHitStruct->m_pszText);
 	g_free(pHitStruct);
 }
@@ -800,6 +811,10 @@
 	rendermetrics_t rendermetrics;
 	map_get_render_metrics(pMap, &rendermetrics);
 
+	if(map_hit_test_locationselections(pMap, &rendermetrics, pMap->m_pLocationSelectionArray, pMapPoint, ppReturnStruct)) {
+		return TRUE;
+	}
+
 	if(map_hit_test_locationsets(pMap, &rendermetrics, pMapPoint, ppReturnStruct)) {
 		return TRUE;
 	}
@@ -1098,6 +1113,66 @@
 	return FALSE;
 }
 
+gboolean hit_test_screenpoint_in_rect(screenpoint_t* pPt, screenrect_t* pRect)
+{
+	return(pPt->m_nX >= pRect->m_A.m_nX && pPt->m_nX <= pRect->m_B.m_nX && pPt->m_nY >= pRect->m_A.m_nY && pPt->m_nY <= pRect->m_B.m_nY);
+}
+
+static gboolean map_hit_test_locationselections(map_t* pMap, rendermetrics_t* pRenderMetrics, GPtrArray* pLocationSelectionArray, mappoint_t* pHitPoint, maphit_t** ppReturnStruct)
+{
+	screenpoint_t screenpoint;
+	screenpoint.m_nX = (gint)SCALE_X(pRenderMetrics, pHitPoint->m_fLongitude);
+	screenpoint.m_nY = (gint)SCALE_Y(pRenderMetrics, pHitPoint->m_fLatitude);
+
+	gint i;
+	for(i=(pLocationSelectionArray->len-1) ; i>=0 ; i--) {
+		locationselection_t* pLocationSelection = g_ptr_array_index(pLocationSelectionArray, i);
+
+		if(pLocationSelection->m_bVisible == FALSE) continue;
+
+		if(hit_test_screenpoint_in_rect(&screenpoint, &(pLocationSelection->m_InfoBoxRect))) {
+			// fill out a new maphit_t struct with details
+			maphit_t* pHitStruct = g_new0(maphit_t, 1);
+
+			if(hit_test_screenpoint_in_rect(&screenpoint, &(pLocationSelection->m_InfoBoxCloseRect))) {
+				pHitStruct->m_eHitType = MAP_HITTYPE_LOCATIONSELECTION_CLOSE;
+				pHitStruct->m_pszText = g_strdup("close");
+				pHitStruct->m_LocationSelectionHit.m_nLocationID = pLocationSelection->m_nLocationID;
+			}
+			else if(hit_test_screenpoint_in_rect(&screenpoint, &(pLocationSelection->m_EditRect))) {
+				pHitStruct->m_eHitType = MAP_HITTYPE_LOCATIONSELECTION_EDIT;
+				pHitStruct->m_pszText = g_strdup("edit");
+				pHitStruct->m_LocationSelectionHit.m_nLocationID = pLocationSelection->m_nLocationID;
+			}
+			else {
+				gboolean bURLMatch = FALSE;
+
+				gint iURL;
+				for(iURL=0 ; iURL<pLocationSelection->m_nNumURLs ; iURL++) {
+					if(hit_test_screenpoint_in_rect(&screenpoint, &(pLocationSelection->m_aURLs[iURL].m_Rect))) {
+						pHitStruct->m_eHitType = MAP_HITTYPE_URL;
+						pHitStruct->m_pszText = g_strdup("click to open location");
+						pHitStruct->m_URLHit.m_pszURL = g_strdup(pLocationSelection->m_aURLs[iURL].m_pszURL);
+
+						bURLMatch = TRUE;
+						break;
+					}
+				}
+
+				// no url match, just return a generic "hit the locationselection box"
+				if(!bURLMatch) {
+					pHitStruct->m_eHitType = MAP_HITTYPE_LOCATIONSELECTION;
+					pHitStruct->m_pszText = g_strdup("");
+					pHitStruct->m_LocationSelectionHit.m_nLocationID = pLocationSelection->m_nLocationID;
+				}
+			}
+			*ppReturnStruct = pHitStruct;
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
 gboolean map_can_zoom_in(map_t* pMap)
 {
 	// can we increase zoom level?
@@ -1109,3 +1184,79 @@
 }
 
 
+//
+// Map selected POI
+//
+
+// lookup a selected location by ID
+locationselection_t* map_location_selection_get(map_t* pMap, gint nLocationID)
+{
+	// XXX: should we add a hash table on locationID to speed up searches?
+	gint i;
+	for(i=0 ; i<pMap->m_pLocationSelectionArray->len ; i++) {
+		locationselection_t* pSel = g_ptr_array_index(pMap->m_pLocationSelectionArray, i);
+		if(pSel->m_nLocationID == nLocationID) return pSel;
+	}
+	return NULL;
+}
+
+gint map_location_selection_sort_callback(gconstpointer ppA, gconstpointer ppB)
+{
+	locationselection_t* pA = *((locationselection_t**)ppA);
+	locationselection_t* pB = *((locationselection_t**)ppB);
+	
+	// we want them ordered from greatest latitude to smallest
+	return ((pA->m_Coordinates.m_fLatitude > pB->m_Coordinates.m_fLatitude) ? -1 : 1);
+}
+
+// add a Location to the selected list
+gboolean map_location_selection_add(map_t* pMap, gint nLocationID)
+{
+	if(map_location_selection_get(pMap, nLocationID) != NULL) {
+		// already here
+		return FALSE;
+	}
+
+	// create a new locationselection_t and initialize it
+	locationselection_t* pNew = g_free_list_alloc(pMap->m_pLocationSelectionAllocator);
+
+	pNew->m_nLocationID = nLocationID;
+	pNew->m_pAttributesArray = g_ptr_array_new();
+	
+	// load all attributes
+	location_load(nLocationID, &(pNew->m_Coordinates), NULL);
+	location_load_attributes(nLocationID, pNew->m_pAttributesArray);
+
+	g_ptr_array_add(pMap->m_pLocationSelectionArray, pNew);
+
+	g_ptr_array_sort(pMap->m_pLocationSelectionArray, map_location_selection_sort_callback);
+
+	return TRUE;	// added
+}
+
+// add a Location to the selected list
+gboolean map_location_selection_remove(map_t* pMap, gint nLocationID)
+{
+	gint i;
+	for(i=0 ; i<pMap->m_pLocationSelectionArray->len ; i++) {
+		locationselection_t* pSel = g_ptr_array_index(pMap->m_pLocationSelectionArray, i);
+		if(pSel->m_nLocationID == nLocationID) {
+			g_ptr_array_remove_index(pMap->m_pLocationSelectionArray, i);
+			return TRUE;
+		}
+	}
+	return FALSE;	// removed
+}
+
+// get an attribute from a selected location
+const gchar* map_location_selection_get_attribute(const map_t* pMap, const locationselection_t* pLocationSelection, const gchar* pszAttributeName)
+{
+	gint i;
+	for(i=0 ; i<pLocationSelection->m_pAttributesArray->len ; i++) {
+		locationattribute_t* pAttribute = g_ptr_array_index(pLocationSelection->m_pAttributesArray, i);
+		if(strcmp(pAttribute->m_pszName, pszAttributeName) == 0) {
+			return pAttribute->m_pszValue;
+		}
+	}
+	return NULL;
+}

Index: map.h
===================================================================
RCS file: /cvs/cairo/roadster/src/map.h,v
retrieving revision 1.16
retrieving revision 1.17
diff -u -d -r1.16 -r1.17
--- map.h	31 Mar 2005 08:29:53 -0000	1.16
+++ map.h	23 Apr 2005 18:13:39 -0000	1.17
@@ -24,6 +24,8 @@
 #ifndef _MAP_H_
 #define _MAP_H_
 
+#include "gfreelist.h"
+
 #define MIN_LATITUDE	(-90.0)
 #define MAX_LATITUDE	(90.0)
 #define MIN_LONGITUDE	(-180.0)
@@ -79,33 +81,33 @@
 #include "scenemanager.h"
 
 // World space
-typedef struct mappoint {
+typedef struct {
 	gdouble m_fLatitude;
 	gdouble m_fLongitude;
 } mappoint_t;
 
-typedef struct maprect {
+typedef struct {
 	mappoint_t m_A;
 	mappoint_t m_B;
 } maprect_t;
 
 // Screen space
-typedef struct screenpoint {
+typedef struct {
 	gint16 m_nX;
 	gint16 m_nY;
 } screenpoint_t;
 
-typedef struct screenrect {
+typedef struct {
 	screenpoint_t m_A;
 	screenpoint_t m_B;
 } screenrect_t;
 
-typedef struct windowdimensions {
+typedef struct {
 	guint16 m_uWidth;
 	guint16 m_uHeight;
 } dimensions_t;
 
-typedef struct zoomlevel {
+typedef struct {
 	guint32 m_uScale;		// ex. 10000 for 1:10000 scale
 	gchar* m_szName;
 } zoomlevel_t;
@@ -138,6 +140,7 @@
 	gint m_nWindowWidth;
 	gint m_nWindowHeight;
 } rendermetrics_t;
+
 #define SCALE_X(p, x)  ((((x) - (p)->m_rWorldBoundingBox.m_A.m_fLongitude) / (p)->m_fScreenLongitude) * (p)->m_nWindowWidth)
 #define SCALE_Y(p, y)  ((p)->m_nWindowHeight - ((((y) - (p)->m_rWorldBoundingBox.m_A.m_fLatitude) / (p)->m_fScreenLatitude) * (p)->m_nWindowHeight))
 
@@ -149,8 +152,7 @@
 	GPtrArray* m_pLocationsArray;
 } maplayer_locations_t;
 
-typedef struct
-{
+typedef struct {
 	mappoint_t 		m_MapCenter;
 	dimensions_t 		m_MapDimensions;
 	guint16 		m_uZoomLevel;
@@ -164,14 +166,24 @@
 	// Locationsets
 	GHashTable		*m_pLocationArrayHashTable;
 
+	GPtrArray		*m_pLocationSelectionArray;
+	GFreeList		*m_pLocationSelectionAllocator;
+
 	// Mutex and the data it controls (always lock before reading/writing)
 	//GMutex* m_pPixmapMutex;
 	GdkPixmap* m_pPixmap;
 } map_t;
 
 typedef enum {
-	MAP_HITTYPE_LOCATION=1,
-	MAP_HITTYPE_ROAD=2,
+	MAP_HITTYPE_LOCATION,
+	MAP_HITTYPE_ROAD,
+	
+	// the following all use m_LocationSelectionHit in the union below
+	MAP_HITTYPE_LOCATIONSELECTION,	// hit somewhere on a locationselection graphic (info balloon)
+	MAP_HITTYPE_LOCATIONSELECTION_CLOSE,	// hit locationselection graphic close graphic (info balloon [X])
+	MAP_HITTYPE_LOCATIONSELECTION_EDIT,	// hit locationselection graphic edit graphic (info balloon "edit")
+
+	MAP_HITTYPE_URL,
 } EMapHitType;
 
 typedef struct {
@@ -187,6 +199,14 @@
 			gint m_nRoadID;
 			mappoint_t m_ClosestPoint;
 		} m_RoadHit;
+
+		struct {
+			gint m_nLocationID;
+		} m_LocationSelectionHit;
+
+		struct {
+			gchar* m_pszURL;
+		} m_URLHit;
 	};
 } maphit_t;
 
@@ -205,6 +225,26 @@
 //	void (*pFunc)(map_t*, cairo_t*, rendermetrics_t*, GPtrArray*, sublayerstyle_t*, textlabelstyle_t*);
 } draworder_t;
 
+#define MAX_LOCATIONSELETION_URLS	(5)
+
+typedef struct {
+	gint m_nLocationID;
+	gboolean m_bVisible;
+
+	mappoint_t m_Coordinates;
+	GPtrArray *m_pAttributesArray;
+
+	screenrect_t m_InfoBoxRect;
+	screenrect_t m_InfoBoxCloseRect;
+	screenrect_t m_EditRect;
+
+	gint m_nNumURLs;
+	struct {
+		screenrect_t m_Rect;
+		gchar* m_pszURL;
+	} m_aURLs[MAX_LOCATIONSELETION_URLS];
+} locationselection_t;
+
 // Draw flags
 #define DRAWFLAG_LABELS 	(1)
 #define DRAWFLAG_GEOMETRY	(2)
@@ -212,7 +252,7 @@
 // next is 4 :)
 #define DRAWFLAG_ALL 		(1|2)
 
-#define NUM_SUBLAYER_TO_DRAW (22)
+#define NUM_SUBLAYER_TO_DRAW (24)
 extern draworder_t layerdraworder[NUM_SUBLAYER_TO_DRAW];	//
 
 void map_init(void);
@@ -262,4 +302,9 @@
 gboolean map_can_zoom_in(map_t* pMap);
 gboolean map_can_zoom_out(map_t* pMap);
 
+gboolean map_location_selection_add(map_t* pMap, gint nLocationID);
+gboolean map_location_selection_remove(map_t* pMap, gint nLocationID);
+
+const gchar* map_location_selection_get_attribute(const map_t* pMap, const locationselection_t* pLocationSelection, const gchar* pszAttributeName);
+
 #endif

Index: map_draw_cairo.c
===================================================================
RCS file: /cvs/cairo/roadster/src/map_draw_cairo.c,v
retrieving revision 1.16
retrieving revision 1.17
diff -u -d -r1.16 -r1.17
--- map_draw_cairo.c	31 Mar 2005 08:29:53 -0000	1.16
+++ map_draw_cairo.c	23 Apr 2005 18:13:39 -0000	1.17
@@ -23,6 +23,8 @@
 
 //#define ENABLE_TIMING
 
+#define LABEL_LIMIT_TO_ROAD
+
 #define RENDERING_THREAD_YIELD	// do nothing
 
 #define	ACCEPTABLE_LINE_LABEL_OVERDRAW_IN_PIXELS_SQUARED (38*38)
@@ -55,11 +57,13 @@
 static void map_draw_cairo_layer_roads(map_t* pMap, cairo_t* pCairo, rendermetrics_t* pRenderMetrics, GPtrArray* pRoadsArray, sublayerstyle_t* pSubLayerStyle, textlabelstyle_t* pLabelStyle);
 static void map_draw_cairo_layer_road_labels(map_t* pMap, cairo_t* pCairo, rendermetrics_t* pRenderMetrics, GPtrArray* pRoadsArray, sublayerstyle_t* pSubLayerStyle, textlabelstyle_t* pLabelStyle);
 static void map_draw_cairo_layer_polygon_labels(map_t* pMap, cairo_t* pCairo, rendermetrics_t* pRenderMetrics, GPtrArray* pRoadsArray, sublayerstyle_t* pSubLayerStyle, textlabelstyle_t* pLabelStyle);
-static void map_draw_cairo_locations(map_t* pMap, cairo_t* pCairo, rendermetrics_t* pRenderMetrics);
+//static void map_draw_cairo_locations(map_t* pMap, cairo_t* pCairo, rendermetrics_t* pRenderMetrics);
 
 // Draw a single line/polygon/point
 static void map_draw_cairo_background(map_t* pMap, cairo_t *pCairo);
 static void map_draw_cairo_layer_points(map_t* pMap, cairo_t* pCairo, rendermetrics_t* pRenderMetrics, GPtrArray* pLocationsArray);
+//static void map_draw_cairo_locationset(map_t* pMap, cairo_t *pCairo, rendermetrics_t* pRenderMetrics, locationset_t* pLocationSet, GPtrArray* pLocationsArray);
+static void map_draw_cairo_locationselection(map_t* pMap, cairo_t *pCairo, rendermetrics_t* pRenderMetrics, GPtrArray* pLocationSelectionArray);
 
 // Draw a single line/polygon label
 static void map_draw_cairo_road_label(map_t* pMap, cairo_t *pCairo, textlabelstyle_t* pLabelStyle, rendermetrics_t* pRenderMetrics, GPtrArray* pPointsArray, gdouble fLineWidth, const gchar* pszLabel);
@@ -67,6 +71,18 @@
 
 // static void map_draw_cairo_crosshair(map_t* pMap, cairo_t* pCairo, rendermetrics_t* pRenderMetrics);
 
+void cairo_underline_text(cairo_t* pCairo, gdouble fLabelWidth)
+{
+#define UNDERLINE_RELIEF	(2.0)
+
+	cairo_rel_move_to(pCairo, 2, UNDERLINE_RELIEF);
+	cairo_rel_line_to(pCairo, fLabelWidth, 0.0);
+	cairo_set_line_width(pCairo, 1.0);
+	cairo_stroke(pCairo);
+	// undo moves for underline
+	cairo_rel_move_to(pCairo, -(fLabelWidth+2), -UNDERLINE_RELIEF);
+}
+
 void map_draw_cairo(map_t* pMap, rendermetrics_t* pRenderMetrics, GdkPixmap* pPixmap, gint nDrawFlags)
 {
 	// 1. Set draw target to X Drawable
@@ -95,8 +111,8 @@
 	for(i=0 ; i<NUM_ELEMS(layerdraworder) ; i++) {
 		gint nLayer = layerdraworder[i].nLayer;
 		gint nSubLayer = layerdraworder[i].nSubLayer;
-
 		gint nRenderType = layerdraworder[i].eSubLayerRenderType;
+
 		if(nRenderType == SUBLAYER_RENDERTYPE_LINES) {
 			if(nDrawFlags & DRAWFLAG_GEOMETRY) {
 				map_draw_cairo_layer_roads(pMap, pCairo,
@@ -136,6 +152,7 @@
 	}
 
 //	map_draw_cairo_locations(pMap, pCairo, pRenderMetrics);
+	map_draw_cairo_locationselection(pMap, pCairo, pRenderMetrics, pMap->m_pLocationSelectionArray);
 
 	// 4. Cleanup
 	cairo_restore(pCairo);
@@ -186,7 +203,7 @@
 }
 
 //
-// Draw a whole layer of polygons
+// Draw a whole layer of polygon labels
 //
 void map_draw_cairo_layer_polygon_labels(map_t* pMap, cairo_t* pCairo, rendermetrics_t* pRenderMetrics, GPtrArray* pRoadsArray, sublayerstyle_t* pSubLayerStyle, textlabelstyle_t* pLabelStyle)
 {
@@ -194,7 +211,7 @@
 	if(fFontSize == 0) return;
 
 	gchar* pszFontFamily = AREA_FONT;
-	
+
 	// set font for whole layer
 	cairo_save(pCairo);
 	cairo_select_font(pCairo, pszFontFamily, CAIRO_FONT_SLANT_NORMAL, pLabelStyle->m_abBoldAtZoomLevel[pRenderMetrics->m_nZoomLevel-1] ? CAIRO_FONT_WEIGHT_BOLD : CAIRO_FONT_WEIGHT_NORMAL);
@@ -408,8 +425,8 @@
 */
 }
 
-void map_draw_cairo_locations(map_t* pMap, cairo_t* pCairo, rendermetrics_t* pRenderMetrics)
-{
+//void map_draw_cairo_locations(map_t* pMap, cairo_t* pCairo, rendermetrics_t* pRenderMetrics)
+//{
 /*
 	location_t loc;
 	location_t* pLoc = &loc;
@@ -441,7 +458,7 @@
 	cairo_fill(pCairo);
 	cairo_restore(pCairo);
 */
-}
+//}
 
 
 #define ROAD_MAX_SEGMENTS 100
@@ -1220,6 +1237,334 @@
         scenemanager_claim_label(pMap->m_pSceneManager, pszLabel);
 }
 
+/*
+static void map_draw_cairo_locations(map_t* pMap, cairo_t *pCairo, rendermetrics_t* pRenderMetrics)
+{
+	const GPtrArray* pLocationSetsArray = locationset_get_array();
+	gint i;
+	for(i=0 ; i<pLocationSetsArray->len ; i++) {
+		locationset_t* pLocationSet = g_ptr_array_index(pLocationSetsArray, i);
+
+		if(!locationset_is_visible(pLocationSet)) continue;
+
+		// 2. Get array of Locations from the hash table using LocationSetID
+		GPtrArray* pLocationsArray;
+		pLocationsArray = g_hash_table_lookup(pMap->m_pLocationArrayHashTable, &(pLocationSet->m_nID));
+		if(pLocationsArray != NULL) {
+			// found existing array
+			map_draw_cairo_locationset(pMap, pCairo, pRenderMetrics, pLocationSet, pLocationsArray);
+		}
+		else {
+			// none to draw
+		}
+	}
+}
+*/
+
+static void map_draw_cairo_locationselection_outline(cairo_t *pCairo, gdouble fPointX, gdouble fPointY, const screenrect_t* pRect, screenrect_t* pCloseRect)
+{
+	gdouble fCorner = 30.0;
+	gdouble fArrowWidth = 30.0; 
+
+	gdouble fBoxX = pRect->m_A.m_nX + 0.5;
+	gdouble fBoxY = pRect->m_A.m_nY + 0.5;
+	gdouble fBoxWidth = pRect->m_B.m_nX - pRect->m_A.m_nX;
+	gdouble fBoxHeight = pRect->m_B.m_nY - pRect->m_A.m_nY;
+
+	typedef enum {
+		SIDE_BOTTOM = 1,
+		SIDE_TOP,
+		SIDE_LEFT,
+		SIDE_RIGHT
+	} ESide;
+
+	ESide eArrowSide = SIDE_BOTTOM;
+
+	// =======================================================================================
+	// BEGIN drawing balloon
+
+#define	 CCPOE	(4.0)	// # pixels to move the control points past the rectangle's corner points
+			// a higher number means less curve.  (what does it stand for?  who cares, it's short)
+
+	// move to spot just below top left curve
+	cairo_move_to(pCairo, fBoxX, (fBoxY + fCorner));
+
+	// top left curver
+	cairo_curve_to(pCairo, 	(fBoxX), (fBoxY - CCPOE),
+				(fBoxX - CCPOE), (fBoxY),
+				(fBoxX + fCorner), (fBoxY));
+
+	// top line
+	cairo_line_to(pCairo, (fBoxX + fBoxWidth) - fCorner, fBoxY);
+
+	// top right corner
+	cairo_curve_to(pCairo,	(fBoxX + fBoxWidth + CCPOE), (fBoxY),
+				(fBoxX + fBoxWidth), (fBoxY - CCPOE),
+				(fBoxX + fBoxWidth), (fBoxY + fCorner));
+
+	// right line
+	cairo_line_to(pCairo, (fBoxX + fBoxWidth), (fBoxY + fBoxHeight) - fCorner);
+
+	// bottom right corner
+	cairo_curve_to(pCairo,  (fBoxX + fBoxWidth), (fBoxY + fBoxHeight + CCPOE),
+				(fBoxX + fBoxWidth + CCPOE), (fBoxY + fBoxHeight), 
+				(fBoxX + fBoxWidth) - fCorner, (fBoxY + fBoxHeight));
+
+	// bottom line (drawing right to left)
+	if(eArrowSide == SIDE_BOTTOM) {
+#ifdef LOCATIONSELECTION_USE_CURVY_BALLOON
+		cairo_curve_to(pCairo,  
+			       (fBoxX + fBoxWidth/2) + fArrowWidth, (fBoxY + fBoxHeight), 
+			       (fBoxX + fBoxWidth/2) + fArrowWidth, (fBoxY + fBoxHeight), 
+			       fPointX, fPointY);
+
+		cairo_curve_to(pCairo,  
+			       (fBoxX + fBoxWidth/2) - 0, (fBoxY + fBoxHeight),
+			       (fBoxX + fBoxWidth/2) - 0, (fBoxY + fBoxHeight),
+			       (fBoxX + fCorner), (fBoxY + fBoxHeight));
+#else
+		// right side of arrow
+		cairo_line_to(pCairo, (fBoxX + fBoxWidth/2) + fArrowWidth, (fBoxY + fBoxHeight));
+
+		// point of array
+		cairo_line_to(pCairo, fPointX, fPointY);
+
+		// left side of arrow
+		cairo_line_to(pCairo, (fBoxX + fBoxWidth/2) - 0, (fBoxY + fBoxHeight));
+
+		// right side of bottom left corner
+		cairo_line_to(pCairo, (fBoxX + fCorner), (fBoxY + fBoxHeight));
+#endif
+	}
+	else {
+		cairo_line_to(pCairo, (fBoxX + fCorner), (fBoxY + fBoxHeight));
+	}
+
+	// bottom left corner
+	cairo_curve_to(pCairo,  (fBoxX - CCPOE), (fBoxY + fBoxHeight),
+				(fBoxX), (fBoxY + fBoxHeight + CCPOE), 
+				(fBoxX), (fBoxY + fBoxHeight) - fCorner);
+
+	// left line, headed up
+	cairo_line_to(pCairo, (fBoxX), (fBoxY + fCorner));
+
+	// DONE drawing balloon
+	// =======================================================================================
+
+	cairo_set_alpha(pCairo, 1.00);
+
+	// fill then stroke
+	cairo_save(pCairo);
+		cairo_set_rgb_color(pCairo, 1.0, 1.0, 1.0);
+		cairo_fill(pCairo);
+	cairo_restore(pCairo);
+
+//	cairo_save(pCairo);
+		cairo_set_rgb_color(pCairo, 0.0, 0.0, 0.0);
+		cairo_set_line_width(pCairo, 1.0);
+		cairo_stroke(pCairo);
+//	cairo_restore(pCairo);
+
+	// BEGIN [X]
+	gdouble fCloseRectRelief = 8.0;
+	gdouble fCloseRectWidth = 13.0;
+
+	cairo_set_line_width(pCairo, 1.0);
+	cairo_set_rgb_color(pCairo, 0.5,0.5,0.5);
+
+	cairo_move_to(pCairo, (fBoxX + fBoxWidth) - fCloseRectRelief, fBoxY + fCloseRectRelief);
+	cairo_rel_line_to(pCairo, 0.0, fCloseRectWidth);
+	cairo_rel_line_to(pCairo, -fCloseRectWidth, 0.0);
+	cairo_rel_line_to(pCairo, 0.0, -fCloseRectWidth);
+	cairo_rel_line_to(pCairo, fCloseRectWidth, 0.0);
+	cairo_stroke(pCairo);
+
+	pCloseRect->m_A.m_nX = (gint)((fBoxX + fBoxWidth) - fCloseRectRelief) - fCloseRectWidth;
+	pCloseRect->m_A.m_nY = (gint)fBoxY + fCloseRectRelief;
+	pCloseRect->m_B.m_nX = (gint)((fBoxX + fBoxWidth) - fCloseRectRelief);
+	pCloseRect->m_B.m_nY = (gint)fBoxY + fCloseRectRelief + fCloseRectWidth;
+
+	cairo_set_line_width(pCairo, 2.0);
+	cairo_move_to(pCairo, (((fBoxX + fBoxWidth) - fCloseRectRelief) - fCloseRectWidth) + 3, fBoxY + fCloseRectRelief + 3);
+	cairo_rel_line_to(pCairo, fCloseRectWidth - 6, fCloseRectWidth - 6);
+
+	cairo_move_to(pCairo, (((fBoxX + fBoxWidth) - fCloseRectRelief) - fCloseRectWidth) + 3, (fBoxY + fCloseRectRelief + fCloseRectWidth) - 3);
+	cairo_rel_line_to(pCairo, (fCloseRectWidth - 6), -(fCloseRectWidth - 6));
+	cairo_stroke(pCairo);
+	// END [X]
+}
+
+static void map_draw_cairo_locationselection(map_t* pMap, cairo_t *pCairo, rendermetrics_t* pRenderMetrics, GPtrArray* pLocationSelectionArray)
+{
+	cairo_save(pCairo);
+
+	gint i;
+	for(i=0 ; i<pLocationSelectionArray->len ; i++) {
+		locationselection_t* pLocationSelection = g_ptr_array_index(pLocationSelectionArray, i);
+
+		pLocationSelection->m_nNumURLs=0;
+
+		// bounding box test
+		if(pLocationSelection->m_Coordinates.m_fLatitude < pRenderMetrics->m_rWorldBoundingBox.m_A.m_fLatitude
+		   || pLocationSelection->m_Coordinates.m_fLongitude < pRenderMetrics->m_rWorldBoundingBox.m_A.m_fLongitude
+		   || pLocationSelection->m_Coordinates.m_fLatitude > pRenderMetrics->m_rWorldBoundingBox.m_B.m_fLatitude
+		   || pLocationSelection->m_Coordinates.m_fLongitude > pRenderMetrics->m_rWorldBoundingBox.m_B.m_fLongitude)
+		{
+			pLocationSelection->m_bVisible = FALSE;
+			continue;   // not visible
+		}
+		pLocationSelection->m_bVisible = TRUE;
+
+		gdouble fX = SCALE_X(pRenderMetrics, pLocationSelection->m_Coordinates.m_fLongitude);
+		fX = floor(fX) + 0.5;
+		gdouble fY = SCALE_Y(pRenderMetrics, pLocationSelection->m_Coordinates.m_fLatitude);
+		fY = floor(fY) + 0.5;
+
+		gdouble fBoxHeight = 105.0;
+		gdouble fBoxWidth = 200.0;
+
+		gdouble fOffsetX = -(fBoxWidth/3);
+		gdouble fOffsetY = -40.0;
+
+		// determine top left corner of box
+		gdouble fBoxX = fX + fOffsetX;
+		gdouble fBoxY = (fY - fBoxHeight) + fOffsetY;
+
+		pLocationSelection->m_InfoBoxRect.m_A.m_nX = (gint)fBoxX;
+		pLocationSelection->m_InfoBoxRect.m_A.m_nY = (gint)fBoxY;
+		pLocationSelection->m_InfoBoxRect.m_B.m_nX = (gint)(fBoxX + fBoxWidth);
+		pLocationSelection->m_InfoBoxRect.m_B.m_nY = (gint)(fBoxY + fBoxHeight);
+
+		map_draw_cairo_locationselection_outline(pCairo, fX, fY, &(pLocationSelection->m_InfoBoxRect), &(pLocationSelection->m_InfoBoxCloseRect));
+
+		gchar* pszFontFamily = ROAD_FONT;
+		gint fTitleFontSize = 15.0;
+		gint fBodyFontSize = 13.0;
+
+		// BEGIN text
+		// shrink box a little
+		fBoxX += 8.0;
+		fBoxY += 10.0;
+		fBoxWidth -= 16.0;
+		fBoxHeight -= 20.0;
+
+#define LINE_RELIEF (4.0)	// space between lines in pixels
+#define BODY_RELIEF (6.0)
+#define LINK_HORIZONTAL_RELIEF (10)
+
+		const gchar* pszString;
+		cairo_text_extents_t extents;
+		cairo_select_font(pCairo, pszFontFamily, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
+		cairo_scale_font(pCairo, fTitleFontSize);
+
+		// draw name
+		pszString = map_location_selection_get_attribute(pMap, pLocationSelection, "name");
+		if(pszString == NULL) pszString = "";	// unnamed POI?!
+		cairo_text_extents(pCairo, pszString, &extents);
+		gdouble fLabelWidth = extents.width;
+		gdouble fFontHeight = extents.height;
+		cairo_set_rgb_color(pCairo, 0.1,0.1,0.1);	// normal text color
+
+		gdouble fTextOffsetX = fBoxX;
+		gdouble fTextOffsetY = fBoxY + fFontHeight;
+
+		cairo_move_to(pCairo, fTextOffsetX, fTextOffsetY);
+		cairo_show_text(pCairo, pszString);
+
+		// draw address, line 1
+		pszString = map_location_selection_get_attribute(pMap, pLocationSelection, "address");
+		if(pszString != NULL) {
+			gchar** aLines = g_strsplit(pszString,"\n", 0);	// "\n" = delimeters, 0 = no max #
+
+			gchar* pszLine;
+			if(aLines[0]) {
+				pszLine = aLines[0];
+				cairo_select_font(pCairo, pszFontFamily, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+				cairo_scale_font(pCairo, fBodyFontSize);
+				cairo_text_extents(pCairo, pszLine, &extents);
+				fLabelWidth = extents.width;
+				fFontHeight = extents.height;
+
+				fTextOffsetY += (BODY_RELIEF + fFontHeight);
+				cairo_move_to(pCairo, fTextOffsetX, fTextOffsetY);
+				cairo_show_text(pCairo, pszLine);
+
+				if(aLines[1]) {
+					g_print("line 2\n");
+					// draw address, line 2
+					pszLine = aLines[1];
+
+					fTextOffsetY += (LINE_RELIEF + fFontHeight);
+					cairo_move_to(pCairo, fTextOffsetX, fTextOffsetY);
+					cairo_show_text(pCairo, pszLine);
+				}
+			}
+			g_strfreev(aLines);	// free the array of strings	
+		}
+
+		// draw phone number
+		pszString = "(617) 576-1369";
+		cairo_text_extents(pCairo, pszString, &extents);
+		fLabelWidth = extents.width;
+		fFontHeight = extents.height;
+
+		fTextOffsetY += (LINE_RELIEF + fFontHeight);
+		cairo_move_to(pCairo, fTextOffsetX, fTextOffsetY);
+		cairo_set_rgb_color(pCairo, 0.1,0.1,0.1);	// normal text color
+		cairo_show_text(pCairo, pszString);
+		// draw underline
+//                 cairo_text_extents(pCairo, pszString, &extents);
+//                 fLabelWidth = extents.width;
+//                 fFontHeight = extents.height;
+//                 cairo_underline_text(pCairo, fLabelWidth);
+		
+
+		// draw website link
+		fTextOffsetX = fBoxX;
+		fTextOffsetY = fBoxY + fBoxHeight;
+		pszString = "1369coffeehouse.com";
+		cairo_text_extents(pCairo, pszString, &extents);
+		fLabelWidth = extents.width;
+		fFontHeight = extents.height;
+		cairo_move_to(pCairo, fTextOffsetX, fTextOffsetY);
+		cairo_set_rgb_color(pCairo, 0.1,0.1,0.4);	// remote link color
+		cairo_show_text(pCairo, pszString);
+		cairo_underline_text(pCairo, fLabelWidth);
+
+		screenrect_t* pRect = &(pLocationSelection->m_aURLs[pLocationSelection->m_nNumURLs].m_Rect);
+		pRect->m_A.m_nX = fTextOffsetX;
+		pRect->m_A.m_nY = fTextOffsetY - fFontHeight;
+		pRect->m_B.m_nX = fTextOffsetX + fLabelWidth;
+		pRect->m_B.m_nY = fTextOffsetY;
+		pLocationSelection->m_aURLs[pLocationSelection->m_nNumURLs].m_pszURL = g_strdup(pszString);
+		pLocationSelection->m_nNumURLs++;
+
+		// draw 'edit' link
+		pszString = "edit";
+		cairo_set_rgb_color(pCairo, 0.1,0.4,0.1);	// local link color
+		cairo_select_font(pCairo, pszFontFamily, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
+		cairo_scale_font(pCairo, fBodyFontSize);
+		cairo_text_extents(pCairo, pszString, &extents);
+		fLabelWidth = extents.width;
+		fFontHeight = extents.height;
+
+		// bottom edge, right justified
+		fTextOffsetX = (fBoxX + fBoxWidth) - fLabelWidth;
+		fTextOffsetY = (fBoxY + fBoxHeight);
+
+		cairo_move_to(pCairo, fTextOffsetX, fTextOffsetY);
+		cairo_show_text(pCairo, pszString);
+		cairo_underline_text(pCairo, fLabelWidth);
+		
+		pRect = &(pLocationSelection->m_EditRect);
+		pRect->m_A.m_nX = fTextOffsetX;
+		pRect->m_A.m_nY = fTextOffsetY - fFontHeight;
+		pRect->m_B.m_nX = fTextOffsetX + fLabelWidth;
+		pRect->m_B.m_nY = fTextOffsetY;
+	}
+	cairo_restore(pCairo);
+}
+
 #ifdef ROADSTER_DEAD_CODE
 /*
 //

Index: map_draw_gdk.c
===================================================================
RCS file: /cvs/cairo/roadster/src/map_draw_gdk.c,v
retrieving revision 1.13
retrieving revision 1.14
diff -u -d -r1.13 -r1.14
--- map_draw_gdk.c	31 Mar 2005 08:29:53 -0000	1.13
+++ map_draw_gdk.c	23 Apr 2005 18:13:39 -0000	1.14
@@ -102,9 +102,9 @@
 static void map_draw_gdk_background(map_t* pMap, GdkPixmap* pPixmap)
 {
 	GdkColor clr;
-	clr.red = 255/255.0 * 65535;
-	clr.green = 227/255.0 * 65535;
-	clr.blue = 181/255.0 * 65535;
+	clr.red = 239/255.0 * 65535;
+	clr.green = 239/255.0 * 65535;
+	clr.blue = 230/255.0 * 65535;
 	gdk_gc_set_rgb_fg_color(pMap->m_pTargetWidget->style->fg_gc[GTK_WIDGET_STATE(pMap->m_pTargetWidget)], &clr);
 	
 	gdk_draw_rectangle(pPixmap, pMap->m_pTargetWidget->style->fg_gc[GTK_WIDGET_STATE(pMap->m_pTargetWidget)],
@@ -366,9 +366,9 @@
 		GdkGC* pGC = pMap->m_pTargetWidget->style->fg_gc[GTK_WIDGET_STATE(pMap->m_pTargetWidget)];
 
 		GdkColor clr1;
-		clr1.red = 20/255.0 * 65535;
-		clr1.green = 135/255.0 * 65535;
-		clr1.blue = 20/255.0 * 65535;
+		clr1.red = 255/255.0 * 65535;
+		clr1.green = 80/255.0 * 65535;
+		clr1.blue = 80/255.0 * 65535;
 		GdkColor clr2;
 		clr2.red = 255/255.0 * 65535;
 		clr2.green = 255/255.0 * 65535;

Index: search_location.c
===================================================================
RCS file: /cvs/cairo/roadster/src/search_location.c,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -d -r1.10 -r1.11
--- search_location.c	1 Apr 2005 09:53:49 -0000	1.10
+++ search_location.c	23 Apr 2005 18:13:39 -0000	1.11
@@ -45,13 +45,12 @@
 //} locationsearch_t;
 
 void search_location_on_cleaned_sentence(const gchar* pszCleanedSentence);
-//void search_location_on_words(gchar** aWords, gint nWordCount);
+void search_location_on_words(gchar** aWords, gint nWordCount);
 void search_location_filter_result(gint nLocationID, const gchar* pszName, const gchar* pszAddress, const mappoint_t* pCoordinates);
 
 void search_location_execute(const gchar* pszSentence)
 {
-	g_print("search_location_execute\n");
-
+//	g_print("search_location_execute\n");
 	TIMER_BEGIN(search, "BEGIN LocationSearch");
 
 	// copy sentence and clean it
@@ -63,61 +62,121 @@
 	TIMER_END(search, "END LocationSearch");
 }
 
-	// Create an array of the words
-/*         gchar** aaWords = g_strsplit(pszCleanedSentence," ", 0);        // " " = delimeters, 0 = no max # */
-/*         gint nWords = g_strv_length(aaWords);                                                             */
-/*         search_location_on_words(aaWords, nWords);                                                        */
-/*         g_strfreev(aaWords);    // free entire array of strings                                           */
 
 void search_location_on_cleaned_sentence(const gchar* pszCleanedSentence)
 {
+	// Create an array of the words
+        gchar** aaWords = g_strsplit(pszCleanedSentence, " ", 0);        // " " = delimeters, 0 = no max #
+        gint nWords = g_strv_length(aaWords);
+        search_location_on_words(aaWords, nWords);
+        g_strfreev(aaWords);    // free entire array of strings
+}
 
-	// Get POI #, Name, Address, and Coordinates. Match a POI if any of the words given are in ANY attributes of the POI.
-	// NOTE: We're using this behavior (http://dev.mysql.com/doc/mysql/en/fulltext-boolean.html):
-	// 'apple banana'
-	//   Find rows that contain at least one of the two words.
+/*
+We require ALL words that the user typed show up in 
+
+Our SQL statement for finding matching POI works like this:
+
+(1) Get a list of LocationIDs that contain each word (use DISTINCT so even if the word shows up in multiple Values, we only put the LocationID in the list once).
+(2) Combine the lists together and count how many lists each LocationID is in (MatchedWords).  This will be 0 -> nWordCount
+(3) Filter on MatchedWords.  If we use nWordCount, all words are required.  We could also use nWordCount-1, or always use 1 and ORDER BY MatchedWords DESC.
+(4) Finally join the Location table to get needed Location details.
+
+Our finished SQL statement will look something like this:
+
+SELECT Location.ID, ASBINARY(Location.Coordinates) 
+FROM 
+(
+  SELECT LocationID, COUNT(*) as MatchedWords FROM ( 
+    SELECT DISTINCT LocationID FROM LocationAttributeValue WHERE MATCH(Value) AGAINST ('coffee' IN BOOLEAN MODE) 
+    UNION ALL 
+    SELECT DISTINCT LocationID FROM LocationAttributeValue WHERE MATCH(Value) AGAINST ('food' IN BOOLEAN MODE) 
+    UNION ALL 
+    SELECT DISTINCT LocationID FROM LocationAttributeValue WHERE MATCH(Value) AGAINST ('wifi' IN BOOLEAN MODE) 
+  ) AS PossibleMatches
+  GROUP BY LocationID			# 
+  HAVING MatchedWords = 3		# use the number of words here to require a full match
+) AS Matches, Location
+WHERE
+  Matches.LocationID = Location.ID	# join our list of Matches to Location to get needed data
+*/
+
+void search_location_on_words(gchar** aWords, gint nWordCount)
+{
+	if(nWordCount == 0) return;
+
+	gchar* pszInnerSelects = g_strdup("");
+	gint i;
+	for(i=0 ; i<nWordCount ; i++) {
+		// add a join
+		gchar* pszNewSelect = g_strdup_printf(
+			" %s SELECT DISTINCT LocationID"	// the DISTINCT means a word showing up in 10 places for a POI will only count as 1 towards MatchedWords below
+			" FROM LocationAttributeValue WHERE MATCH(Value) AGAINST ('%s' IN BOOLEAN MODE)",
+				(i>0) ? "UNION ALL" : "",	// add "UNION ALL" between SELECTs
+				aWords[i]);
+
+		// out with the old, in with the new.  yes, it's slow, but not in user-time. :)
+		gchar* pszTmp = g_strconcat(pszInnerSelects, pszNewSelect, NULL);
+		g_free(pszInnerSelects);
+		g_free(pszNewSelect);
+		pszInnerSelects = pszTmp;
+	}
 
 	gchar* pszSQL = g_strdup_printf(
 		"SELECT Location.ID, LocationAttributeValue_Name.Value AS Name, LocationAttributeValue_Address.Value AS Address, AsBinary(Location.Coordinates)"
-		" FROM LocationAttributeValue"
-		" LEFT JOIN LocationAttributeName ON (LocationAttributeValue.AttributeNameID=LocationAttributeName.ID)"
-		" LEFT JOIN Location ON (LocationAttributeValue.LocationID=Location.ID)"
+		" FROM ("
+		  "SELECT LocationID, COUNT(*) AS MatchedWords FROM ("
+		    "%s"
+		  ") AS PossibleMatches" // unused alias
+		 " GROUP BY LocationID"
+		 " HAVING MatchedWords = %d"
+		 ") AS Matches, Location"
+		
+		// Get the two values we need: Name and Address
 		" LEFT JOIN LocationAttributeValue AS LocationAttributeValue_Name ON (Location.ID=LocationAttributeValue_Name.LocationID AND LocationAttributeValue_Name.AttributeNameID=%d)"
 		" LEFT JOIN LocationAttributeValue AS LocationAttributeValue_Address ON (Location.ID=LocationAttributeValue_Address.LocationID AND LocationAttributeValue_Address.AttributeNameID=%d)"
-		" WHERE"
-		" MATCH(LocationAttributeValue.Value) AGAINST ('%s' IN BOOLEAN MODE)"
-		" GROUP BY Location.ID;",
+
+		" WHERE Matches.LocationID = Location.ID",
+			pszInnerSelects,
+			nWordCount,
 			LOCATION_ATTRIBUTE_ID_NAME,
-			LOCATION_ATTRIBUTE_ID_ADDRESS,
-			pszCleanedSentence
+			LOCATION_ATTRIBUTE_ID_ADDRESS
 		);
 
+	//g_print("SQL: %s\n", pszSQL);
+
+	g_free(pszInnerSelects);
+
 	db_resultset_t* pResultSet;
-	if(db_query(pszSQL, &pResultSet)) {
-		db_row_t aRow;
+	gboolean bQueryResult = db_query(pszSQL, &pResultSet);
+	g_free(pszSQL);
 
-		// get result rows!
-		gint nCount = 0;		
-		while((aRow = mysql_fetch_row(pResultSet))) {
-			nCount++;
-			if(nCount <= SEARCH_RESULT_COUNT_LIMIT) {
-				gint nLocationID = atoi(aRow[0]);
-				gchar* pszLocationName = aRow[1];
-				gchar* pszLocationAddress = aRow[2];
-				// Parse coordinates
-				mappoint_t pt;
-				db_parse_wkb_point(aRow[3], &pt);
+	if(bQueryResult) {
+		if(pResultSet != NULL) {
+			db_row_t aRow;
 
-				search_location_filter_result(nLocationID, pszLocationName, pszLocationAddress, &pt);
-			}
-		}
-		db_free_result(pResultSet);
+			g_assert(pResultSet);
 
-		if(nCount == 0) {
-			g_print("no location search results\n");
+			// get result rows!
+			gint nCount = 0;		
+			while((aRow = db_fetch_row(pResultSet))) {
+				nCount++;
+				if(nCount <= SEARCH_RESULT_COUNT_LIMIT) {
+					gint nLocationID = atoi(aRow[0]);
+					gchar* pszLocationName = aRow[1];
+					gchar* pszLocationAddress = aRow[2];
+					// Parse coordinates
+					mappoint_t pt;
+					db_parse_wkb_point(aRow[3], &pt);
+
+					search_location_filter_result(nLocationID, pszLocationName, pszLocationAddress, &pt);
+				}
+			}
+			db_free_result(pResultSet);
+			//g_print("%d location results\n", nCount);
 		}
 		else {
-			g_print("%d location results\n", nCount);
+			g_print("no location search results\n");
 		}
 	}
 	else {
@@ -125,10 +184,6 @@
 	}
 }
 
-/* void search_location_on_words(gchar** aWords, gint nWordCount) */
-/* {                                                                                         */
-/* }                                                                                         */
-
 #define LOCATION_RESULT_SUGGESTED_ZOOMLEVEL	(7)
 
 void search_location_filter_result(gint nLocationID, const gchar* pszName, const gchar* pszAddress, const mappoint_t* pCoordinates)

Index: search_road.c
===================================================================
RCS file: /cvs/cairo/roadster/src/search_road.c,v
retrieving revision 1.18
retrieving revision 1.19
diff -u -d -r1.18 -r1.19
--- search_road.c	31 Mar 2005 08:29:53 -0000	1.18
+++ search_road.c	23 Apr 2005 18:13:39 -0000	1.19
@@ -50,20 +50,6 @@
 #define MAX_QUERY 				(4000)
 #define ROAD_MIN_LENGTH_FOR_WILDCARD_SEARCH	(3)
 
-// if glib < 2.6
-#if(!GLIB_CHECK_VERSION(2,6,0))
-gint g_strv_length(const gchar** a)
-{
-	gint nCount=0;
-	const gchar** pp = a;
-	while(*pp != NULL) {
-		nCount++;
-		pp++;
-	}
-	return nCount;
-}
-#endif
-
 gchar* g_strjoinv_limit(const gchar* separator, gchar** a, gint iFirst, gint iLast)
 {
 	g_assert(iFirst <= iLast);
@@ -371,7 +357,7 @@
 
 		// get result rows!
 		gint nCount = 0;		
-		while((aRow = mysql_fetch_row(pResultSet))) {
+		while((aRow = db_fetch_row(pResultSet))) {
 			// [0] Road.ID
 			// [1] RoadName.Name
 			// [2] RoadName.SuffixID

Index: searchwindow.c
===================================================================
RCS file: /cvs/cairo/roadster/src/searchwindow.c,v
retrieving revision 1.17
retrieving revision 1.18
diff -u -d -r1.17 -r1.18
--- searchwindow.c	31 Mar 2005 08:29:53 -0000	1.17
+++ searchwindow.c	23 Apr 2005 18:13:39 -0000	1.18
@@ -96,38 +96,45 @@
 	g_SearchWindow.m_nNumResults = 0;
 }
 
+void searchwindow_add_message(gchar* pszMessage)
+{
+	GtkTreeIter iter;
+	gtk_list_store_append(g_SearchWindow.m_pResultsListStore, &iter);
+	gtk_list_store_set(g_SearchWindow.m_pResultsListStore, &iter, RESULTLIST_COLUMN_NAME, pszMessage, RESULTLIST_CLICKABLE, FALSE, -1);
+}
+
 // begin a search
 void searchwindow_on_findbutton_clicked(GtkWidget *pWidget, gpointer* p)
 {
 	// make list unsorted (sorting once at the end is much faster than for each insert)
 //	gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(g_SearchWindow.m_pResultsListStore), MAGIC_GTK_NO_SORT_COLUMN, GTK_SORT_ASCENDING);
+	searchwindow_clear_results();
 
 	const gchar* pszSearch = gtk_entry_get_text(g_SearchWindow.m_pSearchEntry);
 
-	void* pBusy = mainwindow_set_busy();
-	searchwindow_clear_results();
-	search_road_execute(pszSearch);
-	search_location_execute(pszSearch);
-	mainwindow_set_not_busy(&pBusy);
-
-	if(g_SearchWindow.m_nNumResults == 0) {
-		// insert a "no results" message
-		gchar* pszBuffer = g_strdup_printf("<span size='small'><i>No results.</i></span>", pszSearch);
-		GtkTreeIter iter;
-		gtk_list_store_append(g_SearchWindow.m_pResultsListStore, &iter);
-		gtk_list_store_set(g_SearchWindow.m_pResultsListStore, &iter, 
-				   RESULTLIST_COLUMN_NAME, pszBuffer, 
-				   RESULTLIST_CLICKABLE, FALSE,
-				   -1);
+	if(pszSearch[0] == '\0') {
+		gchar* pszBuffer = g_strdup_printf("<span size='small'><i>Type search words above.</i></span>");
+		searchwindow_add_message(pszBuffer);
 		g_free(pszBuffer);
 	}
+	else {
+		void* pBusy = mainwindow_set_busy();
+		search_road_execute(pszSearch);
+		search_location_execute(pszSearch);
+		mainwindow_set_not_busy(&pBusy);
 
+		if(g_SearchWindow.m_nNumResults == 0) {
+			// insert a "no results" message
+			gchar* pszBuffer = g_strdup_printf("<span size='small'><i>No results.</i></span>", pszSearch);
+			searchwindow_add_message(pszBuffer);
+			g_free(pszBuffer);
+		}
+		// Sort the list by distance from viewer!
+		gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(g_SearchWindow.m_pResultsListStore), RESULTLIST_DISTANCE, GTK_SORT_ASCENDING);
+	}
 	// ensure the search results are visible
 	mainwindow_sidebar_set_tab(SIDEBAR_TAB_SEARCH_RESULTS);
 	mainwindow_set_sidebox_visible(TRUE);
-
-	// Sort the list by distance from viewer!
-	gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(g_SearchWindow.m_pResultsListStore), RESULTLIST_DISTANCE, GTK_SORT_ASCENDING);
 }
 
 // add a result row to the list
@@ -188,6 +195,6 @@
 
 static void searchwindow_on_resultslist_selection_changed(GtkTreeSelection *treeselection, gpointer user_data)
 {
-	GTK_PROCESS_MAINLOOP;	// make sure GUI updates before we start our cpu-intensive move to the result
+//	GTK_PROCESS_MAINLOOP;	// make sure GUI updates before we start our cpu-intensive move to the result
 	searchwindow_go_to_selected_result();
 }

Index: util.c
===================================================================
RCS file: /cvs/cairo/roadster/src/util.c,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -d -r1.5 -r1.6
--- util.c	28 Mar 2005 18:49:50 -0000	1.5
+++ util.c	23 Apr 2005 18:13:39 -0000	1.6
@@ -29,6 +29,51 @@
 	gtk_widget_hide(gtk_widget_get_toplevel(pWidget));
 }
 
+gboolean util_running_gnome(void)
+{
+	return((g_getenv("GNOME_DESKTOP_SESSION_ID") != NULL) && (g_find_program_in_path("gnome-open") != NULL));
+}
+
+void util_open_uri(const char* pszDangerousURI)
+{
+	// NOTE: URI is potentially from a 3rd party, so consider it DANGEROUS.
+	// This is why we use g_spawn_async, which lets us mark the URI as a parameter instead
+	// of just part of the command line (it could be "; rm / -rf" or something...)
+
+	char *pszCommand = NULL;
+	GError *pError = NULL;
+
+	// if they are running gnome, use the gnome web browser
+	if (util_running_gnome() == FALSE) return;
+
+
+	gchar **argv = g_malloc0(sizeof(gchar*) * 3);
+	argv[0] = "gnome-open";
+	argv[1] = pszDangerousURI;
+	argv[2] = NULL;
+
+	if(!g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &pError)) {
+		// XXX: error message?
+		g_error_free(pError);
+	}
+	g_free(argv);
+	g_free(pszCommand);
+}
+
+// if glib < 2.6
+#if(!GLIB_CHECK_VERSION(2,6,0))
+gint g_strv_length(const gchar** a)
+{
+	gint nCount=0;
+	const gchar** pp = a;
+	while(*pp != NULL) {
+		nCount++;
+		pp++;
+	}
+	return nCount;
+}
+#endif
+
 #if ROADSTER_DEAD_CODE
 #include <stdlib.h>
 #include "layers.h"		// for color_t -- move it elsewhere!

Index: util.h
===================================================================
RCS file: /cvs/cairo/roadster/src/util.h,v
retrieving revision 1.7
retrieving revision 1.8
diff -u -d -r1.7 -r1.8
--- util.h	28 Mar 2005 18:49:50 -0000	1.7
+++ util.h	23 Apr 2005 18:13:39 -0000	1.8
@@ -57,5 +57,11 @@
    declarations, and make the functions static.
 */
 void util_close_parent_window(GtkWidget* pWidget, gpointer data);
+void util_open_uri(const char* pszURI);
+
+// if glib < 2.6
+#if(!GLIB_CHECK_VERSION(2,6,0))
+gint g_strv_length(const gchar** a);
+#endif
 
 #endif




More information about the cairo-commit mailing list