rhythmbox r6183 - in trunk: . bindings/python lib plugins/audiocd plugins/daap plugins/iradio shell sources widgets



Author: jmatthew
Date: Sun Mar  8 23:47:33 2009
New Revision: 6183
URL: http://svn.gnome.org/viewvc/rhythmbox?rev=6183&view=rev

Log:
2009-03-09  Jonathan Matthew  <jonathan d14n org>

	* sources/Makefile.am:
	* sources/rb-source-search-basic.c: (impl_create_query),
	(impl_set_property), (impl_get_property),
	(rb_source_search_basic_class_init), (rb_source_search_basic_init),
	(rb_source_search_basic_new),
	(rb_source_search_basic_create_for_actions):
	* sources/rb-source-search-basic.h:
	* sources/rb-source-search.c: (default_is_subset),
	(rb_source_search_class_init), (rb_source_search_init),
	(rb_source_search_is_subset), (rb_source_search_create_query),
	(_rb_source_search_create_simple_query),
	(rb_source_search_action_attach),
	(rb_source_search_get_from_action):
	* sources/rb-source-search.h:
	Add a base class and a simple implementation for objects that
	facilitate different kinds of searches.  A search implementation has
	methods for constructing a RhythmDBQuery for a given search string,
	and for determining if the results from one search string will be a
	strict subset of those from another.  The simple implementation
	compares the search string with a single RhythmDBEntry property.

	* sources/rb-source.c: (rb_source_class_init),
	(rb_source_set_property), (rb_source_get_property),
	(rb_source_search), (rb_source_search_type_get_type):
	* sources/rb-source.h:
	Replace impl_can_search with an enum property describing the search
	capabilities of the source.  The available values are 'none',
	'incremental', and 'explicit'.  'incremental' is the existing
	behaviour, and 'explicit' requires that the search entry be activated
	(by pressing enter, etc.) before searching.  The default is 'none'.

	Add more parameters to rb_source_search: the search implementation to
	use, and the previous search text.

	* plugins/audiocd/rb-audiocd-source.c:
	(rb_audiocd_source_class_init):
	* plugins/daap/rb-daap-source.c: (rb_daap_source_class_init):
	* sources/rb-import-errors-source.c:
	(rb_import_errors_source_class_init):
	* sources/rb-missing-files-source.c:
	(rb_missing_files_source_class_init):
	* sources/rb-play-queue-source.c:
	(rb_play_queue_source_class_init):
	* sources/rb-playlist-source.c: (rb_playlist_source_class_init):
	Remove impl_can_search methods.
	
	* widgets/rb-search-entry.c: (rb_search_entry_class_init),
	(rb_search_entry_activate_cb):
	Include the text from the search box when emitting the 'activate'
	signal.

	* shell/rb-source-header.c: (sourcestate_free),
	(rb_source_header_class_init), (rb_source_header_init),
	(rb_source_header_dispose), (merge_source_ui_cb),
	(rb_source_header_refresh_search_bar),
	(rb_source_header_set_source_internal),
	(rb_source_header_set_property), (rb_source_state_sync),
	(search_action_changed_cb), (rb_source_header_search_cb),
	(rb_source_header_clear_search),
	(rb_source_header_view_browser_changed_cb),
	(rb_source_header_sync_control_state),
	(rb_source_header_search_activate_cb):
	* shell/rb-source-header.h:
	* lib/rb-marshal.list:
	* lib/rb-util.c: (rb_signal_accumulator_value_array):
	* lib/rb-util.h:
	Track the active search action in the source header, rather than
	making the source itself do it.  Add signals to allow plugins to
	provide additional search actions (refresh-search-bar,
	get-search-actions).  Add support for 'explicit' search type.

	* shell/rb-shell.c: (rb_shell_class_init), (rb_shell_get_property):
	Add a property providing access to the source header object.

	* plugins/iradio/Makefile.am:
	* plugins/iradio/rb-iradio-source-search.c: (impl_create_query),
	(rb_iradio_source_search_class_init),
	(rb_iradio_source_search_init), (rb_iradio_source_search_new):
	* plugins/iradio/rb-iradio-source-search.h:
	* plugins/iradio/rb-iradio-source.c: (rb_iradio_source_class_init),
	(rb_iradio_source_dispose), (rb_iradio_source_constructor),
	(rb_iradio_source_new), (rb_iradio_source_add_station),
	(impl_search), (rb_iradio_source_do_query):
	* sources/rb-auto-playlist-source.c:
	(rb_auto_playlist_source_class_init),
	(rb_auto_playlist_source_dispose),
	(rb_auto_playlist_source_finalize),
	(rb_auto_playlist_source_constructor),
	(rb_auto_playlist_source_new),
	(rb_auto_playlist_source_set_property),
	(rb_auto_playlist_source_get_property), (impl_reset_filters),
	(impl_search), (impl_get_property_views), (impl_browser_toggled),
	(rb_auto_playlist_source_query_complete_cb),
	(rb_auto_playlist_source_do_query),
	(rb_auto_playlist_source_set_query),
	(rb_auto_playlist_source_get_query),
	(rb_auto_playlist_source_songs_sort_order_changed_cb):
	* sources/rb-browser-source.c: (rb_browser_source_class_init),
	(rb_browser_source_init), (rb_browser_source_dispose),
	(rb_browser_source_constructor), (rb_browser_source_set_property),
	(rb_browser_source_get_property), (impl_search),
	(impl_reset_filters), (rb_browser_source_do_query):
	* sources/rb-podcast-source.c: (rb_podcast_source_class_init),
	(rb_podcast_source_dispose), (rb_podcast_source_constructor),
	(rb_podcast_source_new), (impl_search), (feed_select_change_cb),
	(construct_query_from_selection), (rb_podcast_source_do_query):
	* sources/rb-static-playlist-source.c:
	(rb_static_playlist_source_class_init),
	(rb_static_playlist_source_dispose),
	(rb_static_playlist_source_finalize),
	(rb_static_playlist_source_constructor),
	(rb_static_playlist_source_new), (impl_reset_filters),
	(impl_search), (construct_query_from_selection):
	Rework sources that implement searching to use the new search stuff.
	The internet radio source needs a custom search implementation, since
	it matches on genre and title, but the others are pretty
	straightforward.
	
	* bindings/python/Makefile.am:
	* bindings/python/rb.defs:
	* bindings/python/rb.override:
	Update python bindings for the above.

	Fixes #508750.


Added:
   trunk/plugins/iradio/rb-iradio-source-search.c
   trunk/plugins/iradio/rb-iradio-source-search.h
   trunk/sources/rb-source-search-basic.c
   trunk/sources/rb-source-search-basic.h
   trunk/sources/rb-source-search.c
   trunk/sources/rb-source-search.h
Modified:
   trunk/ChangeLog
   trunk/bindings/python/Makefile.am
   trunk/bindings/python/rb.defs
   trunk/bindings/python/rb.override
   trunk/lib/rb-marshal.list
   trunk/lib/rb-util.c
   trunk/lib/rb-util.h
   trunk/plugins/audiocd/rb-audiocd-source.c
   trunk/plugins/daap/rb-daap-source.c
   trunk/plugins/iradio/Makefile.am
   trunk/plugins/iradio/rb-iradio-source.c
   trunk/shell/rb-shell.c
   trunk/shell/rb-source-header.c
   trunk/shell/rb-source-header.h
   trunk/sources/Makefile.am
   trunk/sources/rb-auto-playlist-source.c
   trunk/sources/rb-browser-source.c
   trunk/sources/rb-import-errors-source.c
   trunk/sources/rb-missing-files-source.c
   trunk/sources/rb-play-queue-source.c
   trunk/sources/rb-playlist-source.c
   trunk/sources/rb-podcast-source.c
   trunk/sources/rb-source.c
   trunk/sources/rb-source.h
   trunk/sources/rb-static-playlist-source.c
   trunk/widgets/rb-search-entry.c

Modified: trunk/bindings/python/Makefile.am
==============================================================================
--- trunk/bindings/python/Makefile.am	(original)
+++ trunk/bindings/python/Makefile.am	Sun Mar  8 23:47:33 2009
@@ -72,6 +72,8 @@
 	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		\

Modified: trunk/bindings/python/rb.defs
==============================================================================
--- trunk/bindings/python/rb.defs	(original)
+++ trunk/bindings/python/rb.defs	Sun Mar  8 23:47:33 2009
@@ -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 ...
 
@@ -191,6 +202,17 @@
   )
 )
 
+(define-enum SourceSearchType
+  (in-module "RB")
+  (c-name "RBSourceSearchType")
+  (gtype-id "RB_TYPE_SOURCE_SEARCH_TYPE")
+  (values
+    '("none" "RB_SOURCE_SEARCH_NONE")
+    '("incremental" "RB_SOURCE_SEARCH_INCREMENTAL")
+    '("explicit" "RB_SOURCE_SEARCH_EXPLICIT")
+  )
+)
+
 (define-enum EntryViewColumn
   (in-module "RB")
   (c-name "RBEntryViewColumn")
@@ -859,18 +881,14 @@
   (return-type "gboolean")
 )
 
-(define-method can_search
-  (of-object "RBSource")
-  (c-name "rb_source_can_search")
-  (return-type "gboolean")
-)
-
 (define-method search
   (of-object "RBSource")
   (c-name "rb_source_search")
   (return-type "none")
   (parameters
-    '("const-char*" "text")
+    '("RBSourceSearch*" "search")
+    '("const-char*" "cur_text")
+    '("const-char*" "new_text")
   )
 )
 
@@ -1089,16 +1107,13 @@
   (return-type "gboolean")
 )
 
-(define-virtual impl_can_search
-  (of-object "RBSource")
-  (return-type "gboolean")
-)
-
 (define-virtual impl_search
   (of-object "RBSource")
   (return-type "none")
   (parameters
-    '("const-char*" "text")
+    '("RBSourceSearch*" "search")
+    '("const-char*" "cur_text")
+    '("const-char*" "new_text")
   )
 )
 
@@ -2490,3 +2505,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")
+  )
+)

Modified: trunk/bindings/python/rb.override
==============================================================================
--- trunk/bindings/python/rb.override	(original)
+++ trunk/bindings/python/rb.override	Sun Mar  8 23:47:33 2009
@@ -30,6 +30,8 @@
 #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"

Modified: trunk/lib/rb-marshal.list
==============================================================================
--- trunk/lib/rb-marshal.list	(original)
+++ trunk/lib/rb-marshal.list	Sun Mar  8 23:47:33 2009
@@ -10,6 +10,7 @@
 STRING:STRING
 VOID:BOOLEAN,BOOLEAN
 BOXED:BOXED
+BOXED:OBJECT
 VOID:BOXED,BOXED
 VOID:BOXED,INT,POINTER,POINTER
 VOID:BOXED,OBJECT

Modified: trunk/lib/rb-util.c
==============================================================================
--- trunk/lib/rb-util.c	(original)
+++ trunk/lib/rb-util.c	Sun Mar  8 23:47:33 2009
@@ -836,6 +836,45 @@
 	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);
+		if (a != NULL) {
+			a = g_value_array_copy (a);
+		}
+	}
+
+	if (a == NULL) {
+		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);
+			a = 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, ...)
 {

Modified: trunk/lib/rb-util.h
==============================================================================
--- trunk/lib/rb-util.h	(original)
+++ trunk/lib/rb-util.h	Sun Mar  8 23:47:33 2009
@@ -82,6 +82,10 @@
 					       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 */
 

Modified: trunk/plugins/audiocd/rb-audiocd-source.c
==============================================================================
--- trunk/plugins/audiocd/rb-audiocd-source.c	(original)
+++ trunk/plugins/audiocd/rb-audiocd-source.c	Sun Mar  8 23:47:33 2009
@@ -102,7 +102,6 @@
 	object_class->dispose = rb_audiocd_source_dispose;
 
 	/* don't bother showing the browser/search bits */
-	source_class->impl_can_search = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_browse = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_paste = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;

Modified: trunk/plugins/daap/rb-daap-source.c
==============================================================================
--- trunk/plugins/daap/rb-daap-source.c	(original)
+++ trunk/plugins/daap/rb-daap-source.c	Sun Mar  8 23:47:33 2009
@@ -145,7 +145,6 @@
 	object_class->set_property = rb_daap_source_set_property;
 
 	source_class->impl_activate = rb_daap_source_activate;
-	source_class->impl_can_search = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_false_function;

Modified: trunk/plugins/iradio/Makefile.am
==============================================================================
--- trunk/plugins/iradio/Makefile.am	(original)
+++ trunk/plugins/iradio/Makefile.am	Sun Mar  8 23:47:33 2009
@@ -3,11 +3,13 @@
 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)

Added: trunk/plugins/iradio/rb-iradio-source-search.c
==============================================================================
--- (empty file)
+++ trunk/plugins/iradio/rb-iradio-source-search.c	Sun Mar  8 23:47:33 2009
@@ -0,0 +1,71 @@
+/* -*- 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.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your 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);
+}
+

Added: trunk/plugins/iradio/rb-iradio-source-search.h
==============================================================================
--- (empty file)
+++ trunk/plugins/iradio/rb-iradio-source-search.h	Sun Mar  8 23:47:33 2009
@@ -0,0 +1,66 @@
+/*
+ *  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.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your 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 */
+

Modified: trunk/plugins/iradio/rb-iradio-source.c
==============================================================================
--- trunk/plugins/iradio/rb-iradio-source.c	(original)
+++ trunk/plugins/iradio/rb-iradio-source.c	Sun Mar  8 23:47:33 2009
@@ -38,6 +38,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"
@@ -58,6 +59,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"
@@ -103,7 +105,7 @@
 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);
@@ -151,10 +153,9 @@
 	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;
@@ -199,7 +200,6 @@
 	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_can_pause = (RBSourceFeatureFunc) rb_false_function;
-	source_class->impl_can_search = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_delete = impl_delete;
 	source_class->impl_get_browser_key  = impl_get_browser_key;
 	source_class->impl_get_entry_view = impl_get_entry_view;
@@ -266,6 +266,16 @@
 		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);
 
@@ -385,6 +395,8 @@
 				 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);
@@ -445,6 +457,7 @@
 					  "entry-type", entry_type,
 					  "source-group", RB_SOURCE_GROUP_LIBRARY,
 					  "plugin", plugin,
+					  "search-type", RB_SOURCE_SEARCH_INCREMENTAL,
 					  NULL));
 	rb_shell_register_entry_type_for_source (shell, source, entry_type);
 	return source;
@@ -534,28 +547,23 @@
 
 	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));
@@ -781,26 +789,12 @@
 				      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);

Modified: trunk/shell/rb-shell.c
==============================================================================
--- trunk/shell/rb-shell.c	(original)
+++ trunk/shell/rb-shell.c	Sun Mar  8 23:47:33 2009
@@ -285,6 +285,7 @@
 	PROP_LIBRARY_SOURCE,
 	PROP_SOURCELIST_MODEL,
 	PROP_SOURCELIST,
+	PROP_SOURCE_HEADER,
 	PROP_VISIBILITY,
 };
 
@@ -645,6 +646,13 @@
 							       "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",
@@ -806,6 +814,9 @@
 	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;

Modified: trunk/shell/rb-source-header.c
==============================================================================
--- trunk/shell/rb-source-header.c	(original)
+++ trunk/shell/rb-source-header.c	Sun Mar  8 23:47:33 2009
@@ -45,6 +45,7 @@
 #include "rb-entry-view.h"
 #include "eel-gconf-extensions.h"
 #include "rb-util.h"
+#include "rb-marshal.h"
 
 /**
  * SECTION:rb-source-header
@@ -63,6 +64,7 @@
 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,
@@ -77,19 +79,28 @@
 					const char *text,
 					RBSourceHeader *header);
 static void rb_source_header_search_activate_cb (RBSearchEntry *search,
+						 const char *text,
 						 RBSourceHeader *header);
 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);
 }
@@ -104,16 +115,16 @@
 
 	GtkWidget *search;
 	GtkWidget *search_bar;
+	GtkRadioAction *search_group_head;
 
 	guint browser_notify_id;
 	guint search_notify_id;
-	gboolean have_search;
+	RBSourceSearchType search_type;
 	gboolean have_browser;
 	gboolean disclosed;
 	char *browser_key;
 
 	GHashTable *source_states;
-
 };
 
 #define RB_SOURCE_HEADER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_SOURCE_HEADER, RBSourceHeaderPrivate))
@@ -132,7 +143,15 @@
 	  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)
 
@@ -208,11 +227,14 @@
 	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;
+
 	/**
 	 * RBSourceHeader:source:
 	 *
@@ -250,6 +272,27 @@
 							      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));
 }
 
@@ -288,6 +331,16 @@
 
 	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
@@ -299,6 +352,26 @@
 }
 
 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)->dispose (object);
+}
+
+static void
 rb_source_header_finalize (GObject *object)
 {
 	RBSourceHeader *header;
@@ -324,6 +397,10 @@
 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",
@@ -331,19 +408,101 @@
 			       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);
+	}
+
+	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;
@@ -374,32 +533,33 @@
 					 G_CALLBACK (rb_source_header_filter_changed_cb),
 					 header, 0);
 
+		g_object_get (header->priv->selected_source, "search-type", &header->priv->search_type, NULL);
 		gtk_widget_set_sensitive (GTK_WIDGET (header->priv->search),
-					  rb_source_can_search (header->priv->selected_source));
-		header->priv->have_search = rb_source_can_search (header->priv->selected_source);
+					  (header->priv->search_type != RB_SOURCE_SEARCH_NONE));
 		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->search_type == RB_SOURCE_SEARCH_NONE)) {
 			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);
 }
 
@@ -418,9 +578,11 @@
 		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:
@@ -521,34 +683,99 @@
 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 = g_strdup (text ? text : "");
+	}
+
+	if (set_disclosure) {
+		state->disclosed = disclosed;
+	}
+
+	if (set_search) {
+		if (state->search_action != action) {
+			/* if the search action changes, there's no way we can use the current
+			 * search text to optimise the query.
+			 */
+			g_free (old_text);
+			old_text = NULL;
+
+			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
@@ -556,12 +783,17 @@
 			    const char *text,
 			    RBSourceHeader *header)
 {
+	if (header->priv->search_type != RB_SOURCE_SEARCH_INCREMENTAL) {
+		return;
+	}
 
-	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);
 }
 
@@ -578,13 +810,15 @@
 	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);
 }
@@ -596,11 +830,14 @@
 	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");
@@ -634,7 +871,7 @@
 	viewall_action = gtk_action_group_get_action (header->priv->actiongroup,
 						      "ViewAll");
 	g_object_set (G_OBJECT (viewall_action), "sensitive",
-		      (header->priv->have_browser || header->priv->have_search) && not_small, NULL);
+		      (header->priv->have_browser || (header->priv->search_type != RB_SOURCE_SEARCH_NONE)) && not_small, NULL);
 
 	gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (viewbrowser_action),
 				      header->priv->disclosed);
@@ -645,9 +882,18 @@
 
 static void
 rb_source_header_search_activate_cb (RBSearchEntry *search,
+				     const char *text,
 				     RBSourceHeader *header)
 {
-	gtk_widget_grab_focus (GTK_WIDGET (header->priv->selected_source));
+	if (header->priv->search_type == RB_SOURCE_SEARCH_EXPLICIT) {
+		rb_source_state_sync (header,
+				      TRUE, text,
+				      FALSE, NULL,
+				      FALSE, FALSE);
+	} else {
+		/* whatever this is supposed to do, I don't think it's doing it */
+		gtk_widget_grab_focus (GTK_WIDGET (header->priv->selected_source));
+	}
 }
 
 /**

Modified: trunk/shell/rb-source-header.h
==============================================================================
--- trunk/shell/rb-source-header.h	(original)
+++ trunk/shell/rb-source-header.h	Sun Mar  8 23:47:33 2009
@@ -58,6 +58,9 @@
 struct _RBSourceHeaderClass
 {
 	GtkTableClass parent_class;
+
+	/* action signal */
+	void	(*refresh_search_bar) (RBSourceHeader *header);
 };
 
 GType			rb_source_header_get_type	(void);

Modified: trunk/sources/Makefile.am
==============================================================================
--- trunk/sources/Makefile.am	(original)
+++ trunk/sources/Makefile.am	Sun Mar  8 23:47:33 2009
@@ -9,6 +9,8 @@
 	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 @@
 	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 =						\

Modified: trunk/sources/rb-auto-playlist-source.c
==============================================================================
--- trunk/sources/rb-auto-playlist-source.c	(original)
+++ trunk/sources/rb-auto-playlist-source.c	Sun Mar  8 23:47:33 2009
@@ -41,6 +41,7 @@
 #include "eel-gconf-extensions.h"
 #include "rb-stock-icons.h"
 #include "rb-playlist-xml.h"
+#include "rb-source-search-basic.h"
 
 /**
  * SECTION:rb-auto-playlist-source
@@ -77,7 +78,7 @@
 /* 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);
@@ -98,16 +99,13 @@
 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 }
 };
 
 enum
@@ -135,16 +133,16 @@
 	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)
@@ -165,7 +163,6 @@
 	source_class->impl_show_popup = impl_show_popup;
 	source_class->impl_can_browse = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_browser_toggled = impl_browser_toggled;
-	source_class->impl_can_search = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_search = impl_search;
 	source_class->impl_reset_filters = impl_reset_filters;
 	source_class->impl_get_property_views = impl_get_property_views;
@@ -205,36 +202,43 @@
 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);
 }
 
@@ -251,7 +255,7 @@
 
 	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 ();
 
@@ -280,10 +284,14 @@
 						    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 */
@@ -320,6 +328,7 @@
 					"is-local", local,
 					"entry-type", RHYTHMDB_ENTRY_TYPE_SONG,
 					"source-group", RB_SOURCE_GROUP_PLAYLISTS,
+					"search-type", RB_SOURCE_SEARCH_INCREMENTAL,
 					NULL));
 }
 
@@ -329,7 +338,7 @@
 				      const GValue *value,
 				      GParamSpec *pspec)
 {
-	/*RBAutoPlaylistSourcePrivate *priv = RB_AUTO_PLAYLIST_SOURCE_GET_PRIVATE (source);*/
+	/*RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (source);*/
 
 	switch (prop_id) {
 	default:
@@ -344,7 +353,7 @@
 				      GValue *value,
 				      GParamSpec *pspec)
 {
-	RBAutoPlaylistSourcePrivate *priv = RB_AUTO_PLAYLIST_SOURCE_GET_PRIVATE (object);
+	RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (object);
 
 	switch (prop_id) {
 	case PROP_BASE_QUERY_MODEL:
@@ -469,16 +478,16 @@
 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)
@@ -486,49 +495,39 @@
 }
 
 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;
+	}
+	
+	/* replace our search query */
+	if (priv->search_query != NULL) {
+		rhythmdb_query_free (priv->search_query);
+		priv->search_query = NULL;
 	}
-	g_free (old_search_text);
+	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);
@@ -553,7 +552,7 @@
 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;
 
@@ -721,7 +720,7 @@
 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) {
@@ -735,7 +734,7 @@
 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;
@@ -745,7 +744,7 @@
 
 	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);
@@ -754,7 +753,7 @@
 
 	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) {
@@ -808,7 +807,7 @@
 				   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));
 
@@ -870,7 +869,7 @@
 
  	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);
@@ -883,7 +882,7 @@
 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)
@@ -922,59 +921,3 @@
 	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);
-	}
-}

Modified: trunk/sources/rb-browser-source.c
==============================================================================
--- trunk/sources/rb-browser-source.c	(original)
+++ trunk/sources/rb-browser-source.c	Sun Mar  8 23:47:33 2009
@@ -51,6 +51,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"
@@ -106,7 +107,7 @@
 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);
@@ -135,12 +136,13 @@
 	RBEntryView *songs;
 	GtkWidget *paned;
 
-	char *search_text;
 	RhythmDBQueryModel *cached_all_query;
+	RhythmDBQuery *search_query;
 	RhythmDBPropType search_prop;
 	gboolean populate;
 	gboolean query_active;
 	gboolean search_on_completion;
+	RBSourceSearch *default_search;
 
 	GtkActionGroup *action_group;
 	GtkActionGroup *search_action_group;
@@ -170,10 +172,10 @@
 
 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[] = {
@@ -186,7 +188,8 @@
 	PROP_0,
 	PROP_SORTING_KEY,
 	PROP_BASE_QUERY_MODEL,
-	PROP_POPULATE
+	PROP_POPULATE,
+	PROP_SEARCH_TYPE
 };
 
 G_DEFINE_ABSTRACT_TYPE (RBBrowserSource, rb_browser_source, RB_TYPE_SOURCE)
@@ -205,7 +208,6 @@
 	object_class->get_property = rb_browser_source_get_property;
 
 	source_class->impl_can_browse = (RBSourceFeatureFunc) rb_true_function;
-	source_class->impl_can_search = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_search = impl_search;
 	source_class->impl_get_entry_view = impl_get_entry_view;
 	source_class->impl_get_property_views = impl_get_property_views;
@@ -246,6 +248,10 @@
 							       TRUE,
 							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 
+	g_object_class_override_property (object_class,
+					  PROP_SEARCH_TYPE,
+					  "search-type");
+
 	g_type_class_add_private (klass, sizeof (RBBrowserSourcePrivate));
 }
 
@@ -253,8 +259,6 @@
 rb_browser_source_init (RBBrowserSource *source)
 {
 	source->priv = RB_BROWSER_SOURCE_GET_PRIVATE (source);
-
-	source->priv->search_prop = RHYTHMDB_PROP_SEARCH_MATCH;
 }
 
 static void
@@ -275,9 +279,9 @@
 		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) {
@@ -290,6 +294,11 @@
 		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);
@@ -334,60 +343,6 @@
 	_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,
@@ -424,11 +379,17 @@
 						    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);
@@ -553,6 +514,9 @@
 			rb_browser_source_populate (source);
 		}
 		break;
+	case PROP_SEARCH_TYPE:
+		/* ignored */
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -577,6 +541,9 @@
 	case PROP_POPULATE:
 		g_value_set_boolean (value, source->priv->populate);
 		break;
+	case PROP_SEARCH_TYPE:
+		g_value_set_enum (value, RB_SOURCE_SEARCH_INCREMENTAL);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -684,43 +651,31 @@
 }
 
 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;
+	gboolean subset;
 
-	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;
-
-	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);
 	}
 }
@@ -754,10 +709,11 @@
 	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);
@@ -956,7 +912,7 @@
 	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);
@@ -968,9 +924,8 @@
 				      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);
 

Modified: trunk/sources/rb-import-errors-source.c
==============================================================================
--- trunk/sources/rb-import-errors-source.c	(original)
+++ trunk/sources/rb-import-errors-source.c	Sun Mar  8 23:47:33 2009
@@ -91,7 +91,6 @@
 	source_class->impl_can_browse = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_get_entry_view = impl_get_entry_view;
 	source_class->impl_can_rename = (RBSourceFeatureFunc) rb_false_function;
-	source_class->impl_can_search = (RBSourceFeatureFunc) rb_false_function;
 
 	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;

Modified: trunk/sources/rb-missing-files-source.c
==============================================================================
--- trunk/sources/rb-missing-files-source.c	(original)
+++ trunk/sources/rb-missing-files-source.c	Sun Mar  8 23:47:33 2009
@@ -101,7 +101,6 @@
 	source_class->impl_can_browse = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_get_entry_view = impl_get_entry_view;
 	source_class->impl_can_rename = (RBSourceFeatureFunc) rb_false_function;
-	source_class->impl_can_search = (RBSourceFeatureFunc) rb_false_function;
 
 	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;

Modified: trunk/sources/rb-play-queue-source.c
==============================================================================
--- trunk/sources/rb-play-queue-source.c	(original)
+++ trunk/sources/rb-play-queue-source.c	Sun Mar  8 23:47:33 2009
@@ -171,7 +171,6 @@
 
 	source_class->impl_can_add_to_queue = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_rename = (RBSourceFeatureFunc) rb_false_function;
-	source_class->impl_can_search = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_browse = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_get_ui_actions = impl_get_ui_actions;
 	source_class->impl_show_popup = impl_show_popup;

Modified: trunk/sources/rb-playlist-source.c
==============================================================================
--- trunk/sources/rb-playlist-source.c	(original)
+++ trunk/sources/rb-playlist-source.c	Sun Mar  8 23:47:33 2009
@@ -175,7 +175,6 @@
 	source_class->impl_get_browser_key = impl_get_browser_key;
 	source_class->impl_get_entry_view = impl_get_entry_view;
 	source_class->impl_can_rename = (RBSourceFeatureFunc) rb_true_function;
-	source_class->impl_can_search = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_false_function;

Modified: trunk/sources/rb-podcast-source.c
==============================================================================
--- trunk/sources/rb-podcast-source.c	(original)
+++ trunk/sources/rb-podcast-source.c	Sun Mar  8 23:47:33 2009
@@ -71,15 +71,9 @@
 #include "rb-podcast-manager.h"
 #include "rb-static-playlist-source.h"
 #include "rb-cut-and-paste-code.h"
+#include "rb-source-search-basic.h"
 #include "rb-cell-renderer-pixbuf.h"
 
-typedef enum
-{
-	RB_PODCAST_QUERY_TYPE_ALL,
-	RB_PODCAST_QUERY_TYPE_ALBUM,
-	RB_PODCAST_QUERY_TYPE_SEARCH,
-} RBPodcastQueryType;
-
 static void rb_podcast_source_class_init 		(RBPodcastSourceClass *klass);
 
 static void rb_podcast_source_init 			(RBPodcastSource *source);
@@ -244,13 +238,14 @@
 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,
@@ -297,10 +292,10 @@
 	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;
 
@@ -341,9 +336,9 @@
 
 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[] = {
@@ -381,7 +376,6 @@
 	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_can_pause = (RBSourceFeatureFunc) rb_true_function;
-	source_class->impl_can_search = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_delete = impl_delete;
 	source_class->impl_get_browser_key  = impl_get_browser_key;
 	source_class->impl_get_config_widget = impl_get_config_widget;
@@ -462,9 +456,9 @@
 		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) {
@@ -509,53 +503,6 @@
 	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,
@@ -601,8 +548,13 @@
 					    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 ();
 
@@ -828,7 +780,7 @@
 			   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),
@@ -843,7 +795,7 @@
 	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);
 }
@@ -903,6 +855,7 @@
 					  "shell", shell,
 					  "entry-type", RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
 					  "source-group", RB_SOURCE_GROUP_LIBRARY,
+					  "search-type", RB_SOURCE_SEARCH_INCREMENTAL,
 					  NULL));
 
 	rb_shell_register_entry_type_for_source (shell, source,
@@ -914,29 +867,20 @@
 }
 
 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;
-	}
-
-	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 (search == NULL) {
+		search = source->priv->default_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));
 }
@@ -1178,7 +1122,7 @@
 
 	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));
 }
 
@@ -1204,34 +1148,12 @@
 				      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) {
@@ -1266,35 +1188,18 @@
 }
 
 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),

Added: trunk/sources/rb-source-search-basic.c
==============================================================================
--- (empty file)
+++ trunk/sources/rb-source-search-basic.c	Sun Mar  8 23:47:33 2009
@@ -0,0 +1,163 @@
+/* -*- 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.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your 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);
+		}
+	}
+}
+

Added: trunk/sources/rb-source-search-basic.h
==============================================================================
--- (empty file)
+++ trunk/sources/rb-source-search-basic.h	Sun Mar  8 23:47:33 2009
@@ -0,0 +1,71 @@
+/*
+ *  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.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your 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 */
+

Added: trunk/sources/rb-source-search.c
==============================================================================
--- (empty file)
+++ trunk/sources/rb-source-search.c	Sun Mar  8 23:47:33 2009
@@ -0,0 +1,158 @@
+/* -*- 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.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your 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 (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 (or NULL if the current search was done with a different
+ *    search implementation and so cannot be considered)
+ * @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[0] == '\0')
+		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);
+}
+

Added: trunk/sources/rb-source-search.h
==============================================================================
--- (empty file)
+++ trunk/sources/rb-source-search.h	Sun Mar  8 23:47:33 2009
@@ -0,0 +1,99 @@
+/*
+ *  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.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your 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.
+ */
+
+#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 */
+

Modified: trunk/sources/rb-source.c
==============================================================================
--- trunk/sources/rb-source.c	(original)
+++ trunk/sources/rb-source.c	Sun Mar  8 23:47:33 2009
@@ -64,7 +64,6 @@
 static char * default_get_browser_key (RBSource *source);
 static GList *default_get_property_views (RBSource *source);
 static gboolean default_can_rename (RBSource *source);
-static gboolean default_can_search (RBSource *source);
 static GList *default_copy (RBSource *source);
 static void default_reset_filters (RBSource *source);
 static gboolean default_try_playlist (RBSource *source);
@@ -124,6 +123,7 @@
 	RhythmDBEntryType entry_type;
 	RBSourceGroup *source_group;
 	RBPlugin *plugin;
+	RBSourceSearchType search_type;
 };
 
 enum
@@ -140,7 +140,8 @@
 	PROP_ENTRY_TYPE,
 	PROP_PLUGIN,
 	PROP_BASE_QUERY_MODEL,
-	PROP_PLAY_ORDER
+	PROP_PLAY_ORDER,
+	PROP_SEARCH_TYPE
 };
 
 enum
@@ -169,7 +170,6 @@
 	klass->impl_browser_toggled = NULL;
 	klass->impl_get_property_views = default_get_property_views;
 	klass->impl_can_rename = default_can_rename;
-	klass->impl_can_search = default_can_search;
 	klass->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
 	klass->impl_can_paste = (RBSourceFeatureFunc) rb_false_function;
 	klass->impl_can_delete = (RBSourceFeatureFunc) rb_false_function;
@@ -354,6 +354,21 @@
 							      G_PARAM_READABLE));
 
 	/**
+	 * RBSource:search-type:
+	 *
+	 * The type of searching this source provides, as a RBSourceSearchType value.
+	 * This is used by the RBSourceHeader to modify the search box widget.
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_SEARCH_TYPE,
+					 g_param_spec_enum ("search-type",
+						 	    "search-type",
+							    "search type",
+							    RB_TYPE_SOURCE_SEARCH_TYPE,
+							    RB_SOURCE_SEARCH_NONE,
+							    G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+	/**
 	 * RBSource::deleted:
 	 * @source: the #RBSource
 	 *
@@ -622,6 +637,9 @@
 	case PROP_PLUGIN:
 		priv->plugin = g_value_get_object (value);
 		break;
+	case PROP_SEARCH_TYPE:
+		priv->search_type = g_value_get_enum (value);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -680,6 +698,9 @@
 	case PROP_PLAY_ORDER:
 		g_value_set_object (value, NULL);		/* ? */
 		break;
+	case PROP_SEARCH_TYPE:
+		g_value_set_enum (value, priv->search_type);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -910,27 +931,6 @@
 	return klass->impl_can_rename (source);
 }
 
-static gboolean
-default_can_search (RBSource *source)
-{
-	return FALSE;
-}
-
-/**
- * rb_source_can_search:
- * @source: a #RBSource
- *
- * Return value: TRUE if the source can be searched using
- * the search box
- */
-gboolean
-rb_source_can_search (RBSource *source)
-{
-	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
-
-	return klass->impl_can_search (source);
-}
-
 /**
  * rb_source_search:
  * @source: a #RBSource
@@ -942,13 +942,15 @@
  */
 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);
 }
 
 /**
@@ -1758,3 +1760,23 @@
 
 	return etype;
 }
+
+GType
+rb_source_search_type_get_type (void)
+{
+	static GType etype = 0;
+
+	if (etype == 0) {
+		static const GEnumValue values[] = {
+			ENUM_ENTRY (RB_SOURCE_SEARCH_NONE, "No search capability"),
+			ENUM_ENTRY (RB_SOURCE_SEARCH_INCREMENTAL, "Immediate incremental search"),
+			ENUM_ENTRY (RB_SOURCE_SEARCH_EXPLICIT, "Explicitly activated search"),
+			{ 0, 0, 0 }
+		};
+
+		etype = g_enum_register_static ("RBSourceSearchType", values);
+	}
+
+	return etype;
+}
+

Modified: trunk/sources/rb-source.h
==============================================================================
--- trunk/sources/rb-source.h	(original)
+++ trunk/sources/rb-source.h	Sun Mar  8 23:47:33 2009
@@ -34,6 +34,7 @@
 #include <gtk/gtk.h>
 
 #include "rb-source-group.h"
+#include "rb-source-search.h"
 #include "rb-entry-view.h"
 #include "rb-shell-preferences.h"
 
@@ -46,9 +47,18 @@
 	RB_SOURCE_EOF_NEXT,
 } RBSourceEOFType;
 
+typedef enum {
+	RB_SOURCE_SEARCH_NONE,
+	RB_SOURCE_SEARCH_INCREMENTAL,
+	RB_SOURCE_SEARCH_EXPLICIT,
+} RBSourceSearchType;
+
 GType rb_source_eof_type_get_type (void);
 #define RB_TYPE_SOURCE_EOF_TYPE	(rb_source_eof_type_get_type())
 
+GType rb_source_search_type_get_type (void);
+#define RB_TYPE_SOURCE_SEARCH_TYPE (rb_source_search_type_get_type())
+
 #define RB_TYPE_SOURCE         (rb_source_get_type ())
 #define RB_SOURCE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_SOURCE, RBSource))
 #define RB_SOURCE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_SOURCE, RBSourceClass))
@@ -92,9 +102,7 @@
 
 	gboolean	(*impl_can_rename)	(RBSource *source);
 
-	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);
 
@@ -156,9 +164,10 @@
 
 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);
 

Modified: trunk/sources/rb-static-playlist-source.c
==============================================================================
--- trunk/sources/rb-static-playlist-source.c	(original)
+++ trunk/sources/rb-static-playlist-source.c	Sun Mar  8 23:47:33 2009
@@ -42,6 +42,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);
@@ -60,7 +61,7 @@
 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);
@@ -104,16 +105,12 @@
 						      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 }
 };
 
 enum
@@ -136,10 +133,10 @@
 	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;
 
@@ -165,7 +162,6 @@
 	source_class->impl_paste = impl_paste;
 	source_class->impl_delete = impl_delete;
 	source_class->impl_receive_drag = impl_receive_drag;
-	source_class->impl_can_search = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_search = impl_search;
 	source_class->impl_reset_filters = impl_reset_filters;
 	source_class->impl_can_browse = (RBSourceFeatureFunc) rb_true_function;
@@ -237,6 +233,11 @@
 		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);
 }
 
@@ -247,7 +248,10 @@
 
 	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);
 }
@@ -287,10 +291,13 @@
 						    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);
 
@@ -346,6 +353,7 @@
 					"is-local", local,
 					"entry-type", entry_type,
 					"source-group", RB_SOURCE_GROUP_PLAYLISTS,
+					"search-type", RB_SOURCE_SEARCH_INCREMENTAL,
 					NULL));
 }
 
@@ -465,10 +473,10 @@
 	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) {
@@ -478,29 +486,22 @@
 }
 
 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));
 }
@@ -538,10 +539,10 @@
 
 	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);
 	}
 
@@ -895,63 +896,6 @@
 	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)
 {

Modified: trunk/widgets/rb-search-entry.c
==============================================================================
--- trunk/widgets/rb-search-entry.c	(original)
+++ trunk/widgets/rb-search-entry.c	Sun Mar  8 23:47:33 2009
@@ -119,6 +119,7 @@
 	/**
 	 * RBSearchEntry::activate:
 	 * @entry: the #RBSearchEntry
+	 * @text: search text
 	 *
 	 * Emitted when the entry is activated.
 	 */
@@ -128,9 +129,10 @@
 			      G_SIGNAL_RUN_LAST,
 			      G_STRUCT_OFFSET (RBSearchEntryClass, activate),
 			      NULL, NULL,
-			      g_cclosure_marshal_VOID__VOID,
+			      g_cclosure_marshal_VOID__STRING,
 			      G_TYPE_NONE,
-			      0);
+			      1,
+			      G_TYPE_STRING);
 
 	g_type_class_add_private (klass, sizeof (RBSearchEntryPrivate));
 }
@@ -340,7 +342,8 @@
 rb_search_entry_activate_cb (GtkEntry *gtkentry,
 			     RBSearchEntry *entry)
 {
-	g_signal_emit (G_OBJECT (entry), rb_search_entry_signals[ACTIVATE], 0);
+	g_signal_emit (G_OBJECT (entry), rb_search_entry_signals[ACTIVATE], 0,
+		       gtk_entry_get_text (GTK_ENTRY (entry->priv->entry)));
 }
 
 /**



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]