[cairo-commit] roadster/src Makefile.am, 1.18, 1.19 db.c, 1.26, 1.27 glyph.c, 1.5, 1.6 glyph.h, 1.2, 1.3 gui.c, 1.11, 1.12 gui.h, 1.3, 1.4 importwindow.c, 1.9, 1.10 importwindow.h, 1.2, 1.3 locationeditwindow.c, 1.1, 1.2 locationeditwindow.h, 1.1, 1.2 locationselection.c, NONE, 1.1 locationselection.h, NONE, 1.1 locationset.c, 1.12, 1.13 locationset.h, 1.6, 1.7 main.c, 1.23, 1.24 main.h, 1.3, 1.4 mainwindow.c, 1.40, 1.41 mainwindow.h, 1.10, 1.11 map.c, 1.44, 1.45 map.h, 1.21, 1.22 map_tile.c, NONE, 1.1 map_tile.h, NONE, 1.1 point.c, 1.5, 1.6 search.c, 1.6, 1.7 search.h, 1.2, 1.3 search_city.c, NONE, 1.1 search_city.h, NONE, 1.1 search_location.c, 1.11, 1.12 search_road.c, 1.22, 1.23 searchwindow.c, 1.20, 1.21 searchwindow.h, 1.4, 1.5 util.c, 1.10, 1.11 util.h, 1.10, 1.11 welcomewindow.c, 1.6, 1.7

Ian McIntosh commit at pdx.freedesktop.org
Fri Sep 23 22:25:27 PDT 2005


Committed by: ian

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

Modified Files:
	Makefile.am db.c glyph.c glyph.h gui.c gui.h importwindow.c 
	importwindow.h locationeditwindow.c locationeditwindow.h 
	locationset.c locationset.h main.c main.h mainwindow.c 
	mainwindow.h map.c map.h point.c search.c search.h 
	search_location.c search_road.c searchwindow.c searchwindow.h 
	util.c util.h welcomewindow.c 
Added Files:
	locationselection.c locationselection.h map_tile.c map_tile.h 
	search_city.c search_city.h 
Log Message:
2005-09-24  Ian McIntosh  <ian_mcintosh at linuxadvocate.org>

	* data/roadster.glade: New "Go" menu with web maps.  General GUI cleanup.
	* src/glyph.c: Complete rework.  Removes direct usage of librsvg.  Now uses only gdkpixbuf loaders (including the librsvg one!)
	* src/locationeditwindow.c: 
	* src/mainwindow.c: Use GLADE_LINK_WIDGET.  Add "web maps" menu options.  Add glyph reload debug menu option.  Possibly fix 'failure to stop scrolling' bug.
	* src/map.c: Beginning of support for "enhancing" map objects at run-time (adding points, smoothing sharp edges).
	* src/map.h: Fix a few 'double' which should be 'gdouble'
	* src/search_city.c: New search type matches city names.
	* src/search_road.c: Removed common code.  No longer do wildcard "search*" matching.  No longer display house #s in results.
	* src/search_location.c: Removed common code.
	* src/search.c: Moved common search code here.
	* src/searchwindow.c: Add icon.  Lots of small "look & feel" updates.
	* src/util.c: Add new word-in-sentence matching for treeview searches.  Add some treeview utility functions.  Add util_str_replace_many() for replacing eg "{LAT}" with a number for web map URLs.
	* src/welcomewindow.c: Update currently unused TIGER Data Web URL.
	* src/db.c: Playing with soundex() matching for roads and city names.
	* configure.ac: Bumped version.  Hey why not.



Index: Makefile.am
===================================================================
RCS file: /cvs/cairo/roadster/src/Makefile.am,v
retrieving revision 1.18
retrieving revision 1.19
diff -u -d -r1.18 -r1.19
--- Makefile.am	14 Sep 2005 20:06:53 -0000	1.18
+++ Makefile.am	24 Sep 2005 05:25:24 -0000	1.19
@@ -24,6 +24,7 @@
 	mainwindow.c\
 	gotowindow.c\
 	map.c\
+	map_tile.c\
 	map_style.c\
 	map_draw_cairo.c\
 	map_draw_gdk.c\
@@ -39,6 +40,7 @@
 	searchwindow.c\
 	search_road.c\
 	search_location.c\
+	search_city.c\
 	search.c\
 	scenemanager.c\
 	point.c\

Index: db.c
===================================================================
RCS file: /cvs/cairo/roadster/src/db.c,v
retrieving revision 1.26
retrieving revision 1.27
diff -u -d -r1.26 -r1.27
--- db.c	14 Sep 2005 20:06:53 -0000	1.26
+++ db.c	24 Sep 2005 05:25:24 -0000	1.27
@@ -442,7 +442,6 @@
 	return TRUE;
 }
 
-
 //
 // insert / select state
 //
@@ -554,6 +553,11 @@
 	db_query("CREATE DATABASE IF NOT EXISTS roadster;", NULL);
 	db_query("USE roadster;", NULL);
 
+	// For development: run these once to update your tables
+//	db_query("ALTER TABLE RoadName ADD COLUMN NameSoundex CHAR(4) NOT NULL;", NULL);
+//	db_query("UPDATE RoadName SET NameSoundex=SOUNDEX(Name);", NULL);
+//	db_query("ALTER TABLE RoadName ADD INDEX (NameSoundex);", NULL);
+
 	// Road
 	db_query("CREATE TABLE IF NOT EXISTS Road("
 		" ID INT4 UNSIGNED NOT NULL AUTO_INCREMENT,"	// XXX: can we get away with INT3 ?
@@ -583,9 +587,12 @@
 	db_query("CREATE TABLE IF NOT EXISTS RoadName("
 		" ID INT3 UNSIGNED NOT NULL auto_increment,"	// NOTE: 3 bytes
 		" Name VARCHAR(30) NOT NULL,"
+		" NameSoundex CHAR(10) NOT NULL,"	// see soundex() function
 		" SuffixID INT1 UNSIGNED NOT NULL,"
-		" PRIMARY KEY (ID),"			// for joining RoadName to Road 
-		" INDEX (Name(7)));", NULL);	// for searching by RoadName. 7 is enough for decent uniqueness(?)
+		" PRIMARY KEY (ID),"				// for joining RoadName to Road 
+		" INDEX (Name(7)));"					// for searching by RoadName. 7 is enough for decent uniqueness(?)
+//		" INDEX (NameSoundex));"			// Nicer way to search by RoadName(?)
+		,NULL);
 
 	// City
 	db_query("CREATE TABLE IF NOT EXISTS City("
@@ -651,5 +658,6 @@
 	db_query("CREATE TABLE IF NOT EXISTS LocationSet("
 		" ID INT3 UNSIGNED NOT NULL AUTO_INCREMENT,"		// NOTE: 3 bytes.	(would 2 be enough?)
 		" Name VARCHAR(60) NOT NULL,"
+		" IconName VARCHAR(60) NOT NULL,"
 		" PRIMARY KEY (ID));", NULL);
 }

Index: glyph.c
===================================================================
RCS file: /cvs/cairo/roadster/src/glyph.c,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -d -r1.5 -r1.6
--- glyph.c	28 Mar 2005 18:49:50 -0000	1.5
+++ glyph.c	24 Sep 2005 05:25:24 -0000	1.6
@@ -24,36 +24,157 @@
 #include <gtk/gtk.h>
 #include <cairo.h>
 
-#ifdef HAVE_LIBSVG
-#include <svg-cairo.h>
-#endif
-
-typedef enum { GLYPHTYPE_NONE=0, GLYPHTYPE_BITMAP, GLYPHTYPE_VECTOR } EGlyphType;
-
-typedef struct glyph {
-	EGlyphType m_eType;
-	
-#ifdef HAVE_LIBSVG
-	svg_cairo_t *m_pCairoSVG;
-#endif
-
-	gint m_nWidth;
-	gint m_nHeight;
-} glyph_t;
+#include "glyph.h"
 
 struct {
-	GPtrArray* m_pGlyphArray;
+	GPtrArray* m_pGlyphArray;	// to store all glyphs we hand out
 } g_Glyph;
 
 void glyph_init(void)
 {
 	g_Glyph.m_pGlyphArray = g_ptr_array_new();
-	g_ptr_array_add(g_Glyph.m_pGlyphArray, NULL); // index 0 is taken! (it's the "no glyph" value)
 }
 
-gint glyph_load(const gchar* pszPath)
+#define MAX_GLYPH_FILE_NAME_LEN		(30)
+
+gboolean glyph_is_safe_file_name(const gchar* pszName)
 {
-#ifdef HAVE_LIBSVG
+	// XXX: this is better done with a regex...
+/*
+	// NOTE: we want to be VERY strict here, as this can come from attackers
+	g_assert(sizeof(gchar) == 1);
+
+	// may contain only: a-z A-z 0-9 - _ and optionally a . followed by 3 or 4 characters
+	gchar* p = pszName;
+	gint nLen = 0;
+	while(*p != '\0' && *p != '.') {
+		if((g_ascii_is_alpha(*p) || g_ascii_is_digit(*p) || *p == '-' || *p == '_') == FALSE) {
+
+		}
+		p++;
+		nLen++;
+	}
+
+	if(*p == '.') {
+		nLen = 0;
+		while(*p != '\0') {
+			if((g_ascii_is_alpha(*p) || g_ascii_is_digit(*p) || *p == '-' || *p == '_') == FALSE) {
+				return FALSE;
+			}
+			p++;
+			nLen++;
+		}
+		if(nLen > 4) {
+			// extension can't be longer than 4
+			return FALSE;
+		}
+	}
+
+     return (nLen < MAX_GLYPH_FILE_NAME_LEN);
+*/
+	return TRUE;
+}
+
+void _glyph_load_at_size_into_struct(glyph_t* pNewGlyph, const gchar* pszName, gint nMaxWidth, gint nMaxHeight)
+{
+	// pszName is an icon name without extension or path
+	static const gchar* apszExtensions[] = {"svg", "png", "jpg", "gif"};	// in the order to try
+
+	gchar* pszPath = "data/";	// XXX: just for development
+
+	GdkPixbuf* pNewPixbuf = NULL;
+
+	// Try base name with each supported extension
+	gint iExtension;
+	for(iExtension = 0 ; iExtension < G_N_ELEMENTS(apszExtensions) ; iExtension++) {
+		gchar* pszFilePath = g_strdup_printf("%s%s.%s", pszPath, pszName, apszExtensions[iExtension]);
+		pNewPixbuf = gdk_pixbuf_new_from_file_at_scale(pszFilePath, nMaxWidth, nMaxHeight, TRUE, NULL);	// NOTE: scales image to fit in this size
+		g_free(pszFilePath);
+
+		if(pNewPixbuf != NULL) break;	// got it!
+	}
+
+	// Create a fake pixbuf if not found
+	if(pNewPixbuf == NULL) {
+		pNewPixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, nMaxWidth, nMaxHeight);
+		gdk_pixbuf_fill(pNewPixbuf, 0xFF000080);
+	}
+	pNewGlyph->m_pPixbuf = pNewPixbuf;
+	pNewGlyph->m_nWidth = gdk_pixbuf_get_width(pNewPixbuf);
+	pNewGlyph->m_nHeight = gdk_pixbuf_get_height(pNewPixbuf);
+}
+
+glyph_t* glyph_load_at_size(const gchar* pszName, gint nMaxWidth, gint nMaxHeight)
+{
+	// NOTE: We always return something!
+	glyph_t* pNewGlyph = g_new0(glyph_t, 1);
+
+	pNewGlyph->m_pszName = g_strdup(pszName);
+	pNewGlyph->m_nMaxWidth = nMaxWidth;
+	pNewGlyph->m_nMaxHeight = nMaxHeight;
+
+	// call internal function to fill the struct
+	_glyph_load_at_size_into_struct(pNewGlyph, pszName, nMaxWidth, nMaxHeight);
+
+	g_ptr_array_add(g_Glyph.m_pGlyphArray, pNewGlyph);
+
+	return pNewGlyph;
+}
+
+GdkPixbuf* glyph_get_pixbuf(const glyph_t* pGlyph)
+{
+	g_assert(pGlyph != NULL);
+	g_assert(pGlyph->m_pPixbuf != NULL);
+
+	return pGlyph->m_pPixbuf;
+}
+
+void glyph_draw_centered(cairo_t* pCairo, gint nGlyphHandle, gdouble fX, gdouble fY)
+{
+//     GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_size("/home/yella/Desktop/interstate-sign.svg", 32, 32, NULL);
+//     gdk_draw_pixbuf(GTK_WIDGET(g_MainWindow.m_pDrawingArea)->window,
+//                     GTK_WIDGET(g_MainWindow.m_pDrawingArea)->style->fg_gc[GTK_WIDGET_STATE(g_MainWindow.m_pDrawingArea)],
+//                     pixbuf,
+//                     0,0,                        // src
+//                     100,100,                    // x/y to draw to
+//                     32,32,                      // width/height
+//                     GDK_RGB_DITHER_NONE,0,0);   // no dithering
+}
+
+void glyph_free(glyph_t* pGlyph)
+{
+	g_assert(pGlyph);
+	gdk_pixbuf_unref(pGlyph->m_pPixbuf);
+	g_free(pGlyph->m_pszName);
+	g_free(pGlyph);
+}
+
+void glyph_deinit(void)
+{
+	gint i;
+	for(i=0 ; i<g_Glyph.m_pGlyphArray->len ; i++) {
+		glyph_free(g_ptr_array_index(g_Glyph.m_pGlyphArray, i));
+	}
+	g_ptr_array_free(g_Glyph.m_pGlyphArray, TRUE);
+}
+
+void glyph_reload_all(void)
+{
+	gint i;
+	for(i=0 ; i<g_Glyph.m_pGlyphArray->len ; i++) {
+		glyph_t* pGlyph = g_ptr_array_index(g_Glyph.m_pGlyphArray, i);
+
+		gdk_pixbuf_unref(pGlyph->m_pPixbuf);	pGlyph->m_pPixbuf = NULL;
+		// the rest of the fields remain.
+
+		_glyph_load_at_size_into_struct(pGlyph, pGlyph->m_pszName, pGlyph->m_nMaxWidth, pGlyph->m_nMaxHeight);
+	}
+}
+
+#ifdef ROADSTER_DEAD_CODE
+/* libsvg is in gdk_pixbuf so we probably don't need this stuff
+
+=== libsvg loading ===
 	svg_cairo_t* pCairoSVG = NULL;
 
 	if(SVG_CAIRO_STATUS_SUCCESS != svg_cairo_create(&pCairoSVG)) {
@@ -75,27 +196,9 @@
 	g_ptr_array_add(g_Glyph.m_pGlyphArray, pNewGlyph);
 
 	return nGlyphHandle;
-#else
-	return 0;
-#endif
-}
-
-static gboolean glyph_lookup(gint nGlyphHandle, glyph_t** ppReturnGlyph)
-{
-	g_assert(ppReturnGlyph != NULL);
-	g_assert(*ppReturnGlyph == NULL);	// must be pointer to NULL pointer
 
-	glyph_t* pGlyph = g_ptr_array_index(g_Glyph.m_pGlyphArray, nGlyphHandle);
-	if(pGlyph != NULL) {
-		*ppReturnGlyph = pGlyph;
-		return TRUE;
-	}
-	return FALSE;
-}
+=== libsvg drawing ===
 
-void glyph_draw_centered(cairo_t* pCairo, gint nGlyphHandle, gdouble fX, gdouble fY)
-{
-#ifdef HAVE_LIBSVG
 	if(nGlyphHandle == 0) return;
 
 	glyph_t* pGlyph = NULL;
@@ -108,14 +211,5 @@
 		cairo_translate(pCairo, (fX - (pGlyph->m_nWidth/2)), (fY - (pGlyph->m_nHeight/2)));
 		svg_cairo_render(pGlyph->m_pCairoSVG, pCairo);
 	cairo_restore(pCairo);
-#else
-	return;
-#endif
-}
-
-void glyph_deinit(void)
-{
-#ifdef HAVE_LIBSVG
-	// svg_cairo_destroy(svgc) all glyphs
+*/
 #endif
-}

Index: glyph.h
===================================================================
RCS file: /cvs/cairo/roadster/src/glyph.h,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- glyph.h	4 Mar 2005 02:27:29 -0000	1.2
+++ glyph.h	24 Sep 2005 05:25:25 -0000	1.3
@@ -24,11 +24,22 @@
 #ifndef _GLYPH_H_
 #define _GLYPH_H_
 
+#include <gdk/gdk.h>
 #include <cairo.h>
 
+typedef struct {
+	GdkPixbuf* m_pPixbuf;
+	gint m_nWidth;
+	gint m_nHeight;
+	gint m_nMaxWidth;
+	gint m_nMaxHeight;
+	gchar* m_pszName;
+} glyph_t;
+
 void glyph_init(void);
-gint glyph_load(const gchar* pszPath);
-void glyph_draw_centered(cairo_t* pCairo, gint nGlyphHandle, gdouble fX, gdouble fY);
+glyph_t* glyph_load_at_size(const gchar* pszName, gint nMaxWidth, gint nMaxHeight);
+GdkPixbuf* glyph_get_pixbuf(const glyph_t* pGlyph);
+//void glyph_draw_centered(cairo_t* pCairo, gint nGlyphHandle, gdouble fX, gdouble fY);
 void glyph_deinit(void);
 
 #endif

Index: gui.c
===================================================================
RCS file: /cvs/cairo/roadster/src/gui.c,v
retrieving revision 1.11
retrieving revision 1.12
diff -u -d -r1.11 -r1.12
--- gui.c	14 Sep 2005 20:06:53 -0000	1.11
+++ gui.c	24 Sep 2005 05:25:25 -0000	1.12
@@ -79,12 +79,7 @@
 
 void gui_run()
 {
-//	if(db_is_empty()) {
-		welcomewindow_show();
-//	}
-//	else {
-//		mainwindow_show();
-//	}
+	welcomewindow_show();
 	gtk_main();
 }
 
@@ -93,7 +88,8 @@
 	// Hide first, then quit (makes the UI feel snappier)
 	mainwindow_hide();
 	gotowindow_hide();
+	importwindow_hide();
+	locationeditwindow_hide();
 
 	gtk_main_quit();
 }
-

Index: gui.h
===================================================================
RCS file: /cvs/cairo/roadster/src/gui.h,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -d -r1.3 -r1.4
--- gui.h	14 Sep 2005 20:06:54 -0000	1.3
+++ gui.h	24 Sep 2005 05:25:25 -0000	1.4
@@ -36,6 +36,6 @@
 //
 void gui_init(void);
 void gui_run(void);
-extern void gui_exit(void);
+void gui_exit(void);
 GladeXML* gui_load_xml(gchar* pszFileName, gchar* pszXMLTreeRoot);
 #endif

Index: importwindow.c
===================================================================
RCS file: /cvs/cairo/roadster/src/importwindow.c,v
retrieving revision 1.9
retrieving revision 1.10
diff -u -d -r1.9 -r1.10
--- importwindow.c	14 Sep 2005 20:06:54 -0000	1.9
+++ importwindow.c	24 Sep 2005 05:25:25 -0000	1.10
@@ -56,6 +56,11 @@
 	gtk_window_present(g_ImportWindow.m_pWindow);
 }
 
+void importwindow_hide(void)
+{
+	gtk_widget_hide(GTK_WIDGET(g_ImportWindow.m_pWindow));
+}
+
 void importwindow_log_append(const gchar* pszFormat, ...)
 {
 	gchar azNewText[5000];

Index: importwindow.h
===================================================================
RCS file: /cvs/cairo/roadster/src/importwindow.h,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- importwindow.h	3 Mar 2005 07:32:46 -0000	1.2
+++ importwindow.h	24 Sep 2005 05:25:25 -0000	1.3
@@ -30,6 +30,7 @@
 
 void importwindow_init(GladeXML* pGladeXML);
 void importwindow_show(void);
+void importwindow_hide(void);
 
 void importwindow_begin(GSList* pSelectedFileList);
 void importwindow_log_append(const gchar* pszText, ...);

Index: locationeditwindow.c
===================================================================
RCS file: /cvs/cairo/roadster/src/locationeditwindow.c,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- locationeditwindow.c	14 Sep 2005 20:06:54 -0000	1.1
+++ locationeditwindow.c	24 Sep 2005 05:25:25 -0000	1.2
@@ -70,6 +70,7 @@
 void locationeditwindow_show_for_new(gint nDefaultLocationSetID);
 void locationeditwindow_show_for_edit(gint nLocationID);
 
+gboolean locationeditwindow_set_expander_label(gpointer _unused);
 
 // When an 'entry' is created to do in-place editing in a tree, we want to attach this "insert-text" hander to it.
 static void callback_install_insert_text_callback_on_entry(GtkCellRenderer *pCellRenderer, GtkCellEditable *pEditable, const gchar *pszTreePath, gpointer pUserData)
@@ -93,6 +94,10 @@
 		gtk_list_store_set(g_LocationEditWindow.m_pAttributeListStore, &iter, nColumn, pszNewValue, -1);
 	}
 	gtk_tree_path_free(pPath);
+
+	// data is now modified
+	g_LocationEditWindow.m_bModified = TRUE;
+	locationeditwindow_set_title();
 }
 
 void locationeditwindow_init(GladeXML* pGladeXML)
@@ -106,18 +111,23 @@
 	GLADE_LINK_WIDGET(pGladeXML, g_LocationEditWindow.m_pLocationAddressTextView, GTK_TEXT_VIEW, "locationaddresstextview");
 
 	GLADE_LINK_WIDGET(pGladeXML, g_LocationEditWindow.m_pAttributeExpander, GTK_EXPANDER, "attributeexpander");
+	gtk_expander_set_use_markup(g_LocationEditWindow.m_pAttributeExpander, TRUE);
+
 	GLADE_LINK_WIDGET(pGladeXML, g_LocationEditWindow.m_pAttributeAddButton, GTK_BUTTON, "attributeaddbutton");
 	GLADE_LINK_WIDGET(pGladeXML, g_LocationEditWindow.m_pAttributeRemoveButton, GTK_BUTTON, "attributeremovebutton");
 
 	locationeditwindow_configure_attribute_list();
 
-	// Update window title with changed to 'name'
+	// 
 	g_signal_connect(G_OBJECT(g_LocationEditWindow.m_pLocationNameEntry), "changed", G_CALLBACK(locationeditwindow_something_changed_callback), NULL);
 	g_signal_connect(G_OBJECT(g_LocationEditWindow.m_pLocationSetComboBox), "changed", G_CALLBACK(locationeditwindow_something_changed_callback), NULL);
+	g_signal_connect(G_OBJECT(gtk_text_view_get_buffer(g_LocationEditWindow.m_pLocationAddressTextView)), "changed", G_CALLBACK(locationeditwindow_something_changed_callback), NULL);
 
 //	g_signal_connect(G_OBJECT(g_LocationEditWindow.m_pLocationAddressTextView), "changed", G_CALLBACK(locationeditwindow_something_changed_callback), NULL);
+//	locationeditwindow_show_for_new(1);		// XXX: debug
 
-	locationeditwindow_show_for_new(1);		// XXX: debug
+	// don't delete window on X, just hide it
+	g_signal_connect(G_OBJECT(g_LocationEditWindow.m_pWindow), "delete_event", G_CALLBACK(gtk_widget_hide), NULL);
 }
 
 static void locationeditwindow_configure_attribute_list()
@@ -191,19 +201,29 @@
 			   -1);
 }
 
+void locationeditwindow_hide(void)
+{
+	gtk_widget_hide(GTK_WIDGET(g_LocationEditWindow.m_pWindow));
+}
+
 void locationeditwindow_show_for_new(gint nDefaultLocationSetID)
 {
 	// Set controls to default values
 	gtk_entry_set_text(g_LocationEditWindow.m_pLocationNameEntry, "");
 	gtk_text_buffer_set_text(gtk_text_view_get_buffer(g_LocationEditWindow.m_pLocationAddressTextView), "", -1);
 	gtk_combo_box_set_active(g_LocationEditWindow.m_pLocationSetComboBox, nDefaultLocationSetID);
-	gtk_expander_set_expanded(g_LocationEditWindow.m_pAttributeExpander, FALSE);
+	gtk_list_store_clear(g_LocationEditWindow.m_pAttributeListStore);
+
+	// Don't change user's previous setting here
+	//gtk_expander_set_expanded(g_LocationEditWindow.m_pAttributeExpander, FALSE);
 
 	g_LocationEditWindow.m_bModified = FALSE;
 	locationeditwindow_set_title();
+	locationeditwindow_set_expander_label(NULL);
 
 	gtk_widget_grab_focus(GTK_WIDGET(g_LocationEditWindow.m_pLocationNameEntry));
 	gtk_widget_show(GTK_WIDGET(g_LocationEditWindow.m_pWindow));
+	gtk_window_present(g_LocationEditWindow.m_pWindow);
 }
 
 void locationeditwindow_show_for_edit(gint nLocationID)
@@ -211,6 +231,26 @@
 	// void location_load_attributes(gint nLocationID, GPtrArray* pAttributeArray);
 	g_LocationEditWindow.m_bModified = FALSE;
 	locationeditwindow_set_title();
+
+	locationeditwindow_set_expander_label(NULL);
+}
+
+gboolean locationeditwindow_set_expander_label(gpointer _unused)
+{
+	gint nNumLocationAttributes = 0; //g_LocationEditWindow.m_pAttributeNameListStore;	// XXX: use a real count
+
+	// NOTE: Do not expand/close the expander-- keep the user's old setting
+	gchar* pszExpanderLabel;
+//	if(gtk_expander_get_expanded(g_LocationEditWindow.m_pAttributeExpander)) {
+		pszExpanderLabel = g_strdup_printf("Custom Values <i>(%d)</i>", nNumLocationAttributes);
+//	}
+//	else {
+//		pszExpanderLabel = g_strdup_printf("Show Custom Values <i>(%d)</i>", nNumLocationAttributes);
+//	}
+	gtk_expander_set_label(g_LocationEditWindow.m_pAttributeExpander, pszExpanderLabel);
+	g_free(pszExpanderLabel);
+
+	return FALSE;	// "don't call us again" -- we're unfortunately an idle-time callback
 }
 
 static void locationeditwindow_set_title()
@@ -229,14 +269,21 @@
 	g_free(pszNewName);
 }
 
-
 // Widget Callbacks
+
 static void locationeditwindow_something_changed_callback(GtkEditable *_unused, gpointer __unused)
 {
+	// NOTE: This callback is shared by several widgets
 	g_LocationEditWindow.m_bModified = TRUE;
 	locationeditwindow_set_title();
 }
 
+void locationeditwindow_on_attributeexpander_activate(GtkExpander *_unused, gpointer __unused)
+{
+	// HACK: doesn't work when called directly (gets wrong value from expander?) so call it later
+	g_idle_add(locationeditwindow_set_expander_label, NULL);
+}
+
 // static void locationeditwindow_nameentry_insert_text(GtkEditable *pEditable, gchar *pszNewText, gint nNewTextLen, gint *pPosition, gpointer pUserData)
 // {
 //     g_print("nNewTextLen=%d\n", nNewTextLen);

Index: locationeditwindow.h
===================================================================
RCS file: /cvs/cairo/roadster/src/locationeditwindow.h,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- locationeditwindow.h	14 Sep 2005 20:06:54 -0000	1.1
+++ locationeditwindow.h	24 Sep 2005 05:25:25 -0000	1.2
@@ -30,6 +30,7 @@
 G_BEGIN_DECLS
 
 void locationeditwindow_init(GladeXML* pGladeXML);
+void locationeditwindow_hide(void);
 
 G_END_DECLS
 

--- NEW FILE: locationselection.c ---


--- NEW FILE: locationselection.h ---


Index: locationset.c
===================================================================
RCS file: /cvs/cairo/roadster/src/locationset.c,v
retrieving revision 1.12
retrieving revision 1.13
diff -u -d -r1.12 -r1.13
--- locationset.c	14 Sep 2005 20:06:54 -0000	1.12
+++ locationset.c	24 Sep 2005 05:25:25 -0000	1.13
@@ -87,7 +87,7 @@
 // Load all locationsets into memory
 void locationset_load_locationsets(void)
 {
-	gchar* pszSQL = g_strdup_printf("SELECT LocationSet.ID, LocationSet.Name, COUNT(Location.ID) FROM LocationSet LEFT JOIN Location ON (LocationSet.ID=Location.LocationSetID) GROUP BY LocationSet.ID;");
+	gchar* pszSQL = g_strdup_printf("SELECT LocationSet.ID, LocationSet.Name, LocationSet.IconName, COUNT(Location.ID) FROM LocationSet LEFT JOIN Location ON (LocationSet.ID=Location.LocationSetID) GROUP BY LocationSet.ID;");
 
 	db_resultset_t* pResultSet = NULL;
 	if(db_query(pszSQL, &pResultSet)) {
@@ -99,7 +99,8 @@
 
 			pNewLocationSet->m_nID = atoi(aRow[0]);
 			pNewLocationSet->m_pszName = g_strdup(aRow[1]);
-			pNewLocationSet->m_nLocationCount = atoi(aRow[2]);
+			pNewLocationSet->m_pszIconName = g_strdup(aRow[2]);
+			pNewLocationSet->m_nLocationCount = atoi(aRow[3]);
 
 			// Add the new set to both data structures
 			g_ptr_array_add(g_LocationSet.m_pLocationSetArray, pNewLocationSet);

Index: locationset.h
===================================================================
RCS file: /cvs/cairo/roadster/src/locationset.h,v
retrieving revision 1.6
retrieving revision 1.7
diff -u -d -r1.6 -r1.7
--- locationset.h	31 Mar 2005 08:29:53 -0000	1.6
+++ locationset.h	24 Sep 2005 05:25:25 -0000	1.7
@@ -28,19 +28,20 @@
 
 #include "map.h"
 
-typedef struct locationsetstyle {
-	// icon?
-	// color?
-	int __unused;
-} locationsetstyle_t;
+// typedef struct locationsetstyle {
+//     // icon?
+//     // color?
+//     int __unused;
+// } locationsetstyle_t;
 
 // a set of locations (eg. "Coffee Shops")
 typedef struct locationset {
 	gint m_nID;
 	gchar* m_pszName;
+	gchar* m_pszIconName;
 	gint m_nLocationCount;
-	gboolean m_bVisible;
-	locationsetstyle_t m_Style;
+	gboolean m_bVisible;		// user has chosen to view these
+//	locationsetstyle_t m_Style;
 } locationset_t;
 
 void locationset_init(void);

Index: main.c
===================================================================
RCS file: /cvs/cairo/roadster/src/main.c,v
retrieving revision 1.23
retrieving revision 1.24
diff -u -d -r1.23 -r1.24
--- main.c	14 Sep 2005 20:06:54 -0000	1.23
+++ main.c	24 Sep 2005 05:25:25 -0000	1.24
@@ -36,10 +36,15 @@
 #include "road.h"
 #include "locationset.h"
 #include "location.h"
+#include "search.h"
 
 static gboolean main_init(void);
 static void main_deinit(void);
 
+
+#include <libgnomevfs/gnome-vfs.h>
+#include <gnome.h>
+
 int main (int argc, char *argv[])
 {
 	#ifdef ENABLE_NLS
@@ -53,7 +58,7 @@
 	if(!main_init())
 		return 1;
 
-	// Insert some POI for testing...
+// Insert some POI for testing...
 /*
 	gint nNewLocationSetID = 0;
 	locationset_insert("Coffee Shops", &nNewLocationSetID);
@@ -112,6 +117,9 @@
 	g_print("initializing map\n");
 	map_init();
 
+	g_print("initializing search\n");
+	search_init();
+
 	g_print("initializing scenemanager\n");
 	scenemanager_init();
 

Index: main.h
===================================================================
RCS file: /cvs/cairo/roadster/src/main.h,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -d -r1.3 -r1.4
--- main.h	14 Sep 2005 20:06:54 -0000	1.3
+++ main.h	24 Sep 2005 05:25:25 -0000	1.4
@@ -29,6 +29,9 @@
 #define USE_GNOME_VFS		// comment this out to get a faster single-threaded compile (can't import, though)
 #define USE_GFREELIST
 
+#define MOUSE_BUTTON_LEFT				(1)		// These are X/GDK/GTK numbers, now with names.
+#define MOUSE_BUTTON_RIGHT				(3)
+
 //#define ENABLE_TIMING
 
 typedef struct color {

Index: mainwindow.c
===================================================================
RCS file: /cvs/cairo/roadster/src/mainwindow.c,v
retrieving revision 1.40
retrieving revision 1.41
diff -u -d -r1.40 -r1.41
--- mainwindow.c	14 Sep 2005 20:06:54 -0000	1.40
+++ mainwindow.c	24 Sep 2005 05:25:25 -0000	1.41
@@ -58,31 +58,36 @@
 #define MAP_STYLE_FILENAME 		("layers.xml")
 
 // how long after stopping various movements should we redraw in high-quality mode
-#define DRAW_PRETTY_SCROLL_TIMEOUT_MS	(110)	// NOTE: should be longer than the SCROLL_TIMEOUT_MS below!!
-#define DRAW_PRETTY_ZOOM_TIMEOUT_MS	(180)
-#define DRAW_PRETTY_DRAG_TIMEOUT_MS	(250)
-#define DRAW_PRETTY_RESIZE_TIMEOUT_MS	(180)
+#define DRAW_PRETTY_SCROLL_TIMEOUT_MS	(200)	// NOTE: should be longer than the SCROLL_TIMEOUT_MS below!!
+#define DRAW_PRETTY_ZOOM_TIMEOUT_MS		(180)
+#define DRAW_PRETTY_DRAG_TIMEOUT_MS		(250)
+#define DRAW_PRETTY_RESIZE_TIMEOUT_MS	(500)	// nice and long so keep window resizing smooth
[...1037 lines suppressed...]
+	apszReplacements[4].m_pszNew = g_strdup_printf("%d", map_get_zoomlevel_scale(g_MainWindow.m_pMap));
+
+	// 
+	gchar* pszURL = util_str_replace_many(pszURLPattern, apszReplacements, G_N_ELEMENTS(apszReplacements));
+	util_open_uri(pszURL);
+	g_free(pszURL);
+
+	// cleanup
+	gint i;
+	for(i=0 ; i<G_N_ELEMENTS(apszReplacements) ; i++) {
+		g_free(apszReplacements[i].m_pszNew);
+		apszReplacements[i].m_pszNew = NULL;
+	}
+}
+
+
+
 #ifdef ROADSTER_DEAD_CODE
 /*
 static gboolean on_searchbox_key_press_event(GtkWidget *widget, GdkEventKey *event, gpointer user_data)

Index: mainwindow.h
===================================================================
RCS file: /cvs/cairo/roadster/src/mainwindow.h,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -d -r1.10 -r1.11
--- mainwindow.h	20 Mar 2005 03:37:09 -0000	1.10
+++ mainwindow.h	24 Sep 2005 05:25:25 -0000	1.11
@@ -80,9 +80,9 @@
 
 void mainwindow_scroll_direction(EDirection eScrollDirection, gint nPixels);
 
-#define SIDEBAR_TAB_SEARCH_RESULTS	0
-#define SIDEBAR_TAB_GPS			1
-#define SIDEBAR_TAB_LOCATIONSETS	2
+#define SIDEBAR_TAB_LOCATIONSETS	0
+#define SIDEBAR_TAB_TRACKS			1
+#define SIDEBAR_TAB_SEARCH_RESULTS	2
 
 void mainwindow_sidebar_set_tab(gint nTab);
 

Index: map.c
===================================================================
RCS file: /cvs/cairo/roadster/src/map.c,v
retrieving revision 1.44
retrieving revision 1.45
diff -u -d -r1.44 -r1.45
--- map.c	14 Sep 2005 20:06:54 -0000	1.44
+++ map.c	24 Sep 2005 05:25:25 -0000	1.45
@@ -42,6 +42,7 @@
 #define ENABLE_RIVER_TO_LAKE_LOADTIME_HACK	// change circular rivers to lakes when loading from disk
 //#define ENABLE_SCENEMANAGER_DEBUG_TEST
 //#define ENABLE_LABELS_WHILE_DRAGGING
+//#define ENABLE_RIVER_SMOOTHING	// hacky. rivers are animated when scrolling. :) only good for proof-of-concept screenshots
 
 #ifdef THREADED_RENDERING
 #define RENDERING_THREAD_YIELD          g_thread_yield()
@@ -83,9 +84,13 @@
 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);
 
+static void map_store_location(map_t* pMap, location_t* pLocation, gint nLocationSetID);
+
 static void map_data_clear(map_t* pMap);
 void map_get_render_metrics(map_t* pMap, rendermetrics_t* pMetrics);
 
+gdouble map_get_straight_line_distance_in_degrees(mappoint_t* p1, mappoint_t* p2);
+
 // Each zoomlevel has a scale and an optional name (name isn't used for anything)
 zoomlevel_t g_sZoomLevels[NUM_ZOOM_LEVELS+1] = {
 	{1,0,0,0,0,"undefined"},	// no zoom level 0
@@ -488,6 +493,62 @@
 	}
 }
 
+static gboolean map_enhance_linestring(GPtrArray* pSourceArray, GPtrArray* pDestArray, gboolean (*callback_alloc_point)(mappoint_t**), gdouble fMaxDistanceBetweenPoints, gdouble fMaxRandomDistance)
+{
+	g_assert(pSourceArray->len >= 2);
+//g_print("pSourceArray->len = %d\n", pSourceArray->len);
+
+	// add first point
+	g_ptr_array_add(pDestArray, g_ptr_array_index(pSourceArray, 0));
+
+	gint i = 0;
+	for(i=0 ; i<(pSourceArray->len-1) ; i++) {
+		mappoint_t* pPoint1 = g_ptr_array_index(pSourceArray, i);
+		mappoint_t* pPoint2 = g_ptr_array_index(pSourceArray, i+1);
+
+		gdouble fRise = (pPoint2->m_fLatitude - pPoint1->m_fLatitude);
+		gdouble fRun = (pPoint2->m_fLongitude - pPoint1->m_fLongitude);
+
+		gdouble fLineLength = sqrt((fRun*fRun) + (fRise*fRise));
+//		g_print("fLineLength = %f\n", fLineLength);
+		gint nNumMiddlePoints = (gint)(fLineLength / fMaxDistanceBetweenPoints);
+//		g_print("(fLineLength / fMaxDistanceBetweenPoints) = nNumNewPoints; %f / %f = %d\n", fLineLength, fMaxDistanceBetweenPoints, nNumNewPoints);
+		if(nNumMiddlePoints == 0) continue;	// nothing to add
+
+//		g_print("fDistanceBetweenPoints = %f\n", fDistanceBetweenPoints);
+
+		gdouble fNormalizedX = fRun / fLineLength;
+		gdouble fNormalizedY = fRise / fLineLength;
+//		g_print("fNormalizedX = %f\n", fNormalizedX);
+//		g_print("fNormalizedY = %f\n", fNormalizedY);
+
+		gdouble fPerpendicularNormalizedX = fRise / fLineLength;
+		gdouble fPerpendicularNormalizedY = -(fRun / fLineLength);
+
+		// add points along the line
+		gdouble fDistanceBetweenPoints = fLineLength / (gdouble)(nNumMiddlePoints + 1);
+
+		gint j;
+		for(j=0 ; j<nNumMiddlePoints ; j++) {
+			mappoint_t* pNewPoint = NULL;
+			callback_alloc_point(&pNewPoint);
+			gdouble fDistanceFromPoint1 = (j+1) * fDistanceBetweenPoints;
+
+			pNewPoint->m_fLongitude = pPoint1->m_fLongitude + (fDistanceFromPoint1 * fNormalizedX);
+			pNewPoint->m_fLatitude = pPoint1->m_fLatitude + (fDistanceFromPoint1 * fNormalizedY);
+
+			gdouble fRandomMovementLength = fMaxRandomDistance * g_random_double_range(-1.0, 1.0);
+			pNewPoint->m_fLongitude += (fPerpendicularNormalizedX * fRandomMovementLength);	// move each component
+			pNewPoint->m_fLatitude += (fPerpendicularNormalizedY * fRandomMovementLength);
+
+			g_ptr_array_add(pDestArray, pNewPoint);
+		}
+	}
+	// add last point
+	g_ptr_array_add(pDestArray, g_ptr_array_index(pSourceArray, pSourceArray->len-1));
+//g_print("pDestArray->len = %d\n", pDestArray->len);
+}
+
 static gboolean map_data_load_geometry(map_t* pMap, maprect_t* pRect)
 {
 	g_return_val_if_fail(pMap != NULL, FALSE);
@@ -508,22 +569,19 @@
 		" FROM Road "
 		" LEFT JOIN RoadName ON (Road.RoadNameID=RoadName.ID)"
 		" WHERE"
-		//" TypeID IN (%s) AND"
-                //" MBRIntersects(@wkb, Coordinates)"
 		" MBRIntersects(GeomFromText('Polygon((%s %s,%s %s,%s %s,%s %s,%s %s))'), Coordinates)"
 		,
+		// upper left
 		g_ascii_dtostr(azCoord1, 20, pRect->m_A.m_fLatitude), g_ascii_dtostr(azCoord2, 20, pRect->m_A.m_fLongitude), 
+		// upper right
 		g_ascii_dtostr(azCoord3, 20, pRect->m_A.m_fLatitude), g_ascii_dtostr(azCoord4, 20, pRect->m_B.m_fLongitude), 
+		// bottom right
 		g_ascii_dtostr(azCoord5, 20, pRect->m_B.m_fLatitude), g_ascii_dtostr(azCoord6, 20, pRect->m_B.m_fLongitude), 
+		// bottom left
 		g_ascii_dtostr(azCoord7, 20, pRect->m_B.m_fLatitude), g_ascii_dtostr(azCoord8, 20, pRect->m_A.m_fLongitude), 
+		// upper left again
 		azCoord1, azCoord2);
 
-//         pRect->m_A.m_fLatitude, pRect->m_A.m_fLongitude,    // upper left
-//         pRect->m_A.m_fLatitude, pRect->m_B.m_fLongitude,    // upper right
-//         pRect->m_B.m_fLatitude, pRect->m_B.m_fLongitude,    // bottom right
-//         pRect->m_B.m_fLatitude, pRect->m_A.m_fLongitude,    // bottom left
-//         pRect->m_A.m_fLatitude, pRect->m_A.m_fLongitude     // upper left again
-//         );
 	//g_print("sql: %s\n", pszSQL);
 
 	db_query(pszSQL, &pResultSet);
@@ -533,7 +591,6 @@
 
 	guint32 uRowCount = 0;
 	if(pResultSet) {
-		TIMER_SHOW(mytimer, "after clear layers");
 		while((aRow = db_fetch_row(pResultSet))) {
 			uRowCount++;
 
@@ -556,17 +613,10 @@
 			}
 
 			if(nTypeID == 12) g_warning("(got a 12)");
-			// Extract points
+
 			road_t* pNewRoad = NULL;
 			road_alloc(&pNewRoad);
 
-			//pointstring_t* pNewPointString = NULL;
-			//if(!pointstring_alloc(&pNewPointString)) {
-			//	g_warning("out of memory loading pointstrings\n");
-			//	continue;
-			//}
-			db_parse_wkb_linestring(aRow[2], pNewRoad->m_pPointsArray, point_alloc);
-
 			// Build name by adding suffix, if one is present
 			gchar azFullName[100] = "";
 
@@ -584,7 +634,24 @@
 
 			pNewRoad->m_pszName = g_strdup(azFullName);
 
-#ifdef ENABLE_RIVER_TO_LAKE_LOADTIME_HACK
+#ifdef	ENABLE_RIVER_SMOOTHING
+			if(nTypeID == MAP_OBJECT_TYPE_RIVER) {
+				// XXX: Hacky. Add randomness to river lines
+				GPtrArray* pTempArray = g_ptr_array_new();
+				db_parse_wkb_linestring(aRow[2], pTempArray, point_alloc);
+				map_enhance_linestring(pTempArray, pNewRoad->m_pPointsArray, point_alloc,
+									   0.00025,      // distance between points
+									   0.000060); 	// randomness
+				g_ptr_array_free(pTempArray, TRUE);
+			}
+			else {
+				db_parse_wkb_linestring(aRow[2], pNewRoad->m_pPointsArray, point_alloc);
+             }
+#else
+			db_parse_wkb_linestring(aRow[2], pNewRoad->m_pPointsArray, point_alloc);
+#endif
+
+#ifdef ENABLE_RIVER_TO_LAKE_LOADTIME_HACK	// XXX: combine this and above hack and you get lakes with squiggly edges. whoops. :)
 			if(nTypeID == MAP_OBJECT_TYPE_RIVER) {
 				mappoint_t* pPointA = g_ptr_array_index(pNewRoad->m_pPointsArray, 0);
 				mappoint_t* pPointB = g_ptr_array_index(pNewRoad->m_pPointsArray, pNewRoad->m_pPointsArray->len-1);
@@ -708,6 +775,8 @@
 
 double map_get_distance_in_meters(mappoint_t* pA, mappoint_t* pB)
 {
+	// XXX: this function is broken.
+
 	// This functions calculates the length of the arc of the "greatcircle" that goes through
 	// the two points A and B and whos center is the center of the sphere, O.
 
@@ -730,6 +799,14 @@
 	return fAOB_Rad * RADIUS_OF_WORLD_IN_METERS;
 }
 
+gdouble map_get_straight_line_distance_in_degrees(mappoint_t* p1, mappoint_t* p2)
+{
+	gdouble fDeltaX = ((p2->m_fLongitude) - (p1->m_fLongitude));
+	gdouble fDeltaY = ((p2->m_fLatitude) - (p1->m_fLatitude));
+
+	return sqrt((fDeltaX*fDeltaX) + (fDeltaY*fDeltaY));
+}
+
 gdouble map_get_distance_in_pixels(map_t* pMap, mappoint_t* p1, mappoint_t* p2)
 {
 	rendermetrics_t metrics;
@@ -744,9 +821,7 @@
 	gdouble fDeltaX = fX2 - fX1;
 	gdouble fDeltaY = fY2 - fY1;
 
-	gdouble d = sqrt((fDeltaX*fDeltaX) + (fDeltaY*fDeltaY));
-//	g_print("%f\n", d);
-	return d;
+	return sqrt((fDeltaX*fDeltaX) + (fDeltaY*fDeltaY));
 }
 
 gboolean map_points_equal(mappoint_t* p1, mappoint_t* p2)

Index: map.h
===================================================================
RCS file: /cvs/cairo/roadster/src/map.h,v
retrieving revision 1.21
retrieving revision 1.22
diff -u -d -r1.21 -r1.22
--- map.h	14 Sep 2005 20:06:54 -0000	1.21
+++ map.h	24 Sep 2005 05:25:25 -0000	1.22
@@ -311,9 +311,11 @@
 // Conversions
 void map_windowpoint_to_mappoint(map_t* pMap, screenpoint_t* pScreenPoint, mappoint_t* pMapPoint);
 gdouble map_distance_in_units_to_degrees(map_t* pMap, gdouble fDistance, gint nDistanceUnit);
-double map_get_distance_in_meters(mappoint_t* pA, mappoint_t* pB);
-double map_pixels_to_degrees(map_t* pMap, gint16 nPixels, guint16 uZoomLevel);
-double map_degrees_to_pixels(map_t* pMap, gdouble fDegrees, guint16 uZoomLevel);
+gdouble map_get_distance_in_meters(mappoint_t* pA, mappoint_t* pB);
+gdouble map_get_straight_line_distance_in_degrees(mappoint_t* p1, mappoint_t* p2);
+
+gdouble map_pixels_to_degrees(map_t* pMap, gint16 nPixels, guint16 uZoomLevel);
+gdouble map_degrees_to_pixels(map_t* pMap, gdouble fDegrees, guint16 uZoomLevel);
 gboolean map_points_equal(mappoint_t* p1, mappoint_t* p2);
 gdouble map_get_distance_in_pixels(map_t* pMap, mappoint_t* p1, mappoint_t* p2);
 

--- NEW FILE: map_tile.c ---


void map_tile_load()
{
	
}

--- NEW FILE: map_tile.h ---


Index: point.c
===================================================================
RCS file: /cvs/cairo/roadster/src/point.c,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -d -r1.5 -r1.6
--- point.c	28 Aug 2005 22:23:14 -0000	1.5
+++ point.c	24 Sep 2005 05:25:25 -0000	1.6
@@ -30,7 +30,7 @@
 #include "gfreelist.h"
 GFreeList* g_pPointFreeList;
 #else
-GMemChunk* g_pPointChunkAllocator;		// chunk allocators to be shared by all geometrysets
+GMemChunk* g_pPointChunkAllocator;
 #endif
 
 void point_init(void)

Index: search.c
===================================================================
RCS file: /cvs/cairo/roadster/src/search.c,v
retrieving revision 1.6
retrieving revision 1.7
diff -u -d -r1.6 -r1.7
--- search.c	14 Sep 2005 20:06:54 -0000	1.6
+++ search.c	24 Sep 2005 05:25:25 -0000	1.7
@@ -24,7 +24,41 @@
 #include <gtk/gtk.h>
 
 #include "main.h"
+#include "util.h"
 #include "search.h"
+#include "search_road.h"
+#include "search_location.h"
+#include "search_city.h"
+#include "glyph.h"
+
+#define SEARCH_RESULT_TYPE_GLYPH_WIDTH		32
+#define SEARCH_RESULT_TYPE_GLYPH_HEIGHT		32
+
+struct {
+	glyph_t* m_apSearchResultTypeGlyphs[ NUM_SEARCH_RESULT_TYPES ];	// don't store pixbufs, store some custom glyph type
+} g_Search = {0};
+
+// functions
+
+void search_init()
+{
+	g_assert(NUM_SEARCH_RESULT_TYPES == 5);		// don't forget to add more here...
+
+	g_Search.m_apSearchResultTypeGlyphs[SEARCH_RESULT_TYPE_ROAD] = glyph_load_at_size("search-result-type-road", SEARCH_RESULT_TYPE_GLYPH_WIDTH, SEARCH_RESULT_TYPE_GLYPH_HEIGHT);
+	g_Search.m_apSearchResultTypeGlyphs[SEARCH_RESULT_TYPE_CITY] = glyph_load_at_size("search-result-type-city", SEARCH_RESULT_TYPE_GLYPH_WIDTH, SEARCH_RESULT_TYPE_GLYPH_HEIGHT);
+	g_Search.m_apSearchResultTypeGlyphs[SEARCH_RESULT_TYPE_STATE] = glyph_load_at_size("search-result-type-state", SEARCH_RESULT_TYPE_GLYPH_WIDTH, SEARCH_RESULT_TYPE_GLYPH_HEIGHT);
+	g_Search.m_apSearchResultTypeGlyphs[SEARCH_RESULT_TYPE_COUNTRY] = glyph_load_at_size("search-result-type-country", SEARCH_RESULT_TYPE_GLYPH_WIDTH, SEARCH_RESULT_TYPE_GLYPH_HEIGHT);
+	g_Search.m_apSearchResultTypeGlyphs[SEARCH_RESULT_TYPE_LOCATION] = glyph_load_at_size("search-result-type-location", SEARCH_RESULT_TYPE_GLYPH_WIDTH, SEARCH_RESULT_TYPE_GLYPH_HEIGHT);
+}
+
+glyph_t* search_glyph_for_search_result_type(ESearchResultType eType)
+{
+	g_assert(eType >= 0);
+	g_assert(eType < NUM_SEARCH_RESULT_TYPES);
+	g_assert(g_Search.m_apSearchResultTypeGlyphs[eType] != NULL);
+
+	return g_Search.m_apSearchResultTypeGlyphs[eType];
+}
 
 // functions common to all searches
 
@@ -85,3 +119,24 @@
 	*pnReturn = nNumber;
 	return TRUE;
 }
+
+void search_all(const gchar* pszSentence)
+{
+	if(pszSentence[0] == 0) {
+		return;	// no results...
+	}
+
+	TIMER_BEGIN(search, "BEGIN SearchAll");
+	
+	gchar* pszCleanedSentence = g_strdup(pszSentence);
+	search_clean_string(pszCleanedSentence);
+
+	// Search each object type
+	search_city_execute(pszCleanedSentence);
+	search_location_execute(pszCleanedSentence);
+	search_road_execute(pszCleanedSentence);
+	
+	TIMER_END(search, "END SearchAll");
+
+	g_free(pszCleanedSentence);
+}

Index: search.h
===================================================================
RCS file: /cvs/cairo/roadster/src/search.h,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- search.h	3 Mar 2005 07:32:46 -0000	1.2
+++ search.h	24 Sep 2005 05:25:25 -0000	1.3
@@ -20,13 +20,27 @@
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
- 
+
 #ifndef _SEARCH_H
 #define _SEARCH_H
 
+#include "glyph.h"
+
+typedef enum {
+	SEARCH_RESULT_TYPE_COUNTRY = 0,		// in order of importance (for search results)
+	SEARCH_RESULT_TYPE_STATE,
+	SEARCH_RESULT_TYPE_CITY,
+	SEARCH_RESULT_TYPE_LOCATION,
+	SEARCH_RESULT_TYPE_ROAD,
+
+	NUM_SEARCH_RESULT_TYPES
+} ESearchResultType;
+
 G_BEGIN_DECLS
 
+void search_init();
 void search_clean_string(gchar* p);
+glyph_t* search_glyph_for_search_result_type(ESearchResultType eType);
 
 gboolean search_address_number_atoi(const gchar* pszText, gint* pnReturn);
 

--- NEW FILE: search_city.c ---
/***************************************************************************
 *            search_city.c
 *
 *  Copyright  2005  Ian McIntosh
 *  ian_mcintosh at linuxadvocate.org
 ****************************************************************************/

/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <gtk/gtk.h>
#include "searchwindow.h"
#include "search.h"
#include "db.h"
#include "util.h"

#define CITY_RESULT_SUGGESTED_ZOOMLEVEL		(3)

void search_city_on_words(gchar** aWords, gint nWordCount);

void search_city_execute(const gchar* pszSentence)
{
	// Create an array of the words
	gchar** aWords = g_strsplit(pszSentence," ", 0);	// " " = delimeters, 0 = no max #
	gint nWordCount = g_strv_length(aWords);

	if(nWordCount > 0) {
		search_city_on_words(aWords, nWordCount);
	}

	// cleanup
	g_strfreev(aWords);	// free the array of strings	
}

void search_city_on_words(gchar** aWords, gint nWordCount)
{
	g_assert(nWordCount > 0);

	gint nStateID = 0;

	// index of first and last words of city name
	gint iFirst = 0;
	gint iLast = nWordCount-1;

	// Start stripping off words as we identify roadsearch_t structure members
	gint nRemainingWordCount = nWordCount;

	// Match state
	gboolean bGotStateName = FALSE;
	if(nRemainingWordCount >= 3) {
		// try two-word state name
		gchar* pszStateName = util_g_strjoinv_limit(" ", aWords, iLast-1, iLast);
		//g_print("trying two-word state name '%s'\n", pszStateName);

		if(db_state_get_id(pszStateName, &nStateID)) {
			//g_print("matched state name!\n");
			iLast -= 2;	// last TWO words taken
			nRemainingWordCount -= 2;
		}
		g_free(pszStateName);
	}

	// try a one-word state name
	if(bGotStateName == FALSE && nRemainingWordCount >= 2) {
		//g_print("trying one-word state name '%s'\n", aWords[iLast]);
		if(db_state_get_id(aWords[iLast], &nStateID)) {
			//g_print("matched state name!\n");
			iLast--;	// last word taken
			nRemainingWordCount--;
		}
	}

	// If we got a StateID, create a bit of SQL to filter on state
	gchar* pszStateClause;
	if(nStateID != 0) {
		pszStateClause = g_strdup_printf(" AND StateID=%d", nStateID);
	}
	else {
		pszStateClause = g_strdup("");
	}

	// Use all remaining words as city name
	gchar* pszCityName = util_g_strjoinv_limit(" ", aWords, iFirst, iLast);

	// Make it safe for DB
	gchar* pszSafeCityName = db_make_escaped_string(pszCityName);

	gchar* pszQuery = g_strdup_printf(
						"SELECT City.Name, State.Name, State.CountryID"
						" FROM City"
						" LEFT JOIN State ON City.StateID = State.ID"
						" WHERE City.Name='%s'"
						" %s",
						pszSafeCityName,
						pszStateClause);

	g_free(pszCityName);
	db_free_escaped_string(pszSafeCityName);
	g_free(pszStateClause);

	//g_print("query: %s\n", pszQuery);

	db_resultset_t* pResultSet;
	gint nCount = 0;		
	if(db_query(pszQuery, &pResultSet)) {
		db_row_t aRow;

		// get result rows!
		while((aRow = db_fetch_row(pResultSet))) {
			// [0] City.Name
			// [1] State.Name
			// [2] State.CountryID
			nCount++;

			gint nCountryID = atoi(aRow[2]);

			mappoint_t point = {0,0};

			gchar* pszResultText = g_strdup_printf("<b>%s,\n%s</b>", aRow[0], aRow[1]);	// XXX: add country?
			searchwindow_add_result(SEARCH_RESULT_TYPE_CITY, pszResultText, &point, CITY_RESULT_SUGGESTED_ZOOMLEVEL);
			g_free(pszResultText);
		}
		db_free_result(pResultSet);
	}
	//g_print("%d city results\n", nCount);
	g_free(pszQuery);
}

--- NEW FILE: search_city.h ---
/***************************************************************************
 *            search_city.h
 *
 *  Copyright  2005  Ian McIntosh
 *  ian_mcintosh at linuxadvocate.org
 ****************************************************************************/

/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
 
#ifndef _SEARCH_CITY_H
#define _SEARCH_CITY_H

G_BEGIN_DECLS

void search_city_execute(const gchar* pszSentence);

G_END_DECLS

#endif /* _SEARCH_CITY_H */

Index: search_location.c
===================================================================
RCS file: /cvs/cairo/roadster/src/search_location.c,v
retrieving revision 1.11
retrieving revision 1.12
diff -u -d -r1.11 -r1.12
--- search_location.c	23 Apr 2005 18:13:39 -0000	1.11
+++ search_location.c	24 Sep 2005 05:25:25 -0000	1.12
@@ -44,32 +44,16 @@
 //	gint m_nWordCount;
 //} locationsearch_t;
 
-void search_location_on_cleaned_sentence(const gchar* pszCleanedSentence);
 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");
-	TIMER_BEGIN(search, "BEGIN LocationSearch");
-
-	// copy sentence and clean it
-	gchar* pszCleanedSentence = g_strdup(pszSentence);
-	search_clean_string(pszCleanedSentence);
-	search_location_on_cleaned_sentence(pszCleanedSentence);
-	g_free(pszCleanedSentence);
-
-	TIMER_END(search, "END LocationSearch");
-}
-
-
-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
+	gchar** aaWords = g_strsplit(pszSentence, " ", 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
 }
 
 /*
@@ -192,7 +176,7 @@
 					       (pszAddress == NULL || pszAddress[0] == '\0') ? "" : "\n",
 					       (pszAddress == NULL || pszAddress[0] == '\0') ? "" : pszAddress);
 
-	searchwindow_add_result(pszResultText, pCoordinates, LOCATION_RESULT_SUGGESTED_ZOOMLEVEL);
+	searchwindow_add_result(SEARCH_RESULT_TYPE_LOCATION, pszResultText, pCoordinates, LOCATION_RESULT_SUGGESTED_ZOOMLEVEL);
 
 	g_free(pszResultText);
 }
@@ -293,7 +277,7 @@
 	g_print("result: %d\n", nLocationID);
 	gchar* p = g_strdup_printf("<span size='larger'><b>Happy Garden</b></span>\n145 Main St.\nCambridge, MA 02141\n617-555-1021");
 	mappoint_t pt = {0,0};
-	searchwindow_add_result(0, p, &pt);
+	searchwindow_add_result(SEARCH_RESULT_TYPE_LOCATION, 0, p, &pt);
 	g_free(p);
 }
 */

Index: search_road.c
===================================================================
RCS file: /cvs/cairo/roadster/src/search_road.c,v
retrieving revision 1.22
retrieving revision 1.23
diff -u -d -r1.22 -r1.23
--- search_road.c	14 Sep 2005 20:06:54 -0000	1.22
+++ search_road.c	24 Sep 2005 05:25:25 -0000	1.23
@@ -36,6 +36,9 @@
 
 #define ROAD_RESULT_SUGGESTED_ZOOMLEVEL		(4)
 
+#define FORMAT_ROAD_RESULT_WITHOUT_NUMBER 	("%s %s\n%s")
+#define FORMAT_ROAD_RESULT_WITH_NUMBER 		("%d %s %s\n%s")
+
 typedef struct {
 	gint m_nNumber;			// house number	eg. 51
 	gchar* m_pszRoadName;	// road name eg. "Washington"
@@ -46,9 +49,10 @@
 } roadsearch_t;
 
 #define ROADSEARCH_NUMBER_NONE			(-1)
-#define SEARCH_RESULT_COUNT_LIMIT		(200)		// how many rows to get from DB
-#define MAX_QUERY 				(4000)
-#define ROAD_MIN_LENGTH_FOR_WILDCARD_SEARCH	(3)
+#define SEARCH_RESULT_COUNT_LIMIT		(400)		// how many rows to get from DB
+#define MAX_QUERY 						(4000)
+
+//#define ROAD_MIN_LENGTH_FOR_WILDCARD_SEARCH	(4)	  wildcard search no longer used
 
 gboolean search_address_match_zipcode(const gchar* pszWord)
 {
@@ -67,7 +71,6 @@
 
 // prototypes
 
-void search_road_on_cleaned_sentence(const gchar* pszCleanedSentence);
 void search_road_on_words(gchar** aWords, gint nWordCount);
 void search_road_on_roadsearch_struct(const roadsearch_t* pRoadSearch);
 void search_road_filter_result(const gchar* pszRoadName, gint nRoadNumber, gint nRoadSuffixID, gint nAddressLeftStart, gint nAddressLeftEnd, gint nAddressRightStart, gint nAddressRightEnd, const gchar* pszCityNameLeft, const gchar* pszCityNameRight, const gchar* pszStateNameLeft, const gchar* pszStateNameRight, const gchar* pszZIPLeft, const gchar* pszZIPRight, pointstring_t* pPointString);
@@ -76,26 +79,8 @@
 
 void search_road_execute(const gchar* pszSentence)
 {
-	if(pszSentence[0] == 0) {
-		return;	// ignore empty searches ?
-	}
-
-	TIMER_BEGIN(search, "SEARCH BEGIN");
-
-	// copy sentence and clean it
-	gchar* pszCleanedSentence = g_strdup(pszSentence);
-	search_clean_string(pszCleanedSentence);
-	search_road_on_cleaned_sentence(pszCleanedSentence);
-	g_free(pszCleanedSentence);
-
-	TIMER_END(search, "SEARCH END");
-}
-
-void search_road_on_cleaned_sentence(const gchar* pszCleanedSentence)
-{
 	// Create an array of the words
-	gchar** aWords = g_strsplit(pszCleanedSentence," ", 0);	// " " = delimeters, 0 = no max #
-
+	gchar** aWords = g_strsplit(pszSentence," ", 0);	// " " = delimeters, 0 = no max #
 	gint nWordCount = g_strv_length(aWords);
 
 	if(nWordCount > 0) {
@@ -146,7 +131,7 @@
 	gboolean bGotStateName = FALSE;
 	if(nRemainingWordCount >= 3) {
 		// try two-word state name
-		gchar* pszStateName = g_strjoinv_limit(" ", aWords, iLast-1, iLast);
+		gchar* pszStateName = util_g_strjoinv_limit(" ", aWords, iLast-1, iLast);
 		//g_print("trying two-word state name '%s'\n", pszStateName);
 
 		if(db_state_get_id(pszStateName, &roadsearch.m_nStateID)) {
@@ -170,7 +155,7 @@
 	gint nCityNameLength;
 	for(nCityNameLength = 5 ; nCityNameLength >= 1 ; nCityNameLength--) {
 		if(nRemainingWordCount > nCityNameLength) {
-			gchar* pszCityName = g_strjoinv_limit(" ", aWords, iLast - (nCityNameLength-1), iLast);
+			gchar* pszCityName = util_g_strjoinv_limit(" ", aWords, iLast - (nCityNameLength-1), iLast);
 
 			if(db_city_get_id(pszCityName, roadsearch.m_nStateID, &roadsearch.m_nCityID)) {
 				iLast -= nCityNameLength;	// several words taken :)
@@ -201,7 +186,7 @@
 	}
 
 	if(nRemainingWordCount > 0) {
-		roadsearch.m_pszRoadName = g_strjoinv_limit(" ", aWords, iFirst, iLast);
+		roadsearch.m_pszRoadName = util_g_strjoinv_limit(" ", aWords, iFirst, iLast);
 		search_road_on_roadsearch_struct(&roadsearch);
 	}
 	else {
@@ -276,19 +261,16 @@
 		//~ pszGroupClause = g_strdup("");
 	//~ }
 
-	gchar azQuery[MAX_QUERY];
 	gchar* pszSafeRoadName = db_make_escaped_string(pRoadSearch->m_pszRoadName);
 	//g_print("pRoadSearch->m_pszRoadName = %s, pszSafeRoadName = %s\n", pRoadSearch->m_pszRoadName, pszSafeRoadName);
 
-	gchar* pszRoadNameCondition;
-	if(strlen(pRoadSearch->m_pszRoadName) < ROAD_MIN_LENGTH_FOR_WILDCARD_SEARCH) {
-		pszRoadNameCondition = g_strdup_printf("RoadName.Name='%s'", pszSafeRoadName);
-	}
-	else {
-		pszRoadNameCondition = g_strdup_printf("RoadName.Name LIKE '%s%%'", pszSafeRoadName);
-	}
+	// XXX: Should we use soundex()? (http://en.wikipedia.org/wiki/Soundex)
+	gchar* pszRoadNameCondition = g_strdup_printf("RoadName.Name='%s'", pszSafeRoadName);
 
-	g_snprintf(azQuery, MAX_QUERY,
+	// Now we use only Soundex
+	//pszRoadNameCondition = g_strdup_printf("RoadName.NameSoundex = SUBSTRING(SOUNDEX('%s') FROM 1 FOR 10)", pszSafeRoadName);
+
+	gchar* pszQuery = g_strdup_printf(
 		"SELECT Road.ID, RoadName.Name, RoadName.SuffixID, AsBinary(Road.Coordinates), Road.AddressLeftStart, Road.AddressLeftEnd, Road.AddressRightStart, Road.AddressRightEnd, CityLeft.Name, CityRight.Name"
 		", StateLeft.Code, StateRight.Code, Road.ZIPCodeLeft, Road.ZIPCodeRight"
 		" FROM RoadName"
@@ -318,8 +300,8 @@
 			   pszCityClause,
 			   pszStateClause,
 			SEARCH_RESULT_COUNT_LIMIT + 1);
-	
-	// free strings
+
+	// free intermediate strings
 	db_free_escaped_string(pszSafeRoadName);
 	g_free(pszAddressClause);
 	g_free(pszRoadNameCondition);
@@ -327,12 +309,11 @@
 	g_free(pszZIPClause);
 	g_free(pszCityClause);
 	g_free(pszStateClause);
-
-//	g_strlcpy(azQuery, , MAX_QUERY);
+	
 	//g_print("SQL: %s\n", azQuery);
 
 	db_resultset_t* pResultSet;
-	if(db_query(azQuery, &pResultSet)) {
+	if(db_query(pszQuery, &pResultSet)) {
 		db_row_t aRow;
 
 		// get result rows!
@@ -362,30 +343,24 @@
 
 				db_parse_wkb_linestring(aRow[3], pPointString->m_pPointsArray, point_alloc);
 
-//	g_print("raw: %s\n", aRow[3]);
 				search_road_filter_result(aRow[1], pRoadSearch->m_nNumber, atoi(aRow[2]), atoi(aRow[4]), atoi(aRow[5]), atoi(aRow[6]), atoi(aRow[7]), aRow[8], aRow[9], aRow[10], aRow[11], aRow[12], aRow[13], pPointString);
-//	g_print("%03d: Road.ID='%s' RoadName.Name='%s', Suffix=%s, L:%s-%s, R:%s-%s\n", nCount, aRow[0], aRow[1], aRow[3], aRow[4], aRow[5], aRow[6], aRow[7]);
+				//g_print("%03d: Road.ID='%s' RoadName.Name='%s', Suffix=%s, L:%s-%s, R:%s-%s\n", nCount, aRow[0], aRow[1], aRow[3], aRow[4], aRow[5], aRow[6], aRow[7]);
 				pointstring_free(pPointString);
 			}
 		}
 		db_free_result(pResultSet);
-
-		if(nCount == 0) {
-			g_print("no address search results\n");
-		}
 	}
 	else {
-		g_print("search failed\n");
+		g_print("road search failed\n");
 	}
+	g_free(pszQuery);
 }
 
 
 static gfloat point_calc_distance(mappoint_t* pA, mappoint_t* pB)
 {
-	// determine slope of the line
 	gdouble fRise = pB->m_fLatitude - pA->m_fLatitude;
 	gdouble fRun = pB->m_fLongitude - pA->m_fLongitude;
-
 	return sqrt((fRun*fRun) + (fRise*fRise));
 }
 
@@ -527,12 +502,15 @@
 //         gint nStart = min4(nAddressLeftStart, nAddressLeftEnd, nAddressRightStart, nAddressRigtEnd);
 //         gint nEnd = min4(nAddressLeftStart, nAddressLeftEnd, nAddressRightStart, nAddressRigtEnd);
 
+/*
 		if(nAddressRightStart == 0 && nAddressRightEnd == 0) {
+*/
 			// show no numbers if they're both 0
-			g_snprintf(azBuffer, BUFFER_SIZE, "%s %s\n%s",
+			g_snprintf(azBuffer, BUFFER_SIZE, FORMAT_ROAD_RESULT_WITHOUT_NUMBER,
 					   pszRoadName,
 					   road_suffix_itoa(nRoadSuffixID, ROAD_SUFFIX_LENGTH_LONG),
 					   pszCSZRight);
+/*
 		}
 		else if(nAddressRightStart < nAddressRightEnd) {
 			g_snprintf(azBuffer, BUFFER_SIZE, "%d-%d %s %s\n%s", nAddressRightStart, nAddressRightEnd, pszRoadName, road_suffix_itoa(nRoadSuffixID, ROAD_SUFFIX_LENGTH_LONG), pszCSZRight);
@@ -541,7 +519,7 @@
 			// reverse start/end for the dear user :)
 			g_snprintf(azBuffer, BUFFER_SIZE, "%d-%d %s %s\n%s", nAddressRightEnd, nAddressRightStart, pszRoadName, road_suffix_itoa(nRoadSuffixID, ROAD_SUFFIX_LENGTH_LONG), pszCSZRight);
 		}
-		searchwindow_add_result(azBuffer, &ptAddress, ROAD_RESULT_SUGGESTED_ZOOMLEVEL);
+		searchwindow_add_result(SEARCH_RESULT_TYPE_ROAD, azBuffer, &ptAddress, ROAD_RESULT_SUGGESTED_ZOOMLEVEL);
 
 		// do left side, same as right side (see above)
 		if(nAddressLeftStart == 0 && nAddressLeftEnd == 0) {
@@ -554,7 +532,7 @@
 			// swap address to keep smaller number to the left
 			g_snprintf(azBuffer, BUFFER_SIZE, "%d-%d %s %s\n%s", nAddressLeftEnd, nAddressLeftStart, pszRoadName, road_suffix_itoa(nRoadSuffixID, ROAD_SUFFIX_LENGTH_LONG), pszCSZLeft);
 		}
-		searchwindow_add_result(azBuffer, &ptAddress, ROAD_RESULT_SUGGESTED_ZOOMLEVEL);		
+*/		searchwindow_add_result(SEARCH_RESULT_TYPE_ROAD, azBuffer, &ptAddress, ROAD_RESULT_SUGGESTED_ZOOMLEVEL);		
 	}
 	else {	// else the search had a road number
 		// NOTE: we have to filter out results like "97-157" when searching for "124" because it's
@@ -578,8 +556,8 @@
 					gfloat fPercent = (gfloat)(nRoadNumber - nAddressLeftStart) / (gfloat)nRange;
 					pointstring_walk_percentage(pPointString, fPercent, ROADSIDE_LEFT, &ptAddress);
 				}
-				g_snprintf(azBuffer, BUFFER_SIZE, "%d %s %s\n%s", nRoadNumber, pszRoadName, road_suffix_itoa(nRoadSuffixID, ROAD_SUFFIX_LENGTH_LONG), pszCSZLeft);
-				searchwindow_add_result(azBuffer, &ptAddress, ROAD_RESULT_SUGGESTED_ZOOMLEVEL);				
+				g_snprintf(azBuffer, BUFFER_SIZE, FORMAT_ROAD_RESULT_WITH_NUMBER, nRoadNumber, pszRoadName, road_suffix_itoa(nRoadSuffixID, ROAD_SUFFIX_LENGTH_LONG), pszCSZLeft);
+				searchwindow_add_result(SEARCH_RESULT_TYPE_ROAD, azBuffer, &ptAddress, ROAD_RESULT_SUGGESTED_ZOOMLEVEL);				
 			}
 			else if(nRoadNumber >= nAddressLeftEnd && nRoadNumber <= nAddressLeftStart) {
 				// MATCH: left side backwards
@@ -594,8 +572,8 @@
 					// flip percent (23 becomes 77, etc.)
 					pointstring_walk_percentage(pPointString, (100.0 - fPercent), ROADSIDE_RIGHT, &ptAddress);
 				}
-				g_snprintf(azBuffer, BUFFER_SIZE, "%d %s %s\n%s", nRoadNumber, pszRoadName, road_suffix_itoa(nRoadSuffixID, ROAD_SUFFIX_LENGTH_LONG), pszCSZLeft);
-				searchwindow_add_result(azBuffer, &ptAddress, ROAD_RESULT_SUGGESTED_ZOOMLEVEL);
+				g_snprintf(azBuffer, BUFFER_SIZE, FORMAT_ROAD_RESULT_WITH_NUMBER, nRoadNumber, pszRoadName, road_suffix_itoa(nRoadSuffixID, ROAD_SUFFIX_LENGTH_LONG), pszCSZLeft);
+				searchwindow_add_result(SEARCH_RESULT_TYPE_ROAD, azBuffer, &ptAddress, ROAD_RESULT_SUGGESTED_ZOOMLEVEL);
 			}
 		}
 
@@ -615,8 +593,8 @@
 					gfloat fPercent = (gfloat)(nRoadNumber - nAddressRightStart) / (gfloat)nRange;
 					pointstring_walk_percentage(pPointString, fPercent, ROADSIDE_RIGHT, &ptAddress);
 				}
-				g_snprintf(azBuffer, BUFFER_SIZE, "%d %s %s\n%s", nRoadNumber, pszRoadName, road_suffix_itoa(nRoadSuffixID, ROAD_SUFFIX_LENGTH_LONG), pszCSZRight);
-				searchwindow_add_result(azBuffer, &ptAddress, ROAD_RESULT_SUGGESTED_ZOOMLEVEL);				
+				g_snprintf(azBuffer, BUFFER_SIZE, FORMAT_ROAD_RESULT_WITH_NUMBER, nRoadNumber, pszRoadName, road_suffix_itoa(nRoadSuffixID, ROAD_SUFFIX_LENGTH_LONG), pszCSZRight);
+				searchwindow_add_result(SEARCH_RESULT_TYPE_ROAD, azBuffer, &ptAddress, ROAD_RESULT_SUGGESTED_ZOOMLEVEL);
 			}
 			else if(nRoadNumber >= nAddressRightEnd && nRoadNumber <= nAddressRightStart) {
 				// MATCH: right side backwards
@@ -631,8 +609,8 @@
 					// flip percent (23 becomes 77, etc.)
 					pointstring_walk_percentage(pPointString, (100.0 - fPercent), ROADSIDE_LEFT, &ptAddress);
 				}
-				g_snprintf(azBuffer, BUFFER_SIZE, "%d %s %s\n%s", nRoadNumber, pszRoadName, road_suffix_itoa(nRoadSuffixID, ROAD_SUFFIX_LENGTH_LONG), pszCSZRight);
-				searchwindow_add_result(azBuffer, &ptAddress, ROAD_RESULT_SUGGESTED_ZOOMLEVEL);				
+				g_snprintf(azBuffer, BUFFER_SIZE, FORMAT_ROAD_RESULT_WITH_NUMBER, nRoadNumber, pszRoadName, road_suffix_itoa(nRoadSuffixID, ROAD_SUFFIX_LENGTH_LONG), pszCSZRight);
+				searchwindow_add_result(SEARCH_RESULT_TYPE_ROAD, azBuffer, &ptAddress, ROAD_RESULT_SUGGESTED_ZOOMLEVEL);
 			}
 		}
 	}

Index: searchwindow.c
===================================================================
RCS file: /cvs/cairo/roadster/src/searchwindow.c,v
retrieving revision 1.20
retrieving revision 1.21
diff -u -d -r1.20 -r1.21
--- searchwindow.c	14 Sep 2005 20:06:54 -0000	1.20
+++ searchwindow.c	24 Sep 2005 05:25:25 -0000	1.21
@@ -29,20 +29,26 @@
 #  include <config.h>
 #endif
 
+//#define ENABLE_SLEEP_DURING_SEARCH_HACK		// just for testing GUI behavior
+
 #include "main.h"
-#include "search_road.h"
-#include "search_location.h"
+#include "search.h"
+//#include "search_road.h"
+//#include "search_location.h"
+//#include "search_city.h"
 #include "mainwindow.h"
 #include "searchwindow.h"
 #include "util.h"
 #include "gui.h"
 
-#define RESULTLIST_COLUMN_NAME 	0	// visible data
-#define RESULTLIST_LATITUDE		1
-#define RESULTLIST_LONGITUDE	2
-#define RESULTLIST_DISTANCE		3
-#define RESULTLIST_ZOOMLEVEL	4
-#define RESULTLIST_CLICKABLE	5
+#define RESULTLIST_COLUMN_NAME 			0	// visible data
+#define RESULTLIST_COLUMN_LATITUDE		1
+#define RESULTLIST_COLUMN_LONGITUDE		2
+#define RESULTLIST_COLUMN_DISTANCE		3
+#define RESULTLIST_COLUMN_ZOOMLEVEL		4
+#define RESULTLIST_COLUMN_CLICKABLE		5
+#define RESULTLIST_COLUMN_PIXBUF		6
+#define RESULTLIST_COLUMN_SORT			7
 
 #define MAGIC_GTK_NO_SORT_COLUMN (-2)	// why -2?  dunno.  is there a real define for this?  dunno.
 
@@ -55,34 +61,58 @@
 
 	// results list (on the sidebar)
 	GtkTreeView* m_pResultsTreeView;
+	GtkTreeViewColumn* m_pResultsTreeViewGlyphColumn;
 	GtkListStore* m_pResultsListStore;
 
+	GHashTable* m_pResultsHashTable;
+	
+	GtkMenuItem* m_pNextSearchResultMenuItem;
+	GtkMenuItem* m_pPreviousSearchResultMenuItem;
+
 	gint m_nNumResults;
 } g_SearchWindow = {0};
 
 static void searchwindow_on_resultslist_selection_changed(GtkTreeSelection *treeselection, gpointer user_data);
+static void searchwindow_set_message(gchar* pszMessage);
 
 void searchwindow_init(GladeXML* pGladeXML)
 {
 	GLADE_LINK_WIDGET(pGladeXML, g_SearchWindow.m_pSearchEntry, GTK_ENTRY, "searchentry");
 	GLADE_LINK_WIDGET(pGladeXML, g_SearchWindow.m_pSearchButton, GTK_BUTTON, "searchbutton");
 	GLADE_LINK_WIDGET(pGladeXML, g_SearchWindow.m_pResultsTreeView, GTK_TREE_VIEW, "searchresultstreeview");
+	GLADE_LINK_WIDGET(pGladeXML, g_SearchWindow.m_pNextSearchResultMenuItem, GTK_MENU_ITEM, "nextresultmenuitem");
+	GLADE_LINK_WIDGET(pGladeXML, g_SearchWindow.m_pPreviousSearchResultMenuItem, GTK_MENU_ITEM, "previousresultmenuitem");
 
 	// create results tree view
-	g_SearchWindow.m_pResultsListStore = gtk_list_store_new(6, G_TYPE_STRING, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_INT, G_TYPE_BOOLEAN);
+	g_SearchWindow.m_pResultsListStore = gtk_list_store_new(8, G_TYPE_STRING, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_INT, G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, G_TYPE_DOUBLE);
 	gtk_tree_view_set_model(g_SearchWindow.m_pResultsTreeView, GTK_TREE_MODEL(g_SearchWindow.m_pResultsListStore));
+//	gtk_tree_view_set_search_equal_func(g_SearchWindow.m_pResultsTreeView, util_treeview_match_all_words_callback, NULL, NULL);
 
 	GtkCellRenderer* pCellRenderer;
   	GtkTreeViewColumn* pColumn;
 
-	// add Name column (with a text renderer)
-	pCellRenderer = gtk_cell_renderer_text_new();
-	pColumn = gtk_tree_view_column_new_with_attributes("Road", pCellRenderer, "markup", RESULTLIST_COLUMN_NAME, NULL);
-	gtk_tree_view_append_column(g_SearchWindow.m_pResultsTreeView, pColumn);	
+		// NEW COLUMN: "Icon"
+		pCellRenderer = gtk_cell_renderer_pixbuf_new();
+		g_object_set(G_OBJECT(pCellRenderer), "xpad", 2, NULL);
+		pColumn = gtk_tree_view_column_new_with_attributes("", pCellRenderer, "pixbuf", RESULTLIST_COLUMN_PIXBUF, NULL);
+		gtk_tree_view_append_column(g_SearchWindow.m_pResultsTreeView, pColumn);
 
-	// attach handler for selection-changed signal
+		g_SearchWindow.m_pResultsTreeViewGlyphColumn = pColumn;
+
+		// NEW COLUMN: "Name" (with a text renderer)
+		pCellRenderer = gtk_cell_renderer_text_new();
+			g_object_set(G_OBJECT(pCellRenderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+		pColumn = gtk_tree_view_column_new_with_attributes("Road", pCellRenderer, "markup", RESULTLIST_COLUMN_NAME, NULL);
+		gtk_tree_view_append_column(g_SearchWindow.m_pResultsTreeView, pColumn);
+
+	// Attach handler for selection-changed signal
 	GtkTreeSelection *pTreeSelection = gtk_tree_view_get_selection(g_SearchWindow.m_pResultsTreeView);
 	g_signal_connect(G_OBJECT(pTreeSelection), "changed", (GtkSignalFunc)searchwindow_on_resultslist_selection_changed, NULL);
+
+	// Add message to the list
+	gchar* pszBuffer = g_strdup_printf(SEARCHWINDOW_INFO_FORMAT, "Type search words above.");
+	searchwindow_set_message(pszBuffer);
+	g_free(pszBuffer);
 }
 
 void searchwindow_clear_results(void)
@@ -92,84 +122,146 @@
 		gtk_list_store_clear(g_SearchWindow.m_pResultsListStore);
 	}
 	g_SearchWindow.m_nNumResults = 0;
+
+	// Scroll window all the way left.  (Vertical scroll is reset by emptying the list.)
+    gtk_adjustment_set_value(gtk_tree_view_get_hadjustment(g_SearchWindow.m_pResultsTreeView), 0.0);
 }
 
-void searchwindow_add_message(gchar* pszMessage)
+void searchwindow_set_message(gchar* pszMessage)
 {
 	// Add a basic text message to the list, instead of a search result (eg. "no results")
 	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,
+					   RESULTLIST_COLUMN_CLICKABLE, FALSE,
 					   -1);
+
+	g_object_set(G_OBJECT(g_SearchWindow.m_pResultsTreeViewGlyphColumn), "visible", FALSE, NULL);
 }
 
+// Begin a search
 void searchwindow_on_findbutton_clicked(GtkWidget *pWidget, gpointer* p)
 {
-	// Begin a search
+	// NOTE: By setting the SEARCH button inactive, we prevent this code from becoming reentrant when
+	// we call GTK_PROCESS_MAINLOOP below.
+	gtk_widget_set_sensitive(GTK_WIDGET(g_SearchWindow.m_pSearchButton), FALSE);
 
 	// XXX: 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();
 
+	g_SearchWindow.m_pResultsHashTable = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
 	const gchar* pszSearch = gtk_entry_get_text(g_SearchWindow.m_pSearchEntry);
+	
+	// ensure the search results are visible
+	mainwindow_sidebar_set_tab(SIDEBAR_TAB_SEARCH_RESULTS);
+	mainwindow_set_sidebox_visible(TRUE);
 
 	if(pszSearch[0] == '\0') {
 		gchar* pszBuffer = g_strdup_printf(SEARCHWINDOW_INFO_FORMAT, "Type search words above.");
-		searchwindow_add_message(pszBuffer);
+		searchwindow_set_message(pszBuffer);
 		g_free(pszBuffer);
 	}
 	else {
+		gchar* pszBuffer = g_strdup_printf(SEARCHWINDOW_INFO_FORMAT, "Searching...");
+		searchwindow_set_message(pszBuffer);
+		g_free(pszBuffer);
+
 		void* pBusy = mainwindow_set_busy();
-		search_road_execute(pszSearch);
-		search_location_execute(pszSearch);
+
+		// XXX: Set search entry to use its parent's busy cursor (why do we need to do this?)
+		gdk_window_set_cursor(g_SearchWindow.m_pSearchEntry->text_area, NULL);
+
+		// Let GTK update the treeview to show the message
+		GTK_PROCESS_MAINLOOP;	// see note above
+
+		// Start the search!
+		search_all(pszSearch);
+
+#ifdef ENABLE_SLEEP_DURING_SEARCH_HACK
+		sleep(5);	// good for seeing how the UI behaves during long searches
+#endif
 		mainwindow_set_not_busy(&pBusy);
 
+		// HACK: Set a cursor for the private 'text_area' member of the search GtkEntry (otherwise it won't show busy cursor)
+		GdkCursor* pCursor = gdk_cursor_new(GDK_XTERM);
+		gdk_window_set_cursor(g_SearchWindow.m_pSearchEntry->text_area, pCursor);
+
 		if(g_SearchWindow.m_nNumResults == 0) {
+			searchwindow_clear_results();
+
 			// insert a "no results" message
-			gchar* pszBuffer = g_strdup_printf(SEARCHWINDOW_INFO_FORMAT, "No results.");
-			searchwindow_add_message(pszBuffer);
+			gchar* pszBuffer = g_strdup_printf(SEARCHWINDOW_INFO_FORMAT, "No search results.");
+			searchwindow_set_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);
+		else {
+			// Show icon column
+			g_object_set(G_OBJECT(g_SearchWindow.m_pResultsTreeViewGlyphColumn), "visible", TRUE, NULL);
+
+			// Sort the list
+			gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(g_SearchWindow.m_pResultsListStore), RESULTLIST_COLUMN_SORT, GTK_SORT_ASCENDING);
+		}
 	}
-	// ensure the search results are visible
-	mainwindow_sidebar_set_tab(SIDEBAR_TAB_SEARCH_RESULTS);
-	mainwindow_set_sidebox_visible(TRUE);
+
+	g_hash_table_destroy(g_SearchWindow.m_pResultsHashTable);
+	gtk_widget_set_sensitive(GTK_WIDGET(g_SearchWindow.m_pSearchButton), TRUE);
 }
 
 // add a result row to the list
-void searchwindow_add_result(const gchar* pszText, mappoint_t* pPoint, gint nZoomLevel)
+void searchwindow_add_result(ESearchResultType eResultType, const gchar* pszText, mappoint_t* pPoint, gint nZoomLevel)
 {
 	GtkTreeIter iter;
 
+	if(g_SearchWindow.m_nNumResults == 0) {
+		// Clear any messages
+		searchwindow_clear_results();
+	}
+
+	if(g_hash_table_lookup(g_SearchWindow.m_pResultsHashTable, pszText)) {
+		// duplicate found
+		return;
+	}
+	g_hash_table_insert(g_SearchWindow.m_pResultsHashTable, g_strdup(pszText), (gpointer)TRUE);
+
 	mappoint_t ptCenter;
 	mainwindow_get_centerpoint(&ptCenter);
 
-	gdouble fDistance = map_get_distance_in_meters(&ptCenter, pPoint);
+	gdouble fDistance = map_get_straight_line_distance_in_degrees(&ptCenter, pPoint);
+
+	gchar* pszTmp = g_strdup_printf("%d.%08d", eResultType, (gint)(fDistance * 100000.0));	// move decimal over 5 places
+	gdouble fSort = g_ascii_strtod(pszTmp, NULL);
+//	g_print("distance=%f, string='%s', sort=%f\n", fDistance, pszTmp, fSort);
+	g_free(pszTmp);
 
 	gchar* pszBuffer = NULL;
 	if(g_utf8_validate(pszText, -1, NULL)) {
-		pszBuffer = g_markup_printf_escaped(SEARCHWINDOW_RESULT_FORMAT, pszText);
+		//pszBuffer = g_markup_printf_escaped(SEARCHWINDOW_RESULT_FORMAT, pszText);
+		pszBuffer = g_strdup_printf(SEARCHWINDOW_RESULT_FORMAT, pszText);
 	}
 	else {
 		g_warning("Search result not UTF-8: '%s'\n", pszText);
 		pszBuffer = g_strdup_printf(SEARCHWINDOW_RESULT_FORMAT, "<i>Invalid Name</i>");
 	}
 
-//	g_print("Adding: (%f,%f) (%f) %s\n", pPoint->m_fLatitude, pPoint->m_fLongitude, fDistance, pszBuffer);
+	glyph_t* pGlyph = search_glyph_for_search_result_type(eResultType);
+	g_assert(pGlyph != NULL);
+	GdkPixbuf* pRowPixbuf = glyph_get_pixbuf(pGlyph); 
+	g_assert(pRowPixbuf != NULL);
 
 	gtk_list_store_append(g_SearchWindow.m_pResultsListStore, &iter);
 	gtk_list_store_set(g_SearchWindow.m_pResultsListStore, &iter,
 		RESULTLIST_COLUMN_NAME, pszBuffer,
-		RESULTLIST_LATITUDE, pPoint->m_fLatitude,
-		RESULTLIST_LONGITUDE, pPoint->m_fLongitude,
-		RESULTLIST_DISTANCE, fDistance,
-		RESULTLIST_ZOOMLEVEL, nZoomLevel,
-		RESULTLIST_CLICKABLE, TRUE,
+		RESULTLIST_COLUMN_LATITUDE, pPoint->m_fLatitude,
+		RESULTLIST_COLUMN_LONGITUDE, pPoint->m_fLongitude,
+		RESULTLIST_COLUMN_DISTANCE, fDistance,
+		RESULTLIST_COLUMN_ZOOMLEVEL, nZoomLevel,
+		RESULTLIST_COLUMN_CLICKABLE, TRUE,
+		RESULTLIST_COLUMN_PIXBUF, pRowPixbuf,
+		RESULTLIST_COLUMN_SORT, fSort,
 		-1);
 
 	g_free(pszBuffer);
@@ -188,29 +280,48 @@
 		gint nZoomLevel;
 		gboolean bClickable;
 		gtk_tree_model_get(GTK_TREE_MODEL(g_SearchWindow.m_pResultsListStore), &iter,
-			RESULTLIST_LATITUDE, &pt.m_fLatitude,
-			RESULTLIST_LONGITUDE, &pt.m_fLongitude,
-			RESULTLIST_ZOOMLEVEL, &nZoomLevel,
-			RESULTLIST_CLICKABLE, &bClickable,
+			RESULTLIST_COLUMN_LATITUDE, &pt.m_fLatitude,
+			RESULTLIST_COLUMN_LONGITUDE, &pt.m_fLongitude,
+			RESULTLIST_COLUMN_ZOOMLEVEL, &nZoomLevel,
+			RESULTLIST_COLUMN_CLICKABLE, &bClickable,
 			-1);
 
 		if(!bClickable) return;	// XXX: is this the right way to make a treeview item not clickable?
 
 		// XXX: Slide or jump?  Should this be a setting?
-//		mainwindow_map_slide_to_mappoint(&pt);
+		//mainwindow_map_slide_to_mappoint(&pt);
 		mainwindow_set_zoomlevel(nZoomLevel);
 		mainwindow_map_center_on_mappoint(&pt);
+
+		void* pBusy = mainwindow_set_busy();
 		mainwindow_draw_map(DRAWFLAG_ALL);
+		mainwindow_set_not_busy(&pBusy);
 	}
 }
 
-void searchwindow_on_addressresultstreeview_row_activated(GtkWidget *pWidget, gpointer* p)
+// void searchwindow_on_addressresultstreeview_row_activated(GtkWidget *pWidget, gpointer* p)
+// {
+//     searchwindow_go_to_selected_result();
+// }
+
+void searchwindow_on_resultslist_row_activated(GtkWidget *pWidget, gpointer* p)
 {
-	searchwindow_go_to_selected_result();
+	// XXX: Double-click on a search result.  Do we want to support this?
+	// We should prevent a double click on an unselected row from firing both this and selection_changed
+	//searchwindow_go_to_selected_result();
 }
 
 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
 	searchwindow_go_to_selected_result();
 }
+
+void searchwindow_on_nextresultbutton_clicked(GtkWidget *pWidget, gpointer* p)
+{
+	util_gtk_tree_view_select_next(g_SearchWindow.m_pResultsTreeView);
+}
+
+void searchwindow_on_previousresultbutton_clicked(GtkWidget *pWidget, gpointer* p)
+{
+	util_gtk_tree_view_select_previous(g_SearchWindow.m_pResultsTreeView);
+}

Index: searchwindow.h
===================================================================
RCS file: /cvs/cairo/roadster/src/searchwindow.h,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -d -r1.4 -r1.5
--- searchwindow.h	7 Mar 2005 05:30:10 -0000	1.4
+++ searchwindow.h	24 Sep 2005 05:25:25 -0000	1.5
@@ -26,12 +26,15 @@
 
 G_BEGIN_DECLS
 
+#include <gtk/gtk.h>
 #include <glade/glade.h>
-#include "gpsclient.h"
-	
+#include "search.h"
+#include "map.h"
+//#include "gpsclient.h"
+
 void searchwindow_init(GladeXML* pGladeXML);
 
-void searchwindow_add_result(const gchar* pszText, mappoint_t* pPoint, gint nZoomLevel);
+void searchwindow_add_result(ESearchResultType eResultType, const gchar* pszText, mappoint_t* pPoint, gint nZoomLevel);
 
 static void searchwindow_go_to_selected_result(void);
 

Index: util.c
===================================================================
RCS file: /cvs/cairo/roadster/src/util.c,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -d -r1.10 -r1.11
--- util.c	14 Sep 2005 20:06:54 -0000	1.10
+++ util.c	24 Sep 2005 05:25:25 -0000	1.11
@@ -24,11 +24,19 @@
 #include "main.h"
 #include "util.h"
 
+//
+// Generic multi-purpose callbacks
+//
+
 void util_close_parent_window(GtkWidget* pWidget, gpointer data)
 {
+	// Good for close buttons of dialogs.
 	gtk_widget_hide(gtk_widget_get_toplevel(pWidget));
 }
 
+//
+// GNOME / environment related
+//
 gboolean util_running_gnome(void)
 {
 	return((g_getenv("GNOME_DESKTOP_SESSION_ID") != NULL) && (g_find_program_in_path("gnome-open") != NULL));
@@ -57,25 +65,9 @@
 	g_free(argv);
 }
 
-gchar* g_strjoinv_limit(const gchar* separator, gchar** a, gint iFirst, gint iLast)
-{
-	g_assert(iFirst <= iLast);
-	g_assert(iLast < g_strv_length(a));
-
-	gchar* pszSave;
-
-	// replace first unwanted string with NULL (save old value)
-	pszSave = a[iLast+1];
-	a[iLast+1] = NULL;
-
-	// use built-in function for joining (returns a newly allocated string)
-	gchar* pszReturn = g_strjoinv(separator, &a[iFirst]);
-
-	// restore old value
-	a[iLast+1] = pszSave;
-	return pszReturn;
-}
-
+//
+// Misc
+//
 gchar** util_split_words_onto_two_lines(const gchar* pszText, gint nMinLineLength, gint nMaxLineLength)
 {
 #define MAX_WORDS_WE_CAN_HANDLE (6)
@@ -128,46 +120,46 @@
 			if(aWordLengths[0] > aWordLengths[2]) {
 				// 1 word on first line, 2 on second
 				aLines[0] = g_strdup(aWords[0]);
-				aLines[1] = g_strjoinv_limit(" ", aWords, 1, 2);
+				aLines[1] = util_g_strjoinv_limit(" ", aWords, 1, 2);
 			}
 			else {
 				// 2 words on first line, 1 on second
-				aLines[0] = g_strjoinv_limit(" ", aWords, 0, 1);
+				aLines[0] = util_g_strjoinv_limit(" ", aWords, 0, 1);
 				aLines[1] = g_strdup(aWords[2]);
 			}
 			break;
 		case 4:
 			if((aWordLengths[0] + aWordLengths[1] + aWordLengths[2]) < aWordLengths[3]) {
 				// 3 and 1
-				aLines[0] = g_strjoinv_limit(" ", aWords, 0, 2);
+				aLines[0] = util_g_strjoinv_limit(" ", aWords, 0, 2);
 				aLines[1] = g_strdup(aWords[3]);
 			}
 			else if(aWordLengths[0] > (aWordLengths[1] + aWordLengths[2] + aWordLengths[3])) {
 				// 1 and 3
 				aLines[0] = g_strdup(aWords[0]);
-				aLines[1] = g_strjoinv_limit(" ", aWords, 1, 3);
+				aLines[1] = util_g_strjoinv_limit(" ", aWords, 1, 3);
 			}
 			else {
 				// 2 and 2
-				aLines[0] = g_strjoinv_limit(" ", aWords, 0, 1);
-				aLines[1] = g_strjoinv_limit(" ", aWords, 2, 3);
+				aLines[0] = util_g_strjoinv_limit(" ", aWords, 0, 1);
+				aLines[1] = util_g_strjoinv_limit(" ", aWords, 2, 3);
 			}
 			break;
 		case 5:
 			if((aWordLengths[0]+aWordLengths[1]) > (aWordLengths[3]+aWordLengths[4])) {
 				// 2 and 3
-				aLines[0] = g_strjoinv_limit(" ", aWords, 0, 1);
-				aLines[1] = g_strjoinv_limit(" ", aWords, 2, 4);
+				aLines[0] = util_g_strjoinv_limit(" ", aWords, 0, 1);
+				aLines[1] = util_g_strjoinv_limit(" ", aWords, 2, 4);
 			}
 			else {
 				// 3 and 2
-				aLines[0] = g_strjoinv_limit(" ", aWords, 0, 2);
-				aLines[1] = g_strjoinv_limit(" ", aWords, 3, 4);
+				aLines[0] = util_g_strjoinv_limit(" ", aWords, 0, 2);
+				aLines[1] = util_g_strjoinv_limit(" ", aWords, 3, 4);
 			}
 			break;
 		case 6:
-			aLines[0] = g_strjoinv_limit(" ", aWords, 0, 2);
-			aLines[1] = g_strjoinv_limit(" ", aWords, 3, 5);
+			aLines[0] = util_g_strjoinv_limit(" ", aWords, 0, 2);
+			aLines[1] = util_g_strjoinv_limit(" ", aWords, 3, 5);
 			break;
 		default:
 			g_assert_not_reached();
@@ -206,8 +198,53 @@
 	pReturnColor->m_fAlpha = (gfloat)strtol(azBuffer, NULL, 16)/255.0;
 }
 
+void util_random_color(void* p)
+{
+	color_t* pColor = (color_t*)p;
+
+	pColor->m_fRed = (random()%1000)/1000.0;
+	pColor->m_fGreen = (random()%1000)/1000.0;
+	pColor->m_fBlue = (random()%1000)/1000.0;
+	pColor->m_fAlpha = 1.0;
+}
+
+//
+// GLib/GTK fill-ins
+//
+
+// Same as g_strjoinv but doesn't necessarily go to end of strings
+gchar* util_g_strjoinv_limit(const gchar* separator, gchar** a, gint iFirst, gint iLast)
+{
+	g_assert(iFirst <= iLast);
+	g_assert(iLast < g_strv_length(a));
+
+	gchar* pszSave;
+
+	// replace first unwanted string with NULL (save old value)
+	pszSave = a[iLast+1];
+	a[iLast+1] = NULL;
+
+	// use built-in function for joining (returns a newly allocated string)
+	gchar* pszReturn = g_strjoinv(separator, &a[iFirst]);
+
+	// restore old value
+	a[iLast+1] = pszSave;
+	return pszReturn;
+}
+
+void util_gtk_widget_set_visible(GtkWidget* pWidget, gboolean bVisible)
+{
+	if(bVisible) {
+		gtk_widget_show(pWidget);
+	}
+	else {
+		gtk_widget_hide(pWidget);
+	}
+}
 
 #if(!GLIB_CHECK_VERSION(2,6,0))
+
+// This one 
 // if glib < 2.6 we need to provide this function ourselves
 gint g_strv_length(const gchar** a)
 {
@@ -221,12 +258,148 @@
 }
 #endif
 
-void util_random_color(void* p)
+gboolean util_match_word_in_sentence(gchar* pszWord, gchar* pszSentence)
 {
-	color_t* pColor = (color_t*)p;
+	// First see if the search string is a prefix of the text...
+	gint nWordLength = strlen(pszWord);
+	if(strncmp(pszSentence, pszWord, nWordLength) == 0) return TRUE;
 
-	pColor->m_fRed = (random()%1000)/1000.0;
-	pColor->m_fGreen = (random()%1000)/1000.0;
-	pColor->m_fBlue = (random()%1000)/1000.0;
-	pColor->m_fAlpha = 1.0;
+	// ...otherwise search inside the text, but only match to beginnings of words.
+	// A little hack here: just search for " butter"	XXX: what about eg. "-butter"?
+	gchar* pszWordWithSpace = g_strdup_printf(" %s", pszWord);
+	gboolean bMatch = (strstr(pszSentence, pszWordWithSpace) != NULL);	// if it returns a pointer, we have a match
+	g_free(pszWordWithSpace);
+
+	return bMatch;
+}
+
+gboolean util_match_all_words_in_sentence(gchar* pszWords, gchar* pszSentence)
+{
+	// Split up search string into an array of word strings
+	gchar** aWords = g_strsplit(pszWords, " ", 0);	// " " = delimeters, 0 = no max #
+
+	// Make sure all words are in the sentence (order doesn't matter)
+	gboolean bAllFound = TRUE;
+	gint i;
+	for(i = 0 ; aWords[i] != NULL ; i++) {
+		if(!util_match_word_in_sentence(aWords[i], pszSentence)) {
+			bAllFound = FALSE;
+			break;
+		}
+	}
+	g_strfreev(aWords);
+	return bAllFound;
+}
+
+// This is a fairly slow function... perhaps too slow for huge lists?
+gboolean util_treeview_match_all_words_callback(GtkTreeModel *pTreeModel, gint nColumn, const gchar *pszSearchText, GtkTreeIter* pIter, gpointer _unused)
+{
+	gboolean bMatch = FALSE;
+
+	// Get the row text from the treeview
+	gchar* pszRowText = NULL;
+	gtk_tree_model_get(pTreeModel, pIter, nColumn, &pszRowText, -1);	// -1 because it's NULL terminated
+
+	// Strip markup from tree view row
+	gchar* pszRowTextClean = NULL;
+	if(pango_parse_markup(pszRowText, -1, 0, NULL, &pszRowTextClean, NULL, NULL)) {
+		// put both strings into lowercase
+		gchar* pszRowTextCleanDown = g_utf8_casefold(pszRowTextClean, -1);	// -1 because it's NULL terminated
+		gchar* pszSearchTextDown = g_utf8_casefold(pszSearchText, -1);
+
+		bMatch = util_match_all_words_in_sentence(pszSearchTextDown, pszRowTextCleanDown);
+
+		g_free(pszRowTextClean);
+		g_free(pszRowTextCleanDown);
+		g_free(pszSearchTextDown);
+	}
+	else {
+		g_warning("pango_parse_markup failed on '%s'", pszRowText);
+		// bMatch remains FALSE...
+	}
+	g_free(pszRowText);
+
+	return (bMatch == FALSE);	// NOTE: we must return FALSE for matches... yeah, believe it.
+}
+
+gboolean util_gtk_tree_view_select_next(GtkTreeView* pTreeView)
+{
+	gboolean bReturn = FALSE;
+
+	GtkTreeSelection* pSelection = gtk_tree_view_get_selection(pTreeView);
+	GtkTreeModel* pModel = gtk_tree_view_get_model(pTreeView);
+	GtkTreeIter iter;
+	if(gtk_tree_selection_get_selected(pSelection, NULL, &iter)) {
+		GtkTreePath* pPath = gtk_tree_model_get_path(pModel, &iter);
+
+		gtk_tree_path_next(pPath);	// NOTE: this returns void for some reason...
+		gtk_tree_selection_select_path(pSelection, pPath);
+		bReturn = TRUE;
+
+		// Make the new cell visible
+		gtk_tree_view_scroll_to_cell(pTreeView, pPath, NULL, FALSE, 0.0, 0.0);
+		gtk_tree_path_free(pPath);
+	}
+	// if none selected, select the first one
+	else if(gtk_tree_model_get_iter_first(pModel, &iter)) {
+		gtk_tree_selection_select_iter(pSelection, &iter);
+		bReturn = TRUE;
+	}
+	return bReturn;
+}
+
+gboolean util_gtk_tree_view_select_previous(GtkTreeView* pTreeView)
+{
+	gboolean bReturn = FALSE;
+
+	GtkTreeSelection* pSelection = gtk_tree_view_get_selection(pTreeView);
+	GtkTreeModel* pModel = gtk_tree_view_get_model(pTreeView);
+	GtkTreeIter iter;
+	if(gtk_tree_selection_get_selected(pSelection, &pModel, &iter)) {
+		
+		GtkTreePath* pPath = gtk_tree_model_get_path(pModel, &iter);
+
+		if(gtk_tree_path_prev(pPath)) {
+			gtk_tree_selection_select_path(pSelection, pPath);
+			bReturn = TRUE;
+		}
+		// XXX: should we wrap to first?
+
+		// Make the new cell visible
+		gtk_tree_view_scroll_to_cell(pTreeView, pPath, NULL, FALSE, 0.0, 0.0);
+		gtk_tree_path_free(pPath);
+	}
+
+	// XXX: we currently don't support starting at the end
+	return bReturn;
+}
+
+gchar* util_str_replace_many(const gchar* pszSource, util_str_replace_t* aReplacements, gint nNumReplacements)
+{
+	GString* pStringBuffer = g_string_sized_new(250);	// initial length... just a guess.
+
+	const gchar* pszSourceWalker = pszSource;
+	while(*pszSourceWalker != '\0') {
+		gboolean bFound = FALSE;
+		gint i;
+		for(i=0 ; i<nNumReplacements ; i++) {
+			//g_print("comparing %s and %s\n", pszSourceWalker, aReplacements[i].m_pszOld);
+			if(strncmp(pszSourceWalker, aReplacements[i].m_pszOld, strlen(aReplacements[i].m_pszOld)) == 0) {
+				pStringBuffer = g_string_append(pStringBuffer, aReplacements[i].m_pszNew);
+				pszSourceWalker += strlen(aReplacements[i].m_pszOld);
+				bFound = TRUE;
+				break;
+			}
+		}
+
+		if(bFound == FALSE) {
+			pStringBuffer = g_string_append_c(pStringBuffer, *pszSourceWalker);
+			pszSourceWalker++;
+		}
+		//g_print("pStringBuffer = %s (%d)\n", pStringBuffer->str, pStringBuffer->len);
+	}
+
+	gchar* pszReturn = pStringBuffer->str;
+	g_string_free(pStringBuffer, FALSE);	// do NOT free the 'str' memory
+	return pszReturn;
 }

Index: util.h
===================================================================
RCS file: /cvs/cairo/roadster/src/util.h,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -d -r1.10 -r1.11
--- util.h	14 Sep 2005 20:06:54 -0000	1.10
+++ util.h	24 Sep 2005 05:25:25 -0000	1.11
@@ -52,9 +52,25 @@
 void util_close_parent_window(GtkWidget* pWidget, gpointer data);
 void util_open_uri(const char* pszURI);
 
+void util_gtk_widget_set_visible(GtkWidget* pWidget, gboolean bVisible);
+gboolean util_treeview_match_all_words_callback(GtkTreeModel *pTreeModel, gint nColumn, const gchar *pszSearchText, GtkTreeIter* pIter, gpointer _unused);
+
+gboolean util_gtk_tree_view_select_next(GtkTreeView* pTreeView);
+gboolean util_gtk_tree_view_select_previous(GtkTreeView* pTreeView);
+
 // if glib < 2.6
 #if(!GLIB_CHECK_VERSION(2,6,0))
 gint g_strv_length(const gchar** a);
 #endif
 
+// Pretend it's glib
+gchar* util_g_strjoinv_limit(const gchar* separator, gchar** a, gint iFirst, gint iLast);
+
+typedef struct {
+	gchar* m_pszOld;
+	gchar* m_pszNew;
+} util_str_replace_t;
+
+gchar* util_str_replace_many(const gchar* pszSource, util_str_replace_t* aReplacements, gint nNumReplacements);
+
 #endif

Index: welcomewindow.c
===================================================================
RCS file: /cvs/cairo/roadster/src/welcomewindow.c,v
retrieving revision 1.6
retrieving revision 1.7
diff -u -d -r1.6 -r1.7
--- welcomewindow.c	28 Mar 2005 18:49:50 -0000	1.6
+++ welcomewindow.c	24 Sep 2005 05:25:25 -0000	1.7
@@ -33,7 +33,7 @@
 #include "mainwindow.h"
 #include "welcomewindow.h"
 
-#define URL_CENSUS_GOV_TIGER_DATA_WEBSITE ("http://www.census.gov/geo/www/tiger/tiger2004fe/tgr2004fe.html")
+#define URL_CENSUS_GOV_TIGER_DATA_WEBSITE ("http://www.census.gov/geo/www/tiger/tiger2004se/tgr2004se.html")
 
 struct {
 	GtkWindow* m_pWindow;



More information about the cairo-commit mailing list