diff --git a/bindings/python/Makefile.am b/bindings/python/Makefile.am
index c30a93e..50c825e 100644
--- a/bindings/python/Makefile.am
+++ b/bindings/python/Makefile.am
@@ -71,6 +71,8 @@ BINDING_HEADERS_SRCDIR_IN = \
 	sources/rb-sourcelist-model.h		\
 	sources/rb-static-playlist-source.h	\
 	sources/rb-streaming-source.h		\
+	sources/rb-source-search.h		\
+	sources/rb-source-search-basic.h	\
 	widgets/rb-entry-view.h			\
 	widgets/rb-library-browser.h		\
 	widgets/rb-property-view.h		\
diff --git a/bindings/python/rb.defs b/bindings/python/rb.defs
index f09c855..9c34420 100644
--- a/bindings/python/rb.defs
+++ b/bindings/python/rb.defs
@@ -153,6 +153,17 @@
 ;  (gtype-id "RB_TYPE_PLAYER_GST_DATA_TEE")
 ;)
 
+(define-object SourceSearch
+  (in-module "RB")
+  (c-name "RBSourceSearch")
+  (gtype-id "RB_TYPE_SOURCE_SEARCH")
+)
+
+(define-object SourceSearchBasic
+  (in-module "RB")
+  (c-name "RBSourceSearchBasic")
+  (gtype-id "RB_TYPE_SOURCE_SEARCH_BASIC")
+)
 
 ;; Enumerations and flags ...
 
@@ -870,7 +881,9 @@
   (c-name "rb_source_search")
   (return-type "none")
   (parameters
-    '("const-char*" "text")
+    '("RBSourceSearch*" "search")
+    '("const-char*" "cur_text")
+    '("const-char*" "new_text")
   )
 )
 
@@ -1098,7 +1111,9 @@
   (of-object "RBSource")
   (return-type "none")
   (parameters
-    '("const-char*" "text")
+    '("RBSourceSearch*" "search")
+    '("const-char*" "cur_text")
+    '("const-char*" "new_text")
   )
 )
 
@@ -2417,3 +2432,91 @@
   )
 )
 
+;; From rb-source-search.h
+
+(define-function rb_source_search_get_type
+  (c-name "rb_source_search_get_type")
+  (return-type "GType")
+)
+
+(define-method is_subset
+  (of-object "RBSourceSearch")
+  (c-name "rb_source_search_is_subset")
+  (return-type "gboolean")
+  (parameters
+    '("const-char*" "current")
+    '("const-char*" "next")
+  )
+)
+
+(define-method create_query
+  (of-object "RBSourceSearch")
+  (c-name "rb_source_search_create_query")
+  (return-type "RhythmDBQuery*")
+  (parameters
+    '("RhythmDB*" "db")
+    '("const-char*" "search_text")
+  )
+)
+
+(define-method action_attach
+  (of-object "RBSourceSearch")
+  (c-name "rb_source_search_action_attach")
+  (return-type "none")
+  (parameters
+    '("GObject*" "action")
+  )
+)
+
+(define-function rb_source_search_get_from_action
+  (c-name "rb_source_search_get_from_action")
+  (return-type "RBSourceSearch*")
+  (parameters
+    '("GObject*" "action")
+  )
+)
+
+(define-virtual is_subset 
+  (of-object "RBSourceSearch")
+  (return-type "gboolean")
+  (parameters
+    '("const-char*" "cur_text")
+    '("const-char*" "new_text")
+  )
+)
+
+(define-virtual create_query
+  (of-object "RBSourceSearch")
+  (return-type "RhythmDBQuery*")
+  (parameters
+    '("RhythmDB*" "db")
+    '("const-char*" "search_text")
+  )
+)
+
+;; From rb-source-search-basic.h
+
+(define-function rb_source_search_basic_get_type
+  (c-name "rb_source_search_basic_get_type")
+  (return-type "GType")
+)
+
+(define-function source_search_basic_new
+  (in-module "rb")
+  (c-name "rb_source_search_basic_new")
+  (is-constructor-of "RBSourceSearchBasic")
+  (return-type "RBSourceSearch*")
+  (properties
+    '("prop")
+  )
+)
+
+(define-function rb_source_search_basic_create_for_actions
+  (c-name "rb_source_search_basic_create_for_actions")
+  (return-type "none")
+  (parameters
+    '("GtkActionGroup*" "action_group")
+    '("GtkRadioActionEntry*" "actions")
+    '("int" "n_actions")
+  )
+)
diff --git a/bindings/python/rb.override b/bindings/python/rb.override
index 065c0a1..8f1e698 100644
--- a/bindings/python/rb.override
+++ b/bindings/python/rb.override
@@ -29,6 +29,8 @@ headers
 #include "rb-shell.h"
 #include "rb-shell-player.h"
 #include "rb-source.h"
+#include "rb-source-search.h"
+#include "rb-source-search-basic.h"
 #include "rb-sourcelist.h"
 #include "rb-sourcelist-model.h"
 #include "rb-static-playlist-source.h"
diff --git a/lib/rb-marshal.list b/lib/rb-marshal.list
index 2845e25..7de2c58 100644
--- a/lib/rb-marshal.list
+++ b/lib/rb-marshal.list
@@ -7,6 +7,7 @@ OBJECT:OBJECT
 STRING:STRING
 VOID:BOOLEAN,BOOLEAN
 BOXED:BOXED
+BOXED:OBJECT
 VOID:BOXED,BOXED
 VOID:BOXED,INT,POINTER,POINTER
 VOID:BOXED,OBJECT
diff --git a/lib/rb-util.c b/lib/rb-util.c
index d9898fa..ab47060 100644
--- a/lib/rb-util.c
+++ b/lib/rb-util.c
@@ -908,6 +908,40 @@ rb_signal_accumulator_object_handled (GSignalInvocationHint *hint,
 	return FALSE;
 }
 
+gboolean
+rb_signal_accumulator_value_array (GSignalInvocationHint *hint,
+				   GValue *return_accu,
+				   const GValue *handler_return,
+				   gpointer bleh)
+{
+	GValueArray *a;
+	GValueArray *b;
+	int i;
+
+	if (handler_return == NULL)
+		return TRUE;
+
+	a = NULL;
+	if (G_VALUE_HOLDS_BOXED (return_accu)) {
+		a = g_value_get_boxed (return_accu);
+	} else {
+		a = g_value_array_new (1);
+	}
+
+	if (G_VALUE_HOLDS_BOXED (handler_return)) {
+		b = g_value_get_boxed (handler_return);
+		for (i=0; i < b->n_values; i++) {
+			GValue *z = g_value_array_get_nth (b, i);
+			g_value_array_append (a, z);
+		}
+	}
+
+	g_value_unset (return_accu);
+	g_value_init (return_accu, G_TYPE_VALUE_ARRAY);
+	g_value_set_boxed (return_accu, a);
+	return TRUE;
+}
+
 void
 rb_value_array_append_data (GValueArray *array, GType type, ...)
 {
diff --git a/lib/rb-util.h b/lib/rb-util.h
index c76522d..08941ce 100644
--- a/lib/rb-util.h
+++ b/lib/rb-util.h
@@ -76,6 +76,10 @@ gboolean rb_signal_accumulator_object_handled (GSignalInvocationHint *hint,
 					       GValue *return_accu,
 					       const GValue *handler_return,
 					       gpointer dummy);
+gboolean rb_signal_accumulator_value_array (GSignalInvocationHint *hint,
+					    GValue *return_accu,
+					    const GValue *handler_return,
+					    gpointer dummy);
 void rb_value_array_append_data (GValueArray *array, GType type, ...);
 void rb_value_free (GValue *val); /* g_value_unset, g_slice_free */
 
diff --git a/plugins/iradio/Makefile.am b/plugins/iradio/Makefile.am
index 72530fd..f2de0ab 100644
--- a/plugins/iradio/Makefile.am
+++ b/plugins/iradio/Makefile.am
@@ -3,11 +3,13 @@ NULL =
 plugindir = $(PLUGINDIR)/iradio
 plugin_LTLIBRARIES = libiradio.la
 
-libiradio_la_SOURCES = \
-	rb-iradio-plugin.c					\
-	rb-iradio-source.c		\
-	rb-iradio-source.h		\
-	rb-station-properties-dialog.c	\
+libiradio_la_SOURCES = 					\
+	rb-iradio-plugin.c				\
+	rb-iradio-source.c				\
+	rb-iradio-source.h				\
+	rb-iradio-source-search.c			\
+	rb-iradio-source-search.h			\
+	rb-station-properties-dialog.c			\
 	rb-station-properties-dialog.h
 
 libiradio_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
diff --git a/plugins/iradio/rb-iradio-source-search.c b/plugins/iradio/rb-iradio-source-search.c
new file mode 100644
index 0000000..056e5be
--- /dev/null
+++ b/plugins/iradio/rb-iradio-source-search.c
@@ -0,0 +1,63 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2008  Jonathan Matthew  <jonathan@d14n.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 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#include "config.h"
+
+#include "rb-iradio-source-search.h"
+
+static void	rb_iradio_source_search_class_init (RBIRadioSourceSearchClass *klass);
+static void	rb_iradio_source_search_init (RBIRadioSourceSearch *search);
+
+G_DEFINE_TYPE (RBIRadioSourceSearch, rb_iradio_source_search, RB_TYPE_SOURCE_SEARCH)
+
+static RhythmDBQuery *
+impl_create_query (RBSourceSearch *bsearch, RhythmDB *db, const char *search_text)
+{
+	return rhythmdb_query_parse (db,
+				     RHYTHMDB_QUERY_PROP_LIKE,
+				     RHYTHMDB_PROP_GENRE_FOLDED,
+				     search_text,
+				     RHYTHMDB_QUERY_DISJUNCTION,
+				     RHYTHMDB_QUERY_PROP_LIKE,
+				     RHYTHMDB_PROP_TITLE_FOLDED,
+				     search_text,
+				     RHYTHMDB_QUERY_END);
+}
+
+static void
+rb_iradio_source_search_class_init (RBIRadioSourceSearchClass *klass)
+{
+	RBSourceSearchClass *search_class = RB_SOURCE_SEARCH_CLASS (klass);
+	search_class->create_query = impl_create_query;
+}
+
+static void
+rb_iradio_source_search_init (RBIRadioSourceSearch *search)
+{
+	/* nothing */
+}
+
+
+RBSourceSearch *
+rb_iradio_source_search_new ()
+{
+	return g_object_new (RB_TYPE_IRADIO_SOURCE_SEARCH, NULL);
+}
+
diff --git a/plugins/iradio/rb-iradio-source-search.h b/plugins/iradio/rb-iradio-source-search.h
new file mode 100644
index 0000000..75eba34
--- /dev/null
+++ b/plugins/iradio/rb-iradio-source-search.h
@@ -0,0 +1,58 @@
+/*
+ *  Copyright (C) 2008 Jonathan Matthew  <jonathan@d14n.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 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#ifndef __RB_IRADIO_IRADIO_SOURCE_SEARCH_H
+#define __RB_IRADIO_IRADIO_SOURCE_SEARCH_H
+
+/*
+ * Source search implementation for iradio (searches title and genre)
+ */
+
+#include <gtk/gtk.h>
+
+#include <rb-source-search.h>
+#include <rhythmdb.h>
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_IRADIO_SOURCE_SEARCH	(rb_iradio_source_search_get_type())
+#define RB_IRADIO_SOURCE_SEARCH(o)     (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_IRADIO_SOURCE_SEARCH, RBIRadioSourceSearch))
+#define RB_IRADIO_SOURCE_SEARCH_CLASS(o) (G_TYPE_CHECK_CLASS_CAST ((o), RB_TYPE_IRADIO_SOURCE_SEARCH, RBIRadioSourceSearchClass))
+#define RB_IS_IRADIO_SOURCE_SEARCH(o)  (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_IRADIO_SOURCE_SEARCH))
+#define RB_IS_IRADIO_SOURCE_SEARCH_CLASS(o)  (G_TYPE_CHECK_CLASS_TYPE ((o), RB_TYPE_IRADIO_SOURCE_SEARCH))
+#define RB_IRADIO_SOURCE_SEARCH_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_IRADIO_SOURCE_SEARCH, RBIRadioSourceSearchClass))
+
+typedef struct
+{
+	RBSourceSearch parent;
+} RBIRadioSourceSearch;
+
+typedef struct
+{
+	RBSourceSearchClass parent_class;
+} RBIRadioSourceSearchClass;
+
+GType		rb_iradio_source_search_get_type	(void);
+
+RBSourceSearch *rb_iradio_source_search_new 		(void);
+
+G_END_DECLS
+
+#endif	/* __RB_IRADIO_SOURCE_SEARCH_H */
+
diff --git a/plugins/iradio/rb-iradio-source.c b/plugins/iradio/rb-iradio-source.c
index 5889d56..03d97db 100644
--- a/plugins/iradio/rb-iradio-source.c
+++ b/plugins/iradio/rb-iradio-source.c
@@ -31,6 +31,7 @@
 #include <libxml/tree.h>
 
 #include "rb-iradio-source.h"
+#include "rb-iradio-source-search.h"
 
 #include "rhythmdb-query-model.h"
 #include "rb-glade-helpers.h"
@@ -51,6 +52,7 @@
 #include "rb-metadata.h"
 #include "rb-plugin.h"
 #include "rb-cut-and-paste-code.h"
+#include "rb-source-search-basic.h"
 
 /* icon names */
 #define IRADIO_SOURCE_ICON  "library-internet-radio"
@@ -96,7 +98,7 @@ static char *guess_uri_scheme (const char *uri);
 static void impl_get_status (RBSource *source, char **text, char **progress_text, float *progress);
 static char *impl_get_browser_key (RBSource *source);
 static RBEntryView *impl_get_entry_view (RBSource *source);
-static void impl_search (RBSource *source, const char *text);
+static void impl_search (RBSource *source, RBSourceSearch *search, const char *cur_text, const char *new_text);
 static void impl_delete (RBSource *source);
 static void impl_song_properties (RBSource *source);
 static gboolean impl_show_popup (RBSource *source);
@@ -144,10 +146,9 @@ struct RBIRadioSourcePrivate
 	RBEntryView *stations;
 	gboolean setting_new_query;
 
-	gboolean initialized;
-
-	char *search_text;
 	char *selected_genre;
+	RhythmDBQuery *search_query;
+	RBSourceSearch *default_search;
 
 	guint prefs_notify_id;
 	guint first_time_notify_id;
@@ -259,6 +260,16 @@ rb_iradio_source_dispose (GObject *object)
 		source->priv->action_group = NULL;
 	}
 
+	if (source->priv->default_search != NULL) {
+		g_object_unref (source->priv->default_search);
+		source->priv->default_search = NULL;
+	}
+
+	if (source->priv->search_query != NULL) {
+		rhythmdb_query_free (source->priv->search_query);
+		source->priv->search_query = NULL;
+	}
+
 	eel_gconf_notification_remove (source->priv->prefs_notify_id);
 	eel_gconf_notification_remove (source->priv->first_time_notify_id);
 
@@ -369,6 +380,8 @@ rb_iradio_source_constructor (GType type,
 				 G_CALLBACK (playing_source_changed_cb),
 				 source, 0);
 
+	source->priv->default_search = rb_iradio_source_search_new ();
+
 	rb_iradio_source_do_query (source);
 
 	return G_OBJECT (source);
@@ -518,28 +531,23 @@ rb_iradio_source_add_station (RBIRadioSource *source,
 
 	g_free (real_uri);
 }
-
 static void
 impl_search (RBSource *asource,
-	     const char *search_text)
+	     RBSourceSearch *search,
+	     const char *cur_text,
+	     const char *new_text)
 {
 	RBIRadioSource *source = RB_IRADIO_SOURCE (asource);
 
-	if (source->priv->initialized) {
-		if (search_text == NULL && source->priv->search_text == NULL)
-			return;
-		if (search_text != NULL &&
-		    source->priv->search_text != NULL
-		    && !strcmp (search_text, source->priv->search_text))
-			return;
+	if (source->priv->search_query != NULL) {
+		rhythmdb_query_free (source->priv->search_query);
 	}
 
-	source->priv->initialized = TRUE;
-	if (search_text != NULL && search_text[0] == '\0')
-		search_text = NULL;
+	if (search == NULL) {
+		search = source->priv->default_search;
+	}
+	source->priv->search_query = rb_source_search_create_query (search, source->priv->db, new_text);
 
-	g_free (source->priv->search_text);
-	source->priv->search_text = g_strdup (search_text);
 	rb_iradio_source_do_query (source);
 
 	rb_source_notify_filter_changed (RB_SOURCE (source));
@@ -765,26 +773,12 @@ rb_iradio_source_do_query (RBIRadioSource *source)
 				      RHYTHMDB_QUERY_END);
 	g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);
 
-	if (source->priv->search_text != NULL) {
-		GPtrArray *subquery;
-
-		subquery = rhythmdb_query_parse (source->priv->db,
-						 RHYTHMDB_QUERY_PROP_LIKE,
-						 RHYTHMDB_PROP_GENRE_FOLDED,
-						 source->priv->search_text,
-						 RHYTHMDB_QUERY_DISJUNCTION,
-						 RHYTHMDB_QUERY_PROP_LIKE,
-						 RHYTHMDB_PROP_TITLE_FOLDED,
-						 source->priv->search_text,
-						 RHYTHMDB_QUERY_END);
-		rb_debug ("searching for \"%s\"", source->priv->search_text);
+	if (source->priv->search_query != NULL) {
 		rhythmdb_query_append (source->priv->db,
 				       query,
 				       RHYTHMDB_QUERY_SUBQUERY,
-				       subquery,
+				       source->priv->search_query,
 				       RHYTHMDB_QUERY_END);
-
-		rhythmdb_query_free (subquery);
 	}
 
 	genre_model = rb_property_view_get_model (source->priv->genres);
diff --git a/shell/rb-shell.c b/shell/rb-shell.c
index 90012be..c7e8d77 100644
--- a/shell/rb-shell.c
+++ b/shell/rb-shell.c
@@ -286,6 +286,7 @@ enum
 	PROP_LIBRARY_SOURCE,
 	PROP_SOURCELIST_MODEL,
 	PROP_SOURCELIST,
+	PROP_SOURCE_HEADER,
 	PROP_VISIBILITY,
 };
 
@@ -647,6 +648,13 @@ rb_shell_class_init (RBShellClass *klass)
 							       "Current window visibility",
 							       TRUE,
 							       G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+					 PROP_SOURCE_HEADER,
+					 g_param_spec_object ("source-header",
+							      "source header widget",
+							      "RBSourceHeader",
+							      RB_TYPE_SOURCE_HEADER,
+							      G_PARAM_READABLE));
 
 	rb_shell_signals[VISIBILITY_CHANGED] =
 		g_signal_new ("visibility_changed",
@@ -808,6 +816,9 @@ rb_shell_get_property (GObject *object,
 	case PROP_VISIBILITY:
 		g_value_set_boolean (value, rb_shell_get_visibility (shell));
 		break;
+	case PROP_SOURCE_HEADER:
+		g_value_set_object (value, shell->priv->source_header);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
diff --git a/shell/rb-source-header.c b/shell/rb-source-header.c
index 85b6975..d3437db 100644
--- a/shell/rb-source-header.c
+++ b/shell/rb-source-header.c
@@ -37,10 +37,12 @@
 #include "rb-entry-view.h"
 #include "eel-gconf-extensions.h"
 #include "rb-util.h"
+#include "rb-marshal.h"
 
 static void rb_source_header_class_init (RBSourceHeaderClass *klass);
 static void rb_source_header_init (RBSourceHeader *shell_player);
 static void rb_source_header_finalize (GObject *object);
+static void rb_source_header_dispose (GObject *object);
 static void rb_source_header_set_property (GObject *object,
 					  guint prop_id,
 					  const GValue *value,
@@ -59,15 +61,23 @@ static void rb_source_header_search_activate_cb (RBSearchEntry *search,
 static void rb_source_header_view_browser_changed_cb (GtkAction *action,
 						      RBSourceHeader *header);
 static void rb_source_header_source_weak_destroy_cb (RBSourceHeader *header, RBSource *source);
+static void search_action_changed_cb (GtkRadioAction *action,
+				      GtkRadioAction *current,
+				      RBSourceHeader *header);
+static void rb_source_header_refresh_search_bar (RBSourceHeader *header);
 
 typedef struct {
-	gboolean disclosed;
-	char     *search_text;
-}  SourceState;
+	gboolean 	disclosed;
+	char     	*search_text;
+	GtkRadioAction  *search_action;
+} SourceState;
 
 static void
 sourcestate_free (SourceState *state)
 {
+	if (state->search_action != NULL) {
+		g_object_unref (state->search_action);
+	}
         g_free (state->search_text);
         g_free (state);
 }
@@ -84,6 +94,7 @@ struct RBSourceHeaderPrivate
 
 	GtkWidget *search;
 	GtkWidget *search_bar;
+	GtkRadioAction *search_group_head;
 
 	guint browser_notify_id;
 	guint search_notify_id;
@@ -93,7 +104,6 @@ struct RBSourceHeaderPrivate
 	char *browser_key;
 
 	GHashTable *source_states;
-
 };
 
 #define RB_SOURCE_HEADER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_SOURCE_HEADER, RBSourceHeaderPrivate))
@@ -112,7 +122,15 @@ static GtkToggleActionEntry rb_source_header_toggle_entries [] =
 	  N_("Change the visibility of the browser"),
 	  G_CALLBACK (rb_source_header_view_browser_changed_cb), FALSE }
 };
-static guint rb_source_header_n_toggle_entries = G_N_ELEMENTS (rb_source_header_toggle_entries);
+
+enum
+{
+	GET_SEARCH_ACTIONS,
+	REFRESH_SEARCH_BAR,
+	LAST_SIGNAL
+};
+
+static guint rb_source_header_signals[LAST_SIGNAL] = { 0 };
 
 G_DEFINE_TYPE (RBSourceHeader, rb_source_header, GTK_TYPE_TABLE)
 
@@ -188,11 +206,14 @@ rb_source_header_class_init (RBSourceHeaderClass *klass)
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
 	object_class->finalize = rb_source_header_finalize;
+	object_class->dispose = rb_source_header_dispose;
 	object_class->constructor = rb_source_header_constructor;
 
 	object_class->set_property = rb_source_header_set_property;
 	object_class->get_property = rb_source_header_get_property;
 
+	klass->refresh_search_bar = rb_source_header_refresh_search_bar;
+
 	g_object_class_install_property (object_class,
 					 PROP_SOURCE,
 					 g_param_spec_object ("source",
@@ -215,6 +236,27 @@ rb_source_header_class_init (RBSourceHeaderClass *klass)
 							      GTK_TYPE_UI_MANAGER,
 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
+	rb_source_header_signals[GET_SEARCH_ACTIONS] =
+		g_signal_new ("get-search-actions",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      0,		/* no need to handle this ourselves */
+			      rb_signal_accumulator_value_array, NULL,
+			      rb_marshal_BOXED__OBJECT,
+			      G_TYPE_VALUE_ARRAY,
+			      1,
+			      RB_TYPE_SOURCE);
+
+	rb_source_header_signals[REFRESH_SEARCH_BAR] =
+		g_signal_new ("refresh-search-bar",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBSourceHeaderClass, refresh_search_bar),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
+
 	g_type_class_add_private (klass, sizeof (RBSourceHeaderPrivate));
 }
 
@@ -256,6 +298,16 @@ rb_source_header_init (RBSourceHeader *header)
 
 	header->priv->source_states = g_hash_table_new_full (g_direct_hash, g_direct_equal,
 							    NULL, (GDestroyNotify)sourcestate_free);
+
+	/* invisible action at the start of the search bar, used to
+	 * simplify the radio action group handling.
+	 */
+	header->priv->search_group_head = gtk_radio_action_new ("InvisibleSearchHead", NULL, NULL, NULL, 0);
+	gtk_action_set_visible (GTK_ACTION (header->priv->search_group_head), FALSE);
+
+	g_signal_connect_object (header->priv->search_group_head,
+				 "changed",
+				 G_CALLBACK (search_action_changed_cb), header, 0);
 }
 
 static void
@@ -267,6 +319,26 @@ rb_source_header_source_weak_unref (RBSource *source, char *text, RBSourceHeader
 }
 
 static void
+rb_source_header_dispose (GObject *object)
+{
+	RBSourceHeader *header;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (RB_IS_SOURCE_HEADER (object));
+
+	header = RB_SOURCE_HEADER (object);
+
+	g_return_if_fail (header->priv != NULL);
+
+	if (header->priv->search_group_head != NULL) {
+		g_object_unref (header->priv->search_group_head);
+		header->priv->search_group_head = NULL;
+	}
+
+	G_OBJECT_CLASS (rb_source_header_parent_class)->finalize (object);
+}
+
+static void
 rb_source_header_finalize (GObject *object)
 {
 	RBSourceHeader *header;
@@ -292,6 +364,10 @@ static void
 merge_source_ui_cb (const char *action,
 		    RBSourceHeader *header)
 {
+	GSList *group;
+	GtkAction *radio_action;
+	char *path;
+
 	gtk_ui_manager_add_ui (header->priv->ui_manager,
 			       header->priv->source_ui_merge_id,
 			       "/SearchBar",
@@ -299,19 +375,102 @@ merge_source_ui_cb (const char *action,
 			       action,
 			       GTK_UI_MANAGER_AUTO,
 			       FALSE);
+
+	/* find the action */
+	path = g_strdup_printf ("/SearchBar/%s", action);
+	radio_action = gtk_ui_manager_get_action (header->priv->ui_manager, path);
+	g_free (path);
+	g_assert (radio_action);
+	
+	/* add it to the group */
+	group = gtk_radio_action_get_group (header->priv->search_group_head);
+	gtk_radio_action_set_group (GTK_RADIO_ACTION (radio_action), group);
+
+	/* ensure it isn't active; we'll activate the right one later */
+	gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (radio_action), FALSE);
+}
+
+static void
+rb_source_header_refresh_search_bar (RBSourceHeader *header)
+{
+	GSList *group;
+	GSList *t;
+
+	if (header->priv->source_ui_merge_id != 0) {
+		gtk_ui_manager_remove_ui (header->priv->ui_manager, header->priv->source_ui_merge_id);
+	}
+
+	/* do we need this here? */
+	gtk_ui_manager_ensure_update (header->priv->ui_manager);
+
+	/* dismember the search action group */
+	group = gtk_radio_action_get_group (header->priv->search_group_head);
+	group = g_slist_copy (group);
+
+	for (t = group; t != NULL; t = t->next) {
+		GtkRadioAction *a = (GtkRadioAction *)t->data;
+		if (a != header->priv->search_group_head) {
+			gtk_radio_action_set_group (a, NULL);
+		}
+	}
+
+	g_slist_free (group);
+
+	gtk_ui_manager_add_ui (header->priv->ui_manager,
+			       header->priv->source_ui_merge_id,
+			       "/SearchBar",
+			       "InvisibleSearchHead",
+			       "InvisibleSearchHead",
+			       GTK_UI_MANAGER_AUTO,
+			       FALSE);
+
+	if (header->priv->selected_source != NULL) {
+		SourceState *source_state;
+		GList *actions;
+		GValueArray *plugin_actions;
+
+		source_state = g_hash_table_lookup (header->priv->source_states,
+						    header->priv->selected_source);
+
+		/* merge the source-specific UI */
+		actions = rb_source_get_search_actions (header->priv->selected_source);
+		g_list_foreach (actions, (GFunc)merge_source_ui_cb, header);
+		rb_list_deep_free (actions);
+
+		/* merge in plugin-supplied search actions */
+		g_signal_emit (header,
+			       rb_source_header_signals[GET_SEARCH_ACTIONS], 0,
+			       header->priv->selected_source,
+			       &plugin_actions);
+		if (plugin_actions != NULL) {
+			int i;
+			for (i = 0; i < plugin_actions->n_values; i++) {
+				GValue *v = g_value_array_get_nth (plugin_actions, i);
+				const char *action;
+
+				action = g_value_get_string (v);
+				merge_source_ui_cb (action, header);
+			}
+
+			g_value_array_free (plugin_actions);
+		}
+
+		/* select the active search for the source */
+		if (source_state != NULL && source_state->search_action != NULL) {
+			gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (source_state->search_action),
+						      TRUE);
+		}
+	}
 }
 
 static void
 rb_source_header_set_source_internal (RBSourceHeader *header,
 				      RBSource *source)
 {
-	GList *actions;
-
 	if (header->priv->selected_source != NULL) {
 		g_signal_handlers_disconnect_by_func (G_OBJECT (header->priv->selected_source),
 						      G_CALLBACK (rb_source_header_filter_changed_cb),
 						      header);
-		gtk_ui_manager_remove_ui (header->priv->ui_manager, header->priv->source_ui_merge_id);
 	}
 
 	header->priv->selected_source = source;
@@ -346,28 +505,28 @@ rb_source_header_set_source_internal (RBSourceHeader *header,
 					  rb_source_can_search (header->priv->selected_source));
 		header->priv->have_search = rb_source_can_search (header->priv->selected_source);
 		header->priv->have_browser = rb_source_can_browse (header->priv->selected_source);
-		if (!header->priv->have_browser)
+		if (!header->priv->have_browser) {
 			header->priv->disclosed = FALSE;
-		else if (header->priv->browser_key)
+		} else if (header->priv->browser_key) {
 			header->priv->disclosed = eel_gconf_get_boolean (header->priv->browser_key);
-		else
+		} else {
 			/* restore the previous state of the source*/
 			header->priv->disclosed = disclosed;
+		}
 
-		if (!header->priv->have_browser && !header->priv->have_search)
+		if (!header->priv->have_browser && !header->priv->have_search) {
 			gtk_widget_hide (GTK_WIDGET (header));
-		else
+		} else {
 			gtk_widget_show (GTK_WIDGET (header));
+		}
 
-		/* merge the source-specific UI */
-		actions = rb_source_get_search_actions (source);
-		g_list_foreach (actions, (GFunc)merge_source_ui_cb, header);
-		rb_list_deep_free (actions);
 	} else {
 		/* no selected source -> hide source header */
 		gtk_widget_hide (GTK_WIDGET (header));
 	}
 
+	rb_source_header_refresh_search_bar (header);
+
 	rb_source_header_sync_control_state (header);
 }
 
@@ -386,9 +545,11 @@ rb_source_header_set_property (GObject *object,
 		break;
 	case PROP_ACTION_GROUP:
 		header->priv->actiongroup = g_value_get_object (value);
+		gtk_action_group_add_action (header->priv->actiongroup,
+					     GTK_ACTION (header->priv->search_group_head));
 		gtk_action_group_add_toggle_actions (header->priv->actiongroup,
 						     rb_source_header_toggle_entries,
-						     rb_source_header_n_toggle_entries,
+						     G_N_ELEMENTS (rb_source_header_toggle_entries),
 						     header);
 		break;
 	case PROP_UI_MANAGER:
@@ -473,34 +634,93 @@ static void
 rb_source_state_sync (RBSourceHeader *header,
 		      gboolean set_text,
 		      const char *text,
+		      gboolean set_search,
+		      GtkRadioAction *action,
 		      gboolean set_disclosure,
 		      gboolean disclosed)
 {
-	SourceState *old_state;
-
-	old_state = g_hash_table_lookup (header->priv->source_states,
-					 header->priv->selected_source);
-
-	if (old_state) {
-		if (set_text)
-			old_state->search_text = text ? g_strdup (text) : NULL;
-		if (set_disclosure)
-			old_state->disclosed = disclosed;
-	} else {
-		SourceState *new_state;
+	SourceState *state;
+	gboolean do_search = FALSE;
+	char *old_text = NULL;
 
+	state = g_hash_table_lookup (header->priv->source_states,
+				     header->priv->selected_source);
+	if (state == NULL) {
 		/* if we haven't seen the source before, monitor it for deletion */
 		g_object_weak_ref (G_OBJECT (header->priv->selected_source),
 				   (GWeakNotify)rb_source_header_source_weak_destroy_cb,
 				   header);
 
-		new_state = g_new (SourceState, 1);
-		new_state->search_text = text ? g_strdup (text) : NULL;
-		new_state->disclosed = disclosed;
+		state = g_new0 (SourceState, 1);
 		g_hash_table_insert (header->priv->source_states,
 				     header->priv->selected_source,
-				     new_state);
+				     state);
+		do_search = TRUE;
+	}
+
+	if (set_text) {
+		if (rb_safe_strcmp (state->search_text, text) != 0) {
+			old_text = state->search_text;
+			do_search = TRUE;
+		} else {
+			old_text = NULL;
+			g_free (state->search_text);
+		}
+		state->search_text = text ? g_strdup (text) : NULL;
+	}
+
+	if (set_disclosure) {
+		state->disclosed = disclosed;
 	}
+
+	if (set_search) {
+		if (state->search_action != action) {
+			do_search = TRUE;
+		}
+		if (state->search_action != NULL) {
+			g_object_unref (state->search_action);
+			state->search_action = NULL;
+		}
+		if (action != NULL) {
+			state->search_action = g_object_ref (action);
+		}
+	}
+
+	if (do_search) {
+		RBSourceSearch *search = NULL;
+
+		if (state->search_action != NULL) {
+			search = rb_source_search_get_from_action (G_OBJECT (state->search_action));
+		}
+
+		rb_source_search (header->priv->selected_source,
+				  search,
+				  old_text,
+				  state->search_text);
+	}
+
+	g_free (old_text);
+}
+
+static void
+search_action_changed_cb (GtkRadioAction *action,
+			  GtkRadioAction *current,
+			  RBSourceHeader *header)
+{
+	if (header->priv->selected_source == NULL) {
+		return;
+	}
+
+	if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current)) == FALSE) {
+		return;
+	}
+
+	rb_debug ("search action %s activated", gtk_action_get_name (GTK_ACTION (current)));
+	rb_source_state_sync (header,
+			      FALSE, NULL,
+			      TRUE, current,
+			      FALSE, FALSE);
+	rb_source_header_sync_control_state (header);
 }
 
 static void
@@ -509,11 +729,13 @@ rb_source_header_search_cb (RBSearchEntry *search,
 			    RBSourceHeader *header)
 {
 
-	rb_debug  ("searching for \"%s\"", text);
+	rb_debug ("searching for \"%s\"", text);
 
-	rb_source_state_sync (header, TRUE, text, FALSE, FALSE);
+	rb_source_state_sync (header,
+			      TRUE, text,
+			      FALSE, NULL,
+			      FALSE, FALSE);
 
-	rb_source_search (header->priv->selected_source, text);
 	rb_source_header_sync_control_state (header);
 }
 
@@ -523,13 +745,15 @@ rb_source_header_clear_search (RBSourceHeader *header)
 	rb_debug ("clearing search");
 
 	if (!rb_search_entry_searching (RB_SEARCH_ENTRY (header->priv->search)))
-	    return;
+		return;
 
 	if (header->priv->selected_source) {
-		rb_source_search (header->priv->selected_source, NULL);
-		rb_source_state_sync (header, TRUE, NULL, FALSE, FALSE);
-
+		rb_source_state_sync (header,
+				      TRUE, NULL,
+				      FALSE, NULL,
+				      FALSE, FALSE);
 	}
+
 	rb_search_entry_clear (RB_SEARCH_ENTRY (header->priv->search));
 	rb_source_header_sync_control_state (header);
 }
@@ -541,11 +765,14 @@ rb_source_header_view_browser_changed_cb (GtkAction *action,
 	rb_debug ("got view browser toggle");
 	header->priv->disclosed = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
 
-	if (header->priv->browser_key)
+	if (header->priv->browser_key) {
 		eel_gconf_set_boolean (header->priv->browser_key,
 				       header->priv->disclosed);
-	else {
-		rb_source_state_sync (header, FALSE, NULL, TRUE, header->priv->disclosed);
+	} else {
+		rb_source_state_sync (header,
+				      FALSE, NULL,
+				      FALSE, NULL,
+				      TRUE, header->priv->disclosed);
 	}
 
 	rb_debug ("got view browser toggle");
diff --git a/shell/rb-source-header.h b/shell/rb-source-header.h
index fd807f0..27ad299 100644
--- a/shell/rb-source-header.h
+++ b/shell/rb-source-header.h
@@ -48,6 +48,9 @@ typedef struct
 typedef struct
 {
 	GtkTableClass parent_class;
+
+	/* action signal */
+	void	(*refresh_search_bar) (RBSourceHeader *header);
 } RBSourceHeaderClass;
 
 GType			rb_source_header_get_type	(void);
@@ -58,8 +61,6 @@ RBSourceHeader *	rb_source_header_new		(GtkUIManager   *mgr,
 void			rb_source_header_set_source	(RBSourceHeader *header,
 							 RBSource *player);
 
-RBSource *		rb_source_header_get_source	(RBSourceHeader *shell_player);
-
 void			rb_source_header_clear_search	(RBSourceHeader *header);
 void			rb_source_header_sync_control_state (RBSourceHeader *header);
 void			rb_source_header_focus_search_box (RBSourceHeader *header);
diff --git a/sources/Makefile.am b/sources/Makefile.am
index 0b3c4e7..a85aef6 100644
--- a/sources/Makefile.am
+++ b/sources/Makefile.am
@@ -9,6 +9,8 @@ libsources_la_SOURCES = 		\
 	rb-streaming-source.h		\
 	rb-source-group.c		\
 	rb-source-group.h		\
+	rb-source-search.c		\
+	rb-source-search.h		\
 	$(NULL)
 
 libsourcesimpl_la_SOURCES =		\
@@ -37,6 +39,8 @@ libsourcesimpl_la_SOURCES =		\
 	rb-missing-files-source.h	\
 	rb-import-errors-source.c	\
 	rb-import-errors-source.h	\
+	rb-source-search-basic.c	\
+	rb-source-search-basic.h	\
 	$(NULL)
 
 INCLUDES =						\
diff --git a/sources/rb-auto-playlist-source.c b/sources/rb-auto-playlist-source.c
index 8ba034c..1ad9ace 100644
--- a/sources/rb-auto-playlist-source.c
+++ b/sources/rb-auto-playlist-source.c
@@ -34,6 +34,7 @@
 #include "eel-gconf-extensions.h"
 #include "rb-stock-icons.h"
 #include "rb-playlist-xml.h"
+#include "rb-source-search-basic.h"
 
 static GObject *rb_auto_playlist_source_constructor (GType type, guint n_construct_properties,
 						      GObjectConstructParam *construct_properties);
@@ -43,7 +44,7 @@ static void rb_auto_playlist_source_finalize (GObject *object);
 /* source methods */
 static gboolean impl_show_popup (RBSource *source);
 static gboolean impl_receive_drag (RBSource *asource, GtkSelectionData *data);
-static void impl_search (RBSource *asource, const char *search_text);
+static void impl_search (RBSource *source, RBSourceSearch *search, const char *cur_text, const char *new_text);
 static void impl_reset_filters (RBSource *asource);
 static void impl_browser_toggled (RBSource *source, gboolean enabled);
 static GList *impl_get_search_actions (RBSource *source);
@@ -64,16 +65,13 @@ void rb_auto_playlist_source_browser_views_activated_cb (GtkWidget *widget,
 static void rb_auto_playlist_source_browser_changed_cb (RBLibraryBrowser *entry,
 							GParamSpec *pspec,
 							RBAutoPlaylistSource *source);
-static void search_action_changed (GtkRadioAction *action,
-				   GtkRadioAction *current,
-				   RBShell *shell);
 
 static GtkRadioActionEntry rb_auto_playlist_source_radio_actions [] =
 {
-	{ "AutoPlaylistSearchAll", NULL, N_("All"), NULL, N_("Search all fields"), 0 },
-	{ "AutoPlaylistSearchArtists", NULL, N_("Artists"), NULL, N_("Search artists"), 1 },
-	{ "AutoPlaylistSearchAlbums", NULL, N_("Albums"), NULL, N_("Search albums"), 2 },
-	{ "AutoPlaylistSearchTitles", NULL, N_("Titles"), NULL, N_("Search titles"), 3 }
+	{ "AutoPlaylistSearchAll", NULL, N_("All"), NULL, N_("Search all fields"), RHYTHMDB_PROP_SEARCH_MATCH },
+	{ "AutoPlaylistSearchArtists", NULL, N_("Artists"), NULL, N_("Search artists"), RHYTHMDB_PROP_ARTIST_FOLDED },
+	{ "AutoPlaylistSearchAlbums", NULL, N_("Albums"), NULL, N_("Search albums"), RHYTHMDB_PROP_ALBUM_FOLDED },
+	{ "AutoPlaylistSearchTitles", NULL, N_("Titles"), NULL, N_("Search titles"), RHYTHMDB_PROP_TITLE_FOLDED }
 };
 
 #define AUTO_PLAYLIST_SOURCE_POPUP_PATH "/AutoPlaylistSourcePopup"
@@ -95,16 +93,16 @@ struct _RBAutoPlaylistSourcePrivate
 	RBLibraryBrowser *browser;
 	gboolean browser_shown;
 
-	char *search_text;
+	RBSourceSearch *default_search;
+	RhythmDBQuery *search_query;
 
 	GtkActionGroup *action_group;
-	RhythmDBPropType search_prop;
 };
 
 static gpointer playlist_pixbuf = NULL;
 
 G_DEFINE_TYPE (RBAutoPlaylistSource, rb_auto_playlist_source, RB_TYPE_PLAYLIST_SOURCE)
-#define RB_AUTO_PLAYLIST_SOURCE_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), RB_TYPE_AUTO_PLAYLIST_SOURCE, RBAutoPlaylistSourcePrivate))
+#define GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), RB_TYPE_AUTO_PLAYLIST_SOURCE, RBAutoPlaylistSourcePrivate))
 
 static void
 rb_auto_playlist_source_class_init (RBAutoPlaylistSourceClass *klass)
@@ -163,36 +161,43 @@ rb_auto_playlist_source_init (RBAutoPlaylistSource *source)
 static void
 rb_auto_playlist_source_dispose (GObject *object)
 {
-	RBAutoPlaylistSourcePrivate *priv = RB_AUTO_PLAYLIST_SOURCE_GET_PRIVATE (object);
+	RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (object);
 
 	if (priv->action_group != NULL) {
 		g_object_unref (priv->action_group);
 		priv->action_group = NULL;
 	}
 
-	if (priv->cached_all_query) {
-		g_object_unref (G_OBJECT (priv->cached_all_query));
+	if (priv->cached_all_query != NULL) {
+		g_object_unref (priv->cached_all_query);
 		priv->cached_all_query = NULL;
 	}
 
+	if (priv->default_search != NULL) {
+		g_object_unref (priv->default_search);
+		priv->default_search = NULL;
+	}
+
 	G_OBJECT_CLASS (rb_auto_playlist_source_parent_class)->dispose (object);
 }
 
 static void
 rb_auto_playlist_source_finalize (GObject *object)
 {
-	RBAutoPlaylistSourcePrivate *priv = RB_AUTO_PLAYLIST_SOURCE_GET_PRIVATE (object);
+	RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (object);
 
 	if (priv->query) {
 		rhythmdb_query_free (priv->query);
 	}
+	
+	if (priv->search_query) {
+		rhythmdb_query_free (priv->search_query);
+	}
 
 	if (priv->limit_value) {
 		g_value_array_free (priv->limit_value);
 	}
 
-	g_free (priv->search_text);
-
 	G_OBJECT_CLASS (rb_auto_playlist_source_parent_class)->finalize (object);
 }
 
@@ -209,7 +214,7 @@ rb_auto_playlist_source_constructor (GType type, guint n_construct_properties,
 
 	source = RB_AUTO_PLAYLIST_SOURCE (
 			parent_class->constructor (type, n_construct_properties, construct_properties));
-	priv = RB_AUTO_PLAYLIST_SOURCE_GET_PRIVATE (source);
+	priv = GET_PRIVATE (source);
 
 	priv->paned = gtk_vpaned_new ();
 
@@ -238,10 +243,14 @@ rb_auto_playlist_source_constructor (GType type, guint n_construct_properties,
 						    rb_auto_playlist_source_radio_actions,
 						    G_N_ELEMENTS (rb_auto_playlist_source_radio_actions),
 						    0,
-						    (GCallback)search_action_changed,
-						    shell);
+						    NULL,
+						    NULL);
+		rb_source_search_basic_create_for_actions (priv->action_group,
+							   rb_auto_playlist_source_radio_actions,
+							   G_N_ELEMENTS (rb_auto_playlist_source_radio_actions));
 	}
-	priv->search_prop = RHYTHMDB_PROP_SEARCH_MATCH;
+	priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH);
+
 	g_object_unref (shell);
 
 	/* reparent the entry view */
@@ -375,16 +384,16 @@ impl_show_popup (RBSource *source)
 static void
 impl_reset_filters (RBSource *source)
 {
-	RBAutoPlaylistSourcePrivate *priv = RB_AUTO_PLAYLIST_SOURCE_GET_PRIVATE (source);
+	RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (source);
 	gboolean changed = FALSE;
 
 	if (rb_library_browser_reset (priv->browser))
 		changed = TRUE;
 
-	if (priv->search_text != NULL) {
+	if (priv->search_query != NULL) {
 		changed = TRUE;
-		g_free (priv->search_text);
-		priv->search_text = NULL;
+		rhythmdb_query_free (priv->search_query);
+		priv->search_query = NULL;
 	}
 
 	if (changed)
@@ -392,49 +401,39 @@ impl_reset_filters (RBSource *source)
 }
 
 static void
-impl_search (RBSource *source, const char *search_text)
+impl_search (RBSource *asource, RBSourceSearch *search, const char *cur_text, const char *new_text)
 {
-	RBAutoPlaylistSourcePrivate *priv = RB_AUTO_PLAYLIST_SOURCE_GET_PRIVATE (source);
-	char *old_search_text = NULL;
-	gboolean subset = FALSE;
-	const char *debug_search_text;
-
-	if (search_text != NULL && search_text[0] == '\0')
-		search_text = NULL;
-
-	if (search_text == NULL && priv->search_text == NULL)
-		return;
-	if (search_text != NULL && priv->search_text != NULL
-	    && !strcmp (search_text, priv->search_text))
-		return;
-
-	old_search_text = priv->search_text;
-	if (search_text == NULL) {
-		priv->search_text = NULL;
-		debug_search_text = "(NULL)";
-	} else {
-		priv->search_text = g_strdup (search_text);
-		debug_search_text = priv->search_text;
+	RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (asource);
+	RhythmDB *db;
+	gboolean subset;
 
-		if (old_search_text != NULL)
-			subset = (g_str_has_prefix (priv->search_text, old_search_text));
+	if (search == NULL) {
+		search = priv->default_search;
 	}
-	g_free (old_search_text);
+	
+	/* replace our search query */
+	if (priv->search_query != NULL) {
+		rhythmdb_query_free (priv->search_query);
+		priv->search_query = NULL;
+	}
+	db = rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (asource));
+	priv->search_query = rb_source_search_create_query (search, db, new_text);
 
 	/* we can only do subset searches once the original query is complete */
+	subset = rb_source_search_is_subset (search, cur_text, new_text);
 	if (priv->query_active && subset) {
-		rb_debug ("deferring search for \"%s\" until query completion", debug_search_text);
+		rb_debug ("deferring search for \"%s\" until query completion", new_text ? new_text : "<NULL>");
 		priv->search_on_completion = TRUE;
 	} else {
-		rb_debug ("doing search for \"%s\"", debug_search_text);
-		rb_auto_playlist_source_do_query (RB_AUTO_PLAYLIST_SOURCE (source), subset);
+		rb_debug ("doing search for \"%s\"", new_text ? new_text : "<NULL>");
+		rb_auto_playlist_source_do_query (RB_AUTO_PLAYLIST_SOURCE (asource), subset);
 	}
 }
 
 static GList *
 impl_get_property_views (RBSource *source)
 {
-	RBAutoPlaylistSourcePrivate *priv = RB_AUTO_PLAYLIST_SOURCE_GET_PRIVATE (source);
+	RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (source);
 	GList *ret;
 
 	ret =  rb_library_browser_get_property_views (priv->browser);
@@ -459,7 +458,7 @@ rb_auto_playlist_source_drag_atom_to_prop (GdkAtom smasher)
 static void
 impl_browser_toggled (RBSource *source, gboolean enabled)
 {
-	RBAutoPlaylistSourcePrivate *priv = RB_AUTO_PLAYLIST_SOURCE_GET_PRIVATE (source);
+	RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (source);
 
 	priv->browser_shown = enabled;
 
@@ -624,7 +623,7 @@ static void
 rb_auto_playlist_source_query_complete_cb (RhythmDBQueryModel *model,
 					   RBAutoPlaylistSource *source)
 {
-	RBAutoPlaylistSourcePrivate *priv = RB_AUTO_PLAYLIST_SOURCE_GET_PRIVATE (source);
+	RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (source);
 
 	priv->query_active = FALSE;
 	if (priv->search_on_completion) {
@@ -638,7 +637,7 @@ rb_auto_playlist_source_query_complete_cb (RhythmDBQueryModel *model,
 static void
 rb_auto_playlist_source_do_query (RBAutoPlaylistSource *source, gboolean subset)
 {
-	RBAutoPlaylistSourcePrivate *priv = RB_AUTO_PLAYLIST_SOURCE_GET_PRIVATE (source);
+	RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (source);
 	RhythmDB *db;
 	RhythmDBQueryModel *query_model;
 	GPtrArray *query;
@@ -648,7 +647,7 @@ rb_auto_playlist_source_do_query (RBAutoPlaylistSource *source, gboolean subset)
 
 	g_assert (priv->cached_all_query);
 
-	if (!priv->search_text) {
+	if (priv->search_query == NULL) {
 		rb_library_browser_set_model (priv->browser,
 					      priv->cached_all_query,
 					      FALSE);
@@ -657,7 +656,7 @@ rb_auto_playlist_source_do_query (RBAutoPlaylistSource *source, gboolean subset)
 
 	query = rhythmdb_query_copy (priv->query);
 	rhythmdb_query_append (db, query,
-			       RHYTHMDB_QUERY_PROP_LIKE, priv->search_prop, priv->search_text,
+			       RHYTHMDB_QUERY_SUBQUERY, priv->search_query,
 			       RHYTHMDB_QUERY_END);
 
 	if (subset) {
@@ -699,7 +698,7 @@ rb_auto_playlist_source_set_query (RBAutoPlaylistSource *source,
 				   const char *sort_key,
 				   gint sort_direction)
 {
-	RBAutoPlaylistSourcePrivate *priv = RB_AUTO_PLAYLIST_SOURCE_GET_PRIVATE (source);
+	RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (source);
 	RhythmDB *db = rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (source));
 	RBEntryView *songs = rb_source_get_entry_view (RB_SOURCE (source));
 
@@ -750,7 +749,7 @@ rb_auto_playlist_source_get_query (RBAutoPlaylistSource *source,
 
  	g_return_if_fail (RB_IS_AUTO_PLAYLIST_SOURCE (source));
 
-	priv = RB_AUTO_PLAYLIST_SOURCE_GET_PRIVATE (source);
+	priv = GET_PRIVATE (source);
 	songs = rb_source_get_entry_view (RB_SOURCE (source));
 
 	*query = rhythmdb_query_copy (priv->query);
@@ -763,7 +762,7 @@ rb_auto_playlist_source_get_query (RBAutoPlaylistSource *source,
 static void
 rb_auto_playlist_source_songs_sort_order_changed_cb (RBEntryView *view, RBAutoPlaylistSource *source)
 {
-	RBAutoPlaylistSourcePrivate *priv = RB_AUTO_PLAYLIST_SOURCE_GET_PRIVATE (source);
+	RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (source);
 
 	/* don't process this if we are in the middle of setting a query */
 	if (priv->query_resetting)
@@ -802,59 +801,3 @@ impl_get_search_actions (RBSource *source)
 	return actions;
 }
 
-static RhythmDBPropType
-search_action_to_prop (GtkAction *action)
-{
-	const char      *name;
-	RhythmDBPropType prop;
-
-	name = gtk_action_get_name (action);
-
-	if (name == NULL) {
-		prop = RHYTHMDB_PROP_SEARCH_MATCH;
-	} else if (strcmp (name, "AutoPlaylistSearchAll") == 0) {
-		prop = RHYTHMDB_PROP_SEARCH_MATCH;
-	} else if (strcmp (name, "AutoPlaylistSearchArtists") == 0) {
-		prop = RHYTHMDB_PROP_ARTIST_FOLDED;
-	} else if (strcmp (name, "AutoPlaylistSearchAlbums") == 0) {
-		prop = RHYTHMDB_PROP_ALBUM_FOLDED;
-	} else if (strcmp (name, "AutoPlaylistSearchTitles") == 0) {
-		prop = RHYTHMDB_PROP_TITLE_FOLDED;
-	} else {
-		prop = RHYTHMDB_PROP_SEARCH_MATCH;
-	}
-
-	return prop;
-}
-
-static void
-search_action_changed (GtkRadioAction  *action,
-		       GtkRadioAction  *current,
-		       RBShell         *shell)
-{
-	RBAutoPlaylistSourcePrivate *priv;
-	gboolean active;
-	RBAutoPlaylistSource *source;
-
-	g_object_get (shell, "selected-source", &source, NULL);
-	if (source == NULL || !RB_IS_AUTO_PLAYLIST_SOURCE (source)) {
-		if (source != NULL)
-			g_object_unref (source);
-		return;
-	}
-
-	priv = RB_AUTO_PLAYLIST_SOURCE_GET_PRIVATE (source);
-
-	active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
-
-	if (active) {
-		/* update query */
-		priv->search_prop = search_action_to_prop (GTK_ACTION (current));
-		rb_auto_playlist_source_do_query (source, FALSE);
-		rb_source_notify_filter_changed (RB_SOURCE (source));
-	}
-
-	if (source != NULL) {
-		g_object_unref (source);
-	}
-}
diff --git a/sources/rb-browser-source.c b/sources/rb-browser-source.c
index 876c912..e232c58 100644
--- a/sources/rb-browser-source.c
+++ b/sources/rb-browser-source.c
@@ -32,6 +32,7 @@
 
 #include "rb-source.h"
 #include "rb-library-source.h"
+#include "rb-source-search-basic.h"
 
 #include "rhythmdb-query-model.h"
 #include "rb-property-view.h"
@@ -87,7 +88,7 @@ static void paned_size_allocate_cb (GtkWidget *widget,
 static RBEntryView *impl_get_entry_view (RBSource *source);
 static GList *impl_get_property_views (RBSource *source);
 static void impl_delete (RBSource *source);
-static void impl_search (RBSource *source, const char *text);
+static void impl_search (RBSource *source, RBSourceSearch *search, const char *cur_text, const char *new_text);
 static void impl_reset_filters (RBSource *source);
 static void impl_song_properties (RBSource *source);
 static GList *impl_get_search_actions (RBSource *source);
@@ -115,12 +116,12 @@ struct RBBrowserSourcePrivate
 	RBEntryView *songs;
 	GtkWidget *paned;
 
-	char *search_text;
 	RhythmDBQueryModel *cached_all_query;
-	RhythmDBPropType search_prop;
+	RhythmDBQuery *search_query;
 	gboolean initialized;
 	gboolean query_active;
 	gboolean search_on_completion;
+	RBSourceSearch *default_search;
 
 	GtkActionGroup *action_group;
 	GtkActionGroup *search_action_group;
@@ -150,10 +151,10 @@ static GtkActionEntry rb_browser_source_actions [] =
 
 static GtkRadioActionEntry rb_browser_source_radio_actions [] =
 {
-	{ "BrowserSourceSearchAll", NULL, N_("All"), NULL, N_("Search all fields"), 0 },
-	{ "BrowserSourceSearchArtists", NULL, N_("Artists"), NULL, N_("Search artists"), 1 },
-	{ "BrowserSourceSearchAlbums", NULL, N_("Albums"), NULL, N_("Search albums"), 2 },
-	{ "BrowserSourceSearchTitles", NULL, N_("Titles"), NULL, N_("Search titles"), 3 }
+	{ "BrowserSourceSearchAll", NULL, N_("All"), NULL, N_("Search all fields"), RHYTHMDB_PROP_SEARCH_MATCH },
+	{ "BrowserSourceSearchArtists", NULL, N_("Artists"), NULL, N_("Search artists"), RHYTHMDB_PROP_ARTIST_FOLDED },
+	{ "BrowserSourceSearchAlbums", NULL, N_("Albums"), NULL, N_("Search albums"), RHYTHMDB_PROP_ALBUM_FOLDED },
+	{ "BrowserSourceSearchTitles", NULL, N_("Titles"), NULL, N_("Search titles"), RHYTHMDB_PROP_TITLE_FOLDED }
 };
 
 static const GtkTargetEntry songs_view_drag_types[] = {
@@ -219,8 +220,6 @@ static void
 rb_browser_source_init (RBBrowserSource *source)
 {
 	source->priv = RB_BROWSER_SOURCE_GET_PRIVATE (source);
-
-	source->priv->search_prop = RHYTHMDB_PROP_SEARCH_MATCH;
 }
 
 static void
@@ -241,9 +240,9 @@ rb_browser_source_dispose (GObject *object)
 		source->priv->db = NULL;
 	}
 
-	if (source->priv->search_text != NULL) {
-		g_free (source->priv->search_text);
-		source->priv->search_text = NULL;
+	if (source->priv->search_query != NULL) {
+		rhythmdb_query_free (source->priv->search_query);
+		source->priv->search_query = NULL;
 	}
 
 	if (source->priv->cached_all_query != NULL) {
@@ -256,6 +255,11 @@ rb_browser_source_dispose (GObject *object)
 		source->priv->action_group = NULL;
 	}
 
+	if (source->priv->default_search != NULL) {
+		g_object_unref (source->priv->default_search);
+		source->priv->default_search = NULL;
+	}
+
 	eel_gconf_notification_remove (source->priv->state_browser_notify_id);
 	eel_gconf_notification_remove (source->priv->state_paned_notify_id);
 	eel_gconf_notification_remove (source->priv->state_sorting_notify_id);
@@ -300,60 +304,6 @@ default_show_entry_popup (RBBrowserSource *source)
 	_rb_source_show_popup (RB_SOURCE (source), "/BrowserSourceViewPopup");
 }
 
-static RhythmDBPropType
-search_action_to_prop (GtkAction *action)
-{
-	const char      *name;
-	RhythmDBPropType prop;
-
-	name = gtk_action_get_name (action);
-
-	if (name == NULL) {
-		prop = RHYTHMDB_PROP_SEARCH_MATCH;
-	} else if (strcmp (name, "BrowserSourceSearchAll") == 0) {
-		prop = RHYTHMDB_PROP_SEARCH_MATCH;
-	} else if (strcmp (name, "BrowserSourceSearchArtists") == 0) {
-		prop = RHYTHMDB_PROP_ARTIST_FOLDED;
-	} else if (strcmp (name, "BrowserSourceSearchAlbums") == 0) {
-		prop = RHYTHMDB_PROP_ALBUM_FOLDED;
-	} else if (strcmp (name, "BrowserSourceSearchTitles") == 0) {
-		prop = RHYTHMDB_PROP_TITLE_FOLDED;
-	} else {
-		prop = RHYTHMDB_PROP_SEARCH_MATCH;
-	}
-
-	return prop;
-}
-
-static void
-search_action_changed (GtkRadioAction  *action,
-		       GtkRadioAction  *current,
-		       RBShell         *shell)
-{
-	gboolean         active;
-	RBBrowserSource *source;
-
-	g_object_get (shell, "selected-source", &source, NULL);
-	if (source == NULL || !RB_IS_BROWSER_SOURCE (source)) {
-		if (source != NULL)
-			g_object_unref (source);
-		return;
-	}
-
-	active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
-
-	if (active) {
-		/* update query */
-		source->priv->search_prop = search_action_to_prop (GTK_ACTION (current));
-		rb_browser_source_do_query (source, FALSE);
-		rb_source_notify_filter_changed (RB_SOURCE (source));
-	}
-
-	if (source != NULL) {
-		g_object_unref (source);
-	}
-}
-
 static GObject *
 rb_browser_source_constructor (GType type,
 			       guint n_construct_properties,
@@ -390,11 +340,17 @@ rb_browser_source_constructor (GType type,
 						    rb_browser_source_radio_actions,
 						    G_N_ELEMENTS (rb_browser_source_radio_actions),
 						    0,
-						    (GCallback)search_action_changed,
-						    shell);
+						    NULL,
+						    NULL);
+
+		rb_source_search_basic_create_for_actions (source->priv->action_group,
+							   rb_browser_source_radio_actions,
+							   G_N_ELEMENTS (rb_browser_source_radio_actions));
 	}
 	g_object_unref (shell);
 
+	source->priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH);
+
 	source->priv->paned = gtk_vpaned_new ();
 
 	source->priv->browser = rb_library_browser_new (source->priv->db, entry_type);
@@ -600,43 +556,31 @@ songs_view_sort_order_changed_cb (RBEntryView *view, RBBrowserSource *source)
 }
 
 static void
-impl_search (RBSource *asource, const char *search_text)
+impl_search (RBSource *asource, RBSourceSearch *search, const char *cur_text, const char *new_text)
 {
 	RBBrowserSource *source = RB_BROWSER_SOURCE (asource);
-	char *old_search_text = NULL;
-	gboolean subset = FALSE;
-	const char *debug_search_text;
-
-	if (search_text != NULL && search_text[0] == '\0')
-		search_text = NULL;
-
-	if (search_text == NULL && source->priv->search_text == NULL)
-		return;
-	if (search_text != NULL && source->priv->search_text != NULL
-	    && !strcmp (search_text, source->priv->search_text))
-		return;
+	gboolean subset;
 
-	old_search_text = source->priv->search_text;
-	if (search_text == NULL) {
-		source->priv->search_text = NULL;
-		debug_search_text = "(NULL)";
-	} else {
-		source->priv->search_text = g_strdup (search_text);
-		debug_search_text = source->priv->search_text;
+	if (search == NULL) {
+		search = source->priv->default_search;
+	}
 
-		if (old_search_text != NULL)
-			subset = (g_str_has_prefix (source->priv->search_text, old_search_text));
+	/* replace our search query */
+	if (source->priv->search_query != NULL) {
+		rhythmdb_query_free (source->priv->search_query);
+		source->priv->search_query = NULL;
 	}
-	g_free (old_search_text);
+	source->priv->search_query = rb_source_search_create_query (search, source->priv->db, new_text);
 
-	/* we can't do subset searches until the original query is complete, because they
-	 * reuse the query model.
+	/* for subset searches, we have to wait until the query
+	 * has finished before we can refine the results.
 	 */
+	subset = rb_source_search_is_subset (search, cur_text, new_text);
 	if (source->priv->query_active && subset) {
-		rb_debug ("deferring search for \"%s\" until query completion", debug_search_text);
+		rb_debug ("deferring search for \"%s\" until query completion", new_text ? new_text : "<NULL>");
 		source->priv->search_on_completion = TRUE;
 	} else {
-		rb_debug ("doing search for \"%s\"", debug_search_text);
+		rb_debug ("doing search for \"%s\"", new_text ? new_text : "<NULL>");
 		rb_browser_source_do_query (source, subset);
 	}
 }
@@ -670,10 +614,11 @@ impl_reset_filters (RBSource *asource)
 	if (rb_library_browser_reset (source->priv->browser))
 		changed = TRUE;
 
-	if (source->priv->search_text != NULL)
+	if (source->priv->search_query != NULL) {
+		rhythmdb_query_free (source->priv->search_query);
+		source->priv->search_query = NULL;
 		changed = TRUE;
-	g_free (source->priv->search_text);
-	source->priv->search_text = NULL;
+	}
 
 	if (changed)
 		rb_browser_source_do_query (source, FALSE);
@@ -852,7 +797,7 @@ rb_browser_source_do_query (RBBrowserSource *source, gboolean subset)
 	RhythmDBEntryType entry_type;
 
 	/* use the cached 'all' query to optimise the no-search case */
-	if (!source->priv->search_text) {
+	if (source->priv->search_query == NULL) {
 		rb_library_browser_set_model (source->priv->browser,
 					      source->priv->cached_all_query,
 					      FALSE);
@@ -864,9 +809,8 @@ rb_browser_source_do_query (RBBrowserSource *source, gboolean subset)
 				      RHYTHMDB_QUERY_PROP_EQUALS,
 				      RHYTHMDB_PROP_TYPE,
 				      entry_type,
-				      RHYTHMDB_QUERY_PROP_LIKE,
-				      source->priv->search_prop,
-				      source->priv->search_text,
+				      RHYTHMDB_QUERY_SUBQUERY,
+				      source->priv->search_query,
 				      RHYTHMDB_QUERY_END);
 	g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);
 
diff --git a/sources/rb-podcast-source.c b/sources/rb-podcast-source.c
index a868dc5..a3b9927 100644
--- a/sources/rb-podcast-source.c
+++ b/sources/rb-podcast-source.c
@@ -53,13 +53,7 @@
 #include "rb-podcast-manager.h"
 #include "rb-static-playlist-source.h"
 #include "rb-cut-and-paste-code.h"
-
-typedef enum
-{
-	RB_PODCAST_QUERY_TYPE_ALL,
-	RB_PODCAST_QUERY_TYPE_ALBUM,
-	RB_PODCAST_QUERY_TYPE_SEARCH,
-} RBPodcastQueryType;
+#include "rb-source-search-basic.h"
 
 static void rb_podcast_source_class_init 		(RBPodcastSourceClass *klass);
 
@@ -212,13 +206,14 @@ static void rb_podcast_source_cmd_new_podcast	 (GtkAction *action,
 static char *impl_get_browser_key	 		(RBSource *source);
 static RBEntryView *impl_get_entry_view 		(RBSource *source);
 static void impl_search 				(RBSource *source,
-							 const char *text);
+							 RBSourceSearch *search,
+							 const char *current,
+							 const char *next);
 static void impl_delete 				(RBSource *source);
 static void impl_song_properties 			(RBSource *source);
 static RBSourceEOFType impl_handle_eos 			(RBSource *asource);
 static gboolean impl_show_popup 			(RBSource *source);
-static void rb_podcast_source_do_query			(RBPodcastSource *source,
-							 RBPodcastQueryType type);
+static void rb_podcast_source_do_query			(RBPodcastSource *source);
 static GtkWidget *impl_get_config_widget 		(RBSource *source,
 							 RBShellPreferences *prefs);
 static gboolean impl_receive_drag 			(RBSource *source,
@@ -264,10 +259,10 @@ struct RBPodcastSourcePrivate
 	RBEntryView *posts;
 	GtkActionGroup *action_group;
 
-	char *search_text;
 	GList *selected_feeds;
-	RhythmDBQueryModel *cached_all_query;
+	RhythmDBQuery *search_query;
 	RhythmDBPropType search_prop;
+	RBSourceSearch *default_search;
 
 	gboolean initialized;
 
@@ -307,9 +302,9 @@ static GtkActionEntry rb_podcast_source_actions [] =
 
 static GtkRadioActionEntry rb_podcast_source_radio_actions [] =
 {
-	{ "PodcastSearchAll", NULL, N_("All"), NULL, N_("Search all fields"), 0 },
-	{ "PodcastSearchFeeds", NULL, N_("Feeds"), NULL, N_("Search podcast feeds"), 1 },
-	{ "PodcastSearchEpisodes", NULL, N_("Episodes"), NULL, N_("Search podcast episodes"), 2 }
+	{ "PodcastSearchAll", NULL, N_("All"), NULL, N_("Search all fields"), RHYTHMDB_PROP_SEARCH_MATCH },
+	{ "PodcastSearchFeeds", NULL, N_("Feeds"), NULL, N_("Search podcast feeds"), RHYTHMDB_PROP_ALBUM_FOLDED },
+	{ "PodcastSearchEpisodes", NULL, N_("Episodes"), NULL, N_("Search podcast episodes"), RHYTHMDB_PROP_TITLE_FOLDED }
 };
 
 static const GtkTargetEntry posts_view_drag_types[] = {
@@ -421,9 +416,9 @@ rb_podcast_source_dispose (GObject *object)
 		source->priv->db = NULL;
 	}
 
-	if (source->priv->cached_all_query) {
-		g_object_unref (source->priv->cached_all_query);
-		source->priv->cached_all_query = NULL;
+	if (source->priv->search_query != NULL) {
+		rhythmdb_query_free (source->priv->search_query);
+		source->priv->search_query = NULL;
 	}
 
 	if (source->priv->action_group != NULL) {
@@ -463,53 +458,6 @@ rb_podcast_source_finalize (GObject *object)
 	G_OBJECT_CLASS (rb_podcast_source_parent_class)->finalize (object);
 }
 
-static RhythmDBPropType
-search_action_to_prop (GtkAction *action)
-{
-	const char      *name;
-	RhythmDBPropType prop;
-
-	name = gtk_action_get_name (action);
-
-	if (name == NULL) {
-		prop = RHYTHMDB_PROP_SEARCH_MATCH;
-	} else if (strcmp (name, "PodcastSearchAll") == 0) {
-		prop = RHYTHMDB_PROP_SEARCH_MATCH;
-	} else if (strcmp (name, "PodcastSearchFeeds") == 0) {
-		prop = RHYTHMDB_PROP_ALBUM_FOLDED;
-	} else if (strcmp (name, "PodcastSearchEpisodes") == 0) {
-		prop = RHYTHMDB_PROP_TITLE_FOLDED;
-	} else {
-		prop = RHYTHMDB_PROP_SEARCH_MATCH;
-	}
-
-	return prop;
-}
-
-static void
-search_action_changed (GtkRadioAction  *action,
-		       GtkRadioAction  *current,
-		       RBShell         *shell)
-{
-	gboolean active;
-	RBPodcastSource *source;
-
-	g_object_get (shell, "selected-source", &source, NULL);
-
-	active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
-
-	if (active) {
-		/* update query */
-		source->priv->search_prop = search_action_to_prop (GTK_ACTION (current));
-		rb_podcast_source_do_query (source, RB_PODCAST_QUERY_TYPE_SEARCH);
-		rb_source_notify_filter_changed (RB_SOURCE (source));
-	}
-
-	if (source != NULL) {
-		g_object_unref (source);
-	}
-}
-
 static GObject *
 rb_podcast_source_constructor (GType type,
 			       guint n_construct_properties,
@@ -541,8 +489,13 @@ rb_podcast_source_constructor (GType type,
 					    rb_podcast_source_radio_actions,
 					    G_N_ELEMENTS (rb_podcast_source_radio_actions),
 					    0,
-					    (GCallback)search_action_changed,
-					    shell);
+					    NULL,
+					    NULL);
+	rb_source_search_basic_create_for_actions (source->priv->action_group,
+						   rb_podcast_source_radio_actions,
+						   G_N_ELEMENTS (rb_podcast_source_radio_actions));
+
+	source->priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH);
 
 	source->priv->paned = gtk_vpaned_new ();
 
@@ -740,7 +693,7 @@ rb_podcast_source_constructor (GType type,
 			   posts_view_drag_types, 2,
 			   GDK_ACTION_COPY | GDK_ACTION_MOVE);
 
-	/* set up propiets page */
+	/* set up properties page */
 	gtk_paned_pack1 (GTK_PANED (source->priv->paned),
 			 GTK_WIDGET (source->priv->feeds), FALSE, FALSE);
 	gtk_paned_pack2 (GTK_PANED (source->priv->paned),
@@ -755,7 +708,7 @@ rb_podcast_source_constructor (GType type,
 	gtk_widget_show_all (GTK_WIDGET (source));
 	rb_podcast_source_state_prefs_sync (source);
 
-	rb_podcast_source_do_query (source, RB_PODCAST_QUERY_TYPE_ALL);
+	rb_podcast_source_do_query (source);
 
 	return G_OBJECT (source);
 }
@@ -818,29 +771,20 @@ rb_podcast_source_new (RBShell *shell)
 }
 
 static void
-impl_search (RBSource *asource, const char *search_text)
+impl_search (RBSource *asource, RBSourceSearch *search, const char *cur_text, const char *new_text)
 {
 	RBPodcastSource *source = RB_PODCAST_SOURCE (asource);
 
-	if (source->priv->initialized) {
-		if (search_text == NULL && source->priv->search_text == NULL)
-			return;
-		if (search_text != NULL &&
-		    source->priv->search_text != NULL
-		    && !strcmp (search_text, source->priv->search_text))
-			return;
+	if (search == NULL) {
+		search = source->priv->default_search;
 	}
 
-	source->priv->initialized = TRUE;
-	if (search_text != NULL && search_text[0] == '\0')
-		search_text = NULL;
-
-	g_free (source->priv->search_text);
-	if (search_text)
-		source->priv->search_text = g_utf8_casefold (search_text, -1);
-	else
-		source->priv->search_text = NULL;
-	rb_podcast_source_do_query (source, RB_PODCAST_QUERY_TYPE_SEARCH);
+	if (source->priv->search_query != NULL) {
+		rhythmdb_query_free (source->priv->search_query);
+		source->priv->search_query = NULL;
+	}
+	source->priv->search_query = rb_source_search_create_query (search, source->priv->db, new_text);
+	rb_podcast_source_do_query (source);
 
 	rb_source_notify_filter_changed (RB_SOURCE (source));
 }
@@ -1082,7 +1026,7 @@ feed_select_change_cb (RBPropertyView *propview,
 
 	source->priv->selected_feeds = rb_string_list_copy (feeds);
 
-	rb_podcast_source_do_query (source, RB_PODCAST_QUERY_TYPE_ALBUM);
+	rb_podcast_source_do_query (source);
 	rb_source_notify_filter_changed (RB_SOURCE (source));
 }
 
@@ -1108,34 +1052,12 @@ construct_query_from_selection (RBPodcastSource *source)
 				      RHYTHMDB_QUERY_END);
 	g_boxed_free (RHYTHMDB_TYPE_ENTRY_TYPE, entry_type);
 
-	if (source->priv->search_text) {
-		GPtrArray *subquery;
-
-		if (source->priv->search_prop == RHYTHMDB_PROP_SEARCH_MATCH) {
-			subquery = rhythmdb_query_parse (source->priv->db,
-							 RHYTHMDB_QUERY_PROP_LIKE,
-							 RHYTHMDB_PROP_ALBUM_FOLDED,
-							 source->priv->search_text,
-							 RHYTHMDB_QUERY_DISJUNCTION,
-							 RHYTHMDB_QUERY_PROP_LIKE,
-							 RHYTHMDB_PROP_TITLE_FOLDED,
-							 source->priv->search_text,
-							 RHYTHMDB_QUERY_END);
-		} else {
-			subquery = rhythmdb_query_parse (source->priv->db,
-							 RHYTHMDB_QUERY_PROP_LIKE,
-							 source->priv->search_prop,
-							 source->priv->search_text,
-							 RHYTHMDB_QUERY_END);
-		}
-
+	if (source->priv->search_query) {
 		rhythmdb_query_append (source->priv->db,
 				       query,
 				       RHYTHMDB_QUERY_SUBQUERY,
-				       subquery,
+				       source->priv->search_query,
 				       RHYTHMDB_QUERY_END);
-
-		rhythmdb_query_free (subquery);
 	}
 
 	if (source->priv->selected_feeds) {
@@ -1170,35 +1092,18 @@ construct_query_from_selection (RBPodcastSource *source)
 }
 
 static void
-rb_podcast_source_do_query (RBPodcastSource *source,
-			    RBPodcastQueryType qtype)
+rb_podcast_source_do_query (RBPodcastSource *source)
 {
 	RhythmDBQueryModel *query_model;
 	GPtrArray *query;
-	gboolean is_all_query;
-
-	rb_debug ("select entry filter");
-
-	is_all_query  = ((qtype == RB_PODCAST_QUERY_TYPE_ALL) ||
-			 ((source->priv->selected_feeds == NULL) &&
-			 (source->priv->search_text == NULL)));
-
-	if (is_all_query && source->priv->cached_all_query) {
-		g_object_unref (source->priv->cached_all_query);
-		source->priv->cached_all_query = NULL;
-	}
 
+	/* set up new query model */
 	query_model = rhythmdb_query_model_new_empty (source->priv->db);
-	if (source->priv->cached_all_query == NULL) {
-		rb_debug ("caching new query");
-		source->priv->cached_all_query = g_object_ref (query_model);
-	}
 
-	rb_debug ("setting empty model");
 	rb_entry_view_set_model (source->priv->posts, query_model);
 	g_object_set (source, "query-model", query_model, NULL);
 
-	rb_debug ("doing query");
+	/* build and run the query */
 	query = construct_query_from_selection (source);
 	rhythmdb_do_full_query_async_parsed (source->priv->db,
 					     RHYTHMDB_QUERY_RESULTS (query_model),
diff --git a/sources/rb-source-search-basic.c b/sources/rb-source-search-basic.c
new file mode 100644
index 0000000..4965f31
--- /dev/null
+++ b/sources/rb-source-search-basic.c
@@ -0,0 +1,155 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2008  Jonathan Matthew  <jonathan@d14n.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 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#include "config.h"
+
+#include "rb-source-search-basic.h"
+
+static void	rb_source_search_basic_class_init (RBSourceSearchBasicClass *klass);
+static void	rb_source_search_basic_init (RBSourceSearchBasic *search);
+
+G_DEFINE_TYPE (RBSourceSearchBasic, rb_source_search_basic, RB_TYPE_SOURCE_SEARCH)
+
+enum
+{
+	PROP_0,
+	PROP_SEARCH_PROP,
+};
+
+static RhythmDBQuery *
+impl_create_query (RBSourceSearch *bsearch, RhythmDB *db, const char *search_text)
+{
+	RBSourceSearchBasic *search = RB_SOURCE_SEARCH_BASIC (bsearch);
+
+	return _rb_source_search_create_simple_query (bsearch, db, search_text, search->search_prop);
+}
+
+static void
+impl_set_property (GObject *object,
+		   guint prop_id,
+		   const GValue *value,
+		   GParamSpec *pspec)
+{
+	RBSourceSearchBasic *search = RB_SOURCE_SEARCH_BASIC (object);
+
+	switch (prop_id) {
+	case PROP_SEARCH_PROP:
+		search->search_prop = g_value_get_int (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+impl_get_property (GObject *object,
+		   guint prop_id,
+		   GValue *value,
+		   GParamSpec *pspec)
+{
+	RBSourceSearchBasic *search = RB_SOURCE_SEARCH_BASIC (object);
+
+	switch (prop_id) {
+	case PROP_SEARCH_PROP:
+		g_value_set_int (value, search->search_prop);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+rb_source_search_basic_class_init (RBSourceSearchBasicClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBSourceSearchClass *search_class = RB_SOURCE_SEARCH_CLASS (klass);
+
+	object_class->set_property = impl_set_property;
+	object_class->get_property = impl_get_property;
+
+	search_class->create_query = impl_create_query;
+
+	g_object_class_install_property (object_class,
+					 PROP_SEARCH_PROP,
+					 g_param_spec_int ("prop",
+							   "propid",
+							   "Property id",
+							   0, RHYTHMDB_NUM_PROPERTIES,
+							   RHYTHMDB_PROP_TYPE,
+							   G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+rb_source_search_basic_init (RBSourceSearchBasic *search)
+{
+	/* nothing */
+}
+
+
+/**
+ * rb_source_search_basic_new:
+ *
+ * Creates a new #RBSourceSearchBasic instance.
+ * This performs simple string matching on a specified
+ * property.
+ *
+ * @prop:	the #RhythmDBPropType to search
+ */
+RBSourceSearch *
+rb_source_search_basic_new (RhythmDBPropType prop)
+{
+	return g_object_new (RB_TYPE_SOURCE_SEARCH_BASIC, "prop", prop, NULL);
+}
+
+/**
+ * rb_source_search_basic_create_for_actions:
+ *
+ * Creates #RBSourceSearchBasic instances for a set of
+ * search actions and associates them with the actions.
+ * The property to match on is taken from the action
+ * value in the GtkRadioActionEntry structure.
+ *
+ * @action_group:	the GtkActionGroup containing the actions
+ * @actions:		the GtkRadioActionEntries for the actions
+ * @n_actions:		the number of actions
+ */
+void
+rb_source_search_basic_create_for_actions (GtkActionGroup *action_group,
+					   GtkRadioActionEntry *actions,
+					   int n_actions)
+{
+	int i;
+	for (i = 0; i < n_actions; i++) {
+		GtkAction *action;
+		RBSourceSearch *search;
+
+		if (actions[i].value != RHYTHMDB_NUM_PROPERTIES) {
+			action = gtk_action_group_get_action (action_group, actions[i].name);
+			g_assert (action != NULL);
+
+			search = rb_source_search_basic_new (actions[i].value);
+			rb_source_search_action_attach (search, G_OBJECT (action));
+			g_object_unref (search);
+		}
+	}
+}
+
diff --git a/sources/rb-source-search-basic.h b/sources/rb-source-search-basic.h
new file mode 100644
index 0000000..dfbbba6
--- /dev/null
+++ b/sources/rb-source-search-basic.h
@@ -0,0 +1,63 @@
+/*
+ *  Copyright (C) 2008 Jonathan Matthew  <jonathan@d14n.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 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#ifndef __RB_SOURCE_SEARCH_BASIC_H
+#define __RB_SOURCE_SEARCH_BASIC_H
+
+/*
+ * Basic source search implementation.
+ */
+
+#include <gtk/gtk.h>
+
+#include <rb-source-search.h>
+#include <rhythmdb.h>
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_SOURCE_SEARCH_BASIC	(rb_source_search_basic_get_type())
+#define RB_SOURCE_SEARCH_BASIC(o)     (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_SOURCE_SEARCH_BASIC, RBSourceSearchBasic))
+#define RB_SOURCE_SEARCH_BASIC_CLASS(o) (G_TYPE_CHECK_CLASS_CAST ((o), RB_TYPE_SOURCE_SEARCH_BASIC, RBSourceSearchBasicClass))
+#define RB_IS_SOURCE_SEARCH_BASIC(o)  (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_SOURCE_SEARCH_BASIC))
+#define RB_IS_SOURCE_SEARCH_BASIC_CLASS(o)  (G_TYPE_CHECK_CLASS_TYPE ((o), RB_TYPE_SOURCE_SEARCH_BASIC))
+#define RB_SOURCE_SEARCH_BASIC_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_SOURCE_SEARCH_BASIC, RBSourceSearchBasicClass))
+
+typedef struct
+{
+	RBSourceSearch parent;
+	RhythmDBPropType search_prop;
+} RBSourceSearchBasic;
+
+typedef struct
+{
+	RBSourceSearchClass parent_class;
+} RBSourceSearchBasicClass;
+
+GType		rb_source_search_basic_get_type	(void);
+
+RBSourceSearch *rb_source_search_basic_new (RhythmDBPropType prop);
+
+void		rb_source_search_basic_create_for_actions (GtkActionGroup *action_group,
+							   GtkRadioActionEntry *actions,
+							   int n_actions);
+
+G_END_DECLS
+
+#endif	/* __RB_SOURCE_SEARCH_BASIC_H */
+
diff --git a/sources/rb-source-search.c b/sources/rb-source-search.c
new file mode 100644
index 0000000..c9f38e6
--- /dev/null
+++ b/sources/rb-source-search.c
@@ -0,0 +1,149 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2008  Jonathan Matthew  <jonathan@d14n.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 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#include "config.h"
+
+#include "rb-source-search.h"
+
+static void	rb_source_search_class_init (RBSourceSearchClass *klass);
+static void	rb_source_search_init (RBSourceSearch *search);
+
+G_DEFINE_TYPE (RBSourceSearch, rb_source_search, G_TYPE_OBJECT)
+
+#define RB_SOURCE_SEARCH_DATA_ITEM	"rb-source-search"
+
+static gboolean
+default_is_subset (RBSourceSearch *source, const char *current, const char *next)
+{
+	/* the most common searches will return a strict subset if the
+	 * next search is the current search with a suffix.
+	 */
+	return (next != NULL && current != NULL && g_str_has_prefix (next, current));
+}
+
+static void
+rb_source_search_class_init (RBSourceSearchClass *klass)
+{
+	klass->is_subset = default_is_subset;
+}
+
+static void
+rb_source_search_init (RBSourceSearch *search)
+{
+	/* nothing */
+}
+
+/**
+ * rb_source_search_is_subset:
+ *
+ * Determines whether the new search text will result in a
+ * subset of entries matched by the previous search.  This is
+ * used to optimise the search query.
+ *
+ * @search: a #RBSourceSearch
+ * @current: the current search text
+ * @next: the new search text
+ * @return: TRUE iff the new search text will match a subset of those matched by the current search.
+ */
+gboolean
+rb_source_search_is_subset (RBSourceSearch *search, const char *current, const char *next)
+{
+	RBSourceSearchClass *klass = RB_SOURCE_SEARCH_GET_CLASS (search);
+	return klass->is_subset (search, current, next);
+}
+
+/**
+ * rb_source_search_create_query:
+ *
+ * Creates a #RhythmDBQuery from the user's search text.
+ *
+ * @search: a #RBSourceSearch
+ * @db: the #RhythmDB
+ * @search_text: the search text
+ * @return: #RhythmDBQuery for the source to use
+ */
+RhythmDBQuery *
+rb_source_search_create_query (RBSourceSearch *search, RhythmDB *db, const char *search_text)
+{
+	RBSourceSearchClass *klass = RB_SOURCE_SEARCH_GET_CLASS (search);
+	g_assert (klass->create_query);
+	return klass->create_query (search, db, search_text);
+}
+
+/**
+ * _rb_source_search_create_simple_query:
+ *
+ * Creates a basic search query.
+ *
+ * @search: the #RBSourceSearch
+ * @db: the #RhythmDB
+ * @search_text: the search text such as RHYTHMDB_PROP_SEARCH_MATCH
+ * @search_prop: the search property
+ * @return: the #RhythmDBQuery for the search text and property, or NULL
+ *   if no search text is specified.
+ */
+RhythmDBQuery *
+_rb_source_search_create_simple_query (RBSourceSearch *search, RhythmDB *db, const char *search_text, RhythmDBPropType search_prop)
+{
+	if (search_text == NULL)
+		return NULL;
+
+	return rhythmdb_query_parse (db, 
+				     RHYTHMDB_QUERY_PROP_LIKE,
+				     search_prop,
+				     search_text,
+				     RHYTHMDB_QUERY_END);
+}
+
+/**
+ * rb_source_search_action_attach:
+ *
+ * Attaches a #RBSourceSearch to a UI action so that
+ * the search implementation will be used when the action is active.
+ *
+ * @search: #RBSourceSearch to associate with the action
+ * @action: UI action to associate the search with
+ */
+void
+rb_source_search_action_attach (RBSourceSearch *search, GObject *action)
+{
+	g_object_set_data_full (action,
+				RB_SOURCE_SEARCH_DATA_ITEM,
+				g_object_ref (search),
+				(GDestroyNotify) g_object_unref);
+}
+
+/**
+ * rb_source_search_get_from_action:
+ *
+ * Returns the #RBSourceSearch associated with the
+ * specified UI action.
+ *
+ * @action: the action to find the #RBSourceSearch for
+ * @return: associated #RBSourceSearch
+ */
+RBSourceSearch *
+rb_source_search_get_from_action (GObject *action)
+{
+	gpointer data;
+	data = g_object_get_data (action, RB_SOURCE_SEARCH_DATA_ITEM);
+	return RB_SOURCE_SEARCH (data);
+}
+
diff --git a/sources/rb-source-search.h b/sources/rb-source-search.h
new file mode 100644
index 0000000..012db38
--- /dev/null
+++ b/sources/rb-source-search.h
@@ -0,0 +1,91 @@
+/*
+ *  Copyright (C) 2008 Jonathan Matthew  <jonathan@d14n.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 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#ifndef __RB_SOURCE_SEARCH_H
+#define __RB_SOURCE_SEARCH_H
+
+/*
+ * Base class for source search implementations.
+ *
+ * These translate the text in the search entry box into a
+ * RhythmDBQuery.  The basic implementation will return
+ * a query like RHYTHMDB_QUERY_PROP_LIKE, RHYTHMDB_PROP_SEARCH_MATCH,
+ * text.  Simple variants can restrict the search to single
+ * properties (artist, album, genre).  More complicated searches
+ * could implement something like the Xesam User Query spec.
+ *
+ * The source header finds the search instance to use by looking
+ * for the 'rb-source-search' data item on the active search
+ * action.  Or something.
+ */
+
+#include <glib-object.h>
+#include <rhythmdb.h>
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_SOURCE_SEARCH	(rb_source_search_get_type())
+#define RB_SOURCE_SEARCH(o)     (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_SOURCE_SEARCH, RBSourceSearch))
+#define RB_SOURCE_SEARCH_CLASS(o) (G_TYPE_CHECK_CLASS_CAST ((o), RB_TYPE_SOURCE_SEARCH, RBSourceSearchClass))
+#define RB_IS_SOURCE_SEARCH(o)  (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_SOURCE_SEARCH))
+#define RB_IS_SOURCE_SEARCH_CLASS(o)  (G_TYPE_CHECK_CLASS_TYPE ((o), RB_TYPE_SOURCE_SEARCH))
+#define RB_SOURCE_SEARCH_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_SOURCE_SEARCH, RBSourceSearchClass))
+
+typedef struct
+{
+	GObject parent;
+} RBSourceSearch;
+
+typedef struct
+{
+	GObjectClass parent_class;
+
+	/* virtual functions */
+	gboolean       (*is_subset)	(RBSourceSearch *search,
+					 const char *current,
+					 const char *next);
+	RhythmDBQuery *(*create_query)	(RBSourceSearch *search,
+					 RhythmDB *db,
+					 const char *search_text);
+
+} RBSourceSearchClass;
+
+GType		rb_source_search_get_type	(void);
+
+gboolean 	rb_source_search_is_subset (RBSourceSearch *search,
+					    const char *current,
+					    const char *next);
+RhythmDBQuery *	rb_source_search_create_query (RBSourceSearch *search,
+					       RhythmDB *db,
+					       const char *search_text);
+
+void		rb_source_search_action_attach (RBSourceSearch *search,
+						GObject *action);
+RBSourceSearch *rb_source_search_get_from_action (GObject *action);
+
+/* for search implementations */
+RhythmDBQuery *	_rb_source_search_create_simple_query (RBSourceSearch *search,
+						       RhythmDB *db,
+						       const char *search_text,
+						       RhythmDBPropType search_prop);
+
+G_END_DECLS
+
+#endif	/* __RB_SOURCE_SEARCH_H */
+
diff --git a/sources/rb-source.c b/sources/rb-source.c
index 3c6c7ec..7818384 100644
--- a/sources/rb-source.c
+++ b/sources/rb-source.c
@@ -718,13 +718,15 @@ rb_source_can_search (RBSource *source)
 
 void
 rb_source_search (RBSource *source,
-		  const char *text)
+		  RBSourceSearch *search,
+		  const char *cur_text,
+		  const char *new_text)
 {
 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
 
 	/* several sources don't have a search ability */
 	if (klass->impl_search != NULL)
-		klass->impl_search (source, text);
+		klass->impl_search (source, search, cur_text, new_text);
 }
 
 GtkWidget *
diff --git a/sources/rb-source.h b/sources/rb-source.h
index 367b997..b7f2cb0 100644
--- a/sources/rb-source.h
+++ b/sources/rb-source.h
@@ -27,6 +27,7 @@
 #include <gtk/gtkactiongroup.h>
 
 #include "rb-source-group.h"
+#include "rb-source-search.h"
 #include "rb-entry-view.h"
 #include "rb-shell-preferences.h"
 
@@ -87,7 +88,7 @@ struct _RBSourceClass
 
 	gboolean	(*impl_can_search)	(RBSource *source);
 
-	void		(*impl_search)		(RBSource *source, const char *text);
+	void		(*impl_search)		(RBSource *source, RBSourceSearch *search, const char *cur_text, const char *new_text);
 	void		(*impl_reset_filters)	(RBSource *source);
 	GtkWidget *	(*impl_get_config_widget)(RBSource *source, RBShellPreferences *prefs);
 
@@ -151,7 +152,9 @@ gboolean	rb_source_can_rename		(RBSource *source);
 
 gboolean	rb_source_can_search		(RBSource *source);
 void		rb_source_search		(RBSource *source,
-						 const char *text);
+						 RBSourceSearch *search,
+						 const char *cur_text,
+						 const char *new_text);
 
 void		rb_source_reset_filters		(RBSource *source);
 
diff --git a/sources/rb-static-playlist-source.c b/sources/rb-static-playlist-source.c
index 2bb04c0..b7fedf6 100644
--- a/sources/rb-static-playlist-source.c
+++ b/sources/rb-static-playlist-source.c
@@ -35,6 +35,7 @@
 #include "rb-stock-icons.h"
 #include "rb-file-helpers.h"
 #include "rb-playlist-xml.h"
+#include "rb-source-search-basic.h"
 
 static GObject *rb_static_playlist_source_constructor (GType type, guint n_construct_properties,
 						       GObjectConstructParam *construct_properties);
@@ -45,7 +46,7 @@ static void rb_static_playlist_source_finalize (GObject *object);
 static GList * impl_cut (RBSource *source);
 static void impl_paste (RBSource *asource, GList *entries);
 static void impl_delete (RBSource *source);
-static void impl_search (RBSource *asource, const char *search_text);
+static void impl_search (RBSource *asource, RBSourceSearch *search, const char *cur_text, const char *new_text);
 static void impl_browser_toggled (RBSource *source, gboolean enabled);
 static void impl_reset_filters (RBSource *asource);
 static gboolean impl_receive_drag (RBSource *source, GtkSelectionData *data);
@@ -86,16 +87,12 @@ static void rb_static_playlist_source_rows_reordered (GtkTreeModel *model,
 						      gint *order_map,
 						      RBStaticPlaylistSource *source);
 
-static void search_action_changed (GtkRadioAction *action,
-				   GtkRadioAction *current,
-				   RBShell *shell);
-
 static GtkRadioActionEntry rb_static_playlist_source_radio_actions [] =
 {
-	{ "StaticPlaylistSearchAll", NULL, N_("All"), NULL, N_("Search all fields"), 0 },
-	{ "StaticPlaylistSearchArtists", NULL, N_("Artists"), NULL, N_("Search artists"), 1 },
-	{ "StaticPlaylistSearchAlbums", NULL, N_("Albums"), NULL, N_("Search albums"), 2 },
-	{ "StaticPlaylistSearchTitles", NULL, N_("Titles"), NULL, N_("Search titles"), 3 }
+	{ "StaticPlaylistSearchAll", NULL, N_("All"), NULL, N_("Search all fields"), RHYTHMDB_PROP_SEARCH_MATCH },
+	{ "StaticPlaylistSearchArtists", NULL, N_("Artists"), NULL, N_("Search artists"), RHYTHMDB_PROP_ARTIST_FOLDED },
+	{ "StaticPlaylistSearchAlbums", NULL, N_("Albums"), NULL, N_("Search albums"), RHYTHMDB_PROP_ALBUM_FOLDED },
+	{ "StaticPlaylistSearchTitles", NULL, N_("Titles"), NULL, N_("Search titles"), RHYTHMDB_PROP_TITLE_FOLDED }
 };
 
 G_DEFINE_TYPE (RBStaticPlaylistSource, rb_static_playlist_source, RB_TYPE_PLAYLIST_SOURCE)
@@ -112,10 +109,10 @@ typedef struct
 	RBLibraryBrowser *browser;
 	gboolean browser_shown;
 
-	char *search_text;
+	RBSourceSearch *default_search;
+	RhythmDBQuery *search_query;
 
 	GtkActionGroup *action_group;
-	RhythmDBPropType search_prop;
 	gboolean dispose_has_run;
 } RBStaticPlaylistSourcePrivate;
 
@@ -207,6 +204,11 @@ rb_static_playlist_source_dispose (GObject *object)
 		priv->action_group = NULL;
 	}
 
+	if (priv->default_search != NULL) {
+		g_object_unref (priv->default_search);
+		priv->default_search = NULL;
+	}
+
 	G_OBJECT_CLASS (rb_static_playlist_source_parent_class)->dispose (object);
 }
 
@@ -217,7 +219,10 @@ rb_static_playlist_source_finalize (GObject *object)
 
 	rb_debug ("Finalizing static playlist source %p", object);
 
-	g_free (priv->search_text);
+	if (priv->search_query != NULL) {
+		rhythmdb_query_free (priv->search_query);
+		priv->search_query = NULL;
+	}
 
 	G_OBJECT_CLASS (rb_static_playlist_source_parent_class)->finalize (object);
 }
@@ -253,10 +258,13 @@ rb_static_playlist_source_constructor (GType type,
 						    rb_static_playlist_source_radio_actions,
 						    G_N_ELEMENTS (rb_static_playlist_source_radio_actions),
 						    0,
-						    (GCallback)search_action_changed,
-						    shell);
+						    NULL,
+						    NULL);
+		rb_source_search_basic_create_for_actions (priv->action_group,
+							   rb_static_playlist_source_radio_actions,
+							   G_N_ELEMENTS (rb_static_playlist_source_radio_actions));
 	}
-	priv->search_prop = RHYTHMDB_PROP_SEARCH_MATCH;
+	priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH);
 
 	g_object_unref (shell);
 
@@ -393,10 +401,10 @@ impl_reset_filters (RBSource *source)
 	if (rb_library_browser_reset (priv->browser))
 		changed = TRUE;
 
-	if (priv->search_text != NULL) {
+	if (priv->search_query != NULL) {
 		changed = TRUE;
-		g_free (priv->search_text);
-		priv->search_text = NULL;
+		rhythmdb_query_free (priv->search_query);
+		priv->search_query = NULL;
 	}
 
 	if (changed) {
@@ -406,29 +414,22 @@ impl_reset_filters (RBSource *source)
 }
 
 static void
-impl_search (RBSource *source, const char *search_text)
+impl_search (RBSource *source, RBSourceSearch *search, const char *cur_text, const char *new_text)
 {
 	RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
-	char *old_search_text = NULL;
-
-	if (search_text != NULL && search_text[0] == '\0')
-		search_text = NULL;
-
-	if (search_text == NULL && priv->search_text == NULL)
-		return;
-	if (search_text != NULL && priv->search_text != NULL
-	    && !strcmp (search_text, priv->search_text))
-		return;
+	RhythmDB *db;
 
-	old_search_text = priv->search_text;
-	if (search_text == NULL) {
-		priv->search_text = NULL;
-	} else {
-		priv->search_text = g_strdup (search_text);
+	if (search == NULL) {
+		search = priv->default_search;
 	}
-	g_free (old_search_text);
 
-	rb_debug ("doing search for \"%s\"", priv->search_text ? priv->search_text : "(NULL)");
+	/* replace our search query */
+	if (priv->search_query != NULL) {
+		rhythmdb_query_free (priv->search_query);
+		priv->search_query = NULL;
+	}
+	db = rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (source));
+	priv->search_query = rb_source_search_create_query (search, db, new_text);
 
 	rb_static_playlist_source_do_query (RB_STATIC_PLAYLIST_SOURCE (source));
 }
@@ -466,10 +467,10 @@ construct_query_from_selection (RBStaticPlaylistSource *source)
 
 	query = g_ptr_array_new();
 
-	if (priv->search_text) {
+	if (priv->search_query != NULL) {
 		rhythmdb_query_append (db,
 				       query,
-				       RHYTHMDB_QUERY_PROP_LIKE, priv->search_prop, priv->search_text,
+				       RHYTHMDB_QUERY_SUBQUERY, priv->search_query,
 				       RHYTHMDB_QUERY_END);
 	}
 
@@ -815,63 +816,6 @@ impl_get_search_actions (RBSource *source)
 	return actions;
 }
 
-static RhythmDBPropType
-search_action_to_prop (GtkAction *action)
-{
-	const char      *name;
-	RhythmDBPropType prop;
-
-	name = gtk_action_get_name (action);
-
-	if (name == NULL) {
-		prop = RHYTHMDB_PROP_SEARCH_MATCH;
-	} else if (strcmp (name, "StaticPlaylistSearchAll") == 0) {
-		prop = RHYTHMDB_PROP_SEARCH_MATCH;
-	} else if (strcmp (name, "StaticPlaylistSearchArtists") == 0) {
-		prop = RHYTHMDB_PROP_ARTIST_FOLDED;
-	} else if (strcmp (name, "StaticPlaylistSearchAlbums") == 0) {
-		prop = RHYTHMDB_PROP_ALBUM_FOLDED;
-	} else if (strcmp (name, "StaticPlaylistSearchTitles") == 0) {
-		prop = RHYTHMDB_PROP_TITLE_FOLDED;
-	} else {
-		prop = RHYTHMDB_PROP_SEARCH_MATCH;
-	}
-
-	return prop;
-}
-
-static void
-search_action_changed (GtkRadioAction  *action,
-		       GtkRadioAction  *current,
-		       RBShell         *shell)
-{
-	RBStaticPlaylistSource *source;
-	RBStaticPlaylistSourcePrivate *priv;
-	gboolean active;
-
-	g_object_get (shell, "selected-source", &source, NULL);
-	if (source == NULL || !RB_IS_STATIC_PLAYLIST_SOURCE (source)) {
-		if (source != NULL)
-			g_object_unref (source);
-		return;
-	}
-
-	priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
-
-	active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (current));
-
-	if (active) {
-		/* update query */
-		priv->search_prop = search_action_to_prop (GTK_ACTION (current));
-		rb_static_playlist_source_do_query (source);
-		rb_source_notify_filter_changed (RB_SOURCE (source));
-	}
-
-	if (source != NULL) {
-		g_object_unref (source);
-	}
-}
-
 static guint
 impl_want_uri (RBSource *source, const char *uri)
 {
