[rhythmbox] rework search UI and add source toolbar



commit dc287cca2385b35c99216ffe7e1647f82591717d
Author: Jonathan Matthew <jonathan d14n org>
Date:   Sun Oct 30 13:29:33 2011 +1000

    rework search UI and add source toolbar
    
    This replaces the search toolbar with a menu accessible through the
    primary icon in the search entry, and makes the search entry look
    more like search entries elsewhere in GNOME, particularly evolution,
    empathy and the control center.
    
    In the same row as the search entry, we now have a toolbar containing
    source-specific actions, such as adding new radio streams or podcasts,
    and toggling the visibility of the browser.  Sources and plugins no
    longer have the ability to add items to the main toolbar.  Since the
    main toolbar layout is now static, we can look at changing to a more
    compact layout.
    
    There is now once toolbar instance per source, rather than one shared
    between them.  The toolbar takes care of remembering search state
    for the source it's attached to, rather than having it stored externally.

 bindings/gi/Makefile.am                            |    6 +-
 data/ui/rhythmbox-ui.xml                           |   73 ++-
 plugins/audiocd/audiocd-ui.xml                     |    7 +
 plugins/audiocd/rb-audiocd-source.c                |   44 +-
 .../rb-audioscrobbler-profile-page.c               |   42 +-
 .../rb-audioscrobbler-radio-source.c               |   27 +-
 .../rb-disc-recorder-plugin.c                      |    9 +-
 plugins/daap/daap-ui.xml                           |    6 +
 plugins/daap/rb-daap-source.c                      |    5 +-
 plugins/fmradio/fmradio-ui.xml                     |    4 +
 plugins/fmradio/rb-fm-radio-source.c               |   35 +-
 plugins/generic-player/generic-player-ui.xml       |    7 +
 plugins/generic-player/rb-generic-player-source.c  |    1 +
 plugins/grilo/rb-grilo-source.c                    |    5 +-
 plugins/ipod/ipod-ui.xml                           |    7 +
 plugins/ipod/rb-ipod-source.c                      |   15 +-
 plugins/iradio/iradio-ui.xml                       |    5 +
 plugins/iradio/rb-iradio-source.c                  |   51 +-
 plugins/mtpdevice/mtp-ui.xml                       |    7 +
 plugins/mtpdevice/rb-mtp-source.c                  |   16 +-
 plugins/visualizer/rb-visualizer-page.c            |    4 +
 podcast/rb-podcast-main-source.c                   |    2 +-
 podcast/rb-podcast-source.c                        |   68 +-
 shell/Makefile.am                                  |    2 -
 shell/rb-shell.c                                   |   60 +--
 shell/rb-source-header.c                           |  906 --------------------
 shell/rb-source-header.h                           |   78 --
 sources/rb-auto-playlist-source.c                  |   47 +-
 sources/rb-browser-source.c                        |   88 +--
 sources/rb-browser-source.h                        |    6 +-
 sources/rb-display-page.c                          |   62 +-
 sources/rb-display-page.h                          |    3 -
 sources/rb-import-errors-source.c                  |    1 -
 sources/rb-library-source.c                        |    3 +-
 sources/rb-media-player-source.c                   |    2 +-
 sources/rb-missing-files-source.c                  |    1 -
 sources/rb-play-queue-source.c                     |   17 +-
 sources/rb-source.c                                |  121 +--
 sources/rb-source.h                                |   18 +-
 sources/rb-static-playlist-source.c                |   62 +-
 sources/rb-static-playlist-source.h                |    2 +-
 widgets/Makefile.am                                |   10 +-
 widgets/rb-search-entry.c                          |  163 +++--
 widgets/rb-search-entry.h                          |    5 +-
 widgets/rb-source-toolbar.c                        |  488 +++++++++++
 widgets/rb-source-toolbar.h                        |   73 ++
 46 files changed, 1115 insertions(+), 1549 deletions(-)
---
diff --git a/bindings/gi/Makefile.am b/bindings/gi/Makefile.am
index 8b74063..3a55f67 100644
--- a/bindings/gi/Makefile.am
+++ b/bindings/gi/Makefile.am
@@ -115,6 +115,10 @@ rb_introspection_sources = \
 		widgets/rb-rating.c \
 		widgets/rb-library-browser.h \
 		widgets/rb-library-browser.c \
+		widgets/rb-search-entry.h \
+		widgets/rb-search-entry.c \
+		widgets/rb-source-toolbar.h \
+		widgets/rb-source-toolbar.c \
 		widgets/rb-segmented-bar.h \
 		widgets/rb-segmented-bar.c \
 		widgets/rb-song-info.h \
@@ -131,7 +135,7 @@ MPID_3_0_gir_PACKAGES = gobject-2.0
 MPID_3_0_gir_SCANNERFLAGS = --symbol-prefix mpid_
 
 RB-3.0.gir: $(top_builddir)/shell/librhythmbox-core.la MPID-3.0.gir
-RB_3_0_gir_INCLUDES = GObject-2.0 Gio-2.0 Gtk-3.0 Gst-0.10 libxml2-2.0
+RB_3_0_gir_INCLUDES = GObject-2.0 Gio-2.0 Gtk-3.0 Gst-0.10 GstPbutils-0.10 libxml2-2.0
 RB_3_0_gir_CFLAGS = $(RHYTHMBOX_CFLAGS_NOWARN) $(TOTEM_PLPARSER_CFLAGS) $(DBUS_CFLAGS) $(GUDEV_CFLAGS) -I$(top_srcdir)
 RB_3_0_gir_LIBS = $(top_builddir)/shell/librhythmbox-core.la
 RB_3_0_gir_FILES = $(addprefix $(top_srcdir)/,$(rb_introspection_sources))
diff --git a/data/ui/rhythmbox-ui.xml b/data/ui/rhythmbox-ui.xml
index 133eae5..ebf29ef 100644
--- a/data/ui/rhythmbox-ui.xml
+++ b/data/ui/rhythmbox-ui.xml
@@ -103,13 +103,6 @@
     <toolitem name="Repeat" action="ControlRepeat"/>
     <toolitem name="Shuffle" action="ControlShuffle"/>
     <separator/>
-    <toolitem name="Browse" action="ViewBrowser"/>
-    <separator/>
-    <placeholder name="PluginPlaceholder" />
-  </toolbar>
-
-  <toolbar name="SearchBar">
-    <placeholder name="PluginPlaceholder" />
   </toolbar>
 
   <popup name="BrowserSourceViewPopup">
@@ -133,6 +126,71 @@
     <menuitem name="PropertiesLibraryPopup" action="MusicProperties"/>
   </popup>
 
+  <popup name="BrowserSourceSearchMenu">
+    <menuitem name="SearchAll" action="BrowserSourceSearchAll"/>
+    <menuitem name="SearchArtists" action="BrowserSourceSearchArtists"/>
+    <menuitem name="SearchAlbums" action="BrowserSourceSearchAlbums"/>
+    <menuitem name="SearchTitles" action="BrowserSourceSearchTitles"/>
+    <placeholder name="PluginPlaceholder"/>
+  </popup>
+
+  <popup name="AutoPlaylistSourceSearchMenu">
+    <menuitem name="SearchAll" action="AutoPlaylistSearchAll"/>
+    <menuitem name="SearchArtists" action="AutoPlaylistSearchArtists"/>
+    <menuitem name="SearchAlbums" action="AutoPlaylistSearchAlbums"/>
+    <menuitem name="SearchTitles" action="AutoPlaylistSearchTitles"/>
+    <placeholder name="PluginPlaceholder"/>
+  </popup>
+
+  <popup name="StaticPlaylistSourceSearchMenu">
+    <menuitem name="SearchAll" action="StaticPlaylistSearchAll"/>
+    <menuitem name="SearchArtists" action="StaticPlaylistSearchArtists"/>
+    <menuitem name="SearchAlbums" action="StaticPlaylistSearchAlbums"/>
+    <menuitem name="SearchTitles" action="StaticPlaylistSearchTitles"/>
+    <placeholder name="PluginPlaceholder"/>
+  </popup>
+
+  <popup name="PodcastSourceSearchMenu">
+    <menuitem name="SearchAll" action="PodcastSearchAll"/>
+    <menuitem name="SearchFeeds" action="PodcastSearchFeeds"/>
+    <menuitem name="SearchEpisodes" action="PodcastSearchEpisodes"/>
+    <placeholder name="PluginPlaceholder"/>
+  </popup>
+
+  <toolbar name="LibrarySourceToolBar">
+    <toolitem name="Browse" action="ViewBrowser"/>
+    <toolitem name="ViewAll" action="ViewAll"/>
+    <toolitem name="LibraryImport" action="MusicImportFolder"/>
+    <placeholder name="PluginPlaceholder"/>
+  </toolbar>
+
+  <toolbar name="QueueSourceToolBar">
+    <toolitem name="ShuffleQueue" action="ShuffleQueue"/>
+    <toolitem name="Clear" action="ClearQueue"/>
+    <placeholder name="PluginPlaceholder"/>
+  </toolbar>
+
+  <toolbar name="StaticPlaylistSourceToolBar">
+    <toolitem name="Browse" action="ViewBrowser"/>
+    <toolitem name="ViewAll" action="ViewAll"/>
+    <placeholder name="PluginPlaceholder"/>
+  </toolbar>
+
+  <toolbar name="AutoPlaylistSourceToolBar">
+    <toolitem name="Browse" action="ViewBrowser"/>
+    <toolitem name="ViewAll" action="ViewAll"/>
+    <toolitem name="Edit" action="EditAutomaticPlaylist"/>
+    <placeholder name="PluginPlaceholder"/>
+  </toolbar>
+
+  <toolbar name="PodcastSourceToolBar">
+    <toolitem name="Browse" action="ViewBrowser"/>
+    <toolitem name="ViewAll" action="ViewAll"/>
+    <toolitem name="NewPodcast" action="MusicNewPodcast"/>
+    <toolitem name="UpdateFeed" action="PodcastFeedUpdate"/>
+    <placeholder name="PluginPlaceholder"/>
+  </toolbar>
+
   <popup name="ImportErrorsViewPopup">
     <menuitem name="DeleteLibraryPopup" action="EditRemove"/>
     <menuitem name="MovetoTrashPopup" action="EditMovetoTrash"/>
@@ -261,6 +319,7 @@
     <menuitem name="PlaylistSourceSavePlaylistPopup" action="MusicPlaylistSavePlaylist"/>
   </popup>
 
+
   <popup name="MissingFilesViewPopup">
     <menuitem name="DeleteMissingFilesPopup" action="EditRemove"/>
     <menuitem name="PropertiesMissingFilesPopup" action="MusicProperties"/>
diff --git a/plugins/audiocd/audiocd-ui.xml b/plugins/audiocd/audiocd-ui.xml
index 3e29135..86fb016 100644
--- a/plugins/audiocd/audiocd-ui.xml
+++ b/plugins/audiocd/audiocd-ui.xml
@@ -4,4 +4,11 @@
     <placeholder name="PluginPlaceholder"/>
     <menuitem name="AudioCdSourcePopupEjectCd" action="RemovableSourceEject"/>
   </popup>
+
+  <toolbar name="AudioCdSourceToolBar">
+    <toolitem name="CopyTracks" action="AudioCdCopyTracks"/>
+    <toolitem name="Eject" action="RemovableSourceEject"/>
+    <toolitem name="Reload" action="AudioCdSourceReloadMetadata"/>
+    <placeholder name="PluginPlaceholder"/>
+  </toolbar>
 </ui>
diff --git a/plugins/audiocd/rb-audiocd-source.c b/plugins/audiocd/rb-audiocd-source.c
index 7a0619c..39c1a86 100644
--- a/plugins/audiocd/rb-audiocd-source.c
+++ b/plugins/audiocd/rb-audiocd-source.c
@@ -48,6 +48,7 @@
 #include "rb-dialog.h"
 #include "rb-builder-helpers.h"
 #include "rb-file-helpers.h"
+#include "rb-source-toolbar.h"
 #include "rb-shell-player.h"
 
 #ifdef HAVE_SJ_METADATA_GETTER
@@ -58,7 +59,6 @@
 enum
 {
 	PROP_0,
-	PROP_SEARCH_TYPE,
 	PROP_VOLUME,
 };
 
@@ -71,7 +71,6 @@ static void impl_get_property (GObject *object, guint prop_id, GValue *value, GP
 
 static gboolean impl_show_popup (RBDisplayPage *page);
 static void impl_delete_thyself (RBDisplayPage *page);
-static GList* impl_get_ui_actions (RBDisplayPage *page);
 
 
 static guint impl_want_uri (RBSource *source, const char *uri);
@@ -167,11 +166,9 @@ static GtkActionEntry rb_audiocd_source_actions[] = {
 	{ "AudioCdCopyTracks", GTK_STOCK_CDROM, N_("_Extract to Library"), NULL,
 	  N_("Copy tracks to the library"),
 	  G_CALLBACK (copy_tracks_cmd) },
-#if defined(HAVE_SJ_METADATA_GETTER)
 	{ "AudioCdSourceReloadMetadata", GTK_STOCK_REFRESH, N_("Reload"), NULL,
 	N_("Reload Album Information"),
 	G_CALLBACK (reload_metadata_cmd) },
-#endif
 };
 
 static void
@@ -226,10 +223,7 @@ rb_audiocd_source_class_init (RBAudioCdSourceClass *klass)
 
 	page_class->show_popup = impl_show_popup;
 	page_class->delete_thyself = impl_delete_thyself;
-	page_class->get_ui_actions = impl_get_ui_actions;
 
-	/* don't bother showing the browser/search bits */
-	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;
 	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_true_function;
@@ -238,9 +232,6 @@ rb_audiocd_source_class_init (RBAudioCdSourceClass *klass)
 	source_class->impl_try_playlist = (RBSourceFeatureFunc) rb_true_function;	/* shouldn't need this. */
 	source_class->impl_want_uri = impl_want_uri;
 
-	g_object_class_override_property (object_class,
-					  PROP_SEARCH_TYPE,
-					  "search-type");
 	g_object_class_install_property (object_class,
 					 PROP_VOLUME,
 					 g_param_spec_object ("volume",
@@ -334,6 +325,7 @@ rb_audiocd_source_constructed (GObject *object)
 	RBEntryView *entry_view;
 	GtkCellRenderer *renderer;
 	GtkTreeViewColumn *extract;
+	GtkUIManager *ui_manager;
 	GtkBuilder *builder;
 	GtkWidget *grid;
 	GtkWidget *widget;
@@ -346,6 +338,7 @@ rb_audiocd_source_constructed (GObject *object)
 	RhythmDBQueryModel *query_model;
 	RhythmDBQuery *query;
 	RhythmDBEntryType *entry_type;
+	RBSourceToolbar *toolbar;
 	char *ui_file;
 	int toggle_width;
 #if defined(HAVE_SJ_METADATA_GETTER)
@@ -362,6 +355,7 @@ rb_audiocd_source_constructed (GObject *object)
 	g_object_get (shell,
 		      "db", &db,
 		      "shell-player", &shell_player,
+		      "ui-manager", &ui_manager,
 		      NULL);
 
 	source->priv->action_group =
@@ -384,6 +378,9 @@ rb_audiocd_source_constructed (GObject *object)
 	action = gtk_action_group_get_action (source->priv->action_group, "AudioCdSourceReloadMetadata");
 	g_object_set (action, "visible", FALSE, NULL);
 #endif
+	/* source toolbar */
+	toolbar = rb_source_toolbar_new (RB_SOURCE (source), ui_manager);
+	g_object_unref (ui_manager);
 
 	g_object_get (source, "entry-type", &entry_type, NULL);
 	query = rhythmdb_query_parse (db,
@@ -496,8 +493,9 @@ rb_audiocd_source_constructed (GObject *object)
 
 	grid = gtk_grid_new ();
 	gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
-	gtk_grid_attach (GTK_GRID (grid), infogrid, 0, 0, 1, 1);
-	gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (entry_view), 0, 1, 1, 1);
+	gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (toolbar), 0, 0, 1, 1);
+	gtk_grid_attach (GTK_GRID (grid), infogrid, 0, 1, 1, 1);
+	gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (entry_view), 0, 2, 1, 1);
 	g_object_unref (builder);
 
 	gtk_widget_show_all (grid);
@@ -545,6 +543,7 @@ rb_audiocd_source_new (GObject *plugin,
 			       "plugin", plugin,
 			       "show-browser", FALSE,
 			       "settings", g_settings_get_child (settings, "source"),
+			       "toolbar-path", "/AudioCdSourceToolBar",
 			       NULL);
 	g_object_unref (settings);
 
@@ -559,9 +558,6 @@ impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSp
 	RBAudioCdSource *source = RB_AUDIOCD_SOURCE (object);
 
 	switch (prop_id) {
-	case PROP_SEARCH_TYPE:
-		/* ignored */
-		break;
 	case PROP_VOLUME:
 		source->priv->volume = g_value_dup_object (value);
 		break;
@@ -577,9 +573,6 @@ impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *ps
 	RBAudioCdSource *source = RB_AUDIOCD_SOURCE (object);
 
 	switch (prop_id) {
-	case PROP_SEARCH_TYPE:
-		g_value_set_enum (value, RB_SOURCE_SEARCH_NONE);
-		break;
 	case PROP_VOLUME:
 		g_value_set_object (value, source->priv->volume);
 		break;
@@ -1185,21 +1178,6 @@ impl_show_popup (RBDisplayPage *page)
 	return TRUE;
 }
 
-static GList *
-impl_get_ui_actions (RBDisplayPage *page)
-{
-	GList *actions = NULL;
-
-	actions = g_list_prepend (actions, g_strdup ("AudioCdCopyTracks"));
-	actions = g_list_prepend (actions, g_strdup ("RemovableSourceEject"));
-
-#ifdef HAVE_SJ_METADATA_GETTER
-	actions = g_list_prepend (actions, g_strdup ("AudioCdSourceReloadMetadata"));
-#endif
-
-	return actions;
-}
-
 static guint
 impl_want_uri (RBSource *source, const char *uri)
 {
diff --git a/plugins/audioscrobbler/rb-audioscrobbler-profile-page.c b/plugins/audioscrobbler/rb-audioscrobbler-profile-page.c
index 13e3871..285857a 100644
--- a/plugins/audioscrobbler/rb-audioscrobbler-profile-page.c
+++ b/plugins/audioscrobbler/rb-audioscrobbler-profile-page.c
@@ -115,6 +115,7 @@ struct _RBAudioscrobblerProfilePagePrivate {
 	char *love_action_name;
 	char *ban_action_name;
 	char *download_action_name;
+	char *toolbar_path;
 };
 
 
@@ -229,13 +230,13 @@ static void list_item_listen_top_fans_activated_cb (GtkMenuItem *menuitem,
 /* RBDisplayPage implementations */
 static void impl_selected (RBDisplayPage *page);
 static void impl_deselected (RBDisplayPage *page);
-static GList *impl_get_ui_actions (RBDisplayPage *page);
 static gboolean impl_show_popup (RBDisplayPage *page);
 static void impl_delete_thyself (RBDisplayPage *page);
 
 enum {
 	PROP_0,
-	PROP_SERVICE
+	PROP_SERVICE,
+	PROP_TOOLBAR_PATH
 };
 
 static GtkActionEntry profile_actions [] =
@@ -300,7 +301,6 @@ rb_audioscrobbler_profile_page_class_init (RBAudioscrobblerProfilePageClass *kla
 	page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	page_class->selected = impl_selected;
 	page_class->deselected = impl_deselected;
-	page_class->get_ui_actions = impl_get_ui_actions;
 	page_class->show_popup = impl_show_popup;
 	page_class->delete_thyself = impl_delete_thyself;
 
@@ -311,6 +311,13 @@ rb_audioscrobbler_profile_page_class_init (RBAudioscrobblerProfilePageClass *kla
 	                                                      "Audioscrobbler service for this page",
 	                                                      RB_TYPE_AUDIOSCROBBLER_SERVICE,
                                                               G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (object_class,
+					 PROP_TOOLBAR_PATH,
+					 g_param_spec_string ("toolbar-path",
+							      "toolbar path",
+							      "toolbar UI path",
+							      NULL,
+							      G_PARAM_READABLE));
 
 	g_type_class_add_private (klass, sizeof (RBAudioscrobblerProfilePagePrivate));
 }
@@ -469,6 +476,7 @@ rb_audioscrobbler_profile_page_finalize (GObject *object)
 	g_free (page->priv->love_action_name);
 	g_free (page->priv->ban_action_name);
 	g_free (page->priv->download_action_name);
+	g_free (page->priv->toolbar_path);
 
 	G_OBJECT_CLASS (rb_audioscrobbler_profile_page_parent_class)->finalize (object);
 }
@@ -479,7 +487,11 @@ rb_audioscrobbler_profile_page_get_property (GObject *object,
                                                GValue *value,
                                                GParamSpec *pspec)
 {
+	RBAudioscrobblerProfilePage *page = RB_AUDIOSCROBBLER_PROFILE_PAGE (object);
 	switch (prop_id) {
+	case PROP_TOOLBAR_PATH:
+		g_value_set_string (value, page->priv->toolbar_path);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -680,6 +692,13 @@ init_actions (RBAudioscrobblerProfilePage *page)
 	}
 	g_object_unref (player);
 
+	/* set up toolbar */
+	page->priv->toolbar_path = g_strdup_printf ("/%sSourceToolBar", rb_audioscrobbler_service_get_name (page->priv->service));
+	gtk_ui_manager_add_ui (ui_manager, page->priv->ui_merge_id, "/ui", "AudioscrobblerToolBar", NULL, GTK_UI_MANAGER_TOOLBAR, TRUE);
+	gtk_ui_manager_add_ui (ui_manager, page->priv->ui_merge_id, page->priv->toolbar_path, "Love", page->priv->love_action_name, GTK_UI_MANAGER_TOOLITEM, FALSE);
+	gtk_ui_manager_add_ui (ui_manager, page->priv->ui_merge_id, page->priv->toolbar_path, "Ban", page->priv->ban_action_name, GTK_UI_MANAGER_TOOLITEM, FALSE);
+	gtk_ui_manager_add_ui (ui_manager, page->priv->ui_merge_id, page->priv->toolbar_path, "Download", page->priv->download_action_name, GTK_UI_MANAGER_TOOLITEM, FALSE);
+
 	g_free (ui_file);
 	g_object_unref (shell);
 	g_object_unref (plugin);
@@ -1814,6 +1833,8 @@ impl_selected (RBDisplayPage *bpage)
 {
 	RBAudioscrobblerProfilePage *page = RB_AUDIOSCROBBLER_PROFILE_PAGE (bpage);
 
+	RB_DISPLAY_PAGE_CLASS (rb_audioscrobbler_profile_page_parent_class)->selected (bpage);
+
 	/* attempt to update now and again every 5 minutes */
 	rb_audioscrobbler_user_update (page->priv->user);
 	page->priv->update_timeout_id = g_timeout_add_seconds (300,
@@ -1826,23 +1847,12 @@ impl_deselected (RBDisplayPage *bpage)
 {
 	RBAudioscrobblerProfilePage *page = RB_AUDIOSCROBBLER_PROFILE_PAGE (bpage);
 
+	RB_DISPLAY_PAGE_CLASS (rb_audioscrobbler_profile_page_parent_class)->deselected (bpage);
+
 	g_source_remove (page->priv->update_timeout_id);
 	page->priv->update_timeout_id = 0;
 }
 
-static GList *
-impl_get_ui_actions (RBDisplayPage *bpage)
-{
-	RBAudioscrobblerProfilePage *page = RB_AUDIOSCROBBLER_PROFILE_PAGE (bpage);
-	GList *actions = NULL;
-
-	actions = g_list_append (actions, g_strdup (page->priv->love_action_name));
-	actions = g_list_append (actions, g_strdup (page->priv->ban_action_name));
-	actions = g_list_append (actions, g_strdup (page->priv->download_action_name));
-
-	return actions;
-}
-
 static gboolean
 impl_show_popup (RBDisplayPage *page)
 {
diff --git a/plugins/audioscrobbler/rb-audioscrobbler-radio-source.c b/plugins/audioscrobbler/rb-audioscrobbler-radio-source.c
index 0a9e1ca..45a9794 100644
--- a/plugins/audioscrobbler/rb-audioscrobbler-radio-source.c
+++ b/plugins/audioscrobbler/rb-audioscrobbler-radio-source.c
@@ -48,6 +48,7 @@
 #include "rb-display-page-tree.h"
 #include "rb-util.h"
 #include "rb-file-helpers.h"
+#include "rb-source-toolbar.h"
 
 
 /* radio type stuff */
@@ -264,7 +265,6 @@ static gboolean emit_coverart_uri_cb (RBAudioscrobblerRadioSource *source);
 /* RBDisplayPage implementations */
 static void impl_selected (RBDisplayPage *page);
 static void impl_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress);
-static GList *impl_get_ui_actions (RBDisplayPage *page);
 static gboolean impl_show_popup (RBDisplayPage *page);
 static void impl_delete_thyself (RBDisplayPage *page);
 
@@ -308,6 +308,7 @@ rb_audioscrobbler_radio_source_new (RBAudioscrobblerProfilePage *parent,
 	RBShell *shell;
 	GObject *plugin;
 	RhythmDB *db;
+	char *toolbar_path;
 
 	g_object_get (parent, "shell", &shell, "plugin", &plugin, NULL);
 	g_object_get (shell, "db", &db, NULL);
@@ -316,6 +317,8 @@ rb_audioscrobbler_radio_source_new (RBAudioscrobblerProfilePage *parent,
 		rb_audioscrobbler_radio_track_register_entry_type (db);
 	}
 
+	g_object_get (parent, "toolbar-path", &toolbar_path, NULL);
+
 	source = g_object_new (RB_TYPE_AUDIOSCROBBLER_RADIO_SOURCE,
 	                       "shell", shell,
 	                       "plugin", plugin,
@@ -326,11 +329,13 @@ rb_audioscrobbler_radio_source_new (RBAudioscrobblerProfilePage *parent,
                                "username", username,
 	                       "session-key", session_key,
 	                       "station-url", station_url,
+			       "toolbar-path", toolbar_path,
 	                       NULL);
 
 	g_object_unref (shell);
 	g_object_unref (plugin);
 	g_object_unref (db);
+	g_free (toolbar_path);
 
 	return source;
 }
@@ -352,7 +357,6 @@ rb_audioscrobbler_radio_source_class_init (RBAudioscrobblerRadioSourceClass *kla
 	page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	page_class->selected = impl_selected;
 	page_class->get_status = impl_get_status;
-	page_class->get_ui_actions = impl_get_ui_actions;
 	page_class->show_popup = impl_show_popup;
 	page_class->delete_thyself = impl_delete_thyself;
 
@@ -441,6 +445,7 @@ rb_audioscrobbler_radio_source_constructed (GObject *object)
 	GtkWidget *password_info_bar_content_area;
 	GObject *plugin;
 	GtkUIManager *ui_manager;
+	RBSourceToolbar *toolbar;
 	char *ui_file;
 
 	RB_CHAIN_GOBJECT_METHOD (rb_audioscrobbler_radio_source_parent_class, constructed, object);
@@ -450,12 +455,18 @@ rb_audioscrobbler_radio_source_constructed (GObject *object)
 	g_object_get (shell,
 		      "db", &db,
 		      "shell-player", &shell_player,
+		      "ui-manager", &ui_manager,
 		      NULL);
 
 	main_vbox = gtk_vbox_new (FALSE, 4);
 	gtk_widget_show (main_vbox);
 	gtk_container_add (GTK_CONTAINER (source), main_vbox);
 
+	/* toolbar */
+	toolbar = rb_source_toolbar_new (RB_SOURCE (source), ui_manager);
+	gtk_box_pack_start (GTK_BOX (main_vbox), GTK_WIDGET (toolbar), FALSE, FALSE, 0);
+	gtk_widget_show_all (GTK_WIDGET (toolbar));
+
 	/* error info bar */
 	source->priv->error_info_bar = gtk_info_bar_new ();
 	source->priv->error_info_bar_label = gtk_label_new ("");
@@ -516,7 +527,7 @@ rb_audioscrobbler_radio_source_constructed (GObject *object)
 				 source, 0);
 
 	/* merge ui */
-	g_object_get (source, "plugin", &plugin, "ui-manager", &ui_manager, NULL);
+	g_object_get (source, "plugin", &plugin, NULL);
 	ui_file = rb_find_plugin_data_file (plugin, "audioscrobbler-radio-ui.xml");
 	source->priv->ui_merge_id = gtk_ui_manager_add_ui_from_file (ui_manager, ui_file, NULL);
 
@@ -1459,6 +1470,8 @@ impl_selected (RBDisplayPage *page)
 {
 	RBAudioscrobblerRadioSource *source = RB_AUDIOSCROBBLER_RADIO_SOURCE (page);
 
+	RB_DISPLAY_PAGE_CLASS (rb_audioscrobbler_radio_source_parent_class)->selected (page);
+
 	/* if the query model is empty then attempt to add some tracks to it */
 	if (rhythmdb_query_model_get_duration (source->priv->track_model) == 0) {
 		tune (source);
@@ -1496,14 +1509,6 @@ impl_handle_eos (RBSource *asource)
 	return RB_SOURCE_EOF_NEXT;
 }
 
-static GList *
-impl_get_ui_actions (RBDisplayPage *page)
-{
-	RBAudioscrobblerRadioSource *source = RB_AUDIOSCROBBLER_RADIO_SOURCE (page);
-
-	return rb_display_page_get_ui_actions (RB_DISPLAY_PAGE (source->priv->parent));
-}
-
 static gboolean
 impl_show_popup (RBDisplayPage *page)
 {
diff --git a/plugins/brasero-disc-recorder/rb-disc-recorder-plugin.c b/plugins/brasero-disc-recorder/rb-disc-recorder-plugin.c
index 91980be..b548323 100644
--- a/plugins/brasero-disc-recorder/rb-disc-recorder-plugin.c
+++ b/plugins/brasero-disc-recorder/rb-disc-recorder-plugin.c
@@ -644,13 +644,14 @@ static struct ui_paths {
 	gboolean for_burn;
 	gboolean for_copy;
 } ui_paths[] = {
-	{ "/MenuBar/MusicMenu/PlaylistMenu/PluginPlaceholder", TRUE, FALSE },
-	{ "/MenuBar/MusicMenu/PluginPlaceholder", FALSE, TRUE },
-	{ "/ToolBar/PluginPlaceholder", TRUE, TRUE },
+	{ "/QueueSourceToolBar/PluginPlaceholder", TRUE, FALSE },
+	{ "/QueueSourcePopup/PluginPlaceholder", TRUE, FALSE },
 	{ "/PlaylistSourcePopup/PluginPlaceholder", TRUE, FALSE },
 	{ "/AutoPlaylistSourcePopup/PluginPlaceholder", TRUE, FALSE },
-	{ "/QueueSourcePopup/PluginPlaceholder", TRUE, FALSE },
+	{ "/AutoPlaylistSourceToolBar/PluginPlaceholder", TRUE, FALSE },
+	{ "/StaticPlaylistSourceToolBar/PluginPlaceholder", TRUE, FALSE },
 	{ "/AudioCdSourcePopup/PluginPlaceholder", FALSE, TRUE },
+	{ "/AudioCdSourceToolBar/PluginPlaceholder", FALSE, TRUE },
 };
 
 static void
diff --git a/plugins/daap/daap-ui.xml b/plugins/daap/daap-ui.xml
index 1ca46bd..8720f62 100644
--- a/plugins/daap/daap-ui.xml
+++ b/plugins/daap/daap-ui.xml
@@ -7,6 +7,12 @@
     </menu>
   </menubar>
 
+  <toolbar name="DAAPSourceToolBar">
+    <toolitem name="Browse" action="ViewBrowser"/>
+    <toolitem name="ViewAll" action="ViewAll"/>
+    <toolitem name="Disconnect" action="DaapSourceDisconnect"/>
+  </toolbar>
+
   <popup name="DAAPSourcePopup">
     <menuitem name="DAAPSrcPopupDisconnect" action="DaapSourceDisconnect"/>
   </popup>
diff --git a/plugins/daap/rb-daap-source.c b/plugins/daap/rb-daap-source.c
index 82d6b2f..dd7f9d6 100644
--- a/plugins/daap/rb-daap-source.c
+++ b/plugins/daap/rb-daap-source.c
@@ -187,7 +187,7 @@ rb_daap_source_class_init (RBDAAPSourceClass *klass)
 	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_false_function;
 
-	browser_source_class->impl_has_drop_support = (RBBrowserSourceFeatureFunc) rb_false_function;
+	browser_source_class->has_drop_support = (RBBrowserSourceFeatureFunc) rb_false_function;
 
 	g_object_class_install_property (object_class,
 					 PROP_SERVICE_NAME,
@@ -342,6 +342,7 @@ rb_daap_source_new (RBShell *shell,
 					  "password-protected", password_protected,
 					  "plugin", G_OBJECT (plugin),
 					  "settings", g_settings_get_child (settings, "source"),
+					  "toolbar-path", "/DAAPSourceToolBar",
 					  NULL));
 	g_object_unref (settings);
 
@@ -645,6 +646,8 @@ rb_daap_source_selected (RBDisplayPage *page)
 	char *name = NULL;
 	RhythmDBEntryType *entry_type;
 
+	RB_DISPLAY_PAGE_CLASS (rb_daap_source_parent_class)->selected (page);
+
 	if (daap_source->priv->connection != NULL) {
 		return;
 	}
diff --git a/plugins/fmradio/fmradio-ui.xml b/plugins/fmradio/fmradio-ui.xml
index a97f664..3d993fa 100644
--- a/plugins/fmradio/fmradio-ui.xml
+++ b/plugins/fmradio/fmradio-ui.xml
@@ -7,6 +7,10 @@
     </menu>
   </menubar>
 
+  <toolbar name="FMRadioSourceToolBar">
+    <toolitem name="New" action="MusicNewFMRadioStation"/>
+  </toolbar>
+
   <popup name="FMRadioSourcePopup">
     <menuitem name="FMRadioSrcPopupNewStation" action="MusicNewFMRadioStation"/>
   </popup>
diff --git a/plugins/fmradio/rb-fm-radio-source.c b/plugins/fmradio/rb-fm-radio-source.c
index ce0f82e..a3b87c4 100644
--- a/plugins/fmradio/rb-fm-radio-source.c
+++ b/plugins/fmradio/rb-fm-radio-source.c
@@ -33,12 +33,13 @@
 #include <glib/gi18n-lib.h>
 #include <gtk/gtk.h>
 
-#include "rhythmdb-query-model.h"
-#include "rb-debug.h"
-#include "rb-shell-player.h"
-#include "rb-entry-view.h"
-#include "rb-uri-dialog.h"
-#include "rb-util.h"
+#include <lib/rb-debug.h>
+#include <lib/rb-util.h>
+#include <rhythmdb/rhythmdb-query-model.h>
+#include <shell/rb-shell-player.h>
+#include <widgets/rb-entry-view.h>
+#include <widgets/rb-uri-dialog.h>
+#include <widgets/rb-source-toolbar.h>
 
 #include "rb-fm-radio-source.h"
 #include "rb-radio-tuner.h"
@@ -67,7 +68,6 @@ static void playing_entry_changed (RBShellPlayer *player, RhythmDBEntry *entry,
 
 static void         impl_delete         (RBSource *source);
 static gboolean     impl_show_popup     (RBDisplayPage *page);
-static GList       *impl_get_ui_actions (RBDisplayPage *page);
 static RBEntryView *impl_get_entry_view (RBSource *source);
 
 static void rb_fm_radio_entry_type_class_init (RBFMRadioEntryTypeClass *klass);
@@ -133,7 +133,6 @@ rb_fm_radio_source_class_init (RBFMRadioSourceClass *class)
 	object_class->dispose = rb_fm_radio_source_dispose;
 
 	page_class->show_popup = impl_show_popup;
-	page_class->get_ui_actions = impl_get_ui_actions;
 
 	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
@@ -161,6 +160,9 @@ rb_fm_radio_source_constructed (GObject *object)
 {
 	RBFMRadioSource *self;
 	RBShell *shell;
+	RBSourceToolbar *toolbar;
+	GtkUIManager *ui_manager;
+	GtkWidget *grid;
 
 	RB_CHAIN_GOBJECT_METHOD (rb_fm_radio_source_parent_class, constructed, object);
 	self = RB_FM_RADIO_SOURCE (object);
@@ -172,6 +174,7 @@ rb_fm_radio_source_constructed (GObject *object)
 	g_object_get (shell,
 		      "db", &self->priv->db,
 		      "shell-player", &self->priv->player,
+		      "ui-manager", &ui_manager,
 		      NULL);
 	g_object_unref (shell);
 
@@ -182,6 +185,9 @@ rb_fm_radio_source_constructed (GObject *object)
 		G_N_ELEMENTS (rb_fm_radio_source_actions),
 		self);
 
+	toolbar = rb_source_toolbar_new (RB_SOURCE (self), ui_manager);
+	g_object_unref (toolbar);
+
 	self->priv->stations = rb_entry_view_new (self->priv->db,
 						  G_OBJECT (self->priv->player),
 						  FALSE, FALSE);
@@ -202,8 +208,10 @@ rb_fm_radio_source_constructed (GObject *object)
 		G_CALLBACK (rb_fm_radio_source_songs_view_show_popup),
 		self, 0);
 
-	gtk_container_add (GTK_CONTAINER (self),
-			   GTK_WIDGET (self->priv->stations));
+	grid = gtk_grid_new ();
+	gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (toolbar), 0, 0, 1, 1);
+	gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (self->priv->stations), 0, 1, 1, 1);
+	gtk_container_add (GTK_CONTAINER (self), grid);
 	gtk_widget_show_all (GTK_WIDGET (self));
 
 	rb_fm_radio_source_do_query (self);
@@ -237,6 +245,7 @@ rb_fm_radio_source_new (RBShell *shell, RBRadioTuner *tuner)
 			     "name", _("FM Radio"),
 			     "shell", shell,
 			     "entry-type", entry_type,
+			     "toolbar-path", "/FMRadioSourceToolBar",
 			     NULL);
 	self->priv->tuner = g_object_ref (tuner);
 	rb_shell_register_entry_type_for_source (shell, RB_SOURCE (self),
@@ -445,12 +454,6 @@ impl_show_popup (RBDisplayPage *page)
 	return TRUE;
 }
 
-static GList *
-impl_get_ui_actions (RBDisplayPage *page)
-{
-	return g_list_prepend (NULL, g_strdup ("MusicNewFMRadioStation"));
-}
-
 static RBEntryView *
 impl_get_entry_view (RBSource *source)
 {
diff --git a/plugins/generic-player/generic-player-ui.xml b/plugins/generic-player/generic-player-ui.xml
index 6a0969b..eb80d36 100644
--- a/plugins/generic-player/generic-player-ui.xml
+++ b/plugins/generic-player/generic-player-ui.xml
@@ -16,4 +16,11 @@
     <separator/>
     <menuitem name="GenericPlayerPlaylistDeletePopup" action="GenericPlayerPlaylistDelete"/>
   </popup>
+
+  <toolbar name="GenericPlayerSourceToolBar">
+    <toolitem name="Browse" action="ViewBrowser"/>
+    <toolitem name="ViewAll" action="ViewAll"/>
+    <toolitem name="Eject" action="RemovableSourceEject"/>
+    <toolitem name="Sync" action="MediaPlayerSourceSync"/>
+  </toolbar>
 </ui>
diff --git a/plugins/generic-player/rb-generic-player-source.c b/plugins/generic-player/rb-generic-player-source.c
index 72cc214..133b7df 100644
--- a/plugins/generic-player/rb-generic-player-source.c
+++ b/plugins/generic-player/rb-generic-player-source.c
@@ -475,6 +475,7 @@ rb_generic_player_source_new (GObject *plugin, RBShell *shell, GMount *mount, MP
 							 "shell", shell,
 							 "device-info", device_info,
 							 "settings", g_settings_get_child (settings, "source"),
+							 "toolbar-path", "/GenericPlayerSourceToolBar",
 							 NULL));
 	g_object_unref (settings);
 
diff --git a/plugins/grilo/rb-grilo-source.c b/plugins/grilo/rb-grilo-source.c
index 84b44ba..57faba3 100644
--- a/plugins/grilo/rb-grilo-source.c
+++ b/plugins/grilo/rb-grilo-source.c
@@ -364,7 +364,7 @@ rb_grilo_source_constructed (GObject *object)
 
 	/* search bar (if the source supports searching) */
 	if (grl_metadata_source_supported_operations (GRL_METADATA_SOURCE (source->priv->grilo_source)) & GRL_OP_SEARCH) {
-		source->priv->search_entry = rb_search_entry_new ();
+		source->priv->search_entry = rb_search_entry_new (FALSE);
 		g_object_set (source->priv->search_entry, "explicit-mode", TRUE, NULL);
 		g_signal_connect (source->priv->search_entry, "search", G_CALLBACK (search_cb), source);
 		g_signal_connect (source->priv->search_entry, "activate", G_CALLBACK (search_cb), source);
@@ -1075,6 +1075,9 @@ static void
 impl_selected (RBDisplayPage *page)
 {
 	RBGriloSource *source = RB_GRILO_SOURCE (page);
+
+	RB_DISPLAY_PAGE_CLASS (rb_grilo_source_parent_class)->selected (page);
+
 	if (source->priv->done_initial_browse == FALSE) {
 		source->priv->done_initial_browse = TRUE;
 		start_browse (source, NULL, NULL, 0);
diff --git a/plugins/ipod/ipod-ui.xml b/plugins/ipod/ipod-ui.xml
index b9273de..25ab190 100644
--- a/plugins/ipod/ipod-ui.xml
+++ b/plugins/ipod/ipod-ui.xml
@@ -13,4 +13,11 @@
     <menuitem name="iPodPlaylistSrcPopupRename" action="iPodPlaylistSourceRename"/>
     <menuitem name="iPodPlaylistSrcPopupDelete" action="iPodPlaylistSourceDelete"/>
   </popup>
+
+  <toolbar name="iPodSourceToolBar">
+    <toolitem name="Browse" action="ViewBrowser"/>
+    <toolitem name="ViewAll" action="ViewAll"/>
+    <toolitem name="Eject" action="RemovableSourceEject"/>
+    <toolitem name="Sync" action="MediaPlayerSourceSync"/>
+  </toolbar>
 </ui>
diff --git a/plugins/ipod/rb-ipod-source.c b/plugins/ipod/rb-ipod-source.c
index 262ca08..4f5dcfd 100644
--- a/plugins/ipod/rb-ipod-source.c
+++ b/plugins/ipod/rb-ipod-source.c
@@ -68,7 +68,6 @@ static void rb_ipod_load_songs (RBiPodSource *source);
 
 static gboolean impl_show_popup (RBDisplayPage *page);
 static void impl_delete_thyself (RBDisplayPage *page);
-static GList* impl_get_ui_actions (RBDisplayPage *page);
 
 static gboolean impl_track_added (RBTransferTarget *target,
 				  RhythmDBEntry *entry,
@@ -176,9 +175,7 @@ rb_ipod_source_class_init (RBiPodSourceClass *klass)
 
 	page_class->delete_thyself = impl_delete_thyself;
 	page_class->show_popup = impl_show_popup;
-	page_class->get_ui_actions = impl_get_ui_actions;
 
-	source_class->impl_can_browse = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_rename = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
@@ -436,6 +433,7 @@ rb_ipod_source_new (GObject *plugin,
 					       "shell", shell,
 					       "device-info", device_info,
 					       "settings", g_settings_get_child (settings, "source"),
+					       "toolbar-path", "/iPodSourceToolBar",
 					       NULL));
 	g_object_unref (settings);
 
@@ -1262,17 +1260,6 @@ rb_ipod_load_songs (RBiPodSource *source)
 	}
 }
 
-static GList*
-impl_get_ui_actions (RBDisplayPage *page)
-{
-	GList *actions = NULL;
-
-	actions = g_list_prepend (actions, g_strdup ("RemovableSourceEject"));
-	actions = g_list_prepend (actions, g_strdup ("MediaPlayerSourceSync"));
-
-	return actions;
-}
-
 static gboolean
 impl_show_popup (RBDisplayPage *page)
 {
diff --git a/plugins/iradio/iradio-ui.xml b/plugins/iradio/iradio-ui.xml
index 67f44e5..fc782ad 100644
--- a/plugins/iradio/iradio-ui.xml
+++ b/plugins/iradio/iradio-ui.xml
@@ -16,4 +16,9 @@
     <separator/>
     <menuitem name="PropertiesIRadioPopup" action="MusicProperties"/>
   </popup>
+
+  <toolbar name="IRadioSourceToolBar">
+    <toolitem name="Browse" action="ViewBrowser"/>
+    <toolitem name="AddStation" action="MusicNewInternetRadioStation"/>
+  </toolbar>
 </ui>
diff --git a/plugins/iradio/rb-iradio-source.c b/plugins/iradio/rb-iradio-source.c
index beec8c8..b007985 100644
--- a/plugins/iradio/rb-iradio-source.c
+++ b/plugins/iradio/rb-iradio-source.c
@@ -53,6 +53,7 @@
 #include "rb-metadata.h"
 #include "rb-cut-and-paste-code.h"
 #include "rb-source-search-basic.h"
+#include "rb-source-toolbar.h"
 
 /* icon names */
 #define IRADIO_SOURCE_ICON  "library-internet-radio"
@@ -89,7 +90,6 @@ GType rb_iradio_entry_type_get_type (void);
 
 /* page methods */
 static gboolean impl_show_popup (RBDisplayPage *page);
-static GList *impl_get_ui_actions (RBDisplayPage *page);
 static void impl_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress);
 
 /* source methods */
@@ -133,8 +133,6 @@ struct RBIRadioSourcePrivate
 {
 	RhythmDB *db;
 
-	GtkWidget *vbox;
-	GtkWidget *paned;
 	GtkActionGroup *action_group;
 
 	RBPropertyView *genres;
@@ -203,9 +201,7 @@ rb_iradio_source_class_init (RBIRadioSourceClass *klass)
 
 	page_class->show_popup = impl_show_popup;
 	page_class->get_status  = impl_get_status;
-	page_class->get_ui_actions = impl_get_ui_actions;
 
-	source_class->impl_can_browse = (RBSourceFeatureFunc) rb_true_function;
 	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;
@@ -236,10 +232,6 @@ rb_iradio_source_init (RBIRadioSource *source)
 
 	source->priv = RB_IRADIO_SOURCE_GET_PRIVATE (source);
 
-	source->priv->vbox = gtk_vbox_new (FALSE, 5);
-
-	gtk_container_add (GTK_CONTAINER (source), source->priv->vbox);
-
 	gtk_icon_size_lookup (RB_SOURCE_ICON_SIZE, &size, NULL);
 	pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
 					   IRADIO_SOURCE_ICON,
@@ -300,16 +292,21 @@ rb_iradio_source_constructed (GObject *object)
 	RBShell *shell;
 	GtkAction *action;
 	GSettings *settings;
+	GtkUIManager *ui_manager;
+	GtkWidget *grid;
+	GtkWidget *paned;
+	RBSourceToolbar *toolbar;
 
 	RB_CHAIN_GOBJECT_METHOD (rb_iradio_source_parent_class, constructed, object);
 	source = RB_IRADIO_SOURCE (object);
 
-	source->priv->paned = gtk_hpaned_new ();
+	paned = gtk_hpaned_new ();
 
 	g_object_get (source, "shell", &shell, NULL);
 	g_object_get (shell,
 		      "db", &source->priv->db,
 		      "shell-player", &source->priv->player,
+		      "ui-manager", &ui_manager,
 		      NULL);
 	g_object_unref (shell);
 
@@ -343,7 +340,7 @@ rb_iradio_source_constructed (GObject *object)
                                               "MusicNewInternetRadioStation");
         /* Translators: this is the toolbar button label for 
            New Internet Radio Station action. */
-        g_object_set (action, "short-label", C_("Radio", "New"), NULL);
+        g_object_set (action, "short-label", C_("Radio", "Add"), NULL);
 
 
 	/* set up stations view */
@@ -395,16 +392,24 @@ rb_iradio_source_constructed (GObject *object)
 	g_object_set (source->priv->genres, "vscrollbar_policy",
 		      GTK_POLICY_AUTOMATIC, NULL);
 
-	gtk_paned_pack1 (GTK_PANED (source->priv->paned),
-			 GTK_WIDGET (source->priv->genres), FALSE, FALSE);
-	gtk_paned_pack2 (GTK_PANED (source->priv->paned),
-			 GTK_WIDGET (source->priv->stations), TRUE, FALSE);
+	gtk_paned_pack1 (GTK_PANED (paned), GTK_WIDGET (source->priv->genres), FALSE, FALSE);
+	gtk_paned_pack2 (GTK_PANED (paned), GTK_WIDGET (source->priv->stations), TRUE, FALSE);
+
+	/* set up toolbar */
+	toolbar = rb_source_toolbar_new (RB_SOURCE (source), ui_manager);
+	rb_source_toolbar_add_search_entry (toolbar, NULL, _("Search your internet radio stations"));
+
+	grid = gtk_grid_new ();
+	gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
+	gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
+	gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (toolbar), 0, 0, 1, 1);
+	gtk_grid_attach (GTK_GRID (grid), paned, 0, 1, 1, 1);
 
-	gtk_box_pack_start (GTK_BOX (source->priv->vbox), source->priv->paned, TRUE, TRUE, 0);
+	gtk_container_add (GTK_CONTAINER (source), grid);
 
 	rb_source_bind_settings (RB_SOURCE (source),
 				 GTK_WIDGET (source->priv->stations),
-				 source->priv->paned,
+				 paned,
 				 GTK_WIDGET (source->priv->genres));
 
 	gtk_widget_show_all (GTK_WIDGET (source));
@@ -482,8 +487,8 @@ rb_iradio_source_new (RBShell *shell, GObject *plugin)
 					  "shell", shell,
 					  "entry-type", entry_type,
 					  "plugin", plugin,
-					  "search-type", RB_SOURCE_SEARCH_INCREMENTAL,
 					  "settings", g_settings_get_child (settings, "source"),
+					  "toolbar-path", "/IRadioSourceToolBar",
 					  NULL));
 	g_object_unref (settings);
 	rb_shell_register_entry_type_for_source (shell, source, entry_type);
@@ -935,16 +940,6 @@ impl_show_popup (RBDisplayPage *page)
 	return TRUE;
 }
 
-static GList*
-impl_get_ui_actions (RBDisplayPage *page)
-{
-	GList *actions = NULL;
-
-	actions = g_list_prepend (actions, g_strdup ("MusicNewInternetRadioStation"));
-
-	return actions;
-}
-
 static void
 new_station_location_added (RBURIDialog    *dialog,
 			    const char     *uri,
diff --git a/plugins/mtpdevice/mtp-ui.xml b/plugins/mtpdevice/mtp-ui.xml
index ccfc133..eec2c5b 100644
--- a/plugins/mtpdevice/mtp-ui.xml
+++ b/plugins/mtpdevice/mtp-ui.xml
@@ -8,4 +8,11 @@
     <separator />
     <menuitem name="MTPSrcPopupProperties" action="MTPSourceProperties"/>
   </popup>
+
+  <toolbar name="MTPSourceToolBar">
+    <toolitem name="Browse" action="ViewBrowser"/>
+    <toolitem name="ViewAll" action="ViewAll"/>
+    <toolitem name="Eject" action="RemovableSourceEject"/>
+    <toolitem name="Sync" action="MediaPlayerSourceSync"/>
+  </toolbar>
 </ui>
diff --git a/plugins/mtpdevice/rb-mtp-source.c b/plugins/mtpdevice/rb-mtp-source.c
index 82102aa..b4918b8 100644
--- a/plugins/mtpdevice/rb-mtp-source.c
+++ b/plugins/mtpdevice/rb-mtp-source.c
@@ -81,7 +81,6 @@ static void rb_mtp_source_get_property (GObject *object,
 static void impl_delete (RBSource *asource);
 static RBTrackTransferBatch *impl_paste (RBSource *asource, GList *entries);
 static gboolean impl_show_popup (RBDisplayPage *page);
-static GList* impl_get_ui_actions (RBDisplayPage *page);
 
 static gboolean impl_track_added (RBTransferTarget *target,
 				  RhythmDBEntry *entry,
@@ -195,9 +194,6 @@ rb_mtp_source_class_init (RBMtpSourceClass *klass)
 	object_class->get_property = rb_mtp_source_get_property;
 
 	page_class->show_popup = impl_show_popup;
-	page_class->get_ui_actions = impl_get_ui_actions;
-
-	source_class->impl_can_browse = (RBSourceFeatureFunc) rb_true_function;
 
 	source_class->impl_can_rename = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
@@ -597,6 +593,7 @@ rb_mtp_source_new (RBShell *shell,
 					      "udi", udi,
 #endif
 					      "settings", g_settings_get_child (settings, "source"),
+					      "toolbar-path", "/MTPSourceToolBar",
 					      NULL));
 	g_object_unref (settings);
 
@@ -1049,17 +1046,6 @@ impl_show_popup (RBDisplayPage *page)
 	return TRUE;
 }
 
-static GList *
-impl_get_ui_actions (RBDisplayPage *page)
-{
-	GList *actions = NULL;
-
-	actions = g_list_prepend (actions, g_strdup ("RemovableSourceEject"));
-	actions = g_list_prepend (actions, g_strdup ("MediaPlayerSourceSync"));
-
-	return actions;
-}
-
 static RhythmDB *
 get_db_for_source (RBMtpSource *source)
 {
diff --git a/plugins/visualizer/rb-visualizer-page.c b/plugins/visualizer/rb-visualizer-page.c
index 430dfb2..8d1e645 100644
--- a/plugins/visualizer/rb-visualizer-page.c
+++ b/plugins/visualizer/rb-visualizer-page.c
@@ -207,6 +207,8 @@ impl_selected (RBDisplayPage *bpage)
 	RBVisualizerPage *page = RB_VISUALIZER_PAGE (bpage);
 	ClutterActor *stage;
 
+	RB_DISPLAY_PAGE_CLASS (rb_visualizer_page_parent_class)->selected (bpage);
+
 	if (page->embed == NULL) {
 		page->embed = gtk_clutter_embed_new ();
 
@@ -228,6 +230,8 @@ impl_deselected (RBDisplayPage *bpage)
 {
 	RBVisualizerPage *page = RB_VISUALIZER_PAGE (bpage);
 
+	RB_DISPLAY_PAGE_CLASS (rb_visualizer_page_parent_class)->deselected (bpage);
+
 	if (page->fullscreen == NULL) {
 		g_signal_emit (page, signals[STOP], 0);
 	} else {
diff --git a/podcast/rb-podcast-main-source.c b/podcast/rb-podcast-main-source.c
index 1f29c9b..c7d0864 100644
--- a/podcast/rb-podcast-main-source.c
+++ b/podcast/rb-podcast-main-source.c
@@ -70,10 +70,10 @@ rb_podcast_main_source_new (RBShell *shell, RBPodcastManager *podcast_manager)
 					  "name", _("Podcasts"),
 					  "shell", shell,
 					  "entry-type", RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
-					  "search-type", RB_SOURCE_SEARCH_INCREMENTAL,
 					  "podcast-manager", podcast_manager,
 					  "base-query", base_query,
 					  "settings", g_settings_get_child (settings, "source"),
+					  "toolbar-path", "/PodcastSourceToolBar",
 					  NULL));
 	g_object_unref (settings);
 
diff --git a/podcast/rb-podcast-source.c b/podcast/rb-podcast-source.c
index 9b87457..31304ec 100644
--- a/podcast/rb-podcast-source.c
+++ b/podcast/rb-podcast-source.c
@@ -64,6 +64,7 @@
 #include "rb-cut-and-paste-code.h"
 #include "rb-source-search-basic.h"
 #include "rb-cell-renderer-pixbuf.h"
+#include "rb-source-toolbar.h"
 
 static void podcast_cmd_new_podcast		(GtkAction *action,
 						 RBPodcastSource *source);
@@ -86,7 +87,6 @@ struct _RBPodcastSourcePrivate
 
 	guint prefs_notify_id;
 
-	GtkWidget *vbox;
 	GtkWidget *paned;
 
 	RhythmDBPropertyModel *feed_model;
@@ -132,9 +132,9 @@ static GtkActionEntry rb_podcast_source_actions [] =
 
 static GtkRadioActionEntry rb_podcast_source_radio_actions [] =
 {
-	{ "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 }
+	{ "PodcastSearchAll", NULL, N_("Search all fields"), NULL, NULL, RHYTHMDB_PROP_SEARCH_MATCH },
+	{ "PodcastSearchFeeds", NULL, N_("Search podcast feeds"), NULL, NULL, RHYTHMDB_PROP_ALBUM_FOLDED },
+	{ "PodcastSearchEpisodes", NULL, N_("Search podcast episodes"), NULL, NULL, RHYTHMDB_PROP_TITLE_FOLDED }
 };
 
 static const GtkTargetEntry posts_view_drag_types[] = {
@@ -523,6 +523,11 @@ podcast_cmd_update_feed (GtkAction *action, RBPodcastSource *source)
 	rb_debug ("Update action");
 
 	feeds = rb_string_list_copy (source->priv->selected_feeds);
+	if (feeds == NULL) {
+		rb_podcast_manager_update_feeds (source->priv->podcast_mgr);
+		return;
+	}
+
 	for (l = feeds; l != NULL; l = g_list_next (l)) {
 		const char *location = l->data;
 
@@ -943,10 +948,10 @@ rb_podcast_source_new (RBShell *shell,
 					  "name", name,
 					  "shell", shell,
 					  "entry-type", RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
-					  "search-type", RB_SOURCE_SEARCH_INCREMENTAL,
 					  "podcast-manager", podcast_manager,
 					  "base-query", base_query,
 					  "settings", g_settings_get_child (settings, "source"),
+					  "toolbar-path", "/PodcastSourceToolBar",
 					  NULL));
 	g_object_unref (settings);
 
@@ -1105,29 +1110,6 @@ impl_get_entry_view (RBSource *asource)
 	return source->priv->posts;
 }
 
-static GList *
-impl_get_search_actions (RBSource *source)
-{
-	GList *actions = NULL;
-
-	actions = g_list_prepend (actions, g_strdup ("PodcastSearchEpisodes"));
-	actions = g_list_prepend (actions, g_strdup ("PodcastSearchFeeds"));
-	actions = g_list_prepend (actions, g_strdup ("PodcastSearchAll"));
-
-	return actions;
-}
-
-static GList *
-impl_get_ui_actions (RBDisplayPage *page)
-{
-	GList *actions = NULL;
-
-	actions = g_list_prepend (actions, g_strdup ("PodcastUpdateAllFeeds"));
-	actions = g_list_prepend (actions, g_strdup ("MusicNewPodcast"));
-
-	return actions;
-}
-
 static RBSourceEOFType
 impl_handle_eos (RBSource *asource)
 {
@@ -1280,6 +1262,9 @@ impl_constructed (GObject *object)
 	GtkAction *action;
 	GSettings *settings;
 	int position;
+	GtkUIManager *ui_manager;
+	GtkWidget *grid;
+	RBSourceToolbar *toolbar;
 
 	RB_CHAIN_GOBJECT_METHOD (rb_podcast_source_parent_class, constructed, object);
 	source = RB_PODCAST_SOURCE (object);
@@ -1288,6 +1273,7 @@ impl_constructed (GObject *object)
 	g_object_get (shell,
 		      "db", &source->priv->db,
 		      "shell-player", &shell_player,
+		      "ui-manager", &ui_manager,
 		      NULL);
 
 	source->priv->action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
@@ -1304,12 +1290,12 @@ impl_constructed (GObject *object)
 					      "MusicNewPodcast");
 	/* Translators: this is the toolbar button label
 	   for New Podcast Feed action. */
-	g_object_set (action, "short-label", C_("Podcast", "New"), NULL);
+	g_object_set (action, "short-label", C_("Podcast", "Add"), NULL);
 
 	action = gtk_action_group_get_action (source->priv->action_group,
-					      "PodcastUpdateAllFeeds");
+					      "PodcastFeedUpdate");
 	/* Translators: this is the toolbar button label
-	   for Update All Feeds action. */
+	   for Update Feed action. */
 	g_object_set (action, "short-label", _("Update"), NULL);
 
 	if (gtk_action_group_get_action (source->priv->action_group,
@@ -1529,13 +1515,23 @@ impl_constructed (GObject *object)
 			   posts_view_drag_types, 2,
 			   GDK_ACTION_COPY | GDK_ACTION_MOVE);
 
+	/* set up toolbar */
+	toolbar = rb_source_toolbar_new (RB_SOURCE (source), ui_manager);
+	rb_source_toolbar_add_search_entry (toolbar, "/PodcastSourceSearchMenu", NULL);
+
 	/* pack the feed and post views into the source */
 	gtk_paned_pack1 (GTK_PANED (source->priv->paned),
 			 GTK_WIDGET (source->priv->feeds), FALSE, FALSE);
 	gtk_paned_pack2 (GTK_PANED (source->priv->paned),
 			 GTK_WIDGET (source->priv->posts), TRUE, FALSE);
 
-	gtk_box_pack_start (GTK_BOX (source->priv->vbox), source->priv->paned, TRUE, TRUE, 0);
+	grid = gtk_grid_new ();
+	gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
+	gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
+	gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (toolbar), 0, 0, 1, 1);
+	gtk_grid_attach (GTK_GRID (grid), source->priv->paned, 0, 1, 1, 1);
+
+	gtk_container_add (GTK_CONTAINER (source), grid);
 
 	gtk_widget_show_all (GTK_WIDGET (source));
 
@@ -1552,11 +1548,11 @@ impl_constructed (GObject *object)
 				 GTK_WIDGET (source->priv->feeds));
 
 	g_object_unref (settings);
+	g_object_unref (ui_manager);
 
 	rb_podcast_source_do_query (source);
 }
 
-
 static void
 impl_dispose (GObject *object)
 {
@@ -1621,9 +1617,6 @@ rb_podcast_source_init (RBPodcastSource *source)
 						    RBPodcastSourcePrivate);
 
 	source->priv->selected_feeds = NULL;
-	source->priv->vbox = gtk_vbox_new (FALSE, 5);
-
-	gtk_container_add (GTK_CONTAINER (source), source->priv->vbox);
 
 	icon_theme = gtk_icon_theme_get_default ();
 	source->priv->error_pixbuf = gtk_icon_theme_load_icon (icon_theme,
@@ -1648,18 +1641,15 @@ rb_podcast_source_class_init (RBPodcastSourceClass *klass)
 
 	page_class->get_status = impl_get_status;
 	page_class->receive_drag = impl_receive_drag;
-	page_class->get_ui_actions = impl_get_ui_actions;
 	page_class->show_popup = impl_show_popup;
 
 	source_class->impl_add_to_queue = impl_add_to_queue;
 	source_class->impl_can_add_to_queue = impl_can_add_to_queue;
-	source_class->impl_can_browse = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_delete = impl_delete;
 	source_class->impl_get_entry_view = impl_get_entry_view;
-	source_class->impl_get_search_actions = impl_get_search_actions;
 	source_class->impl_handle_eos = impl_handle_eos;
 	source_class->impl_search = impl_search;
 	source_class->impl_song_properties = impl_song_properties;
diff --git a/shell/Makefile.am b/shell/Makefile.am
index 7064391..93bf993 100644
--- a/shell/Makefile.am
+++ b/shell/Makefile.am
@@ -92,8 +92,6 @@ librhythmbox_core_la_SOURCES =				\
 	rb-shell-player.c				\
 	rb-shell-preferences.c				\
 	rb-shell-preferences.h				\
-	rb-source-header.c				\
-	rb-source-header.h				\
 	rb-statusbar.c					\
 	rb-statusbar.h					\
 	rb-track-transfer-batch.c			\
diff --git a/shell/rb-shell.c b/shell/rb-shell.c
index 924bc9f..39b8242 100644
--- a/shell/rb-shell.c
+++ b/shell/rb-shell.c
@@ -76,7 +76,6 @@
 #include "rb-track-transfer-queue.h"
 #include "rb-shell-clipboard.h"
 #include "rb-shell-player.h"
-#include "rb-source-header.h"
 #include "rb-statusbar.h"
 #include "rb-shell-preferences.h"
 #include "rb-library-source.h"
@@ -237,7 +236,6 @@ enum
 	PROP_LIBRARY_SOURCE,
 	PROP_DISPLAY_PAGE_MODEL,
 	PROP_DISPLAY_PAGE_TREE,
-	PROP_SOURCE_HEADER,
 	PROP_VISIBILITY,
 	PROP_TRACK_TRANSFER_QUEUE,
 	PROP_AUTOSTARTED,
@@ -304,7 +302,6 @@ struct _RBShellPrivate
 
 	RBShellPlayer *player_shell;
 	RBShellClipboard *clipboard_shell;
-	RBSourceHeader *source_header;
 	RBStatusbar *statusbar;
 	RBPlaylistManager *playlist_manager;
 	RBRemovableMediaManager *removable_media_manager;
@@ -402,7 +399,10 @@ static GtkToggleActionEntry rb_shell_toggle_entries [] =
 	  G_CALLBACK (rb_shell_view_queue_as_sidebar_changed_cb) },
         { "ViewStatusbar", NULL, N_("S_tatusbar"), NULL,
 	  N_("Change the visibility of the statusbar"),
-	  G_CALLBACK (rb_shell_view_statusbar_changed_cb), TRUE }
+	  G_CALLBACK (rb_shell_view_statusbar_changed_cb), TRUE },
+        { "ViewBrowser", NULL, N_("_Browse"), "<control>B",
+	  N_("Change the visibility of the browser"),
+	  NULL, TRUE }
 };
 static guint rb_shell_n_toggle_entries = G_N_ELEMENTS (rb_shell_toggle_entries);
 
@@ -558,9 +558,6 @@ construct_widgets (RBShell *shell)
 	shell->priv->clipboard_shell = rb_shell_clipboard_new (shell->priv->actiongroup,
 							       shell->priv->ui_manager,
 							       shell->priv->db);
-	shell->priv->source_header = rb_source_header_new (shell->priv->ui_manager,
-							   shell->priv->actiongroup);
-	gtk_widget_show_all (GTK_WIDGET (shell->priv->source_header));
 
 	shell->priv->display_page_tree = rb_display_page_tree_new (shell);
 	gtk_widget_show_all (GTK_WIDGET (shell->priv->display_page_tree));
@@ -626,9 +623,6 @@ construct_widgets (RBShell *shell)
 					 NULL);
 
 		gtk_box_pack_start (GTK_BOX (vbox2),
-				    GTK_WIDGET (shell->priv->source_header),
-				    FALSE, FALSE, 3);
-		gtk_box_pack_start (GTK_BOX (vbox2),
 				    shell->priv->notebook,
 				    TRUE, TRUE, 0);
 		gtk_box_pack_start (GTK_BOX (vbox2),
@@ -1350,19 +1344,6 @@ rb_shell_class_init (RBShellClass *klass)
 							       TRUE,
 							       G_PARAM_READWRITE));
 	/**
-	 * RBShell:source-header:
-	 *
-	 * The #RBSourceHeader instance
-	 */
-	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));
-
-	/**
 	 * RBShell:track-transfer-queue:
 	 *
 	 * The #RBTrackTransferQueue instance
@@ -1642,9 +1623,6 @@ rb_shell_get_property (GObject *object,
 	case PROP_VISIBILITY:
 		g_value_set_boolean (value, rb_shell_get_visibility (shell));
 		break;
-	case PROP_SOURCE_HEADER:
-		g_value_set_object (value, shell->priv->source_header);
-		break;
 	case PROP_TRACK_TRANSFER_QUEUE:
 		g_value_set_object (value, shell->priv->track_transfer_queue);
 		break;
@@ -1965,6 +1943,11 @@ rb_shell_constructed (GObject *object)
 					     rb_shell_n_toggle_entries,
 					     shell);
 
+	/* Translators: this is the short label for the 'import folder' action */
+	gtk_action_set_short_label (gtk_action_group_get_action (shell->priv->actiongroup, "MusicImportFolder"), C_("Library", "Import"));
+	/* Translators: this is the short label for the 'view all tracks' action */
+	gtk_action_set_short_label (gtk_action_group_get_action (shell->priv->actiongroup, "ViewAll"), _("Show All"));
+
 	construct_db (shell);
 
 	construct_widgets (shell);
@@ -2466,22 +2449,8 @@ rb_shell_playing_from_queue_cb (RBShellPlayer *player,
 }
 
 static void
-merge_source_ui_cb (const char *action,
-		    RBShell *shell)
-{
-	gtk_ui_manager_add_ui (shell->priv->ui_manager,
-			       shell->priv->source_ui_merge_id,
-			       "/ToolBar",
-			       action,
-			       action,
-			       GTK_UI_MANAGER_AUTO,
-			       FALSE);
-}
-
-static void
 rb_shell_select_page (RBShell *shell, RBDisplayPage *page)
 {
-	GList *actions;
 	int pagenum;
 
 	if (shell->priv->selected_page == page)
@@ -2515,24 +2484,17 @@ rb_shell_select_page (RBShell *shell, RBDisplayPage *page)
 		RBSource *source = RB_SOURCE (page);
 		rb_shell_clipboard_set_source (shell->priv->clipboard_shell, source);
 		rb_shell_player_set_selected_source (shell->priv->player_shell, source);
-		rb_source_header_set_source (shell->priv->source_header, source);
 		g_object_set (shell->priv->playlist_manager, "source", source, NULL);
 		g_object_set (shell->priv->removable_media_manager, "source", source, NULL);
 	} else {
 		rb_shell_clipboard_set_source (shell->priv->clipboard_shell, NULL);
 		rb_shell_player_set_selected_source (shell->priv->player_shell, NULL);	/* ? */
-		rb_source_header_set_source (shell->priv->source_header, NULL);
 
 		/* clear playlist-manager:source? */
 		/* clear removable-media-manager:source? */
 	}
 	rb_statusbar_set_page (shell->priv->statusbar, page);
 
-	/* merge the page-specific UI */
-	actions = rb_display_page_get_ui_actions (page);
-	g_list_foreach (actions, (GFunc)merge_source_ui_cb, shell);
-	rb_list_deep_free (actions);
-
 	g_object_notify (G_OBJECT (shell), "selected-page");
 }
 
@@ -3144,8 +3106,6 @@ rb_shell_sync_smalldisplay (RBShell *shell)
 	rb_shell_sync_statusbar_visibility (shell);
 	rb_shell_sync_toolbar_state (shell);
 
-	rb_source_header_sync_control_state (shell->priv->source_header);
-
 	action = gtk_action_group_get_action (shell->priv->actiongroup,
 					      "ViewSmallDisplay");
 	gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
@@ -3238,8 +3198,6 @@ rb_shell_cmd_view_all (GtkAction *action,
 		rb_debug ("view all");
 
 		rb_source_reset_filters (source);
-		rb_source_header_clear_search (shell->priv->source_header);
-		rb_source_header_focus_search_box (shell->priv->source_header);
 	}
 }
 
diff --git a/sources/rb-auto-playlist-source.c b/sources/rb-auto-playlist-source.c
index a3cf079..28b2424 100644
--- a/sources/rb-auto-playlist-source.c
+++ b/sources/rb-auto-playlist-source.c
@@ -41,6 +41,7 @@
 #include "rb-stock-icons.h"
 #include "rb-playlist-xml.h"
 #include "rb-source-search-basic.h"
+#include "rb-source-toolbar.h"
 
 /**
  * SECTION:rb-auto-playlist-source
@@ -78,7 +79,6 @@ static gboolean impl_show_popup (RBDisplayPage *page);
 static gboolean impl_receive_drag (RBDisplayPage *page, GtkSelectionData *data);
 static void impl_search (RBSource *source, RBSourceSearch *search, const char *cur_text, const char *new_text);
 static void impl_reset_filters (RBSource *asource);
-static GList *impl_get_search_actions (RBSource *source);
 
 /* playlist methods */
 static void impl_save_contents_to_xml (RBPlaylistSource *source,
@@ -100,10 +100,10 @@ static void rb_auto_playlist_source_browser_changed_cb (RBLibraryBrowser *entry,
 
 static GtkRadioActionEntry rb_auto_playlist_source_radio_actions [] =
 {
-	{ "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 }
+	{ "AutoPlaylistSearchAll", NULL, N_("Search all fields"), NULL, NULL, RHYTHMDB_PROP_SEARCH_MATCH },
+	{ "AutoPlaylistSearchArtists", NULL, N_("Search artists"), NULL, NULL, RHYTHMDB_PROP_ARTIST_FOLDED },
+	{ "AutoPlaylistSearchAlbums", NULL, N_("Search albums"), NULL, NULL, RHYTHMDB_PROP_ALBUM_FOLDED },
+	{ "AutoPlaylistSearchTitles", NULL, N_("Search titles"), NULL, NULL, RHYTHMDB_PROP_TITLE_FOLDED }
 };
 
 enum
@@ -130,6 +130,7 @@ struct _RBAutoPlaylistSourcePrivate
 
 	GtkWidget *paned;
 	RBLibraryBrowser *browser;
+	RBSourceToolbar *toolbar;
 
 	RBSourceSearch *default_search;
 	RhythmDBQuery *search_query;
@@ -161,11 +162,9 @@ rb_auto_playlist_source_class_init (RBAutoPlaylistSourceClass *klass)
 
 	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_false_function;
-	source_class->impl_can_browse = (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;
-	source_class->impl_get_search_actions = impl_get_search_actions;
 
 	playlist_class->impl_save_contents_to_xml = impl_save_contents_to_xml;
 
@@ -250,6 +249,8 @@ rb_auto_playlist_source_constructed (GObject *object)
 	RBAutoPlaylistSourcePrivate *priv;
 	RBShell *shell;
 	RhythmDBEntryType *entry_type;
+	GtkUIManager *ui_manager;
+	GtkWidget *grid;
 
 	RB_CHAIN_GOBJECT_METHOD (rb_auto_playlist_source_parent_class, constructed, object);
 
@@ -291,13 +292,26 @@ rb_auto_playlist_source_constructed (GObject *object)
 	}
 	priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH);
 
+
+	/* set up toolbar */
+	g_object_get (shell, "ui-manager", &ui_manager, NULL);
+	priv->toolbar = rb_source_toolbar_new (RB_SOURCE (source), ui_manager);
+	rb_source_toolbar_add_search_entry (priv->toolbar, "/AutoPlaylistSourceSearchMenu", NULL);
+
+	g_object_unref (ui_manager);
 	g_object_unref (shell);
 
 	/* reparent the entry view */
 	g_object_ref (songs);
 	gtk_container_remove (GTK_CONTAINER (source), GTK_WIDGET (songs));
 	gtk_paned_pack2 (GTK_PANED (priv->paned), GTK_WIDGET (songs), TRUE, FALSE);
-	gtk_container_add (GTK_CONTAINER (source), priv->paned);
+
+	grid = gtk_grid_new ();
+	gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
+	gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
+	gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (priv->toolbar), 0, 0, 1, 1);
+	gtk_grid_attach (GTK_GRID (grid), priv->paned, 0, 1, 1, 1);
+	gtk_container_add (GTK_CONTAINER (source), grid);
 
 	rb_source_bind_settings (RB_SOURCE (source), GTK_WIDGET (songs), priv->paned, GTK_WIDGET (priv->browser));
 	g_object_unref (songs);
@@ -326,7 +340,7 @@ rb_auto_playlist_source_new (RBShell *shell, const char *name, gboolean local)
 					"shell", shell,
 					"is-local", local,
 					"entry-type", RHYTHMDB_ENTRY_TYPE_SONG,
-					"search-type", RB_SOURCE_SEARCH_INCREMENTAL,
+					"toolbar-path", "/AutoPlaylistSourceToolBar",
 					NULL));
 }
 
@@ -497,6 +511,8 @@ impl_reset_filters (RBSource *source)
 		priv->search_query = NULL;
 	}
 
+	rb_source_toolbar_clear_search_entry (priv->toolbar);
+
 	if (changed)
 		rb_auto_playlist_source_do_query (RB_AUTO_PLAYLIST_SOURCE (source), FALSE);
 }
@@ -914,16 +930,3 @@ rb_auto_playlist_source_browser_changed_cb (RBLibraryBrowser *browser,
 	rb_source_notify_filter_changed (RB_SOURCE (source));
 }
 
-static GList *
-impl_get_search_actions (RBSource *source)
-{
-	GList *actions = NULL;
-
-	actions = g_list_prepend (actions, g_strdup ("AutoPlaylistSearchTitles"));
-	actions = g_list_prepend (actions, g_strdup ("AutoPlaylistSearchAlbums"));
-	actions = g_list_prepend (actions, g_strdup ("AutoPlaylistSearchArtists"));
-	actions = g_list_prepend (actions, g_strdup ("AutoPlaylistSearchAll"));
-
-	return actions;
-}
-
diff --git a/sources/rb-browser-source.c b/sources/rb-browser-source.c
index a6db245..3fd306b 100644
--- a/sources/rb-browser-source.c
+++ b/sources/rb-browser-source.c
@@ -60,6 +60,7 @@
 #include "rb-debug.h"
 #include "rb-song-info.h"
 #include "rb-search-entry.h"
+#include "rb-source-toolbar.h"
 #include "rb-shell-preferences.h"
 
 static void rb_browser_source_class_init (RBBrowserSourceClass *klass);
@@ -90,9 +91,8 @@ static void impl_delete (RBSource *source);
 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);
 static void default_show_entry_popup (RBBrowserSource *source);
-static void default_pack_paned (RBBrowserSource *source, GtkWidget *paned);
+static void default_pack_content (RBBrowserSource *source, GtkWidget *content);
 
 void rb_browser_source_browser_views_activated_cb (GtkWidget *widget,
 						 RBBrowserSource *source);
@@ -111,9 +111,8 @@ struct RBBrowserSourcePrivate
 	RhythmDB *db;
 
 	RBLibraryBrowser *browser;
-
 	RBEntryView *songs;
-	GtkWidget *paned;
+	RBSourceToolbar *toolbar;
 
 	RhythmDBQueryModel *cached_all_query;
 	RhythmDBQuery *search_query;
@@ -146,10 +145,10 @@ static GtkActionEntry rb_browser_source_actions [] =
 
 static GtkRadioActionEntry rb_browser_source_radio_actions [] =
 {
-	{ "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 }
+	{ "BrowserSourceSearchAll", NULL, N_("Search all fields"), NULL, NULL, RHYTHMDB_PROP_SEARCH_MATCH },
+	{ "BrowserSourceSearchArtists", NULL, N_("Search artists"), NULL, NULL, RHYTHMDB_PROP_ARTIST_FOLDED },
+	{ "BrowserSourceSearchAlbums", NULL, N_("Search albums"), NULL, NULL, RHYTHMDB_PROP_ALBUM_FOLDED },
+	{ "BrowserSourceSearchTitles", NULL, N_("Search titles"), NULL, NULL, RHYTHMDB_PROP_TITLE_FOLDED }
 };
 
 static const GtkTargetEntry songs_view_drag_types[] = {
@@ -162,7 +161,6 @@ enum
 	PROP_0,
 	PROP_BASE_QUERY_MODEL,
 	PROP_POPULATE,
-	PROP_SEARCH_TYPE,
 	PROP_SHOW_BROWSER
 };
 
@@ -181,7 +179,6 @@ rb_browser_source_class_init (RBBrowserSourceClass *klass)
 	object_class->set_property = rb_browser_source_set_property;
 	object_class->get_property = rb_browser_source_get_property;
 
-	source_class->impl_can_browse = (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;
@@ -193,11 +190,10 @@ rb_browser_source_class_init (RBBrowserSourceClass *klass)
 	source_class->impl_can_add_to_queue = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_delete = impl_delete;
-	source_class->impl_get_search_actions = impl_get_search_actions;
 
-	klass->impl_pack_paned = default_pack_paned;
-	klass->impl_has_drop_support = (RBBrowserSourceFeatureFunc) rb_false_function;
-	klass->impl_show_entry_popup = default_show_entry_popup;
+	klass->pack_content = default_pack_content;
+	klass->has_drop_support = (RBBrowserSourceFeatureFunc) rb_false_function;
+	klass->show_entry_popup = default_show_entry_popup;
 
 	g_object_class_override_property (object_class,
 					  PROP_BASE_QUERY_MODEL,
@@ -212,9 +208,6 @@ rb_browser_source_class_init (RBBrowserSourceClass *klass)
 							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 
 	g_object_class_override_property (object_class,
-					  PROP_SEARCH_TYPE,
-					  "search-type");
-	g_object_class_override_property (object_class,
 					  PROP_SHOW_BROWSER,
 					  "show-browser");
 
@@ -291,7 +284,7 @@ rb_browser_source_songs_show_popup_cb (RBEntryView *view,
 	if (over_entry) {
 		RBBrowserSourceClass *klass = RB_BROWSER_SOURCE_GET_CLASS (source);
 
-		klass->impl_show_entry_popup (source);
+		klass->show_entry_popup (source);
 	} else {
 		rb_display_page_show_popup (RB_DISPLAY_PAGE (source));
 	}
@@ -310,7 +303,10 @@ rb_browser_source_constructed (GObject *object)
 	RBBrowserSourceClass *klass;
 	RBShell *shell;
 	GObject *shell_player;
+	GtkUIManager *ui_manager;
 	RhythmDBEntryType *entry_type;
+	GtkWidget *content;
+	GtkWidget *paned;
 
 	RB_CHAIN_GOBJECT_METHOD (rb_browser_source_parent_class, constructed, object);
 
@@ -323,6 +319,7 @@ rb_browser_source_constructed (GObject *object)
 	g_object_get (shell,
 		      "db", &source->priv->db,
 		      "shell-player", &shell_player,
+		      "ui-manager", &ui_manager,
 		      NULL);
 
 	source->priv->action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
@@ -351,12 +348,12 @@ rb_browser_source_constructed (GObject *object)
 
 	source->priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH);
 
-	source->priv->paned = gtk_vpaned_new ();
+	paned = gtk_vpaned_new ();
 
 	source->priv->browser = rb_library_browser_new (source->priv->db, entry_type);
 	gtk_widget_set_no_show_all (GTK_WIDGET (source->priv->browser), TRUE);
-	gtk_paned_pack1 (GTK_PANED (source->priv->paned), GTK_WIDGET (source->priv->browser), TRUE, FALSE);
-	gtk_container_child_set (GTK_CONTAINER (source->priv->paned),
+	gtk_paned_pack1 (GTK_PANED (paned), GTK_WIDGET (source->priv->browser), TRUE, FALSE);
+	gtk_container_child_set (GTK_CONTAINER (paned),
 				 GTK_WIDGET (source->priv->browser),
 				 "resize", FALSE,
 				 NULL);
@@ -390,7 +387,7 @@ rb_browser_source_constructed (GObject *object)
 
 	rb_source_bind_settings (RB_SOURCE (source),
 				 GTK_WIDGET (source->priv->songs),
-				 source->priv->paned,
+				 paned,
 				 GTK_WIDGET (source->priv->browser));
 
 	if (rb_browser_source_has_drop_support (source)) {
@@ -410,10 +407,22 @@ rb_browser_source_constructed (GObject *object)
 					 source, 0);
 	}
 
-	gtk_paned_pack2 (GTK_PANED (source->priv->paned), GTK_WIDGET (source->priv->songs), TRUE, FALSE);
+	gtk_paned_pack2 (GTK_PANED (paned), GTK_WIDGET (source->priv->songs), TRUE, FALSE);
+
+	/* set up toolbar */
+	source->priv->toolbar = rb_source_toolbar_new (RB_SOURCE (source), ui_manager);
+	rb_source_toolbar_add_search_entry (source->priv->toolbar, "/BrowserSourceSearchMenu", NULL);
+
+	content = gtk_grid_new ();
+	gtk_grid_set_column_spacing (GTK_GRID (content), 6);
+	gtk_grid_set_row_spacing (GTK_GRID (content), 6);
+	gtk_grid_attach (GTK_GRID (content), GTK_WIDGET (source->priv->toolbar), 0, 0, 1, 1);
+	gtk_widget_set_vexpand (paned, TRUE);
+	gtk_widget_set_hexpand (paned, TRUE);
+	gtk_grid_attach (GTK_GRID (content), paned, 0, 1, 1, 1);
 
 	klass = RB_BROWSER_SOURCE_GET_CLASS (source);
-	klass->impl_pack_paned (source, source->priv->paned);
+	klass->pack_content (source, content);
 
 	gtk_widget_show_all (GTK_WIDGET (source));
 
@@ -446,9 +455,6 @@ rb_browser_source_set_property (GObject *object,
 			rb_browser_source_populate (source);
 		}
 		break;
-	case PROP_SEARCH_TYPE:
-		/* ignored */
-		break;
 	case PROP_SHOW_BROWSER:
 		if (g_value_get_boolean (value)) {
 			gtk_widget_show (GTK_WIDGET (source->priv->browser));
@@ -478,9 +484,6 @@ rb_browser_source_get_property (GObject *object,
 	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;
 	case PROP_SHOW_BROWSER:
 		g_value_set_boolean (value, gtk_widget_get_visible (GTK_WIDGET (source->priv->browser)));
 		break;
@@ -639,6 +642,8 @@ impl_reset_filters (RBSource *asource)
 		changed = TRUE;
 	}
 
+	rb_source_toolbar_clear_search_entry (source->priv->toolbar);
+
 	if (changed)
 		rb_browser_source_do_query (source, FALSE);
 }
@@ -676,19 +681,6 @@ impl_song_properties (RBSource *asource)
 		rb_debug ("failed to create dialog, or no selection!");
 }
 
-static GList *
-impl_get_search_actions (RBSource *source)
-{
-	GList *actions = NULL;
-
-	actions = g_list_prepend (actions, g_strdup ("BrowserSourceSearchTitles"));
-	actions = g_list_prepend (actions, g_strdup ("BrowserSourceSearchAlbums"));
-	actions = g_list_prepend (actions, g_strdup ("BrowserSourceSearchArtists"));
-	actions = g_list_prepend (actions, g_strdup ("BrowserSourceSearchAll"));
-
-	return actions;
-}
-
 /**
  * rb_browser_source_has_drop_support:
  * @source: a #RBBrowserSource
@@ -703,7 +695,7 @@ rb_browser_source_has_drop_support (RBBrowserSource *source)
 {
 	RBBrowserSourceClass *klass = RB_BROWSER_SOURCE_GET_CLASS (source);
 
-	return klass->impl_has_drop_support (source);
+	return klass->has_drop_support (source);
 }
 
 static void
@@ -809,11 +801,7 @@ rb_browser_source_do_query (RBBrowserSource *source, gboolean subset)
 }
 
 static void
-default_pack_paned (RBBrowserSource *source, GtkWidget *paned)
+default_pack_content (RBBrowserSource *source, GtkWidget *content)
 {
-	GtkWidget *box;
-
-	box = gtk_vbox_new (FALSE, 5);
-	gtk_box_pack_start (GTK_BOX (box), paned, TRUE, TRUE, 0);
-	gtk_container_add (GTK_CONTAINER (source), box);
+	gtk_container_add (GTK_CONTAINER (source), content);
 }
diff --git a/sources/rb-browser-source.h b/sources/rb-browser-source.h
index 7d319f3..1a176f9 100644
--- a/sources/rb-browser-source.h
+++ b/sources/rb-browser-source.h
@@ -58,9 +58,9 @@ struct _RBBrowserSourceClass
 {
 	RBSourceClass parent;
 
-	void		(*impl_pack_paned)		(RBBrowserSource *source, GtkWidget *paned);
-	gboolean	(*impl_has_drop_support)	(RBBrowserSource *source);
-	void		(*impl_show_entry_popup)	(RBBrowserSource *source);
+	void		(*pack_content)		(RBBrowserSource *source, GtkWidget *content);
+	gboolean	(*has_drop_support)	(RBBrowserSource *source);
+	void		(*show_entry_popup)	(RBBrowserSource *source);
 };
 
 typedef gboolean	(*RBBrowserSourceFeatureFunc)	(RBBrowserSource *source);
diff --git a/sources/rb-display-page.c b/sources/rb-display-page.c
index 2576c6d..d9dbe31 100644
--- a/sources/rb-display-page.c
+++ b/sources/rb-display-page.c
@@ -55,6 +55,7 @@ struct _RBDisplayPagePrivate
 {
 	char *name;
 	gboolean visible;
+	gboolean selected;
 	GdkPixbuf *pixbuf;
 	RBDisplayPage *parent;
 
@@ -76,6 +77,7 @@ enum
 	PROP_VISIBLE,
 	PROP_PARENT,
 	PROP_PLUGIN,
+	PROP_SELECTED,
 };
 
 enum
@@ -201,6 +203,9 @@ rb_display_page_selected (RBDisplayPage *page)
 
 	if (klass->selected)
 		klass->selected (page);
+
+	page->priv->selected = TRUE;
+	g_object_notify (G_OBJECT (page), "selected");
 }
 
 /**
@@ -216,6 +221,9 @@ rb_display_page_deselected (RBDisplayPage *page)
 
 	if (klass->deselected)
 		klass->deselected (page);
+
+	page->priv->selected = FALSE;
+	g_object_notify (G_OBJECT (page), "selected");
 }
 
 /**
@@ -259,26 +267,6 @@ rb_display_page_get_config_widget (RBDisplayPage *page,
 }
 
 /**
- * rb_display_page_get_ui_actions:
- * @page: a #RBDisplayPage
- *
- * Returns a list of UI action names.  Items for
- * these actions will be added to the toolbar.
- *
- * Return value: (element-type utf8) (transfer full): list of action names
- */
-GList *
-rb_display_page_get_ui_actions (RBDisplayPage *page)
-{
-	RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
-
-	if (klass->get_ui_actions)
-		return klass->get_ui_actions (page);
-	else
-		return NULL;
-}
-
-/**
  * rb_display_page_get_status:
  * @page: a #RBDisplayPage
  * @text: (inout) (allow-none) (transfer full): holds the returned status text
@@ -521,6 +509,9 @@ impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *ps
 	case PROP_PLUGIN:
 		g_value_set_object (value, page->priv->plugin);
 		break;
+	case PROP_SELECTED:
+		g_value_set_boolean (value, page->priv->selected);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -567,6 +558,16 @@ impl_delete_thyself (RBDisplayPage *page)
 }
 
 static void
+impl_selected (RBDisplayPage *page)
+{
+}
+
+static void
+impl_deselected (RBDisplayPage *page)
+{
+}
+
+static void
 impl_dispose (GObject *object)
 {
 	RBDisplayPage *page;
@@ -619,6 +620,8 @@ rb_display_page_class_init (RBDisplayPageClass *klass)
 	object_class->set_property = impl_set_property;
 	object_class->get_property = impl_get_property;
 
+	klass->selected = impl_selected;
+	klass->deselected = impl_deselected;
 	klass->delete_thyself = impl_delete_thyself;
 
 	/**
@@ -706,6 +709,18 @@ rb_display_page_class_init (RBDisplayPageClass *klass)
 							      G_TYPE_OBJECT,
 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 	/**
+	 * RBDisplayPage:selected:
+	 *
+	 * TRUE when the page is selected in the page tree.
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_SELECTED,
+					 g_param_spec_boolean ("selected",
+							       "selected",
+							       "Whether the page is currently selected",
+							       FALSE,
+							       G_PARAM_READABLE));
+	/**
 	 * RBDisplayPage::deleted:
 	 * @page: the #RBDisplayPage
 	 *
@@ -756,10 +771,3 @@ rb_display_page_class_init (RBDisplayPageClass *klass)
  *
  * Return value: (transfer none): configuration widget
  */
-
-/**
- * impl_get_ui_actions:
- * @source: a #RBSource
- *
- * Return value: (element-type utf8) (transfer full): list of action names
- */
diff --git a/sources/rb-display-page.h b/sources/rb-display-page.h
index 4046601..3c072ee 100644
--- a/sources/rb-display-page.h
+++ b/sources/rb-display-page.h
@@ -71,7 +71,6 @@ struct _RBDisplayPageClass
 	GtkWidget *(*get_config_widget)	(RBDisplayPage *page, RBShellPreferences *prefs);
 
 	void	(*get_status)		(RBDisplayPage *page, char **text, char **progress_text, float *progress);
-	GList *	(*get_ui_actions)	(RBDisplayPage *page);
 	gboolean (*receive_drag)	(RBDisplayPage *page, GtkSelectionData *data);
 	gboolean (*show_popup)		(RBDisplayPage *page);
 	void	(*delete_thyself)	(RBDisplayPage *page);
@@ -90,8 +89,6 @@ void		rb_display_page_activate		(RBDisplayPage *page);
 GtkWidget *	rb_display_page_get_config_widget	(RBDisplayPage *page, RBShellPreferences *prefs);
 void		rb_display_page_get_status		(RBDisplayPage *page, char **text, char **progress_text, float *progress);
 
-GList *		rb_display_page_get_ui_actions		(RBDisplayPage *page);
-
 void		rb_display_page_delete_thyself		(RBDisplayPage *page);
 
 /* things for display page implementations */
diff --git a/sources/rb-import-errors-source.c b/sources/rb-import-errors-source.c
index 830b72c..e88f437 100644
--- a/sources/rb-import-errors-source.c
+++ b/sources/rb-import-errors-source.c
@@ -118,7 +118,6 @@ rb_import_errors_source_class_init (RBImportErrorsSourceClass *klass)
 
 	page_class->get_status = impl_get_status;
 
-	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;
 
diff --git a/sources/rb-library-source.c b/sources/rb-library-source.c
index 074d22a..da7b87f 100644
--- a/sources/rb-library-source.c
+++ b/sources/rb-library-source.c
@@ -184,7 +184,7 @@ rb_library_source_class_init (RBLibrarySourceClass *klass)
 	source_class->impl_want_uri = impl_want_uri;
 	source_class->impl_add_uri = impl_add_uri;
 
-	browser_source_class->impl_has_drop_support = (RBBrowserSourceFeatureFunc) rb_true_function;
+	browser_source_class->has_drop_support = (RBBrowserSourceFeatureFunc) rb_true_function;
 
 	g_type_class_add_private (klass, sizeof (RBLibrarySourcePrivate));
 }
@@ -341,6 +341,7 @@ rb_library_source_new (RBShell *shell)
 					  "shell", shell,
 					  "pixbuf", icon,
 					  "populate", FALSE,		/* wait until the database is loaded */
+					  "toolbar-path", "/LibrarySourceToolBar",
 					  "settings", g_settings_get_child (settings, "source"),
 					  NULL));
 	if (icon != NULL) {
diff --git a/sources/rb-media-player-source.c b/sources/rb-media-player-source.c
index 405bdac..dd8b426 100644
--- a/sources/rb-media-player-source.c
+++ b/sources/rb-media-player-source.c
@@ -155,7 +155,7 @@ rb_media_player_source_class_init (RBMediaPlayerSourceClass *klass)
 	source_class->impl_get_delete_action = impl_get_delete_action;
 	source_class->impl_delete = NULL;
 
-	browser_source_class->impl_has_drop_support = (RBBrowserSourceFeatureFunc) rb_false_function;
+	browser_source_class->has_drop_support = (RBBrowserSourceFeatureFunc) rb_false_function;
 
 	klass->impl_get_entries = NULL;
 	klass->impl_get_capacity = NULL;
diff --git a/sources/rb-missing-files-source.c b/sources/rb-missing-files-source.c
index 53ec1fe..70efbec 100644
--- a/sources/rb-missing-files-source.c
+++ b/sources/rb-missing-files-source.c
@@ -101,7 +101,6 @@ rb_missing_files_source_class_init (RBMissingFilesSourceClass *klass)
 
 	page_class->get_status = impl_get_status;
 
-	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;
 
diff --git a/sources/rb-play-queue-source.c b/sources/rb-play-queue-source.c
index 166353f..3bcdb53 100644
--- a/sources/rb-play-queue-source.c
+++ b/sources/rb-play-queue-source.c
@@ -98,7 +98,6 @@ static void rb_play_queue_source_cmd_clear (GtkAction *action,
 					    RBPlayQueueSource *source);
 static void rb_play_queue_source_cmd_shuffle (GtkAction *action,
 					      RBPlayQueueSource *source);
-static GList *impl_get_ui_actions (RBDisplayPage *page);
 static gboolean impl_show_popup (RBDisplayPage *page);
 
 static void rb_play_queue_dbus_method_call (GDBusConnection *connection,
@@ -211,12 +210,10 @@ rb_play_queue_source_class_init (RBPlayQueueSourceClass *klass)
 	object_class->finalize = rb_play_queue_source_finalize;
 	object_class->dispose  = rb_play_queue_source_dispose;
 
-	page_class->get_ui_actions = impl_get_ui_actions;
 	page_class->show_popup = impl_show_popup;
 
 	source_class->impl_can_add_to_queue = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_rename = (RBSourceFeatureFunc) rb_false_function;
-	source_class->impl_can_browse = (RBSourceFeatureFunc) rb_false_function;
 
 	playlist_class->impl_show_entry_view_popup = impl_show_entry_view_popup;
 	playlist_class->impl_save_contents_to_xml = impl_save_contents_to_xml;
@@ -285,6 +282,9 @@ rb_play_queue_source_constructed (GObject *object)
 	/* Translators: this is the toolbutton label for Clear Queue action */
 	g_object_set (G_OBJECT (action), "short-label", _("Clear"), NULL);
 
+	/* Translators: this is the toolbutton label for the 'shuffle queue' action */
+	gtk_action_set_short_label (gtk_action_group_get_action (priv->action_group, "ShuffleQueue"), C_("Queue", "Shuffle"));
+
 	priv->sidebar = rb_entry_view_new (db, shell_player, TRUE, TRUE);
 	g_object_unref (shell_player);
 
@@ -389,6 +389,8 @@ rb_play_queue_source_new (RBShell *shell)
 					"shell", shell,
 					"is-local", TRUE,
 					"entry-type", NULL,
+					"toolbar-path", "/QueueSourceToolBar",
+					"show-browser", FALSE,
 					NULL));
 }
 
@@ -580,15 +582,6 @@ impl_show_popup (RBDisplayPage *page)
 	return TRUE;
 }
 
-static GList *
-impl_get_ui_actions (RBDisplayPage *page)
-{
-	GList *actions = NULL;
-
-	actions = g_list_prepend (actions, g_strdup ("ClearQueue"));
-	return actions;
-}
-
 static void
 rb_play_queue_dbus_method_call (GDBusConnection *connection,
 				const char *sender,
diff --git a/sources/rb-source.c b/sources/rb-source.c
index 7711fcd..df6d7ac 100644
--- a/sources/rb-source.c
+++ b/sources/rb-source.c
@@ -69,7 +69,6 @@ static RBSourceEOFType default_handle_eos (RBSource *source);
 static RBEntryView *default_get_entry_view (RBSource *source);
 static void default_add_to_queue (RBSource *source, RBSource *queue);
 static void default_move_to_trash (RBSource *source);
-static GList * default_get_search_actions (RBSource *source);
 static char *default_get_delete_action (RBSource *source);
 
 static void rb_source_post_entry_deleted_cb (GtkTreeModel *model,
@@ -109,9 +108,10 @@ struct _RBSourcePrivate
 	guint update_visibility_id;
 	guint update_status_id;
 	RhythmDBEntryType *entry_type;
-	RBSourceSearchType search_type;
 
 	GSettings *settings;
+
+	char *toolbar_path;
 };
 
 enum
@@ -122,9 +122,9 @@ enum
 	PROP_ENTRY_TYPE,
 	PROP_BASE_QUERY_MODEL,
 	PROP_PLAY_ORDER,
-	PROP_SEARCH_TYPE,
 	PROP_SETTINGS,
-	PROP_SHOW_BROWSER
+	PROP_SHOW_BROWSER,
+	PROP_TOOLBAR_PATH
 };
 
 enum
@@ -149,7 +149,6 @@ rb_source_class_init (RBSourceClass *klass)
 	page_class->activate = default_activate;
 	page_class->get_status = default_get_status;
 
-	klass->impl_can_browse = (RBSourceFeatureFunc) rb_false_function;
 	klass->impl_get_property_views = default_get_property_views;
 	klass->impl_can_rename = default_can_rename;
 	klass->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
@@ -165,7 +164,6 @@ rb_source_class_init (RBSourceClass *klass)
 	klass->impl_handle_eos = default_handle_eos;
 	klass->impl_try_playlist = default_try_playlist;
 	klass->impl_add_to_queue = default_add_to_queue;
-	klass->impl_get_search_actions = default_get_search_actions;
 	klass->impl_get_delete_action = default_get_delete_action;
 	klass->impl_move_to_trash = default_move_to_trash;
 
@@ -237,20 +235,6 @@ rb_source_class_init (RBSourceClass *klass)
 							      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:settings:
 	 *
 	 * The #GSettings instance storing settings for the source.  The instance must
@@ -277,6 +261,22 @@ rb_source_class_init (RBSourceClass *klass)
 							       TRUE,
 							       G_PARAM_READWRITE));
 	/**
+	 * RBSource:toolbar-path:
+	 *
+	 * UI manager path for a toolbar to display at the top of the source.
+	 * The #RBSource class doesn't actually display the toolbar anywhere.
+	 * Adding the toolbar to a container is the responsibility of a subclass
+	 * such as #RBBrowserSource.
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_TOOLBAR_PATH,
+					 g_param_spec_string ("toolbar-path",
+							      "toolbar path",
+							      "toolbar UI path",
+							      NULL,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	/**
 	 * RBSource::filter-changed:
 	 * @source: the #RBSource
 	 *
@@ -346,6 +346,8 @@ rb_source_finalize (GObject *object)
 		g_object_unref (source->priv->query_model);
 	}
 
+	g_free (source->priv->toolbar_path);
+
 	G_OBJECT_CLASS (rb_source_parent_class)->finalize (object);
 }
 
@@ -443,15 +445,15 @@ rb_source_set_property (GObject *object,
 	case PROP_ENTRY_TYPE:
 		source->priv->entry_type = g_value_get_object (value);
 		break;
-	case PROP_SEARCH_TYPE:
-		source->priv->search_type = g_value_get_enum (value);
-		break;
 	case PROP_SETTINGS:
 		source->priv->settings = g_value_dup_object (value);
 		break;
 	case PROP_SHOW_BROWSER:
 		/* not connected to anything here */
 		break;
+	case PROP_TOOLBAR_PATH:
+		source->priv->toolbar_path = g_value_dup_string (value);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -482,15 +484,15 @@ rb_source_get_property (GObject *object,
 	case PROP_PLAY_ORDER:
 		g_value_set_object (value, NULL);		/* ? */
 		break;
-	case PROP_SEARCH_TYPE:
-		g_value_set_enum (value, source->priv->search_type);
-		break;
 	case PROP_SETTINGS:
 		g_value_set_object (value, source->priv->settings);
 		break;
 	case PROP_SHOW_BROWSER:
 		g_value_set_boolean (value, FALSE);
 		break;
+	case PROP_TOOLBAR_PATH:
+		g_value_set_string (value, source->priv->toolbar_path);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -534,22 +536,6 @@ default_get_status (RBDisplayPage *page,
 }
 
 /**
- * rb_source_can_browse:
- * @source: a #RBSource
- *
- * Determines whether the source has a browser
- *
- * Return value: TRUE if this source has a browser
- */
-gboolean
-rb_source_can_browse (RBSource *source)
-{
-	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
-
-	return klass->impl_can_browse (source);
-}
-
-/**
  * rb_source_notify_filter_changed:
  * @source: a #RBSource
  *
@@ -1166,31 +1152,6 @@ default_get_entry_view (RBSource *source)
 	return NULL;
 }
 
-static GList *
-default_get_search_actions (RBSource *source)
-{
-	return NULL;
-}
-
-/**
- * rb_source_get_search_actions:
- * @source: a #RBSource
- *
- * Returns a list of UI action names. Buttons for these
- * actions will be added to the search bar.  The source
- * must identify the selected search action when constructing
- * a database query for searching
- *
- * Return value: (element-type utf8) (transfer full): list of search actions
- */
-GList *
-rb_source_get_search_actions (RBSource *source)
-{
-	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
-
-	return klass->impl_get_search_actions (source);
-}
-
 static char *
 default_get_delete_action (RBSource *source)
 {
@@ -1482,26 +1443,6 @@ rb_source_eof_type_get_type (void)
 
 	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, "none"),
-			ENUM_ENTRY (RB_SOURCE_SEARCH_INCREMENTAL, "incremental"),
-			ENUM_ENTRY (RB_SOURCE_SEARCH_EXPLICIT, "explicit"),
-			{ 0, 0, 0 }
-		};
-
-		etype = g_enum_register_static ("RBSourceSearchType", values);
-	}
-
-	return etype;
-}
-
 /* introspection annotations for vmethods */
 
 /**
@@ -1538,9 +1479,3 @@ rb_source_search_type_get_type (void)
  * @entries: (element-type RB.RhythmDBEntry) (transfer none): list of entries to paste
  */
 
-/**
- * impl_get_search_actions:
- * @source: a #RBSource
- *
- * Return value: (element-type utf8) (transfer full): list of action names
- */
diff --git a/sources/rb-source.h b/sources/rb-source.h
index 81f835d..c38868d 100644
--- a/sources/rb-source.h
+++ b/sources/rb-source.h
@@ -34,6 +34,7 @@
 #include <sources/rb-display-page.h>
 #include <sources/rb-source-search.h>
 #include <widgets/rb-entry-view.h>
+#include <widgets/rb-search-entry.h>
 #include <shell/rb-shell-preferences.h>
 #include <shell/rb-track-transfer-batch.h>
 #include <rhythmdb/rhythmdb-import-job.h>
@@ -47,12 +48,6 @@ typedef enum {
 	RB_SOURCE_EOF_NEXT,
 } RBSourceEOFType;
 
-typedef enum {
-	RB_SOURCE_SEARCH_NONE,
-	RB_SOURCE_SEARCH_INCREMENTAL,
-	RB_SOURCE_SEARCH_EXPLICIT,
-} RBSourceSearchType;
-
 typedef struct _RBSource	RBSource;
 typedef struct _RBSourceClass	RBSourceClass;
 typedef struct _RBSourcePrivate	RBSourcePrivate;
@@ -62,9 +57,6 @@ typedef void (*RBSourceActionCallback) (GtkAction *action, RBSource *source);
 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))
@@ -93,8 +85,6 @@ struct _RBSourceClass
 
 	/* methods */
 
-	gboolean	(*impl_can_browse)	(RBSource *source);
-
 	RBEntryView *	(*impl_get_entry_view)	(RBSource *source);
 	GList *		(*impl_get_property_views)	(RBSource *source);
 
@@ -133,10 +123,7 @@ struct _RBSourceClass
 	gboolean	(*impl_can_pause)	(RBSource *source);
 	RBSourceEOFType	(*impl_handle_eos)	(RBSource *source);
 
-	gboolean	(*impl_have_url)	(RBSource *source);
-
 	void		(*impl_delete_thyself)	(RBSource *source);
-	GList *		(*impl_get_search_actions) (RBSource *source);
 	char *		(*impl_get_delete_action) (RBSource *source);
 };
 
@@ -149,8 +136,6 @@ void		rb_source_update_play_statistics(RBSource *source, RhythmDB *db,
 
 /* general interface */
 
-gboolean	rb_source_can_browse		(RBSource *source);
-
 RBEntryView *	rb_source_get_entry_view	(RBSource *source);
 
 GList *		rb_source_get_property_views	(RBSource *source);
@@ -195,7 +180,6 @@ void		rb_source_add_uri		(RBSource *source,
 gboolean	rb_source_can_pause		(RBSource *source);
 RBSourceEOFType	rb_source_handle_eos		(RBSource *source);
 
-GList *		rb_source_get_search_actions	(RBSource *source);
 char *		rb_source_get_delete_action	(RBSource *source);
 
 GList *		rb_source_gather_selected_properties (RBSource *source, RhythmDBPropType prop);
diff --git a/sources/rb-static-playlist-source.c b/sources/rb-static-playlist-source.c
index 3cd50a0..0e772ce 100644
--- a/sources/rb-static-playlist-source.c
+++ b/sources/rb-static-playlist-source.c
@@ -58,6 +58,7 @@
 #include "rb-file-helpers.h"
 #include "rb-playlist-xml.h"
 #include "rb-source-search-basic.h"
+#include "rb-source-toolbar.h"
 
 static void rb_static_playlist_source_constructed (GObject *object);
 static void rb_static_playlist_source_dispose (GObject *object);
@@ -78,7 +79,6 @@ static void impl_delete (RBSource *source);
 static void impl_search (RBSource *asource, RBSourceSearch *search, const char *cur_text, const char *new_text);
 static void impl_reset_filters (RBSource *asource);
 static gboolean impl_receive_drag (RBDisplayPage *page, GtkSelectionData *data);
-static GList *impl_get_search_actions (RBSource *source);
 static guint impl_want_uri (RBSource *source, const char *uri);
 
 static GPtrArray *construct_query_from_selection (RBStaticPlaylistSource *source);
@@ -120,10 +120,10 @@ static void rb_static_playlist_source_rows_reordered (GtkTreeModel *model,
 
 static GtkRadioActionEntry rb_static_playlist_source_radio_actions [] =
 {
-	{ "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 }
+	{ "StaticPlaylistSearchAll", NULL, N_("Search all fields"), NULL, NULL, RHYTHMDB_PROP_SEARCH_MATCH },
+	{ "StaticPlaylistSearchArtists", NULL, N_("Search artists"), NULL, NULL, RHYTHMDB_PROP_ARTIST_FOLDED },
+	{ "StaticPlaylistSearchAlbums", NULL, N_("Search albums"), NULL, NULL, RHYTHMDB_PROP_ALBUM_FOLDED },
+	{ "StaticPlaylistSearchTitles", NULL, N_("Search titles"), NULL, NULL, RHYTHMDB_PROP_TITLE_FOLDED }
 };
 
 enum
@@ -143,9 +143,8 @@ typedef struct
 	RhythmDBQueryModel *base_model;
 	RhythmDBQueryModel *filter_model;
 
-	GtkWidget *paned;
+	RBSourceToolbar *toolbar;
 	RBLibraryBrowser *browser;
-	gboolean browser_shown;
 
 	RBSourceSearch *default_search;
 	RhythmDBQuery *search_query;
@@ -180,9 +179,7 @@ rb_static_playlist_source_class_init (RBStaticPlaylistSourceClass *klass)
 	source_class->impl_delete = impl_delete;
 	source_class->impl_search = impl_search;
 	source_class->impl_reset_filters = impl_reset_filters;
-	source_class->impl_can_browse = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_get_property_views = impl_get_property_views;
-	source_class->impl_get_search_actions = impl_get_search_actions;
 	source_class->impl_want_uri = impl_want_uri;
 
 	playlist_class->impl_save_contents_to_xml = impl_save_contents_to_xml;
@@ -283,6 +280,9 @@ rb_static_playlist_source_constructed (GObject *object)
 	RBEntryView *songs;
 	RBShell *shell;
 	RhythmDBEntryType *entry_type;
+	GtkUIManager *ui_manager;
+	GtkWidget *grid;
+	GtkWidget *paned;
 
 	RB_CHAIN_GOBJECT_METHOD (rb_static_playlist_source_parent_class, constructed, object);
 
@@ -298,7 +298,9 @@ rb_static_playlist_source_constructed (GObject *object)
 				 G_CALLBACK (rb_static_playlist_source_filter_entry_drop),
 				 source, 0);
 
-	priv->paned = gtk_vpaned_new ();
+	paned = gtk_vpaned_new ();
+	gtk_widget_set_hexpand (paned, TRUE);
+	gtk_widget_set_vexpand (paned, TRUE);
 
 	g_object_get (source, "shell", &shell, NULL);
 	priv->action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
@@ -319,6 +321,7 @@ rb_static_playlist_source_constructed (GObject *object)
 	}
 	priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH);
 
+	g_object_get (shell, "ui-manager", &ui_manager, NULL);
 	g_object_unref (shell);
 
 	g_object_get (source, "entry-type", &entry_type, NULL);
@@ -328,7 +331,7 @@ rb_static_playlist_source_constructed (GObject *object)
 		g_object_unref (entry_type);
 	}
 
-	gtk_paned_pack1 (GTK_PANED (priv->paned), GTK_WIDGET (priv->browser), TRUE, FALSE);
+	gtk_paned_pack1 (GTK_PANED (paned), GTK_WIDGET (priv->browser), TRUE, FALSE);
 	g_signal_connect_object (priv->browser, "notify::output-model",
 				 G_CALLBACK (rb_static_playlist_source_browser_changed_cb),
 				 source, 0);
@@ -340,10 +343,22 @@ rb_static_playlist_source_constructed (GObject *object)
 	songs = rb_source_get_entry_view (RB_SOURCE (source));
 	g_object_ref (songs);
 	gtk_container_remove (GTK_CONTAINER (source), GTK_WIDGET (songs));
-	gtk_paned_pack2 (GTK_PANED (priv->paned), GTK_WIDGET (songs), TRUE, FALSE);
-	gtk_container_add (GTK_CONTAINER (source), priv->paned);
-
-	rb_source_bind_settings (RB_SOURCE (source), GTK_WIDGET (songs), priv->paned, GTK_WIDGET (priv->browser));
+	gtk_paned_pack2 (GTK_PANED (paned), GTK_WIDGET (songs), TRUE, FALSE);
+
+	/* set up search box / toolbar */
+	priv->toolbar = rb_source_toolbar_new (RB_SOURCE (source), ui_manager);
+	rb_source_toolbar_add_search_entry (priv->toolbar, "/StaticPlaylistSourceSearchMenu", NULL);
+	g_object_unref (ui_manager);
+
+	/* put it all together */
+	grid = gtk_grid_new ();
+	gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
+	gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
+	gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (priv->toolbar), 0, 0, 1, 1);
+	gtk_grid_attach (GTK_GRID (grid), paned, 0, 1, 1, 1);
+	gtk_container_add (GTK_CONTAINER (source), grid);
+
+	rb_source_bind_settings (RB_SOURCE (source), GTK_WIDGET (songs), paned, GTK_WIDGET (priv->browser));
 	g_object_unref (songs);
 
 	/* watch these to find out when things are dropped into the entry view */
@@ -395,7 +410,7 @@ rb_static_playlist_source_new (RBShell *shell, const char *name, const char *set
 					"shell", shell,
 					"is-local", local,
 					"entry-type", entry_type,
-					"search-type", RB_SOURCE_SEARCH_INCREMENTAL,
+					"toolbar-path", "/StaticPlaylistSourceToolBar",
 					NULL));
 }
 
@@ -548,6 +563,8 @@ impl_reset_filters (RBSource *source)
 		priv->search_query = NULL;
 	}
 
+	rb_source_toolbar_clear_search_entry (priv->toolbar);
+
 	if (changed) {
 		rb_static_playlist_source_do_query (RB_STATIC_PLAYLIST_SOURCE (source));
 		rb_source_notify_filter_changed (source);
@@ -992,19 +1009,6 @@ rb_static_playlist_source_filter_entry_drop (RhythmDBQueryModel *model,
 	return FALSE;
 }
 
-static GList *
-impl_get_search_actions (RBSource *source)
-{
-	GList *actions = NULL;
-
-	actions = g_list_prepend (actions, g_strdup ("StaticPlaylistSearchTitles"));
-	actions = g_list_prepend (actions, g_strdup ("StaticPlaylistSearchAlbums"));
-	actions = g_list_prepend (actions, g_strdup ("StaticPlaylistSearchArtists"));
-	actions = g_list_prepend (actions, g_strdup ("StaticPlaylistSearchAll"));
-
-	return actions;
-}
-
 static guint
 impl_want_uri (RBSource *source, const char *uri)
 {
diff --git a/sources/rb-static-playlist-source.h b/sources/rb-static-playlist-source.h
index dc8ee49..4a2c80f 100644
--- a/sources/rb-static-playlist-source.h
+++ b/sources/rb-static-playlist-source.h
@@ -62,7 +62,7 @@ GType		rb_static_playlist_source_get_type 	(void);
 
 RBSource *	rb_static_playlist_source_new		(RBShell *shell,
 							 const char *name,
-							 const char *sorting_name,
+							 const char *settings_name,
 							 gboolean local,
 							 RhythmDBEntryType *entry_type);
 
diff --git a/widgets/Makefile.am b/widgets/Makefile.am
index 99aa8f4..a2aa52a 100644
--- a/widgets/Makefile.am
+++ b/widgets/Makefile.am
@@ -12,6 +12,7 @@ widgetinclude_HEADERS =					\
 	rb-library-browser.h				\
 	rb-segmented-bar.h				\
 	rb-song-info.h					\
+	rb-source-toolbar.h				\
 	rb-uri-dialog.h
 
 librbwidgets_la_SOURCES =				\
@@ -38,11 +39,12 @@ librbwidgets_la_SOURCES =				\
 	rb-query-creator.h				\
 	rb-query-creator-private.h			\
 	rb-query-creator-properties.c			\
-	rb-uri-dialog.c				\
-	eggwrapbox.c				\
-	eggwrapbox.h				\
+	rb-uri-dialog.c					\
+	eggwrapbox.c					\
+	eggwrapbox.h					\
 	eggwrapbox-enums.c				\
-	eggwrapbox-enums.h
+	eggwrapbox-enums.h				\
+	rb-source-toolbar.c
 
 INCLUDES =						\
 	-DGNOMELOCALEDIR=\""$(datadir)/locale"\"        \
diff --git a/widgets/rb-search-entry.c b/widgets/rb-search-entry.c
index a817baa..a0b5256 100644
--- a/widgets/rb-search-entry.c
+++ b/widgets/rb-search-entry.c
@@ -35,9 +35,11 @@
 #include <gtk/gtk.h>
 
 #include "rb-search-entry.h"
+#include "rb-util.h"
 
 static void rb_search_entry_class_init (RBSearchEntryClass *klass);
 static void rb_search_entry_init (RBSearchEntry *entry);
+static void rb_search_entry_constructed (GObject *object);
 static void rb_search_entry_finalize (GObject *object);
 static gboolean rb_search_entry_timeout_cb (RBSearchEntry *entry);
 static void rb_search_entry_changed_cb (GtkEditable *editable,
@@ -54,14 +56,14 @@ static void rb_search_entry_clear_cb (GtkEntry *entry,
 				      GtkEntryIconPosition icon_pos,
 				      GdkEvent *event,
 				      RBSearchEntry *search_entry);
-static void rb_search_entry_check_style (RBSearchEntry *entry);
+static void rb_search_entry_update_icons (RBSearchEntry *entry);
 
 struct RBSearchEntryPrivate
 {
-	GtkWidget *label;
 	GtkWidget *entry;
 	GtkWidget *button;
 
+	gboolean has_popup;
 	gboolean explicit_mode;
 	gboolean clearing;
 	gboolean searching;
@@ -84,22 +86,21 @@ G_DEFINE_TYPE (RBSearchEntry, rb_search_entry, GTK_TYPE_HBOX)
  *
  * Signals are emitted when the search text changes,
  * arbitrarily rate-limited to one every 300ms.
- *
- * When the text entry widget is non-empty, its colours are
- * changed to display the text in black on yellow.
  */
 
 enum
 {
 	SEARCH,
 	ACTIVATE,
+	SHOW_POPUP,
 	LAST_SIGNAL
 };
 
 enum
 {
 	PROP_0,
-	PROP_EXPLICIT_MODE
+	PROP_EXPLICIT_MODE,
+	PROP_HAS_POPUP
 };
 
 static guint rb_search_entry_signals[LAST_SIGNAL] = { 0 };
@@ -109,6 +110,7 @@ rb_search_entry_class_init (RBSearchEntryClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
+	object_class->constructed = rb_search_entry_constructed;
 	object_class->finalize = rb_search_entry_finalize;
 	object_class->set_property = rb_search_entry_set_property;
 	object_class->get_property = rb_search_entry_get_property;
@@ -152,6 +154,22 @@ rb_search_entry_class_init (RBSearchEntryClass *klass)
 			      G_TYPE_STRING);
 
 	/**
+	 * RBSearchEntry::show-popup:
+	 * @entry: the #RBSearchEntry
+	 *
+	 * Emitted when a popup menu should be shown
+	 */
+	rb_search_entry_signals[SHOW_POPUP] =
+		g_signal_new ("show-popup",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBSearchEntryClass, show_popup),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
+
+	/**
 	 * RBSearchEntry:explicit-mode:
 	 *
 	 * If TRUE, show a button and only emit the 'search' signal when
@@ -164,6 +182,18 @@ rb_search_entry_class_init (RBSearchEntryClass *klass)
 							       "whether in explicit search mode or not",
 							       FALSE,
 							       G_PARAM_READWRITE));
+	/**
+	 * RBSearchEntry:has-popup:
+	 *
+	 * If TRUE, show a primary icon and emit the show-popup when clicked.
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_HAS_POPUP,
+					 g_param_spec_boolean ("has-popup",
+							       "has popup",
+							       "whether to display the search menu icon",
+							       FALSE,
+							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
 	g_type_class_add_private (klass, sizeof (RBSearchEntryPrivate));
 }
@@ -171,10 +201,19 @@ rb_search_entry_class_init (RBSearchEntryClass *klass)
 static void
 rb_search_entry_init (RBSearchEntry *entry)
 {
+	entry->priv = RB_SEARCH_ENTRY_GET_PRIVATE (entry);
+}
+
+static void
+rb_search_entry_constructed (GObject *object)
+{
+	RBSearchEntry *entry;
 	GtkSettings *settings;
 	char *theme;
 
-	entry->priv = RB_SEARCH_ENTRY_GET_PRIVATE (entry);
+	RB_CHAIN_GOBJECT_METHOD (rb_search_entry_parent_class, constructed, object);
+
+	entry = RB_SEARCH_ENTRY (object);
 
 	settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (entry)));
 	g_object_get (settings, "gtk-theme-name", &theme, NULL);
@@ -182,27 +221,27 @@ rb_search_entry_init (RBSearchEntry *entry)
 					strncmp (theme, "LowContrast", strlen ("LowContrast")) == 0;
 	g_free (theme);
 
-	/* this string can only be so long, or there wont be a search entry :) */
-	entry->priv->label = gtk_label_new_with_mnemonic (_("_Search:"));
-	gtk_label_set_justify (GTK_LABEL (entry->priv->label), GTK_JUSTIFY_RIGHT);
-	gtk_box_pack_start (GTK_BOX (entry), entry->priv->label, FALSE, TRUE, 0);
-	gtk_widget_set_no_show_all (entry->priv->label, TRUE);
-	gtk_widget_show (entry->priv->label);
-
 	entry->priv->entry = gtk_entry_new ();
-	gtk_entry_set_icon_from_stock (GTK_ENTRY (entry->priv->entry),
-				       GTK_ENTRY_ICON_SECONDARY,
-				       GTK_STOCK_CLEAR);
-	gtk_entry_set_icon_tooltip_text (GTK_ENTRY (entry->priv->entry),
-					 GTK_ENTRY_ICON_SECONDARY,
-					 _("Clear the search text"));
 	g_signal_connect_object (GTK_ENTRY (entry->priv->entry),
 				 "icon-press",
 				 G_CALLBACK (rb_search_entry_clear_cb),
 				 entry, 0);
 
-	gtk_label_set_mnemonic_widget (GTK_LABEL (entry->priv->label),
-				       entry->priv->entry);
+	gtk_entry_set_icon_tooltip_text (GTK_ENTRY (entry->priv->entry),
+					 GTK_ENTRY_ICON_SECONDARY,
+					 _("Clear the search text"));
+	if (entry->priv->has_popup) {
+		gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry->priv->entry),
+						   GTK_ENTRY_ICON_PRIMARY,
+						   "edit-find-symbolic");
+		gtk_entry_set_icon_tooltip_text (GTK_ENTRY (entry->priv->entry),
+						 GTK_ENTRY_ICON_PRIMARY,
+						 _("Select the search type"));
+	} else {
+		gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry->priv->entry),
+						   GTK_ENTRY_ICON_SECONDARY,
+						   "edit-find-symbolic");
+	}
 
 	gtk_box_pack_start (GTK_BOX (entry), entry->priv->entry, TRUE, TRUE, 0);
 
@@ -236,9 +275,11 @@ rb_search_entry_set_property (GObject *object, guint prop_id, const GValue *valu
 	switch (prop_id) {
 	case PROP_EXPLICIT_MODE:
 		entry->priv->explicit_mode = g_value_get_boolean (value);
-		gtk_widget_set_visible (entry->priv->label, entry->priv->explicit_mode == FALSE);
 		gtk_widget_set_visible (entry->priv->button, entry->priv->explicit_mode == TRUE);
-		rb_search_entry_check_style (entry);
+		rb_search_entry_update_icons (entry);
+		break;
+	case PROP_HAS_POPUP:
+		entry->priv->has_popup = g_value_get_boolean (value);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -255,6 +296,9 @@ rb_search_entry_get_property (GObject *object, guint prop_id, GValue *value, GPa
 	case PROP_EXPLICIT_MODE:
 		g_value_set_boolean (value, entry->priv->explicit_mode);
 		break;
+	case PROP_HAS_POPUP:
+		g_value_set_boolean (value, entry->priv->has_popup);
+		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 		break;
@@ -284,12 +328,14 @@ rb_search_entry_finalize (GObject *object)
  * Return value: new search entry widget.
  */
 RBSearchEntry *
-rb_search_entry_new (void)
+rb_search_entry_new (gboolean has_popup)
 {
 	RBSearchEntry *entry;
 
 	entry = RB_SEARCH_ENTRY (g_object_new (RB_TYPE_SEARCH_ENTRY,
 					       "spacing", 5,
+					       "has-popup", has_popup,
+					       "hexpand", TRUE,
 					       NULL));
 
 	g_return_val_if_fail (entry->priv != NULL, NULL);
@@ -334,31 +380,26 @@ rb_search_entry_set_text (RBSearchEntry *entry, const char *text)
 			    text ? text : "");
 }
 
+/**
+ * rb_search_entry_set_placeholder:
+ * @entry: a #RBSearchEntry
+ * @text: placeholder text
+ *
+ * Sets the placeholder text in the search entry box.
+ */
+void
+rb_search_entry_set_placeholder (RBSearchEntry *entry, const char *text)
+{
+	gtk_entry_set_placeholder_text (GTK_ENTRY (entry->priv->entry), text);
+}
+
 static void
-rb_search_entry_check_style (RBSearchEntry *entry)
+rb_search_entry_update_icons (RBSearchEntry *entry)
 {
-	static const GdkRGBA fallback_bg_color = { 0.9686, 0.9686, 0.7451, 1.0}; /* yellow-ish */
-	static const GdkRGBA fallback_fg_color = { 0, 0, 0, 1.0 }; /* black. */
-	GdkRGBA bg_color = {0,};
-	GdkRGBA fg_color = {0,};
-	const gchar* text;
+	const char *text;
+	const char *icon;
 	gboolean searching;
 
-	if (entry->priv->is_a11y_theme)
-		return;
-
-	/* allow user style to override the colors */
-	if (gtk_style_context_lookup_color (gtk_widget_get_style_context (GTK_WIDGET (entry)),
-					    "rb-search-active-bg",
-					    &bg_color) == FALSE) {
-		bg_color = fallback_bg_color;
-	}
-	if (gtk_style_context_lookup_color (gtk_widget_get_style_context (GTK_WIDGET (entry)),
-					    "rb-search-active-fg",
-					    &fg_color) == FALSE) {
-		fg_color = fallback_fg_color;
-	}
-
 	if (entry->priv->explicit_mode) {
 		searching = entry->priv->searching;
 	} else {
@@ -367,16 +408,16 @@ rb_search_entry_check_style (RBSearchEntry *entry)
 	}
 
 	if (searching) {
-		gtk_widget_override_color (entry->priv->entry, GTK_STATE_NORMAL, &fg_color);
-		gtk_widget_override_background_color (entry->priv->entry, GTK_STATE_NORMAL, &bg_color);
-		gtk_widget_override_cursor (entry->priv->entry, &fg_color, &fg_color);
+		icon = "edit-clear-symbolic";
+	} else if (entry->priv->has_popup) {
+		/* we already use 'find' as the primary icon */
+		icon = NULL;
 	} else {
-		gtk_widget_override_color (entry->priv->entry, GTK_STATE_NORMAL, NULL);
-		gtk_widget_override_background_color (entry->priv->entry, GTK_STATE_NORMAL, NULL);
-		gtk_widget_override_cursor (entry->priv->entry, NULL, NULL);
+		icon = "edit-find-symbolic";
 	}
-
-	gtk_widget_queue_draw (GTK_WIDGET (entry));
+	gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry->priv->entry),
+					   GTK_ENTRY_ICON_SECONDARY,
+					   icon);
 }
 
 static void
@@ -387,7 +428,7 @@ rb_search_entry_changed_cb (GtkEditable *editable,
 
 	if (entry->priv->clearing == TRUE) {
 		entry->priv->searching = FALSE;
-		rb_search_entry_check_style (entry);
+		rb_search_entry_update_icons (entry);
 		return;
 	}
 
@@ -406,7 +447,7 @@ rb_search_entry_changed_cb (GtkEditable *editable,
 		gtk_widget_set_sensitive (entry->priv->button, FALSE);
 		rb_search_entry_timeout_cb (entry);
 	}
-	rb_search_entry_check_style (entry);
+	rb_search_entry_update_icons (entry);
 }
 
 static gboolean
@@ -469,7 +510,7 @@ rb_search_entry_activate_cb (GtkEntry *gtkentry,
 			     RBSearchEntry *entry)
 {
 	entry->priv->searching = TRUE;
-	rb_search_entry_check_style (entry);
+	rb_search_entry_update_icons (entry);
 	g_signal_emit (G_OBJECT (entry), rb_search_entry_signals[ACTIVATE], 0,
 		       gtk_entry_get_text (GTK_ENTRY (entry->priv->entry)));
 }
@@ -478,7 +519,7 @@ static void
 button_clicked_cb (GtkButton *button, RBSearchEntry *entry)
 {
 	entry->priv->searching = TRUE;
-	rb_search_entry_check_style (entry);
+	rb_search_entry_update_icons (entry);
 	g_signal_emit (G_OBJECT (entry), rb_search_entry_signals[SEARCH], 0,
 		       gtk_entry_get_text (GTK_ENTRY (entry->priv->entry)));
 }
@@ -501,5 +542,9 @@ rb_search_entry_clear_cb (GtkEntry *entry,
 			  GdkEvent *event,
 			  RBSearchEntry *search_entry)
 {
-	rb_search_entry_set_text (search_entry, "");
+	if (icon_pos == GTK_ENTRY_ICON_PRIMARY) {
+		g_signal_emit (G_OBJECT (search_entry), rb_search_entry_signals[SHOW_POPUP], 0);
+	} else {
+		rb_search_entry_set_text (search_entry, "");
+	}
 }
diff --git a/widgets/rb-search-entry.h b/widgets/rb-search-entry.h
index f464a00..c307f19 100644
--- a/widgets/rb-search-entry.h
+++ b/widgets/rb-search-entry.h
@@ -57,16 +57,19 @@ struct _RBSearchEntryClass
 
 	void (*search) (RBSearchEntry *view, const char *text);
 	void (*activate) (RBSearchEntry *entry, const char *text);
+	void (*show_popup) (RBSearchEntry *entry);
 };
 
 GType		rb_search_entry_get_type (void);
 
-RBSearchEntry *	rb_search_entry_new      (void);
+RBSearchEntry *	rb_search_entry_new      (gboolean has_popup);
 
 void		rb_search_entry_clear    (RBSearchEntry *entry);
 
 void		rb_search_entry_set_text (RBSearchEntry *entry, const char *text);
 
+void		rb_search_entry_set_placeholder (RBSearchEntry *entry, const char *text);
+
 gboolean	rb_search_entry_searching(RBSearchEntry *entry);
 
 void		rb_search_entry_grab_focus (RBSearchEntry *entry);
diff --git a/widgets/rb-source-toolbar.c b/widgets/rb-source-toolbar.c
new file mode 100644
index 0000000..6849aa8
--- /dev/null
+++ b/widgets/rb-source-toolbar.c
@@ -0,0 +1,488 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2011 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 <widgets/rb-source-toolbar.h>
+#include <lib/rb-util.h>
+
+static void rb_source_toolbar_class_init (RBSourceToolbarClass *klass);
+static void rb_source_toolbar_init (RBSourceToolbar *toolbar);
+
+struct _RBSourceToolbarPrivate
+{
+	GtkUIManager *ui_manager;
+	RBSource *source;
+	RBSearchEntry *search_entry;
+	GtkWidget *search_popup;
+	GtkWidget *toolbar;
+	GBinding *browse_binding;
+	char *popup_path;
+
+	/* search state */
+	int search_value;
+	gulong search_change_cb_id;
+	RBSourceSearch *active_search;
+	char *search_text;
+	GtkRadioAction *search_group;
+};
+
+G_DEFINE_TYPE (RBSourceToolbar, rb_source_toolbar, GTK_TYPE_GRID)
+
+/**
+ * SECTION:rb-source-toolbar
+ * @short_description: toolbar+search entry for sources
+ *
+ * This class combines a toolbar for custom source actions with a
+ * search entry.  The toolbar content is specified using a UI path.
+ * The #RBSourceToolbar takes care of preserving search state when
+ * the selected page changes, and performs searches when the user
+ * selects a new search type or changes the search text.
+ */
+
+enum
+{
+	PROP_0,
+	PROP_SOURCE,
+	PROP_UI_MANAGER,
+};
+
+static void
+prepare_toolbar (GtkWidget *toolbar)
+{
+	static GtkCssProvider *provider = NULL;
+
+	if (provider == NULL) {
+		const char *style =
+			"GtkToolbar {\n"
+                        "       -GtkToolbar-shadow-type: none;\n"
+                        "       border-style: none;\n"
+                        "}";
+
+		provider = gtk_css_provider_new ();
+		gtk_css_provider_load_from_data (provider, style, -1, NULL);
+	}
+
+	gtk_style_context_add_provider (gtk_widget_get_style_context (toolbar),
+					GTK_STYLE_PROVIDER (provider),
+					GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+	gtk_widget_set_hexpand (toolbar, TRUE);
+
+	gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_TEXT);
+}
+
+static void
+search_change_cb (GtkRadioAction *group, GtkRadioAction *current, RBSourceToolbar *toolbar)
+{
+	toolbar->priv->active_search = rb_source_search_get_from_action (G_OBJECT (current));
+
+	if (toolbar->priv->search_text != NULL) {
+		rb_source_search (toolbar->priv->source, toolbar->priv->active_search, NULL, toolbar->priv->search_text);
+	}
+
+	rb_search_entry_set_placeholder (toolbar->priv->search_entry, gtk_action_get_label (GTK_ACTION (current)));
+}
+
+static void
+source_selected_cb (GObject *object, GParamSpec *pspec, RBSourceToolbar *toolbar)
+{
+	gboolean selected;
+
+	g_object_get (object, "selected", &selected, NULL);
+
+	if (selected) {
+		char *toolbar_path;
+		char *browse_path;
+
+		if (toolbar->priv->toolbar != NULL) {
+			gtk_grid_attach (GTK_GRID (toolbar), toolbar->priv->toolbar, 0, 0, 2, 1);
+			gtk_widget_show_all (GTK_WIDGET (toolbar->priv->toolbar));
+		}
+
+		if (toolbar->priv->search_group != NULL) {
+			if (toolbar->priv->search_value != -1) {
+				gtk_radio_action_set_current_value (toolbar->priv->search_group,
+								    toolbar->priv->search_value);
+			}
+
+			toolbar->priv->search_change_cb_id = g_signal_connect (toolbar->priv->search_group,
+									       "changed",
+									       G_CALLBACK (search_change_cb),
+									       toolbar);
+		}
+
+		g_object_get (toolbar->priv->source, "toolbar-path", &toolbar_path, NULL);
+		if (toolbar_path != NULL) {
+			GtkAction *browse_action;
+
+			browse_path = g_strdup_printf ("%s/Browse", toolbar_path);
+			browse_action = gtk_ui_manager_get_action (toolbar->priv->ui_manager, browse_path);
+			g_free (browse_path);
+
+			if (browse_action != NULL) {
+				toolbar->priv->browse_binding =
+					g_object_bind_property (toolbar->priv->source, "show-browser",
+								browse_action, "active",
+								G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+			}
+			g_free (toolbar_path);
+		}
+	} else {
+		if (toolbar->priv->toolbar != NULL) {
+			gtk_container_remove (GTK_CONTAINER (toolbar), toolbar->priv->toolbar);
+		}
+
+		if (toolbar->priv->search_group != NULL) {
+			if (toolbar->priv->search_change_cb_id != 0) {
+				g_signal_handler_disconnect (toolbar->priv->search_group,
+							     toolbar->priv->search_change_cb_id);
+			}
+
+			toolbar->priv->search_value = gtk_radio_action_get_current_value (toolbar->priv->search_group);
+		}
+
+		if (toolbar->priv->browse_binding != NULL) {
+			g_object_unref (toolbar->priv->browse_binding);
+			toolbar->priv->browse_binding = NULL;
+		}
+	}
+}
+
+static void
+search_cb (RBSearchEntry *search_entry, const char *text, RBSourceToolbar *toolbar)
+{
+	rb_source_search (toolbar->priv->source, toolbar->priv->active_search, toolbar->priv->search_text, text);
+
+	g_free (toolbar->priv->search_text);
+	toolbar->priv->search_text = NULL;
+	if (text != NULL) {
+		toolbar->priv->search_text = g_strdup (text);
+	}
+}
+
+static void
+show_popup_cb (RBSearchEntry *search_entry, RBSourceToolbar *toolbar)
+{
+	gtk_menu_popup (GTK_MENU (toolbar->priv->search_popup),
+			NULL, NULL, NULL, NULL, 3,
+			gtk_get_current_event_time ());
+}
+
+
+static void
+impl_finalize (GObject *object)
+{
+	RBSourceToolbar *toolbar = RB_SOURCE_TOOLBAR (object);
+
+	g_free (toolbar->priv->search_text);
+	g_free (toolbar->priv->popup_path);
+
+	G_OBJECT_CLASS (rb_source_toolbar_parent_class)->finalize (object);
+}
+
+static void
+impl_dispose (GObject *object)
+{
+	RBSourceToolbar *toolbar = RB_SOURCE_TOOLBAR (object);
+
+	if (toolbar->priv->ui_manager != NULL) {
+		g_object_unref (toolbar->priv->ui_manager);
+		toolbar->priv->ui_manager = NULL;
+	}
+	if (toolbar->priv->search_popup != NULL) {
+		g_object_unref (toolbar->priv->search_popup);
+		toolbar->priv->search_popup = NULL;
+	}
+	if (toolbar->priv->toolbar != NULL) {
+		g_object_unref (toolbar->priv->toolbar);
+		toolbar->priv->toolbar = NULL;
+	}
+	if (toolbar->priv->browse_binding != NULL) {
+		g_object_unref (toolbar->priv->browse_binding);
+		toolbar->priv->browse_binding = NULL;
+	}
+
+	G_OBJECT_CLASS (rb_source_toolbar_parent_class)->dispose (object);
+}
+
+static void
+toolbar_add_widget_cb (GtkUIManager *ui_manager, GtkWidget *widget, RBSourceToolbar *toolbar)
+{
+	char *toolbar_path;
+	gboolean selected;
+
+	g_object_get (toolbar->priv->source, "toolbar-path", &toolbar_path, "selected", &selected, NULL);
+	toolbar->priv->toolbar = gtk_ui_manager_get_widget (toolbar->priv->ui_manager, toolbar_path);
+	g_free (toolbar_path);
+
+	if (toolbar->priv->toolbar) {
+		g_object_ref (toolbar->priv->toolbar);
+		g_signal_handlers_disconnect_by_func (ui_manager, G_CALLBACK (toolbar_add_widget_cb), toolbar);
+
+		prepare_toolbar (toolbar->priv->toolbar);
+
+		if (selected) {
+			gtk_grid_attach (GTK_GRID (toolbar), toolbar->priv->toolbar, 0, 0, 2, 1);
+			gtk_widget_show_all (GTK_WIDGET (toolbar->priv->toolbar));
+		}
+	}
+}
+
+static void
+impl_constructed (GObject *object)
+{
+	RBSourceToolbar *toolbar;
+	char *toolbar_path;
+	GtkWidget *blank;
+
+	RB_CHAIN_GOBJECT_METHOD (rb_source_toolbar_parent_class, constructed, object);
+
+	toolbar = RB_SOURCE_TOOLBAR (object);
+
+	g_object_get (toolbar->priv->source, "toolbar-path", &toolbar_path, NULL);
+	if (toolbar_path) {
+		toolbar->priv->toolbar = gtk_ui_manager_get_widget (toolbar->priv->ui_manager, toolbar_path);
+		if (toolbar->priv->toolbar == NULL) {
+			g_signal_connect (toolbar->priv->ui_manager, "add-widget", G_CALLBACK (toolbar_add_widget_cb), toolbar);
+		} else {
+			g_object_ref (toolbar->priv->toolbar);
+			prepare_toolbar (toolbar->priv->toolbar);
+		}
+	} else {
+		blank = gtk_toolbar_new ();
+		prepare_toolbar (blank);
+		gtk_grid_attach (GTK_GRID (toolbar), blank, 0, 0, 2 ,1);
+	}
+	g_free (toolbar_path);
+
+	/* search entry gets created later if required */
+
+	g_signal_connect (toolbar->priv->source, "notify::selected", G_CALLBACK (source_selected_cb), toolbar);
+}
+
+static void
+impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	RBSourceToolbar *toolbar = RB_SOURCE_TOOLBAR (object);
+
+	switch (prop_id) {
+	case PROP_SOURCE:
+		g_value_set_object (value, toolbar->priv->source);
+		break;
+	case PROP_UI_MANAGER:
+		g_value_set_object (value, toolbar->priv->ui_manager);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+	RBSourceToolbar *toolbar = RB_SOURCE_TOOLBAR (object);
+
+	switch (prop_id) {
+	case PROP_SOURCE:
+		toolbar->priv->source = g_value_get_object (value);	/* don't take a ref */
+		break;
+	case PROP_UI_MANAGER:
+		toolbar->priv->ui_manager = g_value_dup_object (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+rb_source_toolbar_init (RBSourceToolbar *toolbar)
+{
+	toolbar->priv = G_TYPE_INSTANCE_GET_PRIVATE (toolbar, RB_TYPE_SOURCE_TOOLBAR, RBSourceToolbarPrivate);
+
+	toolbar->priv->search_value = -1;
+}
+
+static void
+rb_source_toolbar_class_init (RBSourceToolbarClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->constructed = impl_constructed;
+	object_class->dispose = impl_dispose;
+	object_class->finalize = impl_finalize;
+	object_class->set_property = impl_set_property;
+	object_class->get_property = impl_get_property;
+
+	/**
+	 * RBSourceToolbar:source:
+	 *
+	 * The #RBSource the toolbar is associated with
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_SOURCE,
+					 g_param_spec_object ("source",
+							      "source",
+							      "RBSource instance",
+							      RB_TYPE_SOURCE,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	/**
+	 * RBSourceToolbar:ui-manager:
+	 *
+	 * The #GtkUIManager instance
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_UI_MANAGER,
+					 g_param_spec_object ("ui-manager",
+							      "ui manager",
+							      "GtkUIManager instance",
+							      GTK_TYPE_UI_MANAGER,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	g_type_class_add_private (klass, sizeof (RBSourceToolbarPrivate));
+}
+
+/**
+ * rb_source_toolbar_new:
+ * @source: a #RBSource
+ * @ui_manager: the #GtkUIManager
+ *
+ * Creates a new source toolbar for @source.  The toolbar does not
+ * initially include a search entry.  Call #rb_source_toolbar_add_search_entry
+ * to add one.  The toolbar content comes from the @RBSource:toolbar-path property.
+ *
+ * Return value: the #RBSourceToolbar
+ */
+RBSourceToolbar *
+rb_source_toolbar_new (RBSource *source, GtkUIManager *ui_manager)
+{
+	GObject *object;
+	object = g_object_new (RB_TYPE_SOURCE_TOOLBAR,
+			       "source", source,
+			       "ui-manager", ui_manager,
+			       "column-spacing", 6,
+			       "column-homogeneous", TRUE,
+			       "row-spacing", 6,
+			       "row-homogeneous", TRUE,
+			       NULL);
+	return RB_SOURCE_TOOLBAR (object);
+}
+
+static void
+setup_search_popup (RBSourceToolbar *toolbar, GtkWidget *popup)
+{
+	GList *items;
+	GSList *l;
+	int active_value;
+
+	toolbar->priv->search_popup = g_object_ref (popup);
+
+	items = gtk_container_get_children (GTK_CONTAINER (toolbar->priv->search_popup));
+	toolbar->priv->search_group = GTK_RADIO_ACTION (gtk_activatable_get_related_action (GTK_ACTIVATABLE (items->data)));
+	g_list_free (items);
+
+	active_value = gtk_radio_action_get_current_value (toolbar->priv->search_group);
+	for (l = gtk_radio_action_get_group (toolbar->priv->search_group); l != NULL; l = l->next) {
+		int value;
+		g_object_get (G_OBJECT (l->data), "value", &value, NULL);
+		if (value == active_value) {
+			rb_search_entry_set_placeholder (toolbar->priv->search_entry,
+							 gtk_action_get_label (GTK_ACTION (l->data)));
+		}
+	}
+
+	g_signal_connect (toolbar->priv->search_entry, "show-popup", G_CALLBACK (show_popup_cb), toolbar);
+}
+
+static void
+popup_add_widget_cb (GtkUIManager *ui_manager, GtkWidget *widget, RBSourceToolbar *toolbar)
+{
+	GtkWidget *popup;
+	popup = gtk_ui_manager_get_widget (toolbar->priv->ui_manager, toolbar->priv->popup_path);
+
+	if (popup) {
+		setup_search_popup (toolbar, popup);
+		g_signal_handlers_disconnect_by_func (ui_manager, G_CALLBACK (popup_add_widget_cb), toolbar);
+	}
+}
+
+
+/**
+ * rb_source_toolbar_add_search_entry:
+ * @toolbar: a #RBSourceToolbar
+ * @popup_path: the UI path for the search popup (or NULL)
+ * @placeholder: the placeholder text for the search entry (or NULL)
+ *
+ * Adds a search entry to the toolbar.  If a popup path is specified,
+ * clicking on the primary icon will show a menu allowing the user to
+ * select a search type, and the placeholder text for the entry will
+ * be the selected search description.  Otherwise, the specified placeholder
+ * text will be displayed.
+ */
+void
+rb_source_toolbar_add_search_entry (RBSourceToolbar *toolbar, const char *popup_path, const char *placeholder)
+{
+	g_assert (toolbar->priv->search_entry == NULL);
+
+	toolbar->priv->search_entry = rb_search_entry_new (popup_path != NULL);
+	gtk_widget_set_margin_right (GTK_WIDGET (toolbar->priv->search_entry), 6);
+	gtk_grid_attach (GTK_GRID (toolbar), GTK_WIDGET (toolbar->priv->search_entry), 2, 0, 1, 1);
+
+	if (placeholder) {
+		rb_search_entry_set_placeholder (toolbar->priv->search_entry, placeholder);
+	}
+
+	g_signal_connect (toolbar->priv->search_entry, "search", G_CALLBACK (search_cb), toolbar);
+	/* activate? */
+
+	if (popup_path != NULL) {
+		GtkWidget *popup;
+		toolbar->priv->popup_path = g_strdup (popup_path);
+
+		popup = gtk_ui_manager_get_widget (toolbar->priv->ui_manager, popup_path);
+		if (popup != NULL) {
+			setup_search_popup (toolbar, popup);
+		} else {
+			g_signal_connect (toolbar->priv->ui_manager, "add-widget", G_CALLBACK (popup_add_widget_cb), toolbar);
+		}
+	}
+}
+
+/**
+ * rb_source_toolbar_clear_search_entry:
+ * @toolbar: a #RBSourceToolbar
+ *
+ * Clears the search entry text.  Call this from RBSource:impl_reset_filters.
+ */
+void
+rb_source_toolbar_clear_search_entry (RBSourceToolbar *toolbar)
+{
+	g_assert (toolbar->priv->search_entry != NULL);
+	rb_search_entry_clear (toolbar->priv->search_entry);
+}
diff --git a/widgets/rb-source-toolbar.h b/widgets/rb-source-toolbar.h
new file mode 100644
index 0000000..2e35c78
--- /dev/null
+++ b/widgets/rb-source-toolbar.h
@@ -0,0 +1,73 @@
+/*
+ *  Copyright (C) 2011 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_TOOLBAR_H
+#define RB_SOURCE_TOOLBAR_H
+
+#include <gtk/gtk.h>
+
+#include <sources/rb-source.h>
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_SOURCE_TOOLBAR         (rb_source_toolbar_get_type ())
+#define RB_SOURCE_TOOLBAR(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_SOURCE_TOOLBAR, RBSourceToolbar))
+#define RB_SOURCE_TOOLBAR_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_SOURCE_TOOLBAR, RBSourceToolbarClass))
+#define RB_IS_SOURCE_TOOLBAR(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_SOURCE_TOOLBAR))
+#define RB_IS_SOURCE_TOOLBAR_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_SOURCE_TOOLBAR))
+#define RB_SOURCE_TOOLBAR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_SOURCE_TOOLBAR, RBSourceToolbarClass))
+
+typedef struct _RBSourceToolbar RBSourceToolbar;
+typedef struct _RBSourceToolbarClass RBSourceToolbarClass;
+typedef struct _RBSourceToolbarPrivate RBSourceToolbarPrivate;
+
+struct _RBSourceToolbar
+{
+	GtkGrid parent;
+
+	RBSourceToolbarPrivate *priv;
+};
+
+struct _RBSourceToolbarClass
+{
+	GtkGridClass parent;
+};
+
+GType		rb_source_toolbar_get_type 		(void);
+
+RBSourceToolbar *rb_source_toolbar_new 			(RBSource *source,
+							 GtkUIManager *ui_manager);
+
+void		rb_source_toolbar_add_search_entry 	(RBSourceToolbar *toolbar,
+							 const char *popup_path,
+							 const char *placeholder);
+
+void		rb_source_toolbar_clear_search_entry	(RBSourceToolbar *toolbar);
+
+G_END_DECLS
+
+#endif  /* RB_SOURCE_TOOLBAR_H */



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