[rhythmbox] Reimplement the source list, introduce display pages



commit 6a781bc3525749bd6c07f50d258c4ce3bb82f9a1
Author: Jonathan Matthew <jonathan d14n org>
Date:   Mon Nov 1 18:35:05 2010 +1000

    Reimplement the source list, introduce display pages
    
    The main impact of this change is to introduce a new abstraction
    above RBSource that allows the inclusion of things that don't
    contain music and cannot be played from.  For want of a meaningful
    name, the new class is called RBDisplayPage.  A few of the basic
    methods from RBSource are now on the RBDisplayPage class, but
    otherwise this doesn't change any existing sources.  The DACP
    pairing source and the last.fm profile source are now direct
    subclasses of RBDisplayPage.
    
    RBDisplayPageModel and RBDisplayPageTree are reimplementations of
    RBSourcelistModel and RBSourceList respectively, with a few minor
    bugs fixed and with the addition that the page group expansion state
    is now stored in gconf.
    
    RBDisplayPageGroup replaces RBSourceGroup, and becomes an actual
    object that exists in the page tree.  It's also a subclass of
    RBDisplayPage, but page groups cannot be selected in the page tree
    and don't have any visible content.  The same predefined groups
    exist.  Rather than being specified as a property on an RBSource
    instance, the group for a page is determined by where it is inserted
    into the page tree.

 bindings/python/rb.defs                            |  471 ++++----
 bindings/python/rb.override                        |   54 +-
 data/rhythmbox.schemas                             |   55 +
 data/ui/rhythmbox-ui.xml                           |    6 +-
 plugins/audiocd/rb-audiocd-plugin.c                |    2 +-
 plugins/audiocd/rb-audiocd-source.c                |   47 +-
 plugins/audioscrobbler/Makefile.am                 |    4 +-
 .../audioscrobbler/audioscrobbler-profile-ui.xml   |    2 +-
 plugins/audioscrobbler/rb-audioscrobbler-plugin.c  |   70 +-
 ...e-source.c => rb-audioscrobbler-profile-page.c} |  957 +++++++-------
 .../rb-audioscrobbler-profile-page.h               |   71 +
 .../rb-audioscrobbler-profile-source.h             |   69 -
 .../rb-audioscrobbler-radio-source.c               |   85 +-
 .../rb-audioscrobbler-radio-source.h               |    4 +-
 .../rb-disc-recorder-plugin.c                      |   57 +-
 plugins/daap/Makefile.am                           |    4 +-
 plugins/daap/rb-daap-plugin.c                      |   15 +-
 plugins/daap/rb-daap-source.c                      |   76 +-
 plugins/daap/rb-dacp-pairing-page.c                |  643 +++++++++
 .../{rb-dacp-source.h => rb-dacp-pairing-page.h}   |   42 +-
 plugins/daap/rb-dacp-source.c                      |  656 ---------
 plugins/fmradio/rb-fm-radio-plugin.c               |    5 +-
 plugins/fmradio/rb-fm-radio-source.c               |   26 +-
 .../rb-generic-player-playlist-source.c            |    9 +-
 plugins/generic-player/rb-generic-player-plugin.c  |   22 +-
 plugins/generic-player/rb-generic-player-source.c  |   50 +-
 plugins/generic-player/rb-nokia770-source.c        |    1 -
 plugins/generic-player/rb-psp-source.c             |    1 -
 plugins/ipod/rb-ipod-plugin.c                      |   28 +-
 plugins/ipod/rb-ipod-source.c                      |   53 +-
 plugins/ipod/rb-ipod-static-playlist-source.c      |   20 +-
 plugins/iradio/rb-iradio-plugin.c                  |    5 +-
 plugins/iradio/rb-iradio-source.c                  |   45 +-
 plugins/jamendo/jamendo/JamendoSource.py           |   10 +-
 plugins/magnatune/magnatune/MagnatuneSource.py     |   12 +-
 plugins/mtpdevice/rb-mtp-plugin.c                  |   22 +-
 plugins/mtpdevice/rb-mtp-source.c                  |   26 +-
 plugins/visualizer/rb-visualizer-plugin.c          |   10 +-
 podcast/rb-podcast-main-source.c                   |   15 +-
 podcast/rb-podcast-source.c                        |   51 +-
 shell/rb-playlist-manager.c                        |  191 ++--
 shell/rb-playlist-manager.h                        |    7 +-
 shell/rb-removable-media-manager.c                 |    6 +-
 shell/rb-shell-clipboard.c                         |   40 +-
 shell/rb-shell-player.c                            |   27 +-
 shell/rb-shell-preferences.c                       |   10 +-
 shell/rb-shell.c                                   |  543 ++++----
 shell/rb-shell.h                                   |   20 +-
 shell/rb-statusbar.c                               |   91 +-
 shell/rb-statusbar.h                               |    6 +-
 sources/Makefile.am                                |   14 +-
 sources/rb-auto-playlist-source.c                  |   35 +-
 sources/rb-browser-source.c                        |   20 +-
 sources/rb-display-page-group.c                    |  351 +++++
 sources/rb-display-page-group.h                    |   95 ++
 sources/rb-display-page-model.c                    |  868 ++++++++++++
 sources/rb-display-page-model.h                    |   93 ++
 sources/rb-display-page-tree.c                     | 1096 +++++++++++++++
 sources/rb-display-page-tree.h                     |   81 ++
 sources/rb-display-page.c                          |  718 ++++++++++
 sources/rb-display-page.h                          |  112 ++
 sources/rb-import-errors-source.c                  |   16 +-
 sources/rb-library-source.c                        |   53 +-
 sources/rb-media-player-source.c                   |    8 +-
 sources/rb-missing-files-source.c                  |   16 +-
 sources/rb-play-queue-source.c                     |   29 +-
 sources/rb-playlist-source.c                       |   34 +-
 sources/rb-removable-media-source.c                |   29 +-
 sources/rb-source-group.c                          |  254 ----
 sources/rb-source-group.h                          |   85 --
 sources/rb-source.c                                |  813 ++----------
 sources/rb-source.h                                |   44 +-
 sources/rb-sourcelist-model.c                      |  648 ---------
 sources/rb-sourcelist-model.h                      |   89 --
 sources/rb-sourcelist.c                            | 1417 --------------------
 sources/rb-sourcelist.h                            |   94 --
 sources/rb-static-playlist-source.c                |   23 +-
 sources/rb-streaming-source.c                      |    6 +-
 widgets/gossip-cell-renderer-expander.c            |   50 +-
 widgets/gossip-cell-renderer-expander.h            |    9 +-
 80 files changed, 6085 insertions(+), 5857 deletions(-)
---
diff --git a/bindings/python/rb.defs b/bindings/python/rb.defs
index d3fb4dc..bba28e1 100644
--- a/bindings/python/rb.defs
+++ b/bindings/python/rb.defs
@@ -22,9 +22,24 @@
   (gtype-id "RB_TYPE_SHELL_PLAYER")
 )
 
-(define-object Source
+(define-object DisplayPage
   (in-module "RB")
   (parent "GtkHBox")
+  (c-name "RBDisplayPage")
+  (gtype-id "RB_TYPE_DISPLAY_PAGE")
+)
+
+(define-object DisplayPageGroup
+  (in-module "RB")
+  (parent "GObject")
+  (c-name "RBDisplayPageGroup")
+  (gtype-id "RB_TYPE_DISPLAY_PAGE_GROUP")
+)
+
+
+(define-object Source
+  (in-module "RB")
+  (parent "RBDisplayPage")
   (c-name "RBSource")
   (gtype-id "RB_TYPE_SOURCE")
 )
@@ -78,20 +93,20 @@
   (gtype-id "RB_TYPE_REMOVABLE_MEDIA_MANAGER")
 )
 
-(define-object SourceList
+(define-object DisplayPageTree
   (in-module "RB")
   (parent "GtkScrolledWindow")
-  (c-name "RBSourceList")
-  (gtype-id "RB_TYPE_SOURCELIST")
+  (c-name "RBDisplayPageTree")
+  (gtype-id "RB_TYPE_DISPLAY_PAGE_TREE")
 )
 
-(define-object SourceListModel
+(define-object DisplayPageModel
   (in-module "RB")
   (parent "GtkTreeModelFilter")
   (interface "RBTreeDragSource")
   (interface "RBTreeDragDest")
-  (c-name "RBSourceListModel")
-  (gtype-id "RB_TYPE_SOURCELIST_MODEL")
+  (c-name "RBDisplayPageModel")
+  (gtype-id "RB_TYPE_DISPLAY_PAGE_MODEL")
 )
 
 (define-object EntryView
@@ -301,17 +316,13 @@
   )
 )
 
-(define-enum SourceListModelColumn
+(define-enum DisplayPageModelColumn
   (in-module "RB")
-  (c-name "RBSourceListModelColumn")
-  (gtype-id "RB_TYPE_SOURCELIST_MODEL_COLUMN")
+  (c-name "RBDisplayPageModelColumn")
+  (gtype-id "RB_TYPE_DISPLAY_PAGE_MODEL_COLUMN")
   (values
     '("column-playing" "RB_SOURCELIST_MODEL_COLUMN_PLAYING")
-    '("column-pixbuf" "RB_SOURCELIST_MODEL_COLUMN_PIXBUF")
-    '("column-name" "RB_SOURCELIST_MODEL_COLUMN_NAME")
-    '("column-source" "RB_SOURCELIST_MODEL_COLUMN_SOURCE")
-    '("column-attributes" "RB_SOURCELIST_MODEL_COLUMN_ATTRIBUTES")
-    '("column-visibility" "RB_SOURCELIST_MODEL_COLUMN_VISIBILITY")
+    '("column-pixbuf" "RB_SOURCELIST_MODEL_COLUMN_PAGE")
   )
 )
 
@@ -324,7 +335,6 @@
     '("right-sidebar" "RB_SHELL_UI_LOCATION_RIGHT_SIDEBAR")
     '("main-top" "RB_SHELL_UI_LOCATION_MAIN_TOP")
     '("main-bottom" "RB_SHELL_UI_LOCATION_MAIN_BOTTOM")
-    '("main-notebook" "RB_SHELL_UI_LOCATION_MAIN_NOTEBOOK")
   )
 )
 
@@ -578,13 +588,13 @@
   (return-type "gboolean")
 )
 
-(define-method append_source
+(define-method append_display_page
   (of-object "RBShell")
-  (c-name "rb_shell_append_source")
+  (c-name "rb_shell_append_display_page")
   (return-type "none")
   (parameters
-    '("RBSource*" "source")
-    '("RBSource*" "parent" (null-ok) (default "NULL"))
+    '("RBDisplayPage*" "page")
+    '("RBDisplayPage*" "parent" (null-ok) (default "NULL"))
   )
 )
 
@@ -597,6 +607,28 @@
   )
 )
 
+(define-method add_widget
+  (of-object "RBShell")
+  (c-name "rb_shell_add_widget")
+  (return-type "none")
+  (parameters
+    '("GtkWidget*" "widget")
+    '("RBShellUILocation" "location")
+    '("gboolean" "expand" (default "FALSE"))
+    '("gboolean" "fill" (default "TRUE"))
+  )
+)
+
+(define-method remove_widget
+  (of-object "RBShell")
+  (c-name "rb_shell_remove_widget")
+  (return-type "none")
+  (parameters
+    '("GtkWidget*" "widget")
+    '("RBShellUILocation" "location")
+  )
+)
+
 ;; From ../../shell/rb-shell-player.h
 
 (define-function rb_shell_player_error_quark
@@ -874,49 +906,162 @@
 
 
 
-;; From ../../sources/rb-source-group.h
+;; From ../../sources/rb-display-page-group.h
 
-(define-enum SourceGroupCategory
+(define-enum DisplayPageGroupCategory
   (in-module "RB")
-  (c-name "RBSourceGroupCategory")
-  (gtype-id "RB_TYPE_SOURCE_GROUP_CATEGORY")
+  (c-name "RBDisplayPageGroupCategory")
+  (gtype-id "RB_TYPE_DISPLAY_PAGE_GROUP_CATEGORY")
   (values
-    '("fixed" "RB_SOURCE_GROUP_CATEGORY_FIXED")
-    '("persistent" "RB_SOURCE_GROUP_CATEGORY_PERSISTENT")
-    '("removable" "RB_SOURCE_GROUP_CATEGORY_REMOVABLE")
-    '("transient" "RB_SOURCE_GROUP_CATEGORY_TRANSIENT")
+    '("fixed" "RB_DISPLAY_PAGE_GROUP_CATEGORY_FIXED")
+    '("persistent" "RB_DISPLAY_PAGE_GROUP_CATEGORY_PERSISTENT")
+    '("removable" "RB_DISPLAY_PAGE_GROUP_CATEGORY_REMOVABLE")
+    '("transient" "RB_DISPLAY_PAGE_GROUP_CATEGORY_TRANSIENT")
   )
 )
 
-(define-boxed SourceGroup
-  (in-module "RB")
-  (c-name "RBSourceGroup")
-  (gtype-id "RB_TYPE_SOURCE_GROUP")
-  (fields
-    '("char*" "name" (access readwrite))
-    '("char*" "display_name" (access readwrite))
-    '("RBSourceGroupCategory" "category" (access readwrite))
+(define-function rb_display_page_group_get_by_id
+  (c-name "rb_display_page_group_get_by_id")
+  (return-type "RBDisplayPageGroup*")
+  (parameters
+    '("const-char*" "id")
   )
 )
 
-(define-function rb_source_group_get_by_name
-  (c-name "rb_source_group_get_by_name")
-  (return-type "RBSourceGroup*")
+;; From ../../sources/rb-display-page.h
+
+(define-function rb_display_page_type
+  (c-name "rb_display_page_get_type")
+  (return-type "GType")
+)
+
+(define-method notify_status_changed
+  (of-object "RBDisplayPage")
+  (c-name "_rb_display_page_notify_status_changed")
+  (return-type "none")
+)
+
+(define-method get_status
+  (of-object "RBDisplayPage")
+  (c-name "rb_display_page_get_status")
+  (return-type "none")
   (parameters
-    '("const-char*" "name")
+    '("char **" "text")
+    '("char **" "progress_text")
+    '("float *" "progress")
   )
 )
 
-(define-function rb_source_group_register
-  (c-name "rb_source_group_register")
-  (return-type "RBSourceGroup*")
+(define-method receive_drag
+  (of-object "RBDisplayPage")
+  (c-name "rb_display_page_receive_drag")
+  (return-type "gboolean")
   (parameters
-    '("const-char*" "name")
-    '("const-char*" "display_name")
-    '("RBSourceGroupCategory" "category")
+    '("GtkSelectionData*" "data")
+  )
+)
+
+(define-method show_popup
+  (of-object "RBDisplayPage")
+  (c-name "rb_display_page_show_popup")
+  (return-type "gboolean")
+)
+
+(define-method delete_thyself
+  (of-object "RBDisplayPage")
+  (c-name "rb_display_page_delete_thyself")
+  (return-type "none")
+)
+
+(define-method selectable
+  (of-object "RBDisplayPage")
+  (c-name "rb_display_page_selectable")
+  (return-type "gboolean")
+)
+
+(define-method selected
+  (of-object "RBDisplayPage")
+  (c-name "rb_display_page_selected")
+  (return-type "none")
+)
+
+(define-method deselected
+  (of-object "RBDisplayPage")
+  (c-name "rb_display_page_deselected")
+  (return-type "none")
+)
+
+(define-method activate
+  (of-object "RBDisplayPage")
+  (c-name "rb_display_page_activate")
+  (return-type "none")
+)
+
+(define-method get_ui_actions
+  (of-object "RBDisplayPage")
+  (c-name "rb_display_page_get_ui_actions")
+  (return-type "GList*")
+)
+
+(define-method show_page_popup
+  (of-object "RBDisplayPage")
+  (c-name "_rb_display_page_show_popup")
+  (return-type "none")
+  (parameters
+    '("const-char*" "ui_path")
+  )
+)
+
+
+(define-virtual get_status
+  (of-object "RBDisplayPage")
+  (return-type "none")
+  (parameters
+    '("char **" "text")
+    '("char **" "progress_text")
+    '("float *" "progress")
+  )
+)
+
+(define-virtual receive_drag
+  (of-object "RBDisplayPage")
+  (return-type "gboolean")
+  (parameters
+    '("GtkSelectionData*" "selection")
   )
 )
 
+(define-virtual show_popup
+  (of-object "RBDisplayPage")
+  (return-type "gboolean")
+)
+
+(define-virtual delete_thyself
+  (of-object "RBDisplayPage")
+  (return-type "none")
+)
+
+(define-virtual selected
+  (of-object "RBDisplayPage")
+  (return-type "none")
+)
+
+(define-virtual deselected
+  (of-object "RBDisplayPage")
+  (return-type "none")
+)
+
+(define-virtual activate
+  (of-object "RBDisplayPage")
+  (return-type "none")
+)
+
+(define-virtual get_ui_actions
+  (of-object "RBDisplayPage")
+  (return-type "GList*")
+)
+
+
 ;; From ../../sources/rb-source.h
 
 (define-function rb_source_get_type
@@ -943,12 +1088,6 @@
   (return-type "none")
 )
 
-(define-method notify_status_changed
-  (of-object "RBSource")
-  (c-name "rb_source_notify_status_changed")
-  (return-type "none")
-)
-
 (define-method update_play_statistics
   (of-object "RBSource")
   (c-name "rb_source_update_play_statistics")
@@ -959,26 +1098,6 @@
   )
 )
 
-(define-method set_pixbuf
-  (of-object "RBSource")
-  (c-name "rb_source_set_pixbuf")
-  (return-type "none")
-  (parameters
-    '("GdkPixbuf*" "pixbuf")
-  )
-)
-
-(define-method get_status
-  (of-object "RBSource")
-  (c-name "rb_source_get_status")
-  (return-type "none")
-  (parameters
-    '("char **" "text")
-    '("char **" "progress_text")
-    '("float *" "progress")
-  )
-)
-
 (define-method can_browse
   (of-object "RBSource")
   (c-name "rb_source_can_browse")
@@ -1137,45 +1256,6 @@
   (return-type "RBSourceEOFType")
 )
 
-(define-method receive_drag
-  (of-object "RBSource")
-  (c-name "rb_source_receive_drag")
-  (return-type "gboolean")
-  (parameters
-    '("GtkSelectionData*" "data")
-  )
-)
-
-(define-method show_popup
-  (of-object "RBSource")
-  (c-name "rb_source_show_popup")
-  (return-type "gboolean")
-)
-
-(define-method delete_thyself
-  (of-object "RBSource")
-  (c-name "rb_source_delete_thyself")
-  (return-type "none")
-)
-
-(define-method activate
-  (of-object "RBSource")
-  (c-name "rb_source_activate")
-  (return-type "none")
-)
-
-(define-method deactivate
-  (of-object "RBSource")
-  (c-name "rb_source_deactivate")
-  (return-type "none")
-)
-
-(define-method get_ui_actions
-  (of-object "RBSource")
-  (c-name "rb_source_get_ui_actions")
-  (return-type "GList*")
-)
-
 (define-method get_search_actions
   (of-object "RBSource")
   (c-name "rb_source_get_search_actions")
@@ -1197,15 +1277,7 @@
   )
 )
 
-(define-method show_source_popup
-  (of-object "RBSource")
-  (c-name "_rb_source_show_popup")
-  (return-type "none")
-  (parameters
-    '("const-char*" "ui_path")
-  )
-)
-
+;; ??
 (define-method want_uri
   (of-object "RBSource")
   (c-name "rb_source_want_uri")
@@ -1229,16 +1301,6 @@
   )
 )
 
-(define-virtual impl_get_status
-  (of-object "RBSource")
-  (return-type "none")
-  (parameters
-    '("char **" "text")
-    '("char **" "progress_text")
-    '("float *" "progress")
-  )
-)
-
 (define-virtual impl_can_browse
   (of-object "RBSource")
   (return-type "gboolean")
@@ -1373,39 +1435,6 @@
   (return-type "RBSourceEOFType")
 )
 
-(define-virtual impl_receive_drag
-  (of-object "RBSource")
-  (return-type "gboolean")
-  (parameters
-    '("GtkSelectionData*" "selection")
-  )
-)
-
-(define-virtual impl_show_popup
-  (of-object "RBSource")
-  (return-type "gboolean")
-)
-
-(define-virtual impl_delete_thyself
-  (of-object "RBSource")
-  (return-type "none")
-)
-
-(define-virtual impl_activate
-  (of-object "RBSource")
-  (return-type "none")
-)
-
-(define-virtual impl_deactivate
-  (of-object "RBSource")
-  (return-type "none")
-)
-
-(define-virtual impl_get_ui_actions
-  (of-object "RBSource")
-  (return-type "GList*")
-)
-
 (define-virtual impl_get_search_actions
   (of-object "RBSource")
   (return-type "GList*")
@@ -1437,97 +1466,98 @@
   )
 )
 
-;; From ../../sources/rb-sourcelist.h
+;; From ../../sources/rb-display-page-tree.h
 
-(define-function rb_sourcelist_get_type
-  (c-name "rb_sourcelist_get_type")
+(define-function rb_display_page_tree_get_type
+  (c-name "rb_display_page_tree_get_type")
   (return-type "GType")
 )
 
-(define-function sourcelist_new
+(define-function display_page_tree_new
   (in-module "rb")
-  (c-name "rb_sourcelist_new")
-  (is-constructor-of "RBSourceList")
+  (c-name "rb_display_page_tree_new")
+  (is-constructor-of "RBDisplayPageTree")
   (return-type "GtkWidget*")
   (properties
     '("shell")
   )
 )
 
-(define-method append
-  (of-object "RBSourceList")
-  (c-name "rb_sourcelist_append")
+(define-method edit_source_name
+  (of-object "RBDisplayPageTree")
+  (c-name "rb_display_page_tree_edit_source_name")
   (return-type "none")
   (parameters
     '("RBSource*" "source")
-    '("RBSource*" "parent")
   )
 )
 
-(define-method add_widget
-  (of-object "RBShell")
-  (c-name "rb_shell_add_widget")
+(define-method select
+  (of-object "RBDisplayPageTree")
+  (c-name "rb_display_page_tree_select")
   (return-type "none")
   (parameters
-    '("GtkWidget*" "widget")
-    '("RBShellUILocation" "location")
-    '("gboolean" "expand" (default "FALSE"))
-    '("gboolean" "fill" (default "TRUE"))
+    '("RBDisplayPage*" "page")
   )
 )
 
-(define-method remove_widget
-  (of-object "RBShell")
-  (c-name "rb_shell_remove_widget")
-  (return-type "none")
-  (parameters
-    '("GtkWidget*" "widget")
-    '("RBShellUILocation" "location")
-  )
+;; From rb-display-page-model.h
+
+(define-function rb_display_page_model_get_type
+  (c-name "rb_display_page_model_get_type")
+  (return-type "GType")
 )
 
-(define-method notebook_set_page
-  (of-object "RBShell")
-  (c-name "rb_shell_notebook_set_page")
+(define-function rb_display_page_model_new
+  (c-name "rb_display_page_model_new")
+  (is-constructor-of "RBDisplayPageModel")
+  (return-type "RBDisplayPageModel*")
+)
+
+(define-method set_playing_source
+  (of-object "RBDisplayPageModel")
+  (c-name "rb_display_page_model_set_playing_source")
   (return-type "none")
   (parameters
-    '("GtkWidget*" "widget" (null-ok))
+    '("RBDisplayPage*" "source")
   )
 )
 
-(define-method set_playing_source
-  (of-object "RBSourceList")
-  (c-name "rb_sourcelist_set_playing_source")
+(define-method add_page
+  (of-object "RBDisplayPageModel")
+  (c-name "rb_display_page_model_add_page")
   (return-type "none")
   (parameters
-    '("RBSource*" "source")
+    '("RBDisplayPage*" "page")
+    '("RBDisplayPage*" "parent")
   )
 )
 
-(define-method edit_source_name
-  (of-object "RBSourceList")
-  (c-name "rb_sourcelist_edit_source_name")
+(define-method remove_page
+  (of-object "RBDisplayPageModel")
+  (c-name "rb_display_page_model_remove_page")
   (return-type "none")
   (parameters
-    '("RBSource*" "source")
+    '("RBDisplayPage*" "page")
   )
 )
 
-(define-method remove
-  (of-object "RBSourceList")
-  (c-name "rb_sourcelist_remove")
-  (return-type "none")
+(define-method find_page
+  (of-object "RBDisplayPageModel")
+  (c-name "rb_display_page_model_find_page")
+  (return-type "gboolean")
   (parameters
-    '("RBSource*" "source")
+    '("RBDisplayPage*" "page")
+    '("GtkTreeIter*" "iter")
   )
 )
 
-(define-method select
-  (of-object "RBSourceList")
-  (c-name "rb_sourcelist_select")
+(define-method set_dnd_targets
+  (of-object "RBDisplayPageModel")
+  (c-name "rb_display_page_model_set_dnd_targets")
   (return-type "none")
   (parameters
-    '("RBSource*" "source")
+    '("GtkTreeView*" "treeview")
   )
 )
 
@@ -2420,29 +2450,6 @@
   )
 )
 
-;; From rb-sourcelist-model.h
-
-(define-function rb_sourcelist_model_get_type
-  (c-name "rb_sourcelist_model_get_type")
-  (return-type "GType")
-)
-
-(define-function rb_sourcelist_model_new
-  (c-name "rb_sourcelist_model_new")
-  (is-constructor-of "RBSourceListModel")
-  (return-type "GtkTreeModel*")
-)
-
-(define-method set_dnd_targets
-  (of-object "RBSourceListModel")
-  (c-name "rb_sourcelist_model_set_dnd_targets")
-  (return-type "none")
-  (parameters
-    '("GtkTreeView*" "treeview")
-  )
-)
-
-
 ;; From ../../backends/rb-player.h
 
 (define-function rb_player_error_quark
diff --git a/bindings/python/rb.override b/bindings/python/rb.override
index 9637e62..5354c99 100644
--- a/bindings/python/rb.override
+++ b/bindings/python/rb.override
@@ -16,6 +16,10 @@ headers
 #include "rb-browser-source.h"
 #include "rb-cut-and-paste-code.h"
 #include "rb-dialog.h"
+#include "rb-display-page.h"
+#include "rb-display-page-group.h"
+#include "rb-display-page-model.h"
+#include "rb-display-page-tree.h"
 #include "rb-entry-view.h"
 #include "rb-file-helpers.h"
 #include "rb-library-browser.h"
@@ -36,8 +40,6 @@ headers
 #include "rb-source.h"
 #include "rb-source-search.h"
 #include "rb-source-search-basic.h"
-#include "rb-sourcelist.h"
-#include "rb-sourcelist-model.h"
 #include "rb-static-playlist-source.h"
 #include "rb-streaming-source.h"
 #include "rb-track-transfer-batch.h"
@@ -227,14 +229,14 @@ _wrap_rb_source_get_search_actions(PyGObject *self)
     return py_list;
 }
 %%
-override rb_source_get_ui_actions noargs
+override rb_display_page_get_ui_actions noargs
 static PyObject *
-_wrap_rb_source_get_ui_actions(PyGObject *self)
+_wrap_rb_display_page_get_ui_actions(PyGObject *self)
 {
     GList *list;
     PyObject *py_list;
 
-    list = rb_source_get_ui_actions (RB_SOURCE(self->obj));
+    list = rb_display_page_get_ui_actions (RB_DISPLAY_PAGE(self->obj));
     py_list = _helper_wrap_string_glist (list);
     rb_list_deep_free (list);
 
@@ -641,9 +643,9 @@ _wrap_RBSource__proxy_do_impl_get_property_views (RBSource *self)
 	return ret;
 }
 %%
-override RBSource__do_impl_get_ui_actions
+override RBDisplayPage__do_get_ui_actions
 static PyObject *
-_wrap_RBSource__do_impl_get_ui_actions(PyObject *cls, PyObject *args, PyObject *kwargs)
+_wrap_RBDisplayPage__do_get_ui_actions(PyObject *cls, PyObject *args, PyObject *kwargs)
 {
 	gpointer klass;
 	static char *kwlist[] = { "self", NULL };
@@ -651,13 +653,13 @@ _wrap_RBSource__do_impl_get_ui_actions(PyObject *cls, PyObject *args, PyObject *
 	GList *ret;
 	PyObject *py_ret;
 
-	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!:RBSource.impl_get_ui_actions", kwlist, &PyRBSource_Type, &self))
+	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!:RBDisplayPage.get_ui_actions", kwlist, &PyRBDisplayPage_Type, &self))
 		return NULL;
 	klass = g_type_class_ref(pyg_type_from_object(cls));
-	if (RB_SOURCE_CLASS(klass)->impl_get_ui_actions)
-		ret = RB_SOURCE_CLASS(klass)->impl_get_ui_actions(RB_SOURCE(self->obj));
+	if (RB_DISPLAY_PAGE_CLASS(klass)->get_ui_actions)
+		ret = RB_DISPLAY_PAGE_CLASS(klass)->get_ui_actions(RB_DISPLAY_PAGE(self->obj));
 	else {
-		PyErr_SetString(PyExc_NotImplementedError, "virtual method RBSource.impl_get_ui_actions not implemented");
+		PyErr_SetString(PyExc_NotImplementedError, "virtual method RBDisplayPage.get_ui_actions not implemented");
 		g_type_class_unref(klass);
 		return NULL;
 	}
@@ -667,9 +669,9 @@ _wrap_RBSource__do_impl_get_ui_actions(PyObject *cls, PyObject *args, PyObject *
 	return py_ret;
 }
 %%
-override RBSource__proxy_do_impl_get_ui_actions
+override RBDisplayPage__proxy_do_get_ui_actions
 static GList *
-_wrap_RBSource__proxy_do_impl_get_ui_actions (RBSource *self)
+_wrap_RBDisplayPage__proxy_do_get_ui_actions (RBDisplayPage *self)
 {
 	PyGILState_STATE __py_state;
 	PyObject *py_self;
@@ -686,7 +688,7 @@ _wrap_RBSource__proxy_do_impl_get_ui_actions (RBSource *self)
 		return NULL;
 	}
 
-	py_method = PyObject_GetAttrString(py_self, "do_impl_get_ui_actions");
+	py_method = PyObject_GetAttrString(py_self, "do_get_ui_actions");
 	if (!py_method) {
 		if (PyErr_Occurred())
 			PyErr_Print();
@@ -985,16 +987,16 @@ _wrap_rb_library_browser_set_selection(PyGObject *self, PyObject *args, PyObject
 }
 
 %%
-override rb_source_get_status noargs
+override rb_display_page_get_status noargs
 static PyObject *
-_wrap_rb_source_get_status(PyGObject *self)
+_wrap_rb_display_page_get_status(PyGObject *self)
 {
     char *status_text = NULL;
     char *progress_text = NULL;
     float progress = 0.0f;
     PyObject *tuple;
 
-    rb_source_get_status (RB_SOURCE (self->obj), &status_text, &progress_text, &progress);
+    rb_display_page_get_status (RB_DISPLAY_PAGE (self->obj), &status_text, &progress_text, &progress);
     tuple = Py_BuildValue ("ssf", status_text, progress_text, progress);
     g_free (status_text);
     g_free (progress_text);
@@ -1002,9 +1004,9 @@ _wrap_rb_source_get_status(PyGObject *self)
 }
 
 %%
-override RBSource__proxy_do_impl_get_status
+override RBDisplayPage__proxy_do_get_status
 static void
-_wrap_RBSource__proxy_do_impl_get_status(RBSource *self, char **status_text, char **progress_text, float *progress)
+_wrap_RBDisplayPage__proxy_do_get_status(RBDisplayPage *self, char **status_text, char **progress_text, float *progress)
 {
     PyGILState_STATE __py_state;
     PyObject *py_self;
@@ -1021,7 +1023,7 @@ _wrap_RBSource__proxy_do_impl_get_status(RBSource *self, char **status_text, cha
         return;
     }
 
-    py_method = PyObject_GetAttrString(py_self, "do_impl_get_status");
+    py_method = PyObject_GetAttrString(py_self, "do_get_status");
     if (!py_method) {
         if (PyErr_Occurred())
             PyErr_Print();
@@ -1066,9 +1068,9 @@ _wrap_RBSource__proxy_do_impl_get_status(RBSource *self, char **status_text, cha
 }
 
 %%
-override RBSource__do_impl_get_status
+override RBDisplayPage__do_get_status
 static PyObject *
-_wrap_RBSource__do_impl_get_status(PyObject *cls, PyObject *args, PyObject *kwargs)
+_wrap_RBDisplayPage__do_get_status(PyObject *cls, PyObject *args, PyObject *kwargs)
 {
     gpointer klass;
     static char *kwlist[] = { "self", NULL };
@@ -1078,14 +1080,14 @@ _wrap_RBSource__do_impl_get_status(PyObject *cls, PyObject *args, PyObject *kwar
     char *progress_text = NULL;
     float progress = 0.0f;
 
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!:RBSource.impl_get_status", kwlist, &PyRBSource_Type, &self))
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!:RBDisplayPage.get_status", kwlist, &PyRBDisplayPage_Type, &self))
 	return NULL;
 
     klass = g_type_class_ref(pyg_type_from_object(cls));
-    if (RB_SOURCE_CLASS(klass)->impl_get_status) {
-	RB_SOURCE_CLASS(klass)->impl_get_status(RB_SOURCE(self->obj), &status_text, &progress_text, &progress);
+    if (RB_DISPLAY_PAGE_CLASS(klass)->get_status) {
+	RB_DISPLAY_PAGE_CLASS(klass)->get_status(RB_DISPLAY_PAGE(self->obj), &status_text, &progress_text, &progress);
     } else {
-	PyErr_SetString(PyExc_NotImplementedError, "virtual method RBSource.impl_get_status not implemented");
+	PyErr_SetString(PyExc_NotImplementedError, "virtual method RBDisplayPage.get_status not implemented");
 	g_type_class_unref(klass);
 	return NULL;
     }
diff --git a/data/rhythmbox.schemas b/data/rhythmbox.schemas
index f66f864..3805c6e 100644
--- a/data/rhythmbox.schemas
+++ b/data/rhythmbox.schemas
@@ -1704,5 +1704,60 @@
 	<long>True if the Zeitgeist plugin is hidden.</long>
 	</locale>
       </schema>
+      <schema>
+        <key>/schemas/apps/rhythmbox/ui/page-groups/devices</key>
+        <applyto>/apps/rhythmbox/ui/page-groups/devices</applyto>
+        <owner>rhythmbox</owner>
+        <type>bool</type>
+        <default>FALSE</default>
+        <locale name="C">
+        <short>True if the Devices page group is collapsed.</short>
+        <long>True if the Devices page group is collapsed.</long>
+	</locale>
+      </schema>
+      <schema>
+        <key>/schemas/apps/rhythmbox/ui/page-groups/library</key>
+        <applyto>/apps/rhythmbox/ui/page-groups/library</applyto>
+        <owner>rhythmbox</owner>
+        <type>bool</type>
+        <default>FALSE</default>
+        <locale name="C">
+        <short>True if the Library page group is collapsed.</short>
+        <long>True if the Library page group is collapsed.</long>
+	</locale>
+      </schema>
+      <schema>
+        <key>/schemas/apps/rhythmbox/ui/page-groups/playlists</key>
+        <applyto>/apps/rhythmbox/ui/page-groups/playlists</applyto>
+        <owner>rhythmbox</owner>
+        <type>bool</type>
+        <default>FALSE</default>
+        <locale name="C">
+        <short>True if the Playlists page group is collapsed.</short>
+        <long>True if the Playlists page group is collapsed.</long>
+	</locale>
+      </schema>
+      <schema>
+        <key>/schemas/apps/rhythmbox/ui/page-groups/shared</key>
+        <applyto>/apps/rhythmbox/ui/page-groups/shared</applyto>
+        <owner>rhythmbox</owner>
+        <type>bool</type>
+        <default>FALSE</default>
+        <locale name="C">
+        <short>True if the Shared page group is collapsed.</short>
+        <long>True if the Shared page group is collapsed.</long>
+	</locale>
+      </schema>
+      <schema>
+        <key>/schemas/apps/rhythmbox/ui/page-groups/stores</key>
+        <applyto>/apps/rhythmbox/ui/page-groups/stores</applyto>
+        <owner>rhythmbox</owner>
+        <type>bool</type>
+        <default>FALSE</default>
+        <locale name="C">
+        <short>True if the Stores page group is collapsed.</short>
+        <long>True if the Stores page group is collapsed.</long>
+	</locale>
+      </schema>
     </schemalist>
 </gconfschemafile>
diff --git a/data/ui/rhythmbox-ui.xml b/data/ui/rhythmbox-ui.xml
index 977f702..a358144 100644
--- a/data/ui/rhythmbox-ui.xml
+++ b/data/ui/rhythmbox-ui.xml
@@ -187,9 +187,9 @@
     <placeholder name="PluginPlaceholder" />
   </popup>
 
-  <popup name="SourceListPopup">
-    <menuitem name="SourceListPopupNewPlaylist" action="MusicPlaylistNewPlaylist"/>
-    <menuitem name="SourceListPopupNewAutomaticPlaylist" action="MusicPlaylistNewAutomaticPlaylist"/>
+  <popup name="DisplayPageTreePopup">
+    <menuitem name="DisplayPageTreePopupNewPlaylist" action="MusicPlaylistNewPlaylist"/>
+    <menuitem name="DisplayPageTreePopupNewAutomaticPlaylist" action="MusicPlaylistNewAutomaticPlaylist"/>
     <separator/>
     <placeholder name="PluginPlaceholder" />
   </popup>
diff --git a/plugins/audiocd/rb-audiocd-plugin.c b/plugins/audiocd/rb-audiocd-plugin.c
index d954c58..1839faa 100644
--- a/plugins/audiocd/rb-audiocd-plugin.c
+++ b/plugins/audiocd/rb-audiocd-plugin.c
@@ -369,7 +369,7 @@ _delete_cb (GVolume         *volume,
 	 * while iterating it.
 	 */
 	g_signal_handlers_block_by_func (source, rb_audiocd_plugin_source_deleted, plugin);
-	rb_source_delete_thyself (source);
+	rb_display_page_delete_thyself (RB_DISPLAY_PAGE (source));
 }
 
 static void
diff --git a/plugins/audiocd/rb-audiocd-source.c b/plugins/audiocd/rb-audiocd-source.c
index 066804e..f425c8d 100644
--- a/plugins/audiocd/rb-audiocd-source.c
+++ b/plugins/audiocd/rb-audiocd-source.c
@@ -66,13 +66,14 @@ static void rb_audiocd_source_constructed (GObject *object);
 static void impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
 static void impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
 
-static gboolean impl_show_popup (RBSource *source);
+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);
 static gboolean impl_uri_is_source (RBSource *source, const char *uri);
 
-static void impl_delete_thyself (RBSource *source);
-static GList* impl_get_ui_actions (RBSource *source);
-
 static void impl_pack_paned (RBBrowserSource *source, GtkWidget *paned);
 
 static gpointer rb_audiocd_load_songs (RBAudioCdSource *source);
@@ -174,6 +175,7 @@ static void
 rb_audiocd_source_class_init (RBAudioCdSourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 	RBBrowserSourceClass *browser_source_class = RB_BROWSER_SOURCE_CLASS (klass);
 
@@ -183,15 +185,16 @@ rb_audiocd_source_class_init (RBAudioCdSourceClass *klass)
 	object_class->set_property = impl_set_property;
 	object_class->get_property = impl_get_property;
 
+	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;
 
-	source_class->impl_show_popup = impl_show_popup;
-	source_class->impl_delete_thyself = impl_delete_thyself;
-	source_class->impl_get_ui_actions = impl_get_ui_actions;
 	source_class->impl_uri_is_source = impl_uri_is_source;
 	source_class->impl_try_playlist = (RBSourceFeatureFunc) rb_true_function;	/* shouldn't need this. */
 	source_class->impl_want_uri = impl_want_uri;
@@ -290,13 +293,13 @@ rb_audiocd_source_constructed (GObject *object)
 	g_object_set (G_OBJECT (source), "name", "Unknown Audio", NULL);
 
 	g_object_get (source, "shell", &shell, NULL);
-	priv->action_group = _rb_source_register_action_group (RB_SOURCE (source),
-							       "AudioCdActions",
-							       NULL, 0, NULL);
-	_rb_action_group_add_source_actions (priv->action_group,
-					     G_OBJECT (shell),
-					     rb_audiocd_source_actions,
-					     G_N_ELEMENTS (rb_audiocd_source_actions));
+	priv->action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
+								     "AudioCdActions",
+								     NULL, 0, NULL);
+	_rb_action_group_add_display_page_actions (priv->action_group,
+						   G_OBJECT (shell),
+						   rb_audiocd_source_actions,
+						   G_N_ELEMENTS (rb_audiocd_source_actions));
 	g_object_unref (shell);
 
 	action = gtk_action_group_get_action (priv->action_group,
@@ -452,7 +455,6 @@ rb_audiocd_source_new (RBPlugin *plugin,
 			       "volume", volume,
 			       "shell", shell,
 			       "sorting-key", NULL,
-			       "source-group", RB_SOURCE_GROUP_DEVICES,
 			       "plugin", plugin,
 			       NULL);
 
@@ -1054,19 +1056,18 @@ error_out:
 }
 
 static void
-impl_delete_thyself (RBSource *source)
+impl_delete_thyself (RBDisplayPage *page)
 {
 	RhythmDB *db;
 	RhythmDBEntryType *entry_type;
 
 	rb_debug ("audio cd ejected");
 
-	/* cancel the loading of metadata */
-	rb_audiocd_load_metadata_cancel (RB_AUDIOCD_SOURCE (source));
+	rb_audiocd_load_metadata_cancel (RB_AUDIOCD_SOURCE (page));
 
-	db = get_db_for_source (RB_AUDIOCD_SOURCE (source));
+	db = get_db_for_source (RB_AUDIOCD_SOURCE (page));
 
-	g_object_get (source, "entry-type", &entry_type, NULL);
+	g_object_get (page, "entry-type", &entry_type, NULL);
 	rhythmdb_entry_delete_by_type (db, entry_type);
 	g_object_unref (entry_type);
 
@@ -1099,14 +1100,14 @@ rb_audiocd_is_mount_audiocd (GMount *mount)
 }
 
 static gboolean
-impl_show_popup (RBSource *source)
+impl_show_popup (RBDisplayPage *page)
 {
-	_rb_source_show_popup (RB_SOURCE (source), "/AudioCdSourcePopup");
+	_rb_display_page_show_popup (page, "/AudioCdSourcePopup");
 	return TRUE;
 }
 
 static GList *
-impl_get_ui_actions (RBSource *source)
+impl_get_ui_actions (RBDisplayPage *page)
 {
 	GList *actions = NULL;
 
diff --git a/plugins/audioscrobbler/Makefile.am b/plugins/audioscrobbler/Makefile.am
index 1e97ab5..9c54b38 100644
--- a/plugins/audioscrobbler/Makefile.am
+++ b/plugins/audioscrobbler/Makefile.am
@@ -7,8 +7,8 @@ libaudioscrobbler_la_SOURCES = \
 	rb-audioscrobbler-plugin.c			\
 	rb-audioscrobbler-entry.h			\
 	rb-audioscrobbler-entry.c			\
-	rb-audioscrobbler-profile-source.h		\
-	rb-audioscrobbler-profile-source.c		\
+	rb-audioscrobbler-profile-page.h		\
+	rb-audioscrobbler-profile-page.c		\
 	rb-audioscrobbler-account.h			\
 	rb-audioscrobbler-account.c			\
 	rb-audioscrobbler-service.h			\
diff --git a/plugins/audioscrobbler/audioscrobbler-profile-ui.xml b/plugins/audioscrobbler/audioscrobbler-profile-ui.xml
index 88db524..fdda9a1 100644
--- a/plugins/audioscrobbler/audioscrobbler-profile-ui.xml
+++ b/plugins/audioscrobbler/audioscrobbler-profile-ui.xml
@@ -1,5 +1,5 @@
 <ui>
-  <popup name="AudioscrobblerProfileSourcePopup">
+  <popup name="AudioscrobblerProfilePagePopup">
     <menuitem name="Refresh" action="AudioscrobblerProfileRefresh"/>
   </popup>
 </ui>
diff --git a/plugins/audioscrobbler/rb-audioscrobbler-plugin.c b/plugins/audioscrobbler/rb-audioscrobbler-plugin.c
index 74128d5..851f5ce 100644
--- a/plugins/audioscrobbler/rb-audioscrobbler-plugin.c
+++ b/plugins/audioscrobbler/rb-audioscrobbler-plugin.c
@@ -35,13 +35,15 @@
 #include <glib.h>
 #include <glib-object.h>
 
+#include <lib/eel-gconf-extensions.h>
+#include <lib/rb-builder-helpers.h>
+#include <lib/rb-debug.h>
+#include <sources/rb-display-page-group.h>
+#include <shell/rb-plugin.h>
+#include <shell/rb-shell.h>
+
 #include "rb-audioscrobbler-service.h"
-#include "rb-audioscrobbler-profile-source.h"
-#include "rb-plugin.h"
-#include "rb-builder-helpers.h"
-#include "rb-debug.h"
-#include "rb-shell.h"
-#include "eel-gconf-extensions.h"
+#include "rb-audioscrobbler-profile-page.h"
 
 
 #define RB_TYPE_AUDIOSCROBBLER_PLUGIN		(rb_audioscrobbler_plugin_get_type ())
@@ -65,12 +67,12 @@ typedef struct
 	/* Last.fm */
 	GtkWidget *lastfm_enabled_check;
 	guint lastfm_enabled_notification_id;
-	RBSource *lastfm_source;
+	RBDisplayPage *lastfm_page;
 
 	/* Libre.fm */
 	GtkWidget *librefm_enabled_check;
 	guint librefm_enabled_notification_id;
-	RBSource *librefm_source;
+	RBDisplayPage *librefm_page;
 } RBAudioscrobblerPlugin;
 
 typedef struct
@@ -152,9 +154,9 @@ impl_activate (RBPlugin *bplugin,
 	if (eel_gconf_get_boolean (CONF_LASTFM_ENABLED) == TRUE) {
 		RBAudioscrobblerService *lastfm;
 		lastfm = rb_audioscrobbler_service_new_lastfm ();
-		plugin->lastfm_source = rb_audioscrobbler_profile_source_new (plugin->shell,
-		                                                              RB_PLUGIN (plugin),
-		                                                              lastfm);
+		plugin->lastfm_page = rb_audioscrobbler_profile_page_new (plugin->shell,
+									  RB_PLUGIN (plugin),
+									  lastfm);
 		g_object_unref (lastfm);
 	}
 
@@ -167,9 +169,9 @@ impl_activate (RBPlugin *bplugin,
 	if (eel_gconf_get_boolean (CONF_LIBREFM_ENABLED) == TRUE) {
 		RBAudioscrobblerService *librefm;
 		librefm = rb_audioscrobbler_service_new_librefm ();
-		plugin->librefm_source = rb_audioscrobbler_profile_source_new (plugin->shell,
-		                                                               RB_PLUGIN (plugin),
-		                                                               librefm);
+		plugin->librefm_page = rb_audioscrobbler_profile_page_new (plugin->shell,
+		                                                           RB_PLUGIN (plugin),
+		                                                           librefm);
 		g_object_unref (librefm);
 	}
 }
@@ -185,14 +187,14 @@ impl_deactivate	(RBPlugin *bplugin,
 		plugin->config_dialog = NULL;
 	}
 
-	if (plugin->lastfm_source != NULL) {
-		rb_source_delete_thyself (plugin->lastfm_source);
-		plugin->lastfm_source = NULL;
+	if (plugin->lastfm_page != NULL) {
+		rb_display_page_delete_thyself (plugin->lastfm_page);
+		plugin->lastfm_page = NULL;
 	}
 
-	if (plugin->librefm_source != NULL) {
-		rb_source_delete_thyself (plugin->librefm_source);
-		plugin->librefm_source = NULL;
+	if (plugin->librefm_page != NULL) {
+		rb_display_page_delete_thyself (plugin->librefm_page);
+		plugin->librefm_page = NULL;
 	}
 }
 
@@ -264,16 +266,16 @@ lastfm_enabled_changed_cb (GConfClient *client,
 
 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (plugin->lastfm_enabled_check),
 	                              enabled);
-	if (enabled == TRUE && plugin->lastfm_source == NULL) {
+	if (enabled == TRUE && plugin->lastfm_page == NULL) {
 		RBAudioscrobblerService *lastfm;
 		lastfm = rb_audioscrobbler_service_new_lastfm ();
-		plugin->lastfm_source = rb_audioscrobbler_profile_source_new (plugin->shell,
-		                                                              RB_PLUGIN (plugin),
-		                                                              lastfm);
+		plugin->lastfm_page = rb_audioscrobbler_profile_page_new (plugin->shell,
+		                                                          RB_PLUGIN (plugin),
+		                                                          lastfm);
 		g_object_unref (lastfm);
-	} else if (enabled == FALSE && plugin->lastfm_source != NULL) {
-		rb_source_delete_thyself (plugin->lastfm_source);
-		plugin->lastfm_source = NULL;
+	} else if (enabled == FALSE && plugin->lastfm_page != NULL) {
+		rb_display_page_delete_thyself (plugin->lastfm_page);
+		plugin->lastfm_page = NULL;
 	}
 }
 
@@ -297,16 +299,16 @@ librefm_enabled_changed_cb (GConfClient *client,
 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (plugin->librefm_enabled_check),
 	                              enabled);
 
-	if (enabled == TRUE && plugin->librefm_source == NULL) {
+	if (enabled == TRUE && plugin->librefm_page == NULL) {
 		RBAudioscrobblerService *librefm;
 
 		librefm = rb_audioscrobbler_service_new_librefm ();
-		plugin->librefm_source = rb_audioscrobbler_profile_source_new (plugin->shell,
-		                                                               RB_PLUGIN (plugin),
-		                                                               librefm);
+		plugin->librefm_page = rb_audioscrobbler_profile_page_new (plugin->shell,
+		                                                           RB_PLUGIN (plugin),
+		                                                           librefm);
 		g_object_unref (librefm);
-	} else if (enabled == FALSE && plugin->librefm_source != NULL) {
-		rb_source_delete_thyself (plugin->librefm_source);
-		plugin->librefm_source = NULL;
+	} else if (enabled == FALSE && plugin->librefm_page != NULL) {
+		rb_display_page_delete_thyself (plugin->librefm_page);
+		plugin->librefm_page = NULL;
 	}
 }
diff --git a/plugins/audioscrobbler/rb-audioscrobbler-profile-source.c b/plugins/audioscrobbler/rb-audioscrobbler-profile-page.c
similarity index 60%
rename from plugins/audioscrobbler/rb-audioscrobbler-profile-source.c
rename to plugins/audioscrobbler/rb-audioscrobbler-profile-page.c
index 2be3bd2..bf24411 100644
--- a/plugins/audioscrobbler/rb-audioscrobbler-profile-source.c
+++ b/plugins/audioscrobbler/rb-audioscrobbler-profile-page.c
@@ -1,5 +1,5 @@
 /*
- * rb-audioscrobbler-profile-source.c
+ * rb-audioscrobbler-profile-page.c
  *
  * Copyright (C) 2010 Jamie Nicol <jamie thenicols net>
  *
@@ -31,26 +31,27 @@
 #include <json-glib/json-glib.h>
 #include <math.h>
 
-#include "eel-gconf-extensions.h"
-
-#include "rb-audioscrobbler-profile-source.h"
+#include <lib/eel-gconf-extensions.h>
+#include <lib/gseal-gtk-compat.h>
+#include <lib/rb-debug.h>
+#include <lib/rb-builder-helpers.h>
+#include <lib/rb-file-helpers.h>
+#include <lib/rb-preferences.h>
+#include <lib/rb-util.h>
+#include <sources/rb-display-page-tree.h>
+#include <sources/rb-display-page-group.h>
+
+#include "rb-audioscrobbler-profile-page.h"
 #include "rb-audioscrobbler.h"
 #include "rb-audioscrobbler-account.h"
 #include "rb-audioscrobbler-user.h"
 #include "rb-audioscrobbler-radio-source.h"
 #include "rb-audioscrobbler-radio-track-entry-type.h"
-#include "rb-debug.h"
-#include "rb-builder-helpers.h"
-#include "rb-file-helpers.h"
-#include "rb-preferences.h"
-#include "rb-sourcelist.h"
-#include "rb-util.h"
-#include "gseal-gtk-compat.h"
 
 #define CONF_AUDIOSCROBBLER_ENABLE_SCROBBLING CONF_PLUGINS_PREFIX "/audioscrobbler/%s/scrobbling_enabled"
-#define AUDIOSCROBBLER_PROFILE_SOURCE_POPUP_PATH "/AudioscrobblerProfileSourcePopup"
+#define AUDIOSCROBBLER_PROFILE_PAGE_POPUP_PATH "/AudioscrobblerProfilePagePopup"
 
-struct _RBAudioscrobblerProfileSourcePrivate {
+struct _RBAudioscrobblerProfilePagePrivate {
 	RBAudioscrobblerService *service;
 	RBAudioscrobblerAccount *account;
 	RBAudioscrobbler *audioscrobbler;
@@ -59,7 +60,7 @@ struct _RBAudioscrobblerProfileSourcePrivate {
 	RBAudioscrobblerUser *user;
 	guint update_timeout_id;
 
-	/* List of radio stations owned by this source */
+	/* List of radio stations owned by this page */
 	GList *radio_sources;
 
 	guint scrobbling_enabled_notification_id;
@@ -114,108 +115,107 @@ struct _RBAudioscrobblerProfileSourcePrivate {
 	char *download_action_name;
 };
 
-#define RB_AUDIOSCROBBLER_PROFILE_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_AUDIOSCROBBLER_PROFILE_SOURCE, RBAudioscrobblerProfileSourcePrivate))
-
-static void rb_audioscrobbler_profile_source_class_init (RBAudioscrobblerProfileSourceClass *klass);
-static void rb_audioscrobbler_profile_source_init (RBAudioscrobblerProfileSource *source);
-static void rb_audioscrobbler_profile_source_constructed (GObject *object);
-static void rb_audioscrobbler_profile_source_dispose (GObject* object);
-static void rb_audioscrobbler_profile_source_finalize (GObject *object);
-static void rb_audioscrobbler_profile_source_get_property (GObject *object,
-                                                           guint prop_id,
-                                                           GValue *value,
-                                                           GParamSpec *pspec);
-static void rb_audioscrobbler_profile_source_set_property (GObject *object,
-                                                           guint prop_id,
-                                                           const GValue *value,
-                                                           GParamSpec *pspec);
+
+static void rb_audioscrobbler_profile_page_class_init (RBAudioscrobblerProfilePageClass *klass);
+static void rb_audioscrobbler_profile_page_init (RBAudioscrobblerProfilePage *page);
+static void rb_audioscrobbler_profile_page_constructed (GObject *object);
+static void rb_audioscrobbler_profile_page_dispose (GObject* object);
+static void rb_audioscrobbler_profile_page_finalize (GObject *object);
+static void rb_audioscrobbler_profile_page_get_property (GObject *object,
+                                                         guint prop_id,
+                                                         GValue *value,
+                                                         GParamSpec *pspec);
+static void rb_audioscrobbler_profile_page_set_property (GObject *object,
+                                                         guint prop_id,
+                                                         const GValue *value,
+                                                         GParamSpec *pspec);
 
 /* UI initialisation functions */
-static void init_login_ui (RBAudioscrobblerProfileSource *source);
-static void init_profile_ui (RBAudioscrobblerProfileSource *source);
-static void init_actions (RBAudioscrobblerProfileSource *source);
+static void init_login_ui (RBAudioscrobblerProfilePage *page);
+static void init_profile_ui (RBAudioscrobblerProfilePage *page);
+static void init_actions (RBAudioscrobblerProfilePage *page);
 
 /* login related callbacks */
 static void login_bar_response_cb (GtkInfoBar *info_bar,
                                    gint response_id,
-                                   RBAudioscrobblerProfileSource *source);
-void logout_button_clicked_cb (GtkButton *button, RBAudioscrobblerProfileSource *source);
+                                   RBAudioscrobblerProfilePage *page);
+void logout_button_clicked_cb (GtkButton *button, RBAudioscrobblerProfilePage *page);
 static void login_status_change_cb (RBAudioscrobblerAccount *account,
                                     RBAudioscrobblerAccountLoginStatus status,
-                                    RBAudioscrobblerProfileSource *source);
+                                    RBAudioscrobblerProfilePage *page);
 
 /* scrobbling enabled preference */
 void scrobbling_enabled_check_toggled_cb (GtkToggleButton *togglebutton,
-                                          RBAudioscrobblerProfileSource *source);
+                                          RBAudioscrobblerProfilePage *page);
 static void scrobbling_enabled_changed_cb (GConfClient *client,
                                            guint cnxn_id,
                                            GConfEntry *entry,
-                                           RBAudioscrobblerProfileSource *source);
+                                           RBAudioscrobblerProfilePage *page);
 
 /* callbacks from scrobbler object */
 static void scrobbler_authentication_error_cb (RBAudioscrobbler *audioscrobbler,
-                                               RBAudioscrobblerProfileSource *source);
+                                               RBAudioscrobblerProfilePage *page);
 static void scrobbler_statistics_changed_cb (RBAudioscrobbler *audioscrobbler,
                                              const char *status_msg,
                                              guint queue_count,
                                              guint submit_count,
                                              const char *submit_time,
-                                             RBAudioscrobblerProfileSource *source);
+                                             RBAudioscrobblerProfilePage *page);
 
 static void playing_song_changed_cb (RBShellPlayer *player,
                                      RhythmDBEntry *entry,
-                                     RBAudioscrobblerProfileSource *source);
-static void update_service_actions_sensitivity (RBAudioscrobblerProfileSource *source);
+                                     RBAudioscrobblerProfilePage *page);
+static void update_service_actions_sensitivity (RBAudioscrobblerProfilePage *page);
 
 /* GtkAction callbacks */
-static void love_track_action_cb (GtkAction *action, RBAudioscrobblerProfileSource *source);
-static void ban_track_action_cb (GtkAction *action, RBAudioscrobblerProfileSource *source);
-static void download_track_action_cb (GtkAction *action, RBAudioscrobblerProfileSource *source);
-static void download_track_batch_complete_cb (RBTrackTransferBatch *batch, RBAudioscrobblerProfileSource *source);
-static void refresh_profile_action_cb (GtkAction *action, RBAudioscrobblerProfileSource *source);
+static void love_track_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page);
+static void ban_track_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page);
+static void download_track_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page);
+static void download_track_batch_complete_cb (RBTrackTransferBatch *batch, RBAudioscrobblerProfilePage *page);
+static void refresh_profile_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page);
 
 /* radio station creation/deletion */
-void station_creator_button_clicked_cb (GtkButton *button, RBAudioscrobblerProfileSource *source);
-static void load_radio_stations (RBAudioscrobblerProfileSource *source);
-static void save_radio_stations (RBAudioscrobblerProfileSource *source);
-static RBSource *add_radio_station (RBAudioscrobblerProfileSource *source,
+void station_creator_button_clicked_cb (GtkButton *button, RBAudioscrobblerProfilePage *page);
+static void load_radio_stations (RBAudioscrobblerProfilePage *page);
+static void save_radio_stations (RBAudioscrobblerProfilePage *page);
+static RBSource *add_radio_station (RBAudioscrobblerProfilePage *page,
                                     const char *url,
                                     const char *name);
 static void radio_station_name_changed_cb (RBAudioscrobblerRadioSource *radio,
                                            GParamSpec *spec,
-                                           RBAudioscrobblerProfileSource *source);
+                                           RBAudioscrobblerProfilePage *page);
 
 /* periodically attempts tp update the profile data */
-static gboolean update_timeout_cb (RBAudioscrobblerProfileSource *source);
+static gboolean update_timeout_cb (RBAudioscrobblerProfilePage *page);
 
 /* callbacks from user profile data requests */
 static void user_info_updated_cb (RBAudioscrobblerUser *user,
                                   RBAudioscrobblerUserData *info,
-                                  RBAudioscrobblerProfileSource *source);
+                                  RBAudioscrobblerProfilePage *page);
 static void recent_tracks_updated_cb (RBAudioscrobblerUser *user,
                                       GPtrArray *recent_tracks,
-                                      RBAudioscrobblerProfileSource *source);
+                                      RBAudioscrobblerProfilePage *page);
 static void top_tracks_updated_cb (RBAudioscrobblerUser *user,
                                    GPtrArray *top_tracks,
-                                   RBAudioscrobblerProfileSource *source);
+                                   RBAudioscrobblerProfilePage *page);
 static void loved_tracks_updated_cb (RBAudioscrobblerUser *user,
                                      GPtrArray *loved_tracks,
-                                     RBAudioscrobblerProfileSource *source);
+                                     RBAudioscrobblerProfilePage *page);
 static void top_artists_updated_cb (RBAudioscrobblerUser *user,
                                     GPtrArray *top_artists,
-                                    RBAudioscrobblerProfileSource *source);
+                                    RBAudioscrobblerProfilePage *page);
 static void recommended_artists_updated_cb (RBAudioscrobblerUser *user,
                                             GPtrArray *recommended_artists,
-                                            RBAudioscrobblerProfileSource *source);
+                                            RBAudioscrobblerProfilePage *page);
 
 /* UI creation for profile data lists, eg top artists, loved tracks */
-static void set_user_list (RBAudioscrobblerProfileSource *source,
+static void set_user_list (RBAudioscrobblerProfilePage *page,
                            GtkWidget *list_table,
                            GPtrArray *list_data);
-static GtkWidget *create_list_button (RBAudioscrobblerProfileSource *source,
+static GtkWidget *create_list_button (RBAudioscrobblerProfilePage *page,
                                       RBAudioscrobblerUserData *data,
                                       int max_sibling_image_width);
-static GtkWidget *create_popup_menu (RBAudioscrobblerProfileSource *source,
+static GtkWidget *create_popup_menu (RBAudioscrobblerProfilePage *page,
                                      RBAudioscrobblerUserData *data);
 static void list_table_pack_start (GtkTable *list_table, GtkWidget *child);
 void list_table_realize_cb (GtkWidget *table,
@@ -225,20 +225,20 @@ void list_table_size_allocate_cb (GtkWidget *layout,
                                   gpointer user_data);
 
 /* callbacks from data list buttons and related popup menus */
-static void list_item_clicked_cb (GtkButton *button, RBAudioscrobblerProfileSource *source);
+static void list_item_clicked_cb (GtkButton *button, RBAudioscrobblerProfilePage *page);
 static void list_item_view_url_activated_cb (GtkMenuItem *menuitem,
-                                             RBAudioscrobblerProfileSource *source);
+                                             RBAudioscrobblerProfilePage *page);
 static void list_item_listen_similar_artists_activated_cb (GtkMenuItem *menuitem,
-                                                           RBAudioscrobblerProfileSource *source);
+                                                           RBAudioscrobblerProfilePage *page);
 static void list_item_listen_top_fans_activated_cb (GtkMenuItem *menuitem,
-                                                    RBAudioscrobblerProfileSource *source);
+                                                    RBAudioscrobblerProfilePage *page);
 
-/* RBSource implementations */
-static void impl_activate (RBSource *asource);
-static void impl_deactivate (RBSource *asource);
-static GList *impl_get_ui_actions (RBSource *asource);
-static gboolean impl_show_popup (RBSource *asource);
-static void impl_delete_thyself (RBSource *asource);
+/* 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,
@@ -253,12 +253,12 @@ static GtkActionEntry profile_actions [] =
 };
 
 
-G_DEFINE_TYPE (RBAudioscrobblerProfileSource, rb_audioscrobbler_profile_source, RB_TYPE_SOURCE)
+G_DEFINE_TYPE (RBAudioscrobblerProfilePage, rb_audioscrobbler_profile_page, RB_TYPE_DISPLAY_PAGE)
 
-RBSource *
-rb_audioscrobbler_profile_source_new (RBShell *shell, RBPlugin *plugin, RBAudioscrobblerService *service)
+RBDisplayPage *
+rb_audioscrobbler_profile_page_new (RBShell *shell, RBPlugin *plugin, RBAudioscrobblerService *service)
 {
-	RBSource *source;
+	RBDisplayPage *page;
 	RhythmDB *db;
 	char *name;
 	gchar *icon_name;
@@ -269,20 +269,18 @@ rb_audioscrobbler_profile_source_new (RBShell *shell, RBPlugin *plugin, RBAudios
 	g_object_get (shell, "db", &db, NULL);
 	g_object_get (service, "name", &name, NULL);
 
-
 	icon_name = g_strconcat (rb_audioscrobbler_service_get_name (service), "-icon.png", NULL);
 	icon_path = rb_plugin_find_file (plugin, icon_name);
 	gtk_icon_size_lookup (GTK_ICON_SIZE_LARGE_TOOLBAR, &icon_size, NULL);
 	icon_pixbuf = gdk_pixbuf_new_from_file_at_size (icon_path, icon_size, icon_size, NULL);
 
-	source = RB_SOURCE (g_object_new (RB_TYPE_AUDIOSCROBBLER_PROFILE_SOURCE,
-	                                  "shell", shell,
-	                                  "plugin", plugin,
-	                                  "name", name,
-	                                  "source-group", RB_SOURCE_GROUP_LIBRARY,
-	                                  "icon", icon_pixbuf,
-	                                  "service", service,
-	                                  NULL));
+	page = RB_DISPLAY_PAGE (g_object_new (RB_TYPE_AUDIOSCROBBLER_PROFILE_PAGE,
+					      "shell", shell,
+					      "plugin", plugin,
+					      "name", name,
+					      "pixbuf", icon_pixbuf,
+					      "service", service,
+					      NULL));
 
 	g_object_unref (db);
 	g_free (name);
@@ -290,122 +288,122 @@ rb_audioscrobbler_profile_source_new (RBShell *shell, RBPlugin *plugin, RBAudios
 	g_free (icon_path);
 	g_object_unref (icon_pixbuf);
 
-	return source;
+	return page;
 }
 
 static void
-rb_audioscrobbler_profile_source_class_init (RBAudioscrobblerProfileSourceClass *klass)
+rb_audioscrobbler_profile_page_class_init (RBAudioscrobblerProfilePageClass *klass)
 {
 	GObjectClass *object_class;
-	RBSourceClass *source_class;
+	RBDisplayPageClass *page_class;
 
 	object_class = G_OBJECT_CLASS (klass);
-	object_class->constructed = rb_audioscrobbler_profile_source_constructed;
-	object_class->dispose = rb_audioscrobbler_profile_source_dispose;
-	object_class->finalize = rb_audioscrobbler_profile_source_finalize;
-	object_class->get_property = rb_audioscrobbler_profile_source_get_property;
-	object_class->set_property = rb_audioscrobbler_profile_source_set_property;
-
-	source_class = RB_SOURCE_CLASS (klass);
-	source_class->impl_activate = impl_activate;
-	source_class->impl_deactivate = impl_deactivate;
-	source_class->impl_get_ui_actions = impl_get_ui_actions;
-	source_class->impl_show_popup = impl_show_popup;
-	source_class->impl_delete_thyself = impl_delete_thyself;
+	object_class->constructed = rb_audioscrobbler_profile_page_constructed;
+	object_class->dispose = rb_audioscrobbler_profile_page_dispose;
+	object_class->finalize = rb_audioscrobbler_profile_page_finalize;
+	object_class->get_property = rb_audioscrobbler_profile_page_get_property;
+	object_class->set_property = rb_audioscrobbler_profile_page_set_property;
+
+	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;
 
 	g_object_class_install_property (object_class,
 	                                 PROP_SERVICE,
 	                                 g_param_spec_object ("service",
 	                                                      "Service",
-	                                                      "Audioscrobbler service that this is a source for",
+	                                                      "Audioscrobbler service for this page",
 	                                                      RB_TYPE_AUDIOSCROBBLER_SERVICE,
                                                               G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
 
-	g_type_class_add_private (klass, sizeof (RBAudioscrobblerProfileSourcePrivate));
+	g_type_class_add_private (klass, sizeof (RBAudioscrobblerProfilePagePrivate));
 }
 
 static void
-rb_audioscrobbler_profile_source_init (RBAudioscrobblerProfileSource *source)
+rb_audioscrobbler_profile_page_init (RBAudioscrobblerProfilePage *page)
 {
-	source->priv = RB_AUDIOSCROBBLER_PROFILE_SOURCE_GET_PRIVATE (source);
+	page->priv = G_TYPE_INSTANCE_GET_PRIVATE (page, RB_TYPE_AUDIOSCROBBLER_PROFILE_PAGE, RBAudioscrobblerProfilePagePrivate);
 
-	source->priv->button_to_popup_menu_map = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
-	source->priv->popup_menu_to_data_map = g_hash_table_new (g_direct_hash, g_direct_equal);
+	page->priv->button_to_popup_menu_map = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
+	page->priv->popup_menu_to_data_map = g_hash_table_new (g_direct_hash, g_direct_equal);
 }
 
 static void
-rb_audioscrobbler_profile_source_constructed (GObject *object)
+rb_audioscrobbler_profile_page_constructed (GObject *object)
 {
-	RBAudioscrobblerProfileSource *source;
+	RBAudioscrobblerProfilePage *page;
 	RBShell *shell;
 	char *scrobbling_enabled_conf_key;
 
-	RB_CHAIN_GOBJECT_METHOD (rb_audioscrobbler_profile_source_parent_class, constructed, object);
+	RB_CHAIN_GOBJECT_METHOD (rb_audioscrobbler_profile_page_parent_class, constructed, object);
 
-	source = RB_AUDIOSCROBBLER_PROFILE_SOURCE (object);
-	g_object_get (source, "shell", &shell, NULL);
+	page = RB_AUDIOSCROBBLER_PROFILE_PAGE (object);
+	g_object_get (page, "shell", &shell, NULL);
 
-	rb_shell_append_source (shell, RB_SOURCE (source), NULL);
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (page), RB_DISPLAY_PAGE_GROUP_LIBRARY);
 
 	g_signal_connect_object (rb_shell_get_player (shell),
 				 "playing-song-changed",
 				 G_CALLBACK (playing_song_changed_cb),
-				 source, 0);
+				 page, 0);
 
 	/* create the UI */
-	source->priv->main_vbox = gtk_vbox_new (FALSE, 4);
-	gtk_box_pack_start (GTK_BOX (source), source->priv->main_vbox, TRUE, TRUE, 0);
-	gtk_widget_show (source->priv->main_vbox);
+	page->priv->main_vbox = gtk_vbox_new (FALSE, 4);
+	gtk_box_pack_start (GTK_BOX (page), page->priv->main_vbox, TRUE, TRUE, 0);
+	gtk_widget_show (page->priv->main_vbox);
 
-	init_login_ui (source);
-	init_profile_ui (source);
-	init_actions (source);
+	init_login_ui (page);
+	init_profile_ui (page);
+	init_actions (page);
 
 	/* create the user */
-	source->priv->user = rb_audioscrobbler_user_new (source->priv->service);
-	g_signal_connect (source->priv->user,
+	page->priv->user = rb_audioscrobbler_user_new (page->priv->service);
+	g_signal_connect (page->priv->user,
 	                  "user-info-updated",
 	                  G_CALLBACK (user_info_updated_cb),
-	                  source);
-	g_signal_connect (source->priv->user,
+	                  page);
+	g_signal_connect (page->priv->user,
 	                  "recent-tracks-updated",
 	                  G_CALLBACK (recent_tracks_updated_cb),
-	                  source);
-	g_signal_connect (source->priv->user,
+	                  page);
+	g_signal_connect (page->priv->user,
 	                  "top-tracks-updated",
 	                  G_CALLBACK (top_tracks_updated_cb),
-	                  source);
-	g_signal_connect (source->priv->user,
+	                  page);
+	g_signal_connect (page->priv->user,
 	                  "loved-tracks-updated",
 	                  G_CALLBACK (loved_tracks_updated_cb),
-	                  source);
-	g_signal_connect (source->priv->user,
+	                  page);
+	g_signal_connect (page->priv->user,
 	                  "top-artists-updated",
 	                  G_CALLBACK (top_artists_updated_cb),
-	                  source);
-	g_signal_connect (source->priv->user,
+	                  page);
+	g_signal_connect (page->priv->user,
 	                  "recommended-artists-updated",
 	                  G_CALLBACK (recommended_artists_updated_cb),
-	                  source);
+	                  page);
 
 	/* create the account */
-	source->priv->account = rb_audioscrobbler_account_new (source->priv->service);
-	g_signal_connect (source->priv->account,
+	page->priv->account = rb_audioscrobbler_account_new (page->priv->service);
+	g_signal_connect (page->priv->account,
 	                  "login-status-changed",
 	                  (GCallback)login_status_change_cb,
-	                  source);
-	login_status_change_cb (source->priv->account,
-	                        rb_audioscrobbler_account_get_login_status (source->priv->account),
-	                        source);
+	                  page);
+	login_status_change_cb (page->priv->account,
+	                        rb_audioscrobbler_account_get_login_status (page->priv->account),
+	                        page);
 
 	/* scrobbling enabled gconf stuff */
 	scrobbling_enabled_conf_key = g_strdup_printf (CONF_AUDIOSCROBBLER_ENABLE_SCROBBLING,
-	                                               rb_audioscrobbler_service_get_name (source->priv->service));
-	source->priv->scrobbling_enabled_notification_id =
+	                                               rb_audioscrobbler_service_get_name (page->priv->service));
+	page->priv->scrobbling_enabled_notification_id =
 		eel_gconf_notification_add (scrobbling_enabled_conf_key,
 				            (GConfClientNotifyFunc) scrobbling_enabled_changed_cb,
-				            source);
-	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (source->priv->scrobbling_enabled_check),
+				            page);
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (page->priv->scrobbling_enabled_check),
 	                              eel_gconf_get_boolean (scrobbling_enabled_conf_key));
 
 
@@ -414,65 +412,65 @@ rb_audioscrobbler_profile_source_constructed (GObject *object)
 }
 
 static void
-rb_audioscrobbler_profile_source_dispose (GObject* object)
+rb_audioscrobbler_profile_page_dispose (GObject* object)
 {
-	RBAudioscrobblerProfileSource *source;
+	RBAudioscrobblerProfilePage *page;
 
-	source = RB_AUDIOSCROBBLER_PROFILE_SOURCE (object);
+	page = RB_AUDIOSCROBBLER_PROFILE_PAGE (object);
 
-	if (source->priv->service != NULL) {
-		g_object_unref (source->priv->service);
-		source->priv->service = NULL;
+	if (page->priv->service != NULL) {
+		g_object_unref (page->priv->service);
+		page->priv->service = NULL;
 	}
 
-	if (source->priv->audioscrobbler != NULL) {
-		g_object_unref (source->priv->audioscrobbler);
-		source->priv->audioscrobbler = NULL;
+	if (page->priv->audioscrobbler != NULL) {
+		g_object_unref (page->priv->audioscrobbler);
+		page->priv->audioscrobbler = NULL;
 	}
 
-	if (source->priv->account != NULL) {
-		g_object_unref (source->priv->account);
-		source->priv->account = NULL;
+	if (page->priv->account != NULL) {
+		g_object_unref (page->priv->account);
+		page->priv->account = NULL;
 	}
 
-	if (source->priv->user != NULL) {
-		g_object_unref (source->priv->user);
-		source->priv->user = NULL;
+	if (page->priv->user != NULL) {
+		g_object_unref (page->priv->user);
+		page->priv->user = NULL;
 	}
 
-	if (source->priv->scrobbling_enabled_notification_id != 0) {
-		eel_gconf_notification_remove (source->priv->scrobbling_enabled_notification_id);
-		source->priv->scrobbling_enabled_notification_id = 0;
+	if (page->priv->scrobbling_enabled_notification_id != 0) {
+		eel_gconf_notification_remove (page->priv->scrobbling_enabled_notification_id);
+		page->priv->scrobbling_enabled_notification_id = 0;
 	}
 
-	if (source->priv->button_to_popup_menu_map != NULL) {
-		g_hash_table_unref (source->priv->button_to_popup_menu_map);
-		source->priv->button_to_popup_menu_map = NULL;
+	if (page->priv->button_to_popup_menu_map != NULL) {
+		g_hash_table_unref (page->priv->button_to_popup_menu_map);
+		page->priv->button_to_popup_menu_map = NULL;
 	}
 
-	if (source->priv->popup_menu_to_data_map != NULL) {
-		g_hash_table_unref (source->priv->popup_menu_to_data_map);
-		source->priv->popup_menu_to_data_map = NULL;
+	if (page->priv->popup_menu_to_data_map != NULL) {
+		g_hash_table_unref (page->priv->popup_menu_to_data_map);
+		page->priv->popup_menu_to_data_map = NULL;
 	}
 
-	G_OBJECT_CLASS (rb_audioscrobbler_profile_source_parent_class)->dispose (object);
+	G_OBJECT_CLASS (rb_audioscrobbler_profile_page_parent_class)->dispose (object);
 }
 
 static void
-rb_audioscrobbler_profile_source_finalize (GObject *object)
+rb_audioscrobbler_profile_page_finalize (GObject *object)
 {
-	RBAudioscrobblerProfileSource *source;
-	source = RB_AUDIOSCROBBLER_PROFILE_SOURCE (object);
+	RBAudioscrobblerProfilePage *page;
+	page = RB_AUDIOSCROBBLER_PROFILE_PAGE (object);
 
-	g_free (source->priv->love_action_name);
-	g_free (source->priv->ban_action_name);
-	g_free (source->priv->download_action_name);
+	g_free (page->priv->love_action_name);
+	g_free (page->priv->ban_action_name);
+	g_free (page->priv->download_action_name);
 
-	G_OBJECT_CLASS (rb_audioscrobbler_profile_source_parent_class)->finalize (object);
+	G_OBJECT_CLASS (rb_audioscrobbler_profile_page_parent_class)->finalize (object);
 }
 
 static void
-rb_audioscrobbler_profile_source_get_property (GObject *object,
+rb_audioscrobbler_profile_page_get_property (GObject *object,
                                                guint prop_id,
                                                GValue *value,
                                                GParamSpec *pspec)
@@ -485,15 +483,15 @@ rb_audioscrobbler_profile_source_get_property (GObject *object,
 }
 
 static void
-rb_audioscrobbler_profile_source_set_property (GObject *object,
+rb_audioscrobbler_profile_page_set_property (GObject *object,
                                                guint prop_id,
                                                const GValue *value,
                                                GParamSpec *pspec)
 {
-	RBAudioscrobblerProfileSource *source = RB_AUDIOSCROBBLER_PROFILE_SOURCE (object);
+	RBAudioscrobblerProfilePage *page = RB_AUDIOSCROBBLER_PROFILE_PAGE (object);
 	switch (prop_id) {
 	case PROP_SERVICE:
-		source->priv->service = g_value_dup_object (value);
+		page->priv->service = g_value_dup_object (value);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -502,27 +500,27 @@ rb_audioscrobbler_profile_source_set_property (GObject *object,
 }
 
 static void
-init_login_ui (RBAudioscrobblerProfileSource *source)
+init_login_ui (RBAudioscrobblerProfilePage *page)
 {
 	GtkWidget *content_area;
 
-	source->priv->login_bar = gtk_info_bar_new ();
-	source->priv->login_status_label = gtk_label_new ("");
-	source->priv->login_response_button = gtk_button_new ();
-	content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (source->priv->login_bar));
-	gtk_container_add (GTK_CONTAINER (content_area), source->priv->login_status_label);
-	source->priv->login_response_button =
-		gtk_info_bar_add_button (GTK_INFO_BAR (source->priv->login_bar),
+	page->priv->login_bar = gtk_info_bar_new ();
+	page->priv->login_status_label = gtk_label_new ("");
+	page->priv->login_response_button = gtk_button_new ();
+	content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (page->priv->login_bar));
+	gtk_container_add (GTK_CONTAINER (content_area), page->priv->login_status_label);
+	page->priv->login_response_button =
+		gtk_info_bar_add_button (GTK_INFO_BAR (page->priv->login_bar),
 		                         "", GTK_RESPONSE_OK);
-	g_signal_connect (source->priv->login_bar,
+	g_signal_connect (page->priv->login_bar,
 	                  "response",
 	                  G_CALLBACK (login_bar_response_cb),
-	                  source);
-	gtk_box_pack_start (GTK_BOX (source->priv->main_vbox), source->priv->login_bar, FALSE, FALSE, 0);
+	                  page);
+	gtk_box_pack_start (GTK_BOX (page->priv->main_vbox), page->priv->login_bar, FALSE, FALSE, 0);
 }
 
 static void
-init_profile_ui (RBAudioscrobblerProfileSource *source)
+init_profile_ui (RBAudioscrobblerProfilePage *page)
 {
 	RBPlugin *plugin;
 	char *builder_file;
@@ -530,57 +528,57 @@ init_profile_ui (RBAudioscrobblerProfileSource *source)
 	GtkWidget *combo_container;
 	int i;
 
-	g_object_get (source, "plugin", &plugin, NULL);
+	g_object_get (page, "plugin", &plugin, NULL);
 
 	builder_file = rb_plugin_find_file (plugin, "audioscrobbler-profile.ui");
 	g_assert (builder_file != NULL);
-	builder = rb_builder_load (builder_file, source);
+	builder = rb_builder_load (builder_file, page);
 
-	source->priv->profile_window = GTK_WIDGET (gtk_builder_get_object (builder, "profile_window"));
+	page->priv->profile_window = GTK_WIDGET (gtk_builder_get_object (builder, "profile_window"));
 
-	source->priv->user_info_area = GTK_WIDGET (gtk_builder_get_object (builder, "user_info_area"));
-	source->priv->profile_image = GTK_WIDGET (gtk_builder_get_object (builder, "profile_image"));
-	source->priv->username_label = GTK_WIDGET (gtk_builder_get_object (builder, "username_label"));
-	source->priv->playcount_label = GTK_WIDGET (gtk_builder_get_object (builder, "playcount_label"));
-	source->priv->scrobbling_enabled_check = GTK_WIDGET (gtk_builder_get_object (builder, "scrobbling_enabled_check"));
-	source->priv->view_profile_link = GTK_WIDGET (gtk_builder_get_object (builder, "view_profile_link"));
+	page->priv->user_info_area = GTK_WIDGET (gtk_builder_get_object (builder, "user_info_area"));
+	page->priv->profile_image = GTK_WIDGET (gtk_builder_get_object (builder, "profile_image"));
+	page->priv->username_label = GTK_WIDGET (gtk_builder_get_object (builder, "username_label"));
+	page->priv->playcount_label = GTK_WIDGET (gtk_builder_get_object (builder, "playcount_label"));
+	page->priv->scrobbling_enabled_check = GTK_WIDGET (gtk_builder_get_object (builder, "scrobbling_enabled_check"));
+	page->priv->view_profile_link = GTK_WIDGET (gtk_builder_get_object (builder, "view_profile_link"));
 
 	/* scrobbler statistics */
-	source->priv->scrobbler_status_msg_label = GTK_WIDGET (gtk_builder_get_object (builder, "scrobbler_status_msg_label"));
-	source->priv->scrobbler_queue_count_label = GTK_WIDGET (gtk_builder_get_object (builder, "scrobbler_queue_count_label"));
-	source->priv->scrobbler_submit_count_label = GTK_WIDGET (gtk_builder_get_object (builder, "scrobbler_submit_count_label"));
-	source->priv->scrobbler_submit_time_label = GTK_WIDGET (gtk_builder_get_object (builder, "scrobbler_submit_time_label"));
+	page->priv->scrobbler_status_msg_label = GTK_WIDGET (gtk_builder_get_object (builder, "scrobbler_status_msg_label"));
+	page->priv->scrobbler_queue_count_label = GTK_WIDGET (gtk_builder_get_object (builder, "scrobbler_queue_count_label"));
+	page->priv->scrobbler_submit_count_label = GTK_WIDGET (gtk_builder_get_object (builder, "scrobbler_submit_count_label"));
+	page->priv->scrobbler_submit_time_label = GTK_WIDGET (gtk_builder_get_object (builder, "scrobbler_submit_time_label"));
 
 	/* station creator */
-	source->priv->station_creator_arg_entry = GTK_WIDGET (gtk_builder_get_object (builder, "station_creator_arg_entry"));
+	page->priv->station_creator_arg_entry = GTK_WIDGET (gtk_builder_get_object (builder, "station_creator_arg_entry"));
 	combo_container = GTK_WIDGET (gtk_builder_get_object (builder, "station_creator_combo_container"));
-	source->priv->station_creator_type_combo = gtk_combo_box_text_new ();
-	gtk_container_add (GTK_CONTAINER (combo_container), source->priv->station_creator_type_combo);
+	page->priv->station_creator_type_combo = gtk_combo_box_text_new ();
+	gtk_container_add (GTK_CONTAINER (combo_container), page->priv->station_creator_type_combo);
 	for (i = 0; i < RB_AUDIOSCROBBLER_RADIO_TYPE_LAST; i++) {
-		gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (source->priv->station_creator_type_combo),
+		gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (page->priv->station_creator_type_combo),
 						rb_audioscrobbler_radio_type_get_text (i));
 	}
-	gtk_combo_box_set_active (GTK_COMBO_BOX (source->priv->station_creator_type_combo), 0);
-	gtk_widget_show (source->priv->station_creator_type_combo);
+	gtk_combo_box_set_active (GTK_COMBO_BOX (page->priv->station_creator_type_combo), 0);
+	gtk_widget_show (page->priv->station_creator_type_combo);
 
 	/* lists of data */
-	source->priv->recent_tracks_area = GTK_WIDGET (gtk_builder_get_object (builder, "recent_tracks_area"));
-	source->priv->recent_tracks_table = GTK_WIDGET (gtk_builder_get_object (builder, "recent_tracks_table"));
+	page->priv->recent_tracks_area = GTK_WIDGET (gtk_builder_get_object (builder, "recent_tracks_area"));
+	page->priv->recent_tracks_table = GTK_WIDGET (gtk_builder_get_object (builder, "recent_tracks_table"));
 
-	source->priv->top_tracks_area = GTK_WIDGET (gtk_builder_get_object (builder, "top_tracks_area"));
-	source->priv->top_tracks_table = GTK_WIDGET (gtk_builder_get_object (builder, "top_tracks_table"));
+	page->priv->top_tracks_area = GTK_WIDGET (gtk_builder_get_object (builder, "top_tracks_area"));
+	page->priv->top_tracks_table = GTK_WIDGET (gtk_builder_get_object (builder, "top_tracks_table"));
 
-	source->priv->loved_tracks_area = GTK_WIDGET (gtk_builder_get_object (builder, "loved_tracks_area"));
-	source->priv->loved_tracks_table = GTK_WIDGET (gtk_builder_get_object (builder, "loved_tracks_table"));
+	page->priv->loved_tracks_area = GTK_WIDGET (gtk_builder_get_object (builder, "loved_tracks_area"));
+	page->priv->loved_tracks_table = GTK_WIDGET (gtk_builder_get_object (builder, "loved_tracks_table"));
 
-	source->priv->top_artists_area = GTK_WIDGET (gtk_builder_get_object (builder, "top_artists_area"));
-	source->priv->top_artists_table = GTK_WIDGET (gtk_builder_get_object (builder, "top_artists_table"));
+	page->priv->top_artists_area = GTK_WIDGET (gtk_builder_get_object (builder, "top_artists_area"));
+	page->priv->top_artists_table = GTK_WIDGET (gtk_builder_get_object (builder, "top_artists_table"));
 
-	source->priv->recommended_artists_area = GTK_WIDGET (gtk_builder_get_object (builder, "recommended_artists_area"));
-	source->priv->recommended_artists_table = GTK_WIDGET (gtk_builder_get_object (builder, "recommended_artists_table"));
+	page->priv->recommended_artists_area = GTK_WIDGET (gtk_builder_get_object (builder, "recommended_artists_area"));
+	page->priv->recommended_artists_table = GTK_WIDGET (gtk_builder_get_object (builder, "recommended_artists_table"));
 
 	/* pack profile into main vbox */
-	gtk_box_pack_start (GTK_BOX (source->priv->main_vbox), source->priv->profile_window, TRUE, TRUE, 0);
+	gtk_box_pack_start (GTK_BOX (page->priv->main_vbox), page->priv->profile_window, TRUE, TRUE, 0);
 
 
 	g_object_unref (plugin);
@@ -589,7 +587,7 @@ init_profile_ui (RBAudioscrobblerProfileSource *source)
 }
 
 static void
-init_actions (RBAudioscrobblerProfileSource *source)
+init_actions (RBAudioscrobblerProfilePage *page)
 {
 	char *ui_file;
 	RBShell *shell;
@@ -597,48 +595,48 @@ init_actions (RBAudioscrobblerProfileSource *source)
 	GtkUIManager *ui_manager;
 	char *group_name;
 
-	g_object_get (source, "shell", &shell, "plugin", &plugin, "ui-manager", &ui_manager, NULL);
+	g_object_get (page, "shell", &shell, "plugin", &plugin, "ui-manager", &ui_manager, NULL);
 	ui_file = rb_plugin_find_file (plugin, "audioscrobbler-profile-ui.xml");
-	source->priv->ui_merge_id = gtk_ui_manager_add_ui_from_file (ui_manager, ui_file, NULL);
+	page->priv->ui_merge_id = gtk_ui_manager_add_ui_from_file (ui_manager, ui_file, NULL);
 
-	source->priv->profile_action_group = _rb_source_register_action_group (RB_SOURCE (source),
-	                                                                       "AudioscrobblerProfileActions",
-	                                                                       NULL, 0,
-	                                                                       source);
-	_rb_action_group_add_source_actions (source->priv->profile_action_group,
-					     G_OBJECT (shell),
-					     profile_actions,
-					     G_N_ELEMENTS (profile_actions));
+	page->priv->profile_action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (page),
+										   "AudioscrobblerProfileActions",
+										   NULL, 0,
+										   page);
+	_rb_action_group_add_display_page_actions (page->priv->profile_action_group,
+						   G_OBJECT (shell),
+						   profile_actions,
+						   G_N_ELEMENTS (profile_actions));
 
 	/* Unfortunately we can't use the usual trick of declaring a static array of GtkActionEntry,
 	 * and simply using _rb_source_register_action_group with that array.
-	 * This is because each instance of this source needs its own love and ban actions
+	 * This is because each instance of this page needs its own love and ban actions
 	 * so tracks can be loved/banned differently for different audioscrobbler services.
 	 */
-	group_name = g_strdup_printf ("%sActions", rb_audioscrobbler_service_get_name (source->priv->service));
-	source->priv->love_action_name = g_strdup_printf ("%sLoveTrack", rb_audioscrobbler_service_get_name (source->priv->service));
-	source->priv->ban_action_name = g_strdup_printf ("%sBanTrack", rb_audioscrobbler_service_get_name (source->priv->service));
-	source->priv->download_action_name = g_strdup_printf ("%sDownloadTrack", rb_audioscrobbler_service_get_name (source->priv->service));
+	group_name = g_strdup_printf ("%sActions", rb_audioscrobbler_service_get_name (page->priv->service));
+	page->priv->love_action_name = g_strdup_printf ("%sLoveTrack", rb_audioscrobbler_service_get_name (page->priv->service));
+	page->priv->ban_action_name = g_strdup_printf ("%sBanTrack", rb_audioscrobbler_service_get_name (page->priv->service));
+	page->priv->download_action_name = g_strdup_printf ("%sDownloadTrack", rb_audioscrobbler_service_get_name (page->priv->service));
 
 	GtkActionEntry service_actions [] =
 	{
-		{ source->priv->love_action_name, "emblem-favorite", N_("Love"), NULL,
+		{ page->priv->love_action_name, "emblem-favorite", N_("Love"), NULL,
 		  N_("Mark this song as loved"),
 		  G_CALLBACK (love_track_action_cb) },
-		{ source->priv->ban_action_name, GTK_STOCK_CANCEL, N_("Ban"), NULL,
+		{ page->priv->ban_action_name, GTK_STOCK_CANCEL, N_("Ban"), NULL,
 		  N_("Ban the current track from being played again"),
 		  G_CALLBACK (ban_track_action_cb) },
-		{ source->priv->download_action_name, GTK_STOCK_SAVE, N_("Download"), NULL,
+		{ page->priv->download_action_name, GTK_STOCK_SAVE, N_("Download"), NULL,
 		  N_("Download the currently playing track"),
 		  G_CALLBACK (download_track_action_cb) }
 	};
 
-	source->priv->service_action_group = _rb_source_register_action_group (RB_SOURCE (source),
-	                                                                       group_name,
-	                                                                       service_actions,
-	                                                                       G_N_ELEMENTS (service_actions),
-	                                                                       source);
-	update_service_actions_sensitivity (source);
+	page->priv->service_action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (page),
+										   group_name,
+										   service_actions,
+										   G_N_ELEMENTS (service_actions),
+										   page);
+	update_service_actions_sensitivity (page);
 
 	g_free (ui_file);
 	g_object_unref (shell);
@@ -650,17 +648,17 @@ init_actions (RBAudioscrobblerProfileSource *source)
 static void
 login_bar_response_cb (GtkInfoBar *info_bar,
                        gint response_id,
-                       RBAudioscrobblerProfileSource *source)
+                       RBAudioscrobblerProfilePage *page)
 {
-	switch (rb_audioscrobbler_account_get_login_status (source->priv->account)) {
+	switch (rb_audioscrobbler_account_get_login_status (page->priv->account)) {
 	case RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGED_OUT:
 	case RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_AUTH_ERROR:
 	case RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_CONNECTION_ERROR:
-		rb_audioscrobbler_account_authenticate (source->priv->account);
+		rb_audioscrobbler_account_authenticate (page->priv->account);
 		break;
 	case RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGING_IN:
 	case RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGED_IN:
-		rb_audioscrobbler_account_logout (source->priv->account);
+		rb_audioscrobbler_account_logout (page->priv->account);
 		break;
 	default:
 		g_assert_not_reached ();
@@ -670,15 +668,15 @@ login_bar_response_cb (GtkInfoBar *info_bar,
 
 void
 logout_button_clicked_cb (GtkButton *button,
-                          RBAudioscrobblerProfileSource *source)
+                          RBAudioscrobblerProfilePage *page)
 {
-	rb_audioscrobbler_account_logout (source->priv->account);
+	rb_audioscrobbler_account_logout (page->priv->account);
 }
 
 static void
 login_status_change_cb (RBAudioscrobblerAccount *account,
                         RBAudioscrobblerAccountLoginStatus status,
-                        RBAudioscrobblerProfileSource *source)
+                        RBAudioscrobblerProfilePage *page)
 {
 	const char *username;
 	const char *session_key;
@@ -688,46 +686,46 @@ login_status_change_cb (RBAudioscrobblerAccount *account,
 	gboolean show_login_bar;
 	gboolean show_profile;
 
-	username = rb_audioscrobbler_account_get_username (source->priv->account);
-	session_key = rb_audioscrobbler_account_get_session_key (source->priv->account);
+	username = rb_audioscrobbler_account_get_username (page->priv->account);
+	session_key = rb_audioscrobbler_account_get_session_key (page->priv->account);
 
 	/* delete old scrobbler */
-	if (source->priv->audioscrobbler != NULL) {
-		g_object_unref (source->priv->audioscrobbler);
-		source->priv->audioscrobbler = NULL;
+	if (page->priv->audioscrobbler != NULL) {
+		g_object_unref (page->priv->audioscrobbler);
+		page->priv->audioscrobbler = NULL;
 	}
 
 	/* create new scrobbler if new user has logged in and scrobbling is enabled */
 	scrobbling_enabled_conf_key = g_strdup_printf (CONF_AUDIOSCROBBLER_ENABLE_SCROBBLING,
-	                                               rb_audioscrobbler_service_get_name (source->priv->service));
+	                                               rb_audioscrobbler_service_get_name (page->priv->service));
 	if (status == RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGED_IN &&
 	    eel_gconf_get_boolean (scrobbling_enabled_conf_key)) {
 		RBShell *shell;
-		g_object_get (source, "shell", &shell, NULL);
-		source->priv->audioscrobbler =
-			rb_audioscrobbler_new (source->priv->service,
+		g_object_get (page, "shell", &shell, NULL);
+		page->priv->audioscrobbler =
+			rb_audioscrobbler_new (page->priv->service,
 				               RB_SHELL_PLAYER (rb_shell_get_player (shell)),
-                                               rb_audioscrobbler_account_get_username (source->priv->account),
-			                       rb_audioscrobbler_account_get_session_key (source->priv->account));
-		g_signal_connect (source->priv->audioscrobbler,
+                                               rb_audioscrobbler_account_get_username (page->priv->account),
+			                       rb_audioscrobbler_account_get_session_key (page->priv->account));
+		g_signal_connect (page->priv->audioscrobbler,
 			          "authentication-error",
 			          G_CALLBACK (scrobbler_authentication_error_cb),
-			          source);
-		g_signal_connect (source->priv->audioscrobbler,
+			          page);
+		g_signal_connect (page->priv->audioscrobbler,
 			          "statistics-changed",
 			          G_CALLBACK (scrobbler_statistics_changed_cb),
-			          source);
-		rb_audioscrobbler_statistics_changed (source->priv->audioscrobbler);
+			          page);
+		rb_audioscrobbler_statistics_changed (page->priv->audioscrobbler);
 		g_object_unref (shell);
 	}
 
 	/* set the new user details */
-	rb_audioscrobbler_user_set_authentication_details (source->priv->user, username, session_key);
+	rb_audioscrobbler_user_set_authentication_details (page->priv->user, username, session_key);
 	if (username != NULL) {
-		rb_audioscrobbler_user_update (source->priv->user);
+		rb_audioscrobbler_user_update (page->priv->user);
 	}
 
-	load_radio_stations (source);
+	load_radio_stations (page);
 
 	/* update the login ui */
 	switch (status) {
@@ -736,14 +734,14 @@ login_status_change_cb (RBAudioscrobblerAccount *account,
 		show_profile = FALSE;
 		label_text = g_strdup (_("You are not currently logged in."));
 		button_text = g_strdup (_("Log in"));
-		gtk_info_bar_set_message_type (GTK_INFO_BAR (source->priv->login_bar), GTK_MESSAGE_INFO);
+		gtk_info_bar_set_message_type (GTK_INFO_BAR (page->priv->login_bar), GTK_MESSAGE_INFO);
 		break;
 	case RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGING_IN:
 		show_login_bar = TRUE;
 		show_profile = FALSE;
 		label_text = g_strdup (_("Waiting for authentication..."));
 		button_text = g_strdup (_("Cancel"));
-		gtk_info_bar_set_message_type (GTK_INFO_BAR (source->priv->login_bar), GTK_MESSAGE_INFO);
+		gtk_info_bar_set_message_type (GTK_INFO_BAR (page->priv->login_bar), GTK_MESSAGE_INFO);
 		break;
 	case RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_LOGGED_IN:
 		show_login_bar = FALSE;
@@ -754,34 +752,34 @@ login_status_change_cb (RBAudioscrobblerAccount *account,
 		show_profile = FALSE;
 		label_text = g_strdup (_("Authentication error. Please try logging in again."));
 		button_text = g_strdup (_("Log in"));
-		gtk_info_bar_set_message_type (GTK_INFO_BAR (source->priv->login_bar), GTK_MESSAGE_WARNING);
+		gtk_info_bar_set_message_type (GTK_INFO_BAR (page->priv->login_bar), GTK_MESSAGE_WARNING);
 		break;
 	case RB_AUDIOSCROBBLER_ACCOUNT_LOGIN_STATUS_CONNECTION_ERROR:
 		show_login_bar = TRUE;
 		show_profile = FALSE;
 		label_text = g_strdup (_("Connection error. Please try logging in again."));
 		button_text = g_strdup (_("Log in"));
-		gtk_info_bar_set_message_type (GTK_INFO_BAR (source->priv->login_bar), GTK_MESSAGE_WARNING);
+		gtk_info_bar_set_message_type (GTK_INFO_BAR (page->priv->login_bar), GTK_MESSAGE_WARNING);
 		break;
 	default:
 		g_assert_not_reached ();
 		break;
 	}
 
-	gtk_label_set_label (GTK_LABEL (source->priv->login_status_label), label_text);
-	gtk_button_set_label (GTK_BUTTON (source->priv->login_response_button), button_text);
+	gtk_label_set_label (GTK_LABEL (page->priv->login_status_label), label_text);
+	gtk_button_set_label (GTK_BUTTON (page->priv->login_response_button), button_text);
 	if (show_login_bar == TRUE) {
-		gtk_widget_show_all (source->priv->login_bar);
+		gtk_widget_show_all (page->priv->login_bar);
 	} else {
-		gtk_widget_hide (source->priv->login_bar);
+		gtk_widget_hide (page->priv->login_bar);
 	}
 	if (show_profile == TRUE) {
-		gtk_label_set_label (GTK_LABEL (source->priv->username_label),
+		gtk_label_set_label (GTK_LABEL (page->priv->username_label),
 			             username);
-		gtk_widget_show (source->priv->username_label);
-		gtk_widget_show (source->priv->profile_window);
+		gtk_widget_show (page->priv->username_label);
+		gtk_widget_show (page->priv->profile_window);
 	} else {
-		gtk_widget_hide (source->priv->profile_window);
+		gtk_widget_hide (page->priv->profile_window);
 	}
 
 	g_free (scrobbling_enabled_conf_key);
@@ -791,12 +789,12 @@ login_status_change_cb (RBAudioscrobblerAccount *account,
 
 void
 scrobbling_enabled_check_toggled_cb (GtkToggleButton *togglebutton,
-                                     RBAudioscrobblerProfileSource *source)
+                                     RBAudioscrobblerProfilePage *page)
 {
 	char *conf_key;
 
 	conf_key = g_strdup_printf (CONF_AUDIOSCROBBLER_ENABLE_SCROBBLING,
-	                            rb_audioscrobbler_service_get_name (source->priv->service));
+	                            rb_audioscrobbler_service_get_name (page->priv->service));
 	eel_gconf_set_boolean (conf_key,
 			       gtk_toggle_button_get_active (togglebutton));
 	g_free (conf_key);
@@ -806,43 +804,43 @@ static void
 scrobbling_enabled_changed_cb (GConfClient *client,
                                guint cnxn_id,
                                GConfEntry *entry,
-                               RBAudioscrobblerProfileSource *source)
+                               RBAudioscrobblerProfilePage *page)
 {
 	gboolean enabled = gconf_value_get_bool (entry->value);
-	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (source->priv->scrobbling_enabled_check),
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (page->priv->scrobbling_enabled_check),
 	                              enabled);
 
-	if (source->priv->audioscrobbler != NULL && enabled == FALSE) {
-		g_object_unref (source->priv->audioscrobbler);
-		source->priv->audioscrobbler = NULL;
-		gtk_label_set_label (GTK_LABEL (source->priv->scrobbler_status_msg_label),
+	if (page->priv->audioscrobbler != NULL && enabled == FALSE) {
+		g_object_unref (page->priv->audioscrobbler);
+		page->priv->audioscrobbler = NULL;
+		gtk_label_set_label (GTK_LABEL (page->priv->scrobbler_status_msg_label),
 		                     _("Disabled"));
-	} else if (source->priv->audioscrobbler == NULL && enabled == TRUE) {
+	} else if (page->priv->audioscrobbler == NULL && enabled == TRUE) {
 		RBShell *shell;
-		g_object_get (source, "shell", &shell, NULL);
-		source->priv->audioscrobbler =
-			rb_audioscrobbler_new (source->priv->service,
+		g_object_get (page, "shell", &shell, NULL);
+		page->priv->audioscrobbler =
+			rb_audioscrobbler_new (page->priv->service,
 				               RB_SHELL_PLAYER (rb_shell_get_player (shell)),
-                                               rb_audioscrobbler_account_get_username (source->priv->account),
-			                       rb_audioscrobbler_account_get_session_key (source->priv->account));
-		g_signal_connect (source->priv->audioscrobbler,
+                                               rb_audioscrobbler_account_get_username (page->priv->account),
+			                       rb_audioscrobbler_account_get_session_key (page->priv->account));
+		g_signal_connect (page->priv->audioscrobbler,
 			          "authentication-error",
 			          G_CALLBACK (scrobbler_authentication_error_cb),
-			          source);
-		g_signal_connect (source->priv->audioscrobbler,
+			          page);
+		g_signal_connect (page->priv->audioscrobbler,
 			          "statistics-changed",
 			          G_CALLBACK (scrobbler_statistics_changed_cb),
-			          source);
-		rb_audioscrobbler_statistics_changed (source->priv->audioscrobbler);
+			          page);
+		rb_audioscrobbler_statistics_changed (page->priv->audioscrobbler);
 		g_object_unref (shell);
 	}
 }
 
 static void
 scrobbler_authentication_error_cb (RBAudioscrobbler *audioscrobbler,
-                                   RBAudioscrobblerProfileSource *source)
+                                   RBAudioscrobblerProfilePage *page)
 {
-	rb_audioscrobbler_account_notify_of_auth_error (source->priv->account);
+	rb_audioscrobbler_account_notify_of_auth_error (page->priv->account);
 }
 
 static void
@@ -851,20 +849,20 @@ scrobbler_statistics_changed_cb (RBAudioscrobbler *audioscrobbler,
                                  guint queue_count,
                                  guint submit_count,
                                  const char *submit_time,
-                                 RBAudioscrobblerProfileSource *source)
+                                 RBAudioscrobblerProfilePage *page)
 {
 	char *queue_count_text;
 	char *submit_count_text;
 
-	gtk_label_set_text (GTK_LABEL (source->priv->scrobbler_status_msg_label), status_msg);
+	gtk_label_set_text (GTK_LABEL (page->priv->scrobbler_status_msg_label), status_msg);
 
 	queue_count_text = g_strdup_printf ("%u", queue_count);
-	gtk_label_set_text (GTK_LABEL (source->priv->scrobbler_queue_count_label), queue_count_text);
+	gtk_label_set_text (GTK_LABEL (page->priv->scrobbler_queue_count_label), queue_count_text);
 
 	submit_count_text = g_strdup_printf ("%u", submit_count);
-	gtk_label_set_text (GTK_LABEL (source->priv->scrobbler_submit_count_label), submit_count_text);
+	gtk_label_set_text (GTK_LABEL (page->priv->scrobbler_submit_count_label), submit_count_text);
 
-	gtk_label_set_text (GTK_LABEL (source->priv->scrobbler_submit_time_label), submit_time);
+	gtk_label_set_text (GTK_LABEL (page->priv->scrobbler_submit_time_label), submit_time);
 
 	g_free (queue_count_text);
 	g_free (submit_count_text);
@@ -873,13 +871,13 @@ scrobbler_statistics_changed_cb (RBAudioscrobbler *audioscrobbler,
 static void
 playing_song_changed_cb (RBShellPlayer *player,
                          RhythmDBEntry *entry,
-                         RBAudioscrobblerProfileSource *source)
+                         RBAudioscrobblerProfilePage *page)
 {
-	update_service_actions_sensitivity (source);
+	update_service_actions_sensitivity (page);
 }
 
 static void
-update_service_actions_sensitivity (RBAudioscrobblerProfileSource *source)
+update_service_actions_sensitivity (RBAudioscrobblerProfilePage *page)
 {
 	RBShell *shell;
 	RhythmDBEntry *playing;
@@ -887,12 +885,12 @@ update_service_actions_sensitivity (RBAudioscrobblerProfileSource *source)
 	GtkAction *ban;
 	GtkAction *download;
 
-	g_object_get (source, "shell", &shell, NULL);
+	g_object_get (page, "shell", &shell, NULL);
 	playing = rb_shell_player_get_playing_entry (RB_SHELL_PLAYER (rb_shell_get_player (shell)));
 
 	/* enable love/ban if an entry is playing */
-	love = gtk_action_group_get_action (source->priv->service_action_group, source->priv->love_action_name);
-	ban = gtk_action_group_get_action (source->priv->service_action_group, source->priv->ban_action_name);
+	love = gtk_action_group_get_action (page->priv->service_action_group, page->priv->love_action_name);
+	ban = gtk_action_group_get_action (page->priv->service_action_group, page->priv->ban_action_name);
 	if (playing == NULL) {
 		gtk_action_set_sensitive (love, FALSE);
 		gtk_action_set_sensitive (ban, FALSE);
@@ -902,13 +900,13 @@ update_service_actions_sensitivity (RBAudioscrobblerProfileSource *source)
 	}
 
 	/* enable download if the playing entry is a radio track from this service which provides a download url */
-	download = gtk_action_group_get_action (source->priv->service_action_group, source->priv->download_action_name);
+	download = gtk_action_group_get_action (page->priv->service_action_group, page->priv->download_action_name);
 	if (playing != NULL &&
 	    rhythmdb_entry_get_entry_type (playing) == RHYTHMDB_ENTRY_TYPE_AUDIOSCROBBLER_RADIO_TRACK) {
 		RBAudioscrobblerRadioTrackData *data;
 		data = RHYTHMDB_ENTRY_GET_TYPE_DATA (playing, RBAudioscrobblerRadioTrackData);
 
-		if (data->service == source->priv->service && data->download_url != NULL) {
+		if (data->service == page->priv->service && data->download_url != NULL) {
 			gtk_action_set_sensitive (download, TRUE);
 		} else {
 			gtk_action_set_sensitive (download, FALSE);
@@ -922,19 +920,18 @@ update_service_actions_sensitivity (RBAudioscrobblerProfileSource *source)
 		rhythmdb_entry_unref (playing);
 	}
 }
-
 static void
-love_track_action_cb (GtkAction *action, RBAudioscrobblerProfileSource *source)
+love_track_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page)
 {
 	RBShell *shell;
 	RhythmDBEntry *playing;
 	GtkAction *ban_action;
 
-	g_object_get (source, "shell", &shell, NULL);
+	g_object_get (page, "shell", &shell, NULL);
 	playing = rb_shell_player_get_playing_entry (RB_SHELL_PLAYER (rb_shell_get_player (shell)));
 
 	if (playing != NULL) {
-		rb_audioscrobbler_user_love_track (source->priv->user,
+		rb_audioscrobbler_user_love_track (page->priv->user,
 			                           rhythmdb_entry_get_string (playing, RHYTHMDB_PROP_TITLE),
 			                           rhythmdb_entry_get_string (playing, RHYTHMDB_PROP_ARTIST));
 		rhythmdb_entry_unref (playing);
@@ -942,23 +939,23 @@ love_track_action_cb (GtkAction *action, RBAudioscrobblerProfileSource *source)
 
 	/* disable love/ban */
 	gtk_action_set_sensitive (action, FALSE);
-	ban_action = gtk_action_group_get_action (source->priv->service_action_group, source->priv->ban_action_name);
+	ban_action = gtk_action_group_get_action (page->priv->service_action_group, page->priv->ban_action_name);
 	gtk_action_set_sensitive (ban_action, FALSE);
 
 	g_object_unref (shell);
 }
 
 static void
-ban_track_action_cb (GtkAction *action, RBAudioscrobblerProfileSource *source)
+ban_track_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page)
 {
 	RBShell *shell;
 	RhythmDBEntry *playing;
 
-	g_object_get (source, "shell", &shell, NULL);
+	g_object_get (page, "shell", &shell, NULL);
 	playing = rb_shell_player_get_playing_entry (RB_SHELL_PLAYER (rb_shell_get_player (shell)));
 
 	if (playing != NULL) {
-		rb_audioscrobbler_user_ban_track (source->priv->user,
+		rb_audioscrobbler_user_ban_track (page->priv->user,
 			                          rhythmdb_entry_get_string (playing, RHYTHMDB_PROP_TITLE),
 			                          rhythmdb_entry_get_string (playing, RHYTHMDB_PROP_ARTIST));
 		rhythmdb_entry_unref (playing);
@@ -971,7 +968,7 @@ ban_track_action_cb (GtkAction *action, RBAudioscrobblerProfileSource *source)
 }
 
 static void
-download_track_action_cb (GtkAction *action, RBAudioscrobblerProfileSource *source)
+download_track_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page)
 {
 	RBShell *shell;
 	RhythmDBEntry *playing;
@@ -979,7 +976,7 @@ download_track_action_cb (GtkAction *action, RBAudioscrobblerProfileSource *sour
 	/* disable the action */
 	gtk_action_set_sensitive (action, FALSE);
 
-	g_object_get (source, "shell", &shell, NULL);
+	g_object_get (page, "shell", &shell, NULL);
 	playing = rb_shell_player_get_playing_entry (RB_SHELL_PLAYER (rb_shell_get_player (shell)));
 
 	if (playing != NULL &&
@@ -1027,7 +1024,7 @@ download_track_action_cb (GtkAction *action, RBAudioscrobblerProfileSource *sour
 				g_signal_connect_object (batch,
 				                         "complete",
 				                         G_CALLBACK (download_track_batch_complete_cb),
-				                         source,
+				                         page,
 				                         0);
 			}
 
@@ -1046,7 +1043,7 @@ download_track_action_cb (GtkAction *action, RBAudioscrobblerProfileSource *sour
 
 static void
 download_track_batch_complete_cb (RBTrackTransferBatch *batch,
-                                  RBAudioscrobblerProfileSource *source)
+                                  RBAudioscrobblerProfilePage *page)
 {
 	GList *entries;
 	RBShell *shell;
@@ -1054,7 +1051,7 @@ download_track_batch_complete_cb (RBTrackTransferBatch *batch,
 	GList *i;
 
 	g_object_get (batch, "entry-list", &entries, NULL);
-	g_object_get (source, "shell", &shell, NULL);
+	g_object_get (page, "shell", &shell, NULL);
 	g_object_get (shell, "db", &db, NULL);
 
 	/* delete the entries which were transfered.
@@ -1072,18 +1069,18 @@ download_track_batch_complete_cb (RBTrackTransferBatch *batch,
 }
 
 static void
-refresh_profile_action_cb (GtkAction *action, RBAudioscrobblerProfileSource *source)
+refresh_profile_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page)
 {
-	rb_audioscrobbler_user_force_update (source->priv->user);
+	rb_audioscrobbler_user_force_update (page->priv->user);
 }
 
 void
 station_creator_button_clicked_cb (GtkButton *button,
-                                   RBAudioscrobblerProfileSource *source)
+                                   RBAudioscrobblerProfilePage *page)
 {
 	const char *arg;
 
-	arg = gtk_entry_get_text (GTK_ENTRY (source->priv->station_creator_arg_entry));
+	arg = gtk_entry_get_text (GTK_ENTRY (page->priv->station_creator_arg_entry));
 
 	if (arg[0] != '\0') {
 		RBAudioscrobblerRadioType type;
@@ -1091,41 +1088,41 @@ station_creator_button_clicked_cb (GtkButton *button,
 		char *name;
 		RBSource *radio;
 		RBShell *shell;
-		RBSourceList *sourcelist;
+		RBDisplayPageTree *page_tree;
 
-		type = gtk_combo_box_get_active (GTK_COMBO_BOX (source->priv->station_creator_type_combo));
+		type = gtk_combo_box_get_active (GTK_COMBO_BOX (page->priv->station_creator_type_combo));
 
 		url = g_strdup_printf (rb_audioscrobbler_radio_type_get_url (type),
 		                       arg);
 		name = g_strdup_printf (rb_audioscrobbler_radio_type_get_default_name (type),
 		                        arg);
 
-		radio = add_radio_station (source, url, name);
-		g_object_get (source, "shell", &shell, NULL);
-		g_object_get (shell, "sourcelist", &sourcelist, NULL);
-		rb_sourcelist_select (sourcelist, radio);
+		radio = add_radio_station (page, url, name);
+		g_object_get (page, "shell", &shell, NULL);
+		g_object_get (shell, "display-page-tree", &page_tree, NULL);
+		rb_display_page_tree_select (page_tree, RB_DISPLAY_PAGE (radio));
 
-		gtk_entry_set_text (GTK_ENTRY (source->priv->station_creator_arg_entry), "");
+		gtk_entry_set_text (GTK_ENTRY (page->priv->station_creator_arg_entry), "");
 
 		g_free (url);
 		g_free (name);
 		g_object_unref (shell);
-		g_object_unref (sourcelist);
+		g_object_unref (page_tree);
 	}
 }
 
 /* delete old user's radio sources and load ones for new user */
 static void
-load_radio_stations (RBAudioscrobblerProfileSource *source)
+load_radio_stations (RBAudioscrobblerProfilePage *page)
 {
 	/* destroy existing sources */
-	while (source->priv->radio_sources != NULL) {
-		rb_source_delete_thyself (source->priv->radio_sources->data);
-		source->priv->radio_sources = g_list_remove (source->priv->radio_sources, source->priv->radio_sources->data);
+	while (page->priv->radio_sources != NULL) {
+		rb_display_page_delete_thyself (RB_DISPLAY_PAGE (page->priv->radio_sources->data));
+		page->priv->radio_sources = g_list_remove (page->priv->radio_sources, page->priv->radio_sources->data);
 	}
 
 	/* load the user's saved stations */
-	if (rb_audioscrobbler_account_get_username (source->priv->account) != NULL) {
+	if (rb_audioscrobbler_account_get_username (page->priv->account) != NULL) {
 		JsonParser *parser;
 		char *filename;
 
@@ -1133,8 +1130,8 @@ load_radio_stations (RBAudioscrobblerProfileSource *source)
 		filename = g_build_filename (rb_user_data_dir (),
 		                             "audioscrobbler",
 		                             "stations",
-		                             rb_audioscrobbler_service_get_name (source->priv->service),
-		                             rb_audioscrobbler_account_get_username (source->priv->account),
+		                             rb_audioscrobbler_service_get_name (page->priv->service),
+		                             rb_audioscrobbler_account_get_username (page->priv->account),
 		                             NULL);
 
 		if (json_parser_load_from_file (parser, filename, NULL)) {
@@ -1153,45 +1150,45 @@ load_radio_stations (RBAudioscrobblerProfileSource *source)
 				name = json_object_get_string_member (station, "name");
 				url = json_object_get_string_member (station, "url");
 
-				radio = rb_audioscrobbler_radio_source_new (source,
-				                                            source->priv->service,
-				                                            rb_audioscrobbler_account_get_username (source->priv->account),
-				                                            rb_audioscrobbler_account_get_session_key (source->priv->account),
+				radio = rb_audioscrobbler_radio_source_new (page,
+				                                            page->priv->service,
+				                                            rb_audioscrobbler_account_get_username (page->priv->account),
+				                                            rb_audioscrobbler_account_get_session_key (page->priv->account),
 				                                            name,
 				                                            url);
-				source->priv->radio_sources = g_list_append (source->priv->radio_sources, radio);
+				page->priv->radio_sources = g_list_append (page->priv->radio_sources, radio);
 				g_signal_connect (radio, "notify::name",
 						  G_CALLBACK (radio_station_name_changed_cb),
-						  source);
+						  page);
 			}
 		}
 
 		/* if the list of stations is still empty then add some defaults */
-		if (source->priv->radio_sources == NULL) {
+		if (page->priv->radio_sources == NULL) {
 			char *url;
 			char *name;
 
 			/* user's library */
 			url = g_strdup_printf (rb_audioscrobbler_radio_type_get_url (RB_AUDIOSCROBBLER_RADIO_TYPE_LIBRARY),
-			                       rb_audioscrobbler_account_get_username (source->priv->account));
+			                       rb_audioscrobbler_account_get_username (page->priv->account));
 			name = g_strdup (_("My Library"));
-			add_radio_station (source, url, name);
+			add_radio_station (page, url, name);
 			g_free (url);
 			g_free (name);
 
 			/* user's recommendations */
 			url = g_strdup_printf (rb_audioscrobbler_radio_type_get_url (RB_AUDIOSCROBBLER_RADIO_TYPE_RECOMMENDATION),
-			                       rb_audioscrobbler_account_get_username (source->priv->account));
+			                       rb_audioscrobbler_account_get_username (page->priv->account));
 			name = g_strdup (_("My Recommendations"));
-			add_radio_station (source, url, name);
+			add_radio_station (page, url, name);
 			g_free (url);
 			g_free (name);
 
 			/* user's neighbourhood */
 			url = g_strdup_printf (rb_audioscrobbler_radio_type_get_url (RB_AUDIOSCROBBLER_RADIO_TYPE_NEIGHBOURS),
-			                       rb_audioscrobbler_account_get_username (source->priv->account));
+			                       rb_audioscrobbler_account_get_username (page->priv->account));
 			name = g_strdup (_("My Neighbourhood"));
-			add_radio_station (source, url, name);
+			add_radio_station (page, url, name);
 			g_free (url);
 			g_free (name);
 
@@ -1200,7 +1197,7 @@ load_radio_stations (RBAudioscrobblerProfileSource *source)
 			                       "rhythmbox");
 			name = g_strdup_printf (rb_audioscrobbler_radio_type_get_default_name (RB_AUDIOSCROBBLER_RADIO_TYPE_GROUP),
 			                       "Rhythmbox");
-			add_radio_station (source, url, name);
+			add_radio_station (page, url, name);
 			g_free (url);
 			g_free (name);
 		}
@@ -1212,7 +1209,7 @@ load_radio_stations (RBAudioscrobblerProfileSource *source)
 
 /* save user's radio stations */
 static void
-save_radio_stations (RBAudioscrobblerProfileSource *source)
+save_radio_stations (RBAudioscrobblerProfilePage *page)
 {
 	JsonNode *root;
 	JsonArray *stations;
@@ -1225,7 +1222,7 @@ save_radio_stations (RBAudioscrobblerProfileSource *source)
 	root = json_node_new (JSON_NODE_ARRAY);
 	stations = json_array_new ();
 
-	for (i = source->priv->radio_sources; i != NULL; i = i->next) {
+	for (i = page->priv->radio_sources; i != NULL; i = i->next) {
 		JsonObject *station;
 		char *name;
 		char *url;
@@ -1248,8 +1245,8 @@ save_radio_stations (RBAudioscrobblerProfileSource *source)
 	filename = g_build_filename (rb_user_data_dir (),
 	                             "audioscrobbler",
 	                             "stations",
-	                             rb_audioscrobbler_service_get_name (source->priv->service),
-	                             rb_audioscrobbler_account_get_username (source->priv->account),
+	                             rb_audioscrobbler_service_get_name (page->priv->service),
+	                             rb_audioscrobbler_account_get_username (page->priv->account),
 	                             NULL);
 
 	uri = g_filename_to_uri (filename, NULL, NULL);
@@ -1265,7 +1262,7 @@ save_radio_stations (RBAudioscrobblerProfileSource *source)
 
 /* adds a new radio station for the user, if it doesn't already exist */
 static RBSource *
-add_radio_station (RBAudioscrobblerProfileSource *source,
+add_radio_station (RBAudioscrobblerProfilePage *page,
                    const char *url,
                    const char *name)
 {
@@ -1273,7 +1270,7 @@ add_radio_station (RBAudioscrobblerProfileSource *source,
 	RBSource *radio = NULL;
 
 	/* check for existing station */
-	for (i = source->priv->radio_sources; i != NULL; i = i->next) {
+	for (i = page->priv->radio_sources; i != NULL; i = i->next) {
 		char *existing_url;
 		g_object_get (i->data, "station-url", &existing_url, NULL);
 
@@ -1289,21 +1286,21 @@ add_radio_station (RBAudioscrobblerProfileSource *source,
 		const char *session_key;
 		RBShell *shell;
 
-		username = rb_audioscrobbler_account_get_username (source->priv->account);
-		session_key = rb_audioscrobbler_account_get_session_key (source->priv->account);
-		g_object_get (source, "shell", &shell, NULL);
+		username = rb_audioscrobbler_account_get_username (page->priv->account);
+		session_key = rb_audioscrobbler_account_get_session_key (page->priv->account);
+		g_object_get (page, "shell", &shell, NULL);
 
-		radio = rb_audioscrobbler_radio_source_new (source,
-		                                            source->priv->service,
+		radio = rb_audioscrobbler_radio_source_new (page,
+		                                            page->priv->service,
 		                                            username,
 		                                            session_key,
 		                                            name,
 		                                            url);
-		source->priv->radio_sources = g_list_append (source->priv->radio_sources, radio);
+		page->priv->radio_sources = g_list_append (page->priv->radio_sources, radio);
 		g_signal_connect (radio, "notify::name",
 		                  G_CALLBACK (radio_station_name_changed_cb),
-		                  source);
-		save_radio_stations (source);
+		                  page);
+		save_radio_stations (page);
 
 		g_object_unref (shell);
 	}
@@ -1315,32 +1312,32 @@ add_radio_station (RBAudioscrobblerProfileSource *source,
 static void
 radio_station_name_changed_cb (RBAudioscrobblerRadioSource *radio,
                                GParamSpec *spec,
-                               RBAudioscrobblerProfileSource *source)
+                               RBAudioscrobblerProfilePage *page)
 {
 	/* save list of stations with new name */
-	save_radio_stations (source);
+	save_radio_stations (page);
 }
 
 /* removes a station from user's list of radio stations, deletes the source */
 void
-rb_audioscrobbler_profile_source_remove_radio_station (RBAudioscrobblerProfileSource *source,
-                                                       RBSource *station)
+rb_audioscrobbler_profile_page_remove_radio_station (RBAudioscrobblerProfilePage *page,
+						     RBSource *station)
 {
 	GList *i;
 
-	i = g_list_find (source->priv->radio_sources, station);
+	i = g_list_find (page->priv->radio_sources, station);
 
 	if (i != NULL) {
-		rb_source_delete_thyself (i->data);
-		source->priv->radio_sources = g_list_remove (source->priv->radio_sources, i->data);
-		save_radio_stations (source);
+		rb_display_page_delete_thyself (i->data);
+		page->priv->radio_sources = g_list_remove (page->priv->radio_sources, i->data);
+		save_radio_stations (page);
 	}
 }
 
 static gboolean
-update_timeout_cb (RBAudioscrobblerProfileSource *source)
+update_timeout_cb (RBAudioscrobblerProfilePage *page)
 {
-	rb_audioscrobbler_user_update (source->priv->user);
+	rb_audioscrobbler_user_update (page->priv->user);
 
 	return TRUE;
 }
@@ -1348,106 +1345,106 @@ update_timeout_cb (RBAudioscrobblerProfileSource *source)
 static void
 user_info_updated_cb (RBAudioscrobblerUser *user,
                       RBAudioscrobblerUserData *data,
-                      RBAudioscrobblerProfileSource *source)
+                      RBAudioscrobblerProfilePage *page)
 {
 	if (data != NULL) {
 		char *playcount_text;
 
-		gtk_label_set_label (GTK_LABEL (source->priv->username_label),
+		gtk_label_set_label (GTK_LABEL (page->priv->username_label),
 			             data->user_info.username);
-		gtk_widget_show (source->priv->username_label);
+		gtk_widget_show (page->priv->username_label);
 
 		playcount_text = g_strdup_printf (_("%s plays"), data->user_info.playcount);
-		gtk_label_set_label (GTK_LABEL (source->priv->playcount_label),
+		gtk_label_set_label (GTK_LABEL (page->priv->playcount_label),
 		                     playcount_text);
 		g_free (playcount_text);
-		gtk_widget_show (source->priv->playcount_label);
+		gtk_widget_show (page->priv->playcount_label);
 
-		gtk_link_button_set_uri (GTK_LINK_BUTTON (source->priv->view_profile_link),
+		gtk_link_button_set_uri (GTK_LINK_BUTTON (page->priv->view_profile_link),
 		                         data->url);
-		gtk_widget_show (source->priv->view_profile_link);
+		gtk_widget_show (page->priv->view_profile_link);
 
 		if (data->image != NULL) {
-			gtk_image_set_from_pixbuf (GTK_IMAGE (source->priv->profile_image), data->image);
+			gtk_image_set_from_pixbuf (GTK_IMAGE (page->priv->profile_image), data->image);
 			/* show the parent because the image is packed in a viewport so it has a shadow */
-			gtk_widget_show (gtk_widget_get_parent (source->priv->profile_image));
+			gtk_widget_show (gtk_widget_get_parent (page->priv->profile_image));
 		} else {
-			gtk_widget_hide (gtk_widget_get_parent (source->priv->profile_image));
+			gtk_widget_hide (gtk_widget_get_parent (page->priv->profile_image));
 		}
 	} else {
-		gtk_widget_hide (source->priv->playcount_label);
-		gtk_widget_hide (source->priv->view_profile_link);
-		gtk_widget_hide (gtk_widget_get_parent (source->priv->profile_image));
+		gtk_widget_hide (page->priv->playcount_label);
+		gtk_widget_hide (page->priv->view_profile_link);
+		gtk_widget_hide (gtk_widget_get_parent (page->priv->profile_image));
 	}
 }
 
 static void
 recent_tracks_updated_cb (RBAudioscrobblerUser *user,
                           GPtrArray *recent_tracks,
-                          RBAudioscrobblerProfileSource *source)
+                          RBAudioscrobblerProfilePage *page)
 {
-	set_user_list (source, source->priv->recent_tracks_table, recent_tracks);
+	set_user_list (page, page->priv->recent_tracks_table, recent_tracks);
 
 	if (recent_tracks != NULL && recent_tracks->len != 0) {
-		gtk_widget_show_all (source->priv->recent_tracks_area);
+		gtk_widget_show_all (page->priv->recent_tracks_area);
 	} else {
-		gtk_widget_hide (source->priv->recent_tracks_area);
+		gtk_widget_hide (page->priv->recent_tracks_area);
 	}
 }
 
 static void
 top_tracks_updated_cb (RBAudioscrobblerUser *user,
                        GPtrArray *top_tracks,
-                       RBAudioscrobblerProfileSource *source)
+                       RBAudioscrobblerProfilePage *page)
 {
-	set_user_list (source, source->priv->top_tracks_table, top_tracks);
+	set_user_list (page, page->priv->top_tracks_table, top_tracks);
 
 	if (top_tracks != NULL && top_tracks->len != 0) {
-		gtk_widget_show_all (source->priv->top_tracks_area);
+		gtk_widget_show_all (page->priv->top_tracks_area);
 	} else {
-		gtk_widget_hide (source->priv->top_tracks_area);
+		gtk_widget_hide (page->priv->top_tracks_area);
 	}
 }
 
 static void
 loved_tracks_updated_cb (RBAudioscrobblerUser *user,
                          GPtrArray *loved_tracks,
-                         RBAudioscrobblerProfileSource *source)
+                         RBAudioscrobblerProfilePage *page)
 {
-	set_user_list (source, source->priv->loved_tracks_table, loved_tracks);
+	set_user_list (page, page->priv->loved_tracks_table, loved_tracks);
 
 	if (loved_tracks != NULL && loved_tracks->len != 0) {
-		gtk_widget_show_all (source->priv->loved_tracks_area);
+		gtk_widget_show_all (page->priv->loved_tracks_area);
 	} else {
-		gtk_widget_hide (source->priv->loved_tracks_area);
+		gtk_widget_hide (page->priv->loved_tracks_area);
 	}
 }
 
 static void
 top_artists_updated_cb (RBAudioscrobblerUser *user,
                         GPtrArray *top_artists,
-                        RBAudioscrobblerProfileSource *source)
+                        RBAudioscrobblerProfilePage *page)
 {
-	set_user_list (source, source->priv->top_artists_table, top_artists);
+	set_user_list (page, page->priv->top_artists_table, top_artists);
 
 	if (top_artists != NULL && top_artists->len != 0) {
-		gtk_widget_show_all (source->priv->top_artists_area);
+		gtk_widget_show_all (page->priv->top_artists_area);
 	} else {
-		gtk_widget_hide (source->priv->top_artists_area);
+		gtk_widget_hide (page->priv->top_artists_area);
 	}
 }
 
 static void
 recommended_artists_updated_cb (RBAudioscrobblerUser *user,
                                 GPtrArray *recommended_artists,
-                                RBAudioscrobblerProfileSource *source)
+                                RBAudioscrobblerProfilePage *page)
 {
-	set_user_list (source, source->priv->recommended_artists_table, recommended_artists);
+	set_user_list (page, page->priv->recommended_artists_table, recommended_artists);
 
 	if (recommended_artists != NULL && recommended_artists->len != 0) {
-		gtk_widget_show_all (source->priv->recommended_artists_area);
+		gtk_widget_show_all (page->priv->recommended_artists_area);
 	} else {
-		gtk_widget_hide (source->priv->recommended_artists_area);
+		gtk_widget_hide (page->priv->recommended_artists_area);
 	}
 }
 
@@ -1455,7 +1452,7 @@ recommended_artists_updated_cb (RBAudioscrobblerUser *user,
  * eg user's top tracks or recommended artists
  */
 static void
-set_user_list (RBAudioscrobblerProfileSource *source,
+set_user_list (RBAudioscrobblerProfilePage *page,
                GtkWidget *list_table,
                GPtrArray *list_data)
 {
@@ -1466,9 +1463,9 @@ set_user_list (RBAudioscrobblerProfileSource *source,
 	     button_node != NULL;
 	     button_node = g_list_next (button_node)) {
 		GtkMenu *menu;
-		menu = g_hash_table_lookup (source->priv->button_to_popup_menu_map, button_node->data);
-		g_hash_table_remove (source->priv->button_to_popup_menu_map, button_node->data);
-		g_hash_table_remove (source->priv->popup_menu_to_data_map, menu);
+		menu = g_hash_table_lookup (page->priv->button_to_popup_menu_map, button_node->data);
+		g_hash_table_remove (page->priv->button_to_popup_menu_map, button_node->data);
+		g_hash_table_remove (page->priv->popup_menu_to_data_map, menu);
 		gtk_widget_destroy (button_node->data);
 	}
 
@@ -1500,11 +1497,11 @@ set_user_list (RBAudioscrobblerProfileSource *source,
 			GtkWidget *menu;
 
 			data = g_ptr_array_index (list_data, i);
-			button = create_list_button (source, data, max_image_width);
-			menu = create_popup_menu (source, data);
+			button = create_list_button (page, data, max_image_width);
+			menu = create_popup_menu (page, data);
 
-			g_hash_table_insert (source->priv->button_to_popup_menu_map, button, g_object_ref_sink (menu));
-			g_hash_table_insert (source->priv->popup_menu_to_data_map, menu, data);
+			g_hash_table_insert (page->priv->button_to_popup_menu_map, button, g_object_ref_sink (menu));
+			g_hash_table_insert (page->priv->popup_menu_to_data_map, menu, data);
 
 			list_table_pack_start (GTK_TABLE (list_table), button);
 		}
@@ -1513,7 +1510,7 @@ set_user_list (RBAudioscrobblerProfileSource *source,
 
 /* creates a button for use in a list */
 static GtkWidget *
-create_list_button (RBAudioscrobblerProfileSource *source,
+create_list_button (RBAudioscrobblerProfilePage *page,
                     RBAudioscrobblerUserData *data,
                     int max_sibling_image_width)
 {
@@ -1593,14 +1590,14 @@ create_list_button (RBAudioscrobblerProfileSource *source,
 	g_signal_connect (button,
 		          "clicked",
 		          G_CALLBACK (list_item_clicked_cb),
-		          source);
+		          page);
 
 	return button;
 }
 
 /* creates a menu to be popped up when a button is clicked */
 static GtkWidget *
-create_popup_menu (RBAudioscrobblerProfileSource *source,
+create_popup_menu (RBAudioscrobblerProfilePage *page,
                    RBAudioscrobblerUserData *data)
 {
 	GtkWidget *menu;
@@ -1616,12 +1613,12 @@ create_popup_menu (RBAudioscrobblerProfileSource *source,
 		 * This is the label for menu item which when activated will take the user to the
 		 * artist/track's page on the service's website. */
 		item_text = g_strdup_printf (_("_View on %s"),
-		                             rb_audioscrobbler_service_get_name (source->priv->service));
+		                             rb_audioscrobbler_service_get_name (page->priv->service));
 		view_url_item = gtk_menu_item_new_with_mnemonic (item_text);
 		g_signal_connect (view_url_item,
 				  "activate",
 				  G_CALLBACK (list_item_view_url_activated_cb),
-				  source);
+				  page);
 
 		gtk_menu_shell_append (GTK_MENU_SHELL (menu), view_url_item);
 		g_free (item_text);
@@ -1636,7 +1633,7 @@ create_popup_menu (RBAudioscrobblerProfileSource *source,
 		g_signal_connect (similar_artists_item,
 				  "activate",
 				  G_CALLBACK (list_item_listen_similar_artists_activated_cb),
-				  source);
+				  page);
 
 		gtk_menu_shell_append (GTK_MENU_SHELL (menu), similar_artists_item);
 	}
@@ -1650,7 +1647,7 @@ create_popup_menu (RBAudioscrobblerProfileSource *source,
 		g_signal_connect (top_fans_item,
 				  "activate",
 				  G_CALLBACK (list_item_listen_top_fans_activated_cb),
-				  source);
+				  page);
 
 		gtk_menu_shell_append (GTK_MENU_SHELL (menu), top_fans_item);
 	}
@@ -1769,11 +1766,11 @@ list_table_size_allocate_cb (GtkWidget *table,
 
 /* popup the appropriate menu */
 static void
-list_item_clicked_cb (GtkButton *button, RBAudioscrobblerProfileSource *source)
+list_item_clicked_cb (GtkButton *button, RBAudioscrobblerProfilePage *page)
 {
 	GtkWidget *menu;
 
-	menu = g_hash_table_lookup (source->priv->button_to_popup_menu_map, button);
+	menu = g_hash_table_lookup (page->priv->button_to_popup_menu_map, button);
 
 	/* show menu if it has any items in it */
 	if (g_list_length (gtk_container_get_children (GTK_CONTAINER (menu))) != 0) {
@@ -1783,13 +1780,13 @@ list_item_clicked_cb (GtkButton *button, RBAudioscrobblerProfileSource *source)
 
 static void
 list_item_view_url_activated_cb (GtkMenuItem *menuitem,
-                                 RBAudioscrobblerProfileSource *source)
+                                 RBAudioscrobblerProfilePage *page)
 {
 	GtkWidget *menu;
 	RBAudioscrobblerUserData *data;
 
 	menu = gtk_widget_get_parent (GTK_WIDGET (menuitem));
-	data = g_hash_table_lookup (source->priv->popup_menu_to_data_map, menu);
+	data = g_hash_table_lookup (page->priv->popup_menu_to_data_map, menu);
 
 	/* some urls are given to us without the http:// prefix */
 	if (g_str_has_prefix (data->url, "http://";) == TRUE) {
@@ -1804,7 +1801,7 @@ list_item_view_url_activated_cb (GtkMenuItem *menuitem,
 
 static void
 list_item_listen_similar_artists_activated_cb (GtkMenuItem *menuitem,
-                                               RBAudioscrobblerProfileSource *source)
+                                               RBAudioscrobblerProfilePage *page)
 {
 	GtkWidget *menu;
 	RBAudioscrobblerUserData *data;
@@ -1813,10 +1810,10 @@ list_item_listen_similar_artists_activated_cb (GtkMenuItem *menuitem,
 	char *radio_name;
 	RBSource *radio;
 	RBShell *shell;
-	RBSourceList *sourcelist;
+	RBDisplayPageTree *page_tree;
 
 	menu = gtk_widget_get_parent (GTK_WIDGET (menuitem));
-	data = g_hash_table_lookup (source->priv->popup_menu_to_data_map, menu);
+	data = g_hash_table_lookup (page->priv->popup_menu_to_data_map, menu);
 	if (data->type == RB_AUDIOSCROBBLER_USER_DATA_TYPE_ARTIST) {
 		artist = data->artist.name;
 	} else if (data->type == RB_AUDIOSCROBBLER_USER_DATA_TYPE_TRACK) {
@@ -1828,20 +1825,20 @@ list_item_listen_similar_artists_activated_cb (GtkMenuItem *menuitem,
 	radio_name = g_strdup_printf (rb_audioscrobbler_radio_type_get_default_name (RB_AUDIOSCROBBLER_RADIO_TYPE_SIMILAR_ARTISTS),
 	                              artist);
 
-	radio = add_radio_station (source, radio_url, radio_name);
-	g_object_get (source, "shell", &shell, NULL);
-	g_object_get (shell, "sourcelist", &sourcelist, NULL);
-	rb_sourcelist_select (sourcelist, radio);
+	radio = add_radio_station (page, radio_url, radio_name);
+	g_object_get (page, "shell", &shell, NULL);
+	g_object_get (shell, "display-page-tree", &page_tree, NULL);
+	rb_display_page_tree_select (page_tree, RB_DISPLAY_PAGE (radio));
 
 	g_free (radio_url);
 	g_free (radio_name);
 	g_object_unref (shell);
-	g_object_unref (sourcelist);
+	g_object_unref (page_tree);
 }
 
 static void
 list_item_listen_top_fans_activated_cb (GtkMenuItem *menuitem,
-                                        RBAudioscrobblerProfileSource *source)
+                                        RBAudioscrobblerProfilePage *page)
 {
 	GtkWidget *menu;
 	RBAudioscrobblerUserData *data;
@@ -1850,10 +1847,10 @@ list_item_listen_top_fans_activated_cb (GtkMenuItem *menuitem,
 	char *radio_name;
 	RBSource *radio;
 	RBShell *shell;
-	RBSourceList *sourcelist;
+	RBDisplayPageTree *page_tree;
 
 	menu = gtk_widget_get_parent (GTK_WIDGET (menuitem));
-	data = g_hash_table_lookup (source->priv->popup_menu_to_data_map, menu);
+	data = g_hash_table_lookup (page->priv->popup_menu_to_data_map, menu);
 	if (data->type == RB_AUDIOSCROBBLER_USER_DATA_TYPE_ARTIST) {
 		artist = data->artist.name;
 	} else if (data->type == RB_AUDIOSCROBBLER_USER_DATA_TYPE_TRACK) {
@@ -1865,76 +1862,76 @@ list_item_listen_top_fans_activated_cb (GtkMenuItem *menuitem,
 	radio_name = g_strdup_printf (rb_audioscrobbler_radio_type_get_default_name (RB_AUDIOSCROBBLER_RADIO_TYPE_TOP_FANS),
 	                              artist);
 
-	radio = add_radio_station (source, radio_url, radio_name);
-	g_object_get (source, "shell", &shell, NULL);
-	g_object_get (shell, "sourcelist", &sourcelist, NULL);
-	rb_sourcelist_select (sourcelist, radio);
+	radio = add_radio_station (page, radio_url, radio_name);
+	g_object_get (page, "shell", &shell, NULL);
+	g_object_get (shell, "display-page-tree", &page_tree, NULL);
+	rb_display_page_tree_select (page_tree, RB_DISPLAY_PAGE (radio));
 
 	g_free (radio_url);
 	g_free (radio_name);
 	g_object_unref (shell);
-	g_object_unref (sourcelist);
+	g_object_unref (page_tree);
 }
 
 static void
-impl_activate (RBSource *asource)
+impl_selected (RBDisplayPage *bpage)
 {
-	RBAudioscrobblerProfileSource *source = RB_AUDIOSCROBBLER_PROFILE_SOURCE (asource);
+	RBAudioscrobblerProfilePage *page = RB_AUDIOSCROBBLER_PROFILE_PAGE (bpage);
 
 	/* attempt to update now and again every 5 minutes */
-	rb_audioscrobbler_user_update (source->priv->user);
-	source->priv->update_timeout_id = g_timeout_add_seconds (300,
-	                                                         (GSourceFunc) update_timeout_cb,
-	                                                         source);
+	rb_audioscrobbler_user_update (page->priv->user);
+	page->priv->update_timeout_id = g_timeout_add_seconds (300,
+							       (GSourceFunc) update_timeout_cb,
+							       page);
 }
 
 static void
-impl_deactivate (RBSource *asource)
+impl_deselected (RBDisplayPage *bpage)
 {
-	RBAudioscrobblerProfileSource *source = RB_AUDIOSCROBBLER_PROFILE_SOURCE (asource);
+	RBAudioscrobblerProfilePage *page = RB_AUDIOSCROBBLER_PROFILE_PAGE (bpage);
 
-	g_source_remove (source->priv->update_timeout_id);
-	source->priv->update_timeout_id = 0;
+	g_source_remove (page->priv->update_timeout_id);
+	page->priv->update_timeout_id = 0;
 }
 
 static GList *
-impl_get_ui_actions (RBSource *asource)
+impl_get_ui_actions (RBDisplayPage *bpage)
 {
-	RBAudioscrobblerProfileSource *source = RB_AUDIOSCROBBLER_PROFILE_SOURCE (asource);
+	RBAudioscrobblerProfilePage *page = RB_AUDIOSCROBBLER_PROFILE_PAGE (bpage);
 	GList *actions = NULL;
 
-	actions = g_list_append (actions, g_strdup (source->priv->love_action_name));
-	actions = g_list_append (actions, g_strdup (source->priv->ban_action_name));
-	actions = g_list_append (actions, g_strdup (source->priv->download_action_name));
+	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 (RBSource *asource)
+impl_show_popup (RBDisplayPage *page)
 {
-	_rb_source_show_popup (asource, AUDIOSCROBBLER_PROFILE_SOURCE_POPUP_PATH);
+	_rb_display_page_show_popup (page, AUDIOSCROBBLER_PROFILE_PAGE_POPUP_PATH);
 	return TRUE;
 }
 
 static void
-impl_delete_thyself (RBSource *asource)
+impl_delete_thyself (RBDisplayPage *bpage)
 {
-	RBAudioscrobblerProfileSource *source;
+	RBAudioscrobblerProfilePage *page;
 	GList *i;
 	GtkUIManager *ui_manager;
 
-	rb_debug ("deleting profile source");
+	rb_debug ("deleting profile page");
 
-	source = RB_AUDIOSCROBBLER_PROFILE_SOURCE (asource);
+	page = RB_AUDIOSCROBBLER_PROFILE_PAGE (bpage);
 
-	for (i = source->priv->radio_sources; i != NULL; i = i->next) {
-		rb_source_delete_thyself (i->data);
+	for (i = page->priv->radio_sources; i != NULL; i = i->next) {
+		rb_display_page_delete_thyself (i->data);
 	}
 
-	g_object_get (source, "ui-manager", &ui_manager, NULL);
-	gtk_ui_manager_remove_ui (ui_manager, source->priv->ui_merge_id);
-	gtk_ui_manager_remove_action_group (ui_manager, source->priv->service_action_group);
+	g_object_get (page, "ui-manager", &ui_manager, NULL);
+	gtk_ui_manager_remove_ui (ui_manager, page->priv->ui_merge_id);
+	gtk_ui_manager_remove_action_group (ui_manager, page->priv->service_action_group);
 
 	g_object_unref (ui_manager);
 }
diff --git a/plugins/audioscrobbler/rb-audioscrobbler-profile-page.h b/plugins/audioscrobbler/rb-audioscrobbler-profile-page.h
new file mode 100644
index 0000000..056281a
--- /dev/null
+++ b/plugins/audioscrobbler/rb-audioscrobbler-profile-page.h
@@ -0,0 +1,71 @@
+/*
+ * rb-audioscrobbler-profile-page.h
+ *
+ * Copyright (C) 2010 Jamie Nicol <jamie thenicols net>
+ *
+ * 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, 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_AUDIOSCROBBLER_PROFILE_PAGE_H
+#define __RB_AUDIOSCROBBLER_PROFILE_PAGE_H
+
+#include <sources/rb-display-page.h>
+#include <sources/rb-source.h>
+#include <shell/rb-shell.h>
+#include <shell/rb-plugin.h>
+
+#include "rb-audioscrobbler-service.h"
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_AUDIOSCROBBLER_PROFILE_PAGE         (rb_audioscrobbler_profile_page_get_type ())
+#define RB_AUDIOSCROBBLER_PROFILE_PAGE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_AUDIOSCROBBLER_PROFILE_PAGE, RBAudioscrobblerProfilePage))
+#define RB_AUDIOSCROBBLER_PROFILE_PAGE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_AUDIOSCROBBLER_PROFILE_PAGE, RBAudioscrobblerProfilePageClass))
+#define RB_IS_AUDIOSCROBBLER_PROFILE_PAGE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_AUDIOSCROBBLER_PROFILE_PAGE))
+#define RB_IS_AUDIOSCROBBLER_PROFILE_PAGE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_AUDIOSCROBBLER_PROFILE_PAGE))
+#define RB_AUDIOSCROBBLER_PROFILE_PAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_AUDIOSCROBBLER_PROFILE_PAGE, RBAudioscrobblerProfilePageClass))
+
+typedef struct _RBAudioscrobblerProfilePagePrivate RBAudioscrobblerProfilePagePrivate;
+
+typedef struct
+{
+	RBDisplayPage parent;
+
+	RBAudioscrobblerProfilePagePrivate *priv;
+} RBAudioscrobblerProfilePage;
+
+typedef struct
+{
+	RBDisplayPageClass parent_class;
+} RBAudioscrobblerProfilePageClass;
+
+GType 		rb_audioscrobbler_profile_page_get_type (void);
+RBDisplayPage  *rb_audioscrobbler_profile_page_new (RBShell *shell,
+						    RBPlugin *plugin,
+						    RBAudioscrobblerService *service);
+void 		rb_audioscrobbler_profile_page_remove_radio_station (RBAudioscrobblerProfilePage *page,
+								     RBSource *station);
+
+G_END_DECLS
+
+#endif /* __RB_AUDIOSCROBBLER_PROFILE_PAGE_H */
diff --git a/plugins/audioscrobbler/rb-audioscrobbler-radio-source.c b/plugins/audioscrobbler/rb-audioscrobbler-radio-source.c
index 6f73575..af57e3a 100644
--- a/plugins/audioscrobbler/rb-audioscrobbler-radio-source.c
+++ b/plugins/audioscrobbler/rb-audioscrobbler-radio-source.c
@@ -45,7 +45,7 @@
 #include "rb-audioscrobbler-radio-track-entry-type.h"
 #include "rb-audioscrobbler-play-order.h"
 #include "rb-debug.h"
-#include "rb-sourcelist.h"
+#include "rb-display-page-tree.h"
 #include "rb-util.h"
 
 
@@ -146,7 +146,7 @@ rb_audioscrobbler_radio_type_get_default_name (RBAudioscrobblerRadioType type)
 /* source declarations */
 struct _RBAudioscrobblerRadioSourcePrivate
 {
-	RBAudioscrobblerProfileSource *parent;
+	RBAudioscrobblerProfilePage *parent;
 
 	RBAudioscrobblerService *service;
 	char *username;
@@ -253,14 +253,16 @@ static void extra_metadata_gather_cb (RhythmDB *db,
                                       RBAudioscrobblerRadioSource *source);
 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);
+
 /* RBSource implementations */
-static void impl_activate (RBSource *source);
 static RBEntryView *impl_get_entry_view (RBSource *asource);
-static void impl_get_status (RBSource *asource, char **text, char **progress_text, float *progress);
 static RBSourceEOFType impl_handle_eos (RBSource *asource);
-static GList *impl_get_ui_actions (RBSource *asource);
-static gboolean impl_show_popup (RBSource *asource);
-static void impl_delete_thyself (RBSource *asource);
 
 enum {
 	PROP_0,
@@ -287,7 +289,7 @@ static GtkActionEntry rb_audioscrobbler_radio_source_actions [] =
 G_DEFINE_TYPE (RBAudioscrobblerRadioSource, rb_audioscrobbler_radio_source, RB_TYPE_STREAMING_SOURCE)
 
 RBSource *
-rb_audioscrobbler_radio_source_new (RBAudioscrobblerProfileSource *parent,
+rb_audioscrobbler_radio_source_new (RBAudioscrobblerProfilePage *parent,
                                     RBAudioscrobblerService *service,
                                     const char *username,
                                     const char *session_key,
@@ -329,6 +331,7 @@ static void
 rb_audioscrobbler_radio_source_class_init (RBAudioscrobblerRadioSourceClass *klass)
 {
 	GObjectClass *object_class;
+	RBDisplayPageClass *page_class;
 	RBSourceClass *source_class;
 
 	object_class = G_OBJECT_CLASS (klass);
@@ -338,26 +341,28 @@ rb_audioscrobbler_radio_source_class_init (RBAudioscrobblerRadioSourceClass *kla
 	object_class->get_property = rb_audioscrobbler_radio_source_get_property;
 	object_class->set_property = rb_audioscrobbler_radio_source_set_property;
 
+	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;
+
 	source_class = RB_SOURCE_CLASS (klass);
 	source_class->impl_can_rename = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_pause = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_try_playlist = (RBSourceFeatureFunc) rb_false_function;
-	source_class->impl_activate = impl_activate;
 	source_class->impl_get_entry_view = impl_get_entry_view;
-	source_class->impl_get_status = impl_get_status;
 	source_class->impl_handle_eos = impl_handle_eos;
-	source_class->impl_get_ui_actions = impl_get_ui_actions;
-	source_class->impl_show_popup = impl_show_popup;
-	source_class->impl_delete_thyself = impl_delete_thyself;
 
 	g_object_class_install_property (object_class,
 	                                 PROP_PARENT,
 	                                 g_param_spec_object ("parent",
 	                                                      "Parent",
-	                                                      "Profile source which created this radio source",
-	                                                      RB_TYPE_AUDIOSCROBBLER_PROFILE_SOURCE,
+	                                                      "Profile page that created this radio source",
+	                                                      RB_TYPE_AUDIOSCROBBLER_PROFILE_PAGE,
                                                               G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
 
 	g_object_class_install_property (object_class,
@@ -497,16 +502,16 @@ rb_audioscrobbler_radio_source_constructed (GObject *object)
 	source->priv->ui_merge_id = gtk_ui_manager_add_ui_from_file (ui_manager, ui_file, NULL);
 
 	/* actions */
-	source->priv->action_group = _rb_source_register_action_group (RB_SOURCE (source),
-								       "AudioscrobblerRadioActions",
-								       NULL, 0,
-								       source);
-	_rb_action_group_add_source_actions (source->priv->action_group,
-					     G_OBJECT (shell),
-					     rb_audioscrobbler_radio_source_actions,
-					     G_N_ELEMENTS (rb_audioscrobbler_radio_source_actions));
+	source->priv->action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
+									     "AudioscrobblerRadioActions",
+									     NULL, 0,
+									     source);
+	_rb_action_group_add_display_page_actions (source->priv->action_group,
+						   G_OBJECT (shell),
+						   rb_audioscrobbler_radio_source_actions,
+						   G_N_ELEMENTS (rb_audioscrobbler_radio_source_actions));
 
-	rb_shell_append_source (shell, RB_SOURCE (source), RB_SOURCE (source->priv->parent));
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (source), RB_DISPLAY_PAGE (source->priv->parent));
 
 	g_object_unref (shell);
 	g_object_unref (db);
@@ -1308,21 +1313,21 @@ static void
 rename_station_action_cb (GtkAction *action, RBAudioscrobblerRadioSource *source)
 {
 	RBShell *shell;
-	RBSourceList *sourcelist;
+	RBDisplayPageTree *page_tree;
 
 	g_object_get (source, "shell", &shell, NULL);
-	g_object_get (shell, "sourcelist", &sourcelist, NULL);
+	g_object_get (shell, "display-page-tree", &page_tree, NULL);
 
-	rb_sourcelist_edit_source_name (sourcelist, RB_SOURCE (source));
+	rb_display_page_tree_edit_source_name (page_tree, RB_SOURCE (source));
 
 	g_object_unref (shell);
-	g_object_unref (sourcelist);
+	g_object_unref (page_tree);
 }
 
 static void
 delete_station_action_cb (GtkAction *action, RBAudioscrobblerRadioSource *source)
 {
-	rb_audioscrobbler_profile_source_remove_radio_station (source->priv->parent, RB_SOURCE (source));
+	rb_audioscrobbler_profile_page_remove_radio_station (source->priv->parent, RB_SOURCE (source));
 }
 
 /* cover art */
@@ -1419,9 +1424,9 @@ emit_coverart_uri_cb (RBAudioscrobblerRadioSource *source)
 }
 
 static void
-impl_activate (RBSource *asource)
+impl_selected (RBDisplayPage *page)
 {
-	RBAudioscrobblerRadioSource *source = RB_AUDIOSCROBBLER_RADIO_SOURCE (asource);
+	RBAudioscrobblerRadioSource *source = RB_AUDIOSCROBBLER_RADIO_SOURCE (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) {
@@ -1438,9 +1443,9 @@ impl_get_entry_view (RBSource *asource)
 }
 
 static void
-impl_get_status (RBSource *asource, char **text, char **progress_text, float *progress)
+impl_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress)
 {
-	RBAudioscrobblerRadioSource *source = RB_AUDIOSCROBBLER_RADIO_SOURCE (asource);
+	RBAudioscrobblerRadioSource *source = RB_AUDIOSCROBBLER_RADIO_SOURCE (page);
 
 	/* pulse progressbar if we're busy, otherwise see what the streaming source part of us has to say */
 	if (source->priv->is_busy) {
@@ -1461,22 +1466,22 @@ impl_handle_eos (RBSource *asource)
 }
 
 static GList *
-impl_get_ui_actions (RBSource *asource)
+impl_get_ui_actions (RBDisplayPage *page)
 {
-	RBAudioscrobblerRadioSource *source = RB_AUDIOSCROBBLER_RADIO_SOURCE (asource);
+	RBAudioscrobblerRadioSource *source = RB_AUDIOSCROBBLER_RADIO_SOURCE (page);
 
-	return rb_source_get_ui_actions (RB_SOURCE (source->priv->parent));
+	return rb_display_page_get_ui_actions (RB_DISPLAY_PAGE (source->priv->parent));
 }
 
 static gboolean
-impl_show_popup (RBSource *asource)
+impl_show_popup (RBDisplayPage *page)
 {
-	_rb_source_show_popup (asource, AUDIOSCROBBLER_RADIO_SOURCE_POPUP_PATH);
+	_rb_display_page_show_popup (page, AUDIOSCROBBLER_RADIO_SOURCE_POPUP_PATH);
 	return TRUE;
 }
 
 static void
-impl_delete_thyself (RBSource *asource)
+impl_delete_thyself (RBDisplayPage *page)
 {
 	RBAudioscrobblerRadioSource *source;
 	RBShell *shell;
@@ -1487,7 +1492,7 @@ impl_delete_thyself (RBSource *asource)
 
 	rb_debug ("deleting radio source");
 
-	source = RB_AUDIOSCROBBLER_RADIO_SOURCE (asource);
+	source = RB_AUDIOSCROBBLER_RADIO_SOURCE (page);
 
 	g_object_get (source, "shell", &shell, "ui-manager", &ui_manager, NULL);
 	g_object_get (shell, "db", &db, NULL);
diff --git a/plugins/audioscrobbler/rb-audioscrobbler-radio-source.h b/plugins/audioscrobbler/rb-audioscrobbler-radio-source.h
index 5468c0b..d4a37f4 100644
--- a/plugins/audioscrobbler/rb-audioscrobbler-radio-source.h
+++ b/plugins/audioscrobbler/rb-audioscrobbler-radio-source.h
@@ -30,7 +30,7 @@
 #define __RB_AUDIOSCROBBLER_RADIO_SOURCE_H
 
 #include "rb-streaming-source.h"
-#include "rb-audioscrobbler-profile-source.h"
+#include "rb-audioscrobbler-profile-page.h"
 #include "rb-audioscrobbler-service.h"
 #include "rb-audioscrobbler-account.h"
 
@@ -76,7 +76,7 @@ typedef struct
 } RBAudioscrobblerRadioSourceClass;
 
 GType rb_audioscrobbler_radio_source_get_type (void);
-RBSource *rb_audioscrobbler_radio_source_new (RBAudioscrobblerProfileSource *parent,
+RBSource *rb_audioscrobbler_radio_source_new (RBAudioscrobblerProfilePage *parent,
                                               RBAudioscrobblerService *service,
                                               const char *username,
                                               const char *session_key,
diff --git a/plugins/brasero-disc-recorder/rb-disc-recorder-plugin.c b/plugins/brasero-disc-recorder/rb-disc-recorder-plugin.c
index 38cfae3..ae4e69a 100644
--- a/plugins/brasero-disc-recorder/rb-disc-recorder-plugin.c
+++ b/plugins/brasero-disc-recorder/rb-disc-recorder-plugin.c
@@ -73,7 +73,7 @@ typedef struct
 	GtkActionGroup *action_group;
 	guint           ui_merge_id;
 
-	RBSource       *selected_source;
+	RBDisplayPage  *selected_page;
 	guint           enabled : 1;
 } RBDiscRecorderPlugin;
 
@@ -173,7 +173,7 @@ rb_disc_recorder_plugin_start_burning (RBDiscRecorderPlugin *pi,
 		g_ptr_array_add (array, "-r");
 	g_ptr_array_add (array, (gpointer) path);
 
-	main_window = gtk_widget_get_toplevel (GTK_WIDGET (pi->selected_source));
+	g_object_get (pi->shell, "window", &main_window, NULL);
 	screen = gtk_widget_get_screen (main_window);
 	window = gtk_widget_get_window (main_window);
 	if (window) {
@@ -494,8 +494,8 @@ static void
 cmd_burn_source (GtkAction            *action,
 		 RBDiscRecorderPlugin *pi)
 {
-	if (pi->selected_source != NULL) {
-		source_burn (pi, pi->selected_source);
+	if (pi->selected_page != NULL && RB_IS_SOURCE (pi->selected_page)) {
+		source_burn (pi, RB_SOURCE (pi->selected_page));
 	}
 }
 
@@ -506,10 +506,10 @@ cmd_duplicate_cd (GtkAction         	*action,
 	gchar *device;
 	GVolume *volume;
 
-	if (!pi->selected_source)
+	if (pi->selected_page == NULL || RB_IS_SOURCE (pi->selected_page) == FALSE)
 		return;
 
-	g_object_get (pi->selected_source, "volume", &volume, NULL);
+	g_object_get (pi->selected_page, "volume", &volume, NULL);
 	if (G_IS_VOLUME (volume))
 		device = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
 	else
@@ -591,25 +591,25 @@ update_source (RBDiscRecorderPlugin *pi,
 {
 	GtkAction *burn_action, *copy_action;
 	gboolean   playlist_active, is_audiocd_active;
-	RBSource  *selected_source;
-	const char *source_type;
+	RBDisplayPage *selected_page;
+	const char *page_type;
 
-	if (pi->selected_source != NULL) {
+	if (pi->selected_page != NULL && RB_IS_SOURCE (pi->selected_page)) {
 		RhythmDBQueryModel *model;
 
-		g_object_get (pi->selected_source, "query-model", &model, NULL);
+		g_object_get (pi->selected_page, "query-model", &model, NULL);
 		g_signal_handlers_disconnect_by_func (model, playlist_row_inserted_cb, pi);
 		g_signal_handlers_disconnect_by_func (model, playlist_entries_changed, pi);
 		g_object_unref (model);
 	}
 
-	g_object_get (shell, "selected-source", &selected_source, NULL);
+	g_object_get (shell, "selected-page", &selected_page, NULL);
 
 	/* for now restrict to playlist sources */
-	playlist_active = RB_IS_PLAYLIST_SOURCE (selected_source);
+	playlist_active = RB_IS_PLAYLIST_SOURCE (selected_page);
 
-	source_type = G_OBJECT_TYPE_NAME (selected_source);
-	is_audiocd_active = g_str_equal (source_type, "RBAudioCdSource");
+	page_type = G_OBJECT_TYPE_NAME (selected_page);
+	is_audiocd_active = g_str_equal (page_type, "RBAudioCdSource");
 
 	burn_action = gtk_action_group_get_action (pi->action_group,
 						   "MusicPlaylistBurnToDiscPlaylist");
@@ -619,7 +619,7 @@ update_source (RBDiscRecorderPlugin *pi,
 	if (pi->enabled && playlist_active && rb_disc_recorder_has_burner (pi)) {
 		RhythmDBQueryModel *model;
 
-		g_object_get (selected_source, "query-model", &model, NULL);
+		g_object_get (selected_page, "query-model", &model, NULL);
 		/* monitor for changes, to enable/disable the burn menu item */
 		g_signal_connect_object (G_OBJECT (model),
 					 "row_inserted",
@@ -643,19 +643,18 @@ update_source (RBDiscRecorderPlugin *pi,
 		gtk_action_set_visible (copy_action, FALSE);
 	}
 
-	if (pi->selected_source != NULL) {
-		g_object_unref (pi->selected_source);
+	if (pi->selected_page != NULL) {
+		g_object_unref (pi->selected_page);
 	}
-	pi->selected_source = selected_source;
+	pi->selected_page = selected_page;
 }
 
 static void
-shell_selected_source_notify_cb (RBShell            *shell,
-				 GParamSpec         *param,
-				 RBDiscRecorderPlugin *pi)
+shell_selected_page_notify_cb (RBShell            *shell,
+			       GParamSpec         *param,
+			       RBDiscRecorderPlugin *pi)
 {
-	rb_debug ("RBDiscRecorderPlugin selected source changed");
-
+	rb_debug ("RBDiscRecorderPlugin selected page changed");
 	update_source (pi, shell);
 }
 
@@ -695,8 +694,8 @@ impl_activate (RBPlugin *plugin,
 		      NULL);
 
 	g_signal_connect_object (G_OBJECT (shell),
-				 "notify::selected-source",
-				 G_CALLBACK (shell_selected_source_notify_cb),
+				 "notify::selected-page",
+				 G_CALLBACK (shell_selected_page_notify_cb),
 				 pi, 0);
 
 	/* add UI */
@@ -755,12 +754,12 @@ impl_deactivate	(RBPlugin *plugin,
 
 	update_source (pi, shell);
 
-	if (pi->selected_source) {
-		g_object_unref (pi->selected_source);
-		pi->selected_source = NULL;
+	if (pi->selected_page) {
+		g_object_unref (pi->selected_page);
+		pi->selected_page = NULL;
 	}
 
-	g_signal_handlers_disconnect_by_func (shell, shell_selected_source_notify_cb, pi);
+	g_signal_handlers_disconnect_by_func (shell, shell_selected_page_notify_cb, pi);
 
 	g_object_get (shell,
 		      "ui-manager", &uimanager,
diff --git a/plugins/daap/Makefile.am b/plugins/daap/Makefile.am
index 8f9ea28..df40b8a 100644
--- a/plugins/daap/Makefile.am
+++ b/plugins/daap/Makefile.am
@@ -22,8 +22,8 @@ libdaap_la_SOURCES = \
 	rb-dmap-container-db-adapter.h		  \
 	rb-daap-dialog.c			  \
 	rb-daap-dialog.h			  \
-	rb-dacp-source.c			  \
-	rb-dacp-source.h			  \
+	rb-dacp-pairing-page.c			  \
+	rb-dacp-pairing-page.h			  \
 	rb-dacp-player.c			  \
 	rb-dacp-player.h			  \
 	rb-rhythmdb-dmap-db-adapter.c             \
diff --git a/plugins/daap/rb-daap-plugin.c b/plugins/daap/rb-daap-plugin.c
index c674c5f..4694ebf 100644
--- a/plugins/daap/rb-daap-plugin.c
+++ b/plugins/daap/rb-daap-plugin.c
@@ -51,8 +51,9 @@
 #include "rb-daap-source.h"
 #include "rb-daap-sharing.h"
 #include "rb-daap-src.h"
-#include "rb-dacp-source.h"
+#include "rb-dacp-pairing-page.h"
 #include "rb-uri-dialog.h"
+#include "rb-display-page-group.h"
 
 #include <libdmapsharing/dmap.h>
 
@@ -301,10 +302,10 @@ impl_activate (RBPlugin *bplugin,
 	gtk_action_group_add_actions (plugin->priv->daap_action_group,
 				      rb_daap_plugin_actions, G_N_ELEMENTS (rb_daap_plugin_actions),
 				      plugin);
-	_rb_action_group_add_source_actions (plugin->priv->daap_action_group,
-					     G_OBJECT (shell),
-					     rb_daap_source_actions,
-					     G_N_ELEMENTS (rb_daap_source_actions));
+	_rb_action_group_add_display_page_actions (plugin->priv->daap_action_group,
+						   G_OBJECT (shell),
+						   rb_daap_source_actions,
+						   G_N_ELEMENTS (rb_daap_source_actions));
 	gtk_ui_manager_insert_action_group (uimanager, plugin->priv->daap_action_group, 0);
 
 	/* add UI */
@@ -534,7 +535,7 @@ mdns_service_added (DMAPMdnsBrowser *browser,
 	if (source == NULL) {
 		source = rb_daap_source_new (plugin->priv->shell, RB_PLUGIN (plugin), service->service_name, service->name, service->host, service->port, service->password_protected);
 		g_hash_table_insert (plugin->priv->source_lookup, g_strdup (service->service_name), source);
-		rb_shell_append_source (plugin->priv->shell, source, NULL);
+		rb_shell_append_display_page (plugin->priv->shell, RB_DISPLAY_PAGE (source), RB_DISPLAY_PAGE_GROUP_SHARED);
 	} else {
 		g_object_set (G_OBJECT (source),
 			      "name", service->name,
@@ -575,7 +576,7 @@ remove_source (RBSource *source)
 	rb_debug ("Removing DAAP source: %s", service_name);
 
 	rb_daap_source_disconnect (RB_DAAP_SOURCE (source));
-	rb_source_delete_thyself (source);
+	rb_display_page_delete_thyself (RB_DISPLAY_PAGE (source));
 
 	g_free (service_name);
 }
diff --git a/plugins/daap/rb-daap-source.c b/plugins/daap/rb-daap-source.c
index 6cde517..47e1ac3 100644
--- a/plugins/daap/rb-daap-source.c
+++ b/plugins/daap/rb-daap-source.c
@@ -52,6 +52,7 @@
 #include "rb-daap-src.h"
 #include "rb-daap-record-factory.h"
 #include "rb-rhythmdb-dmap-db-adapter.h"
+#include "rb-display-page.h"
 
 #include "rb-daap-dialog.h"
 #include "rb-daap-plugin.h"
@@ -69,12 +70,13 @@ static void rb_daap_source_get_property  (GObject *object,
 					  guint prop_id,
 					  GValue *value,
 				 	  GParamSpec *pspec);
-static void rb_daap_source_activate (RBSource *source);
 
-static gboolean rb_daap_source_show_popup (RBSource *source);
+static void rb_daap_source_selected (RBDisplayPage *page);
+static gboolean rb_daap_source_show_popup (RBDisplayPage *page);
+static void rb_daap_source_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress);
+
 static char * rb_daap_source_get_browser_key (RBSource *source);
 static char * rb_daap_source_get_paned_key (RBBrowserSource *source);
-static void rb_daap_source_get_status (RBSource *source, char **text, char **progress_text, float *progress);
 
 #define CONF_STATE_SORTING CONF_PREFIX "/state/daap/sorting"
 #define CONF_STATE_PANED_POSITION CONF_PREFIX "/state/daap/paned_position"
@@ -151,6 +153,7 @@ static void
 rb_daap_source_class_init (RBDAAPSourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 	RBBrowserSourceClass *browser_source_class = RB_BROWSER_SOURCE_CLASS (klass);
 
@@ -159,17 +162,14 @@ rb_daap_source_class_init (RBDAAPSourceClass *klass)
 	object_class->get_property = rb_daap_source_get_property;
 	object_class->set_property = rb_daap_source_set_property;
 
-	source_class->impl_activate = rb_daap_source_activate;
+	page_class->selected = rb_daap_source_selected;
+	page_class->get_status = rb_daap_source_get_status;
+	page_class->show_popup = rb_daap_source_show_popup;
+
 	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_false_function;
-	source_class->impl_paste = NULL;
-	source_class->impl_receive_drag = NULL;
-	source_class->impl_delete = NULL;
-	source_class->impl_show_popup = rb_daap_source_show_popup;
-	source_class->impl_get_config_widget = NULL;
 	source_class->impl_get_browser_key = rb_daap_source_get_browser_key;
-	source_class->impl_get_status = rb_daap_source_get_status;
 
 	browser_source_class->impl_get_paned_key = rb_daap_source_get_paned_key;
 	browser_source_class->impl_has_drop_support = (RBBrowserSourceFeatureFunc) rb_false_function;
@@ -314,12 +314,11 @@ rb_daap_source_new (RBShell *shell,
 					  "host", host,
 					  "port", port,
 					  "entry-type", entry_type,
-					  "icon", icon,
+					  "pixbuf", icon,
 					  "shell", shell,
 					  "visibility", TRUE,
 					  "sorting-key", CONF_STATE_SORTING,
 					  "password-protected", password_protected,
-					  "source-group", RB_SOURCE_GROUP_SHARED,
 					  "plugin", RB_PLUGIN (plugin),
 					  NULL));
 
@@ -491,7 +490,7 @@ connection_connecting_cb (DMAPConnection       *connection,
 
 	source->priv->connection_progress = progress;
 
-	rb_source_notify_status_changed (RB_SOURCE (source));
+	_rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
 
 	is_connected = dmap_connection_is_connected (DMAP_CONNECTION (connection));
 
@@ -528,7 +527,7 @@ connection_disconnected_cb (DMAPConnection   *connection,
 		icon = rb_daap_plugin_get_icon (RB_DAAP_PLUGIN (plugin),
 						source->priv->password_protected,
 						FALSE);
-		g_object_set (source, "icon", icon, NULL);
+		g_object_set (source, "pixbuf", icon, NULL);
 		if (icon != NULL) {
 			g_object_unref (icon);
 		}
@@ -597,7 +596,7 @@ rb_daap_source_connection_cb (DMAPConnection   *connection,
 
 		g_list_foreach (playlist->uris, (GFunc)_add_location_to_playlist, playlist_source);
 
-		rb_shell_append_source (shell, playlist_source, RB_SOURCE (daap_source));
+		rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (playlist_source), RB_DISPLAY_PAGE (daap_source));
 		daap_source->priv->playlist_sources = g_slist_prepend (daap_source->priv->playlist_sources, playlist_source);
 	}
 
@@ -606,9 +605,9 @@ rb_daap_source_connection_cb (DMAPConnection   *connection,
 }
 
 static void
-rb_daap_source_activate (RBSource *source)
+rb_daap_source_selected (RBDisplayPage *page)
 {
-	RBDAAPSource *daap_source = RB_DAAP_SOURCE (source);
+	RBDAAPSource *daap_source = RB_DAAP_SOURCE (page);
 	RBShell *shell = NULL;
 	DMAPRecordFactory *factory;
 	RhythmDB *rdb = NULL;
@@ -631,11 +630,11 @@ rb_daap_source_activate (RBSource *source)
 	factory = DMAP_RECORD_FACTORY (rb_daap_record_factory_new ());
 
 	daap_source->priv->connection = dmap_connection_new (name,
-								daap_source->priv->host,
-								daap_source->priv->port,
-								daap_source->priv->password_protected,
-								db,
-								factory);
+							     daap_source->priv->host,
+							     daap_source->priv->port,
+							     daap_source->priv->password_protected,
+							     db,
+							     factory);
 	g_object_unref (entry_type);
 	g_object_add_weak_pointer (G_OBJECT (daap_source->priv->connection), (gpointer *)&daap_source->priv->connection);
 
@@ -644,22 +643,22 @@ rb_daap_source_activate (RBSource *source)
         g_signal_connect (daap_source->priv->connection,
 			  "authenticate",
                           G_CALLBACK (connection_auth_cb),
-			  source);
+			  page);
         g_signal_connect (daap_source->priv->connection,
 			  "connecting",
                           G_CALLBACK (connection_connecting_cb),
-			  source);
+			  page);
         g_signal_connect (daap_source->priv->connection,
 			  "disconnected",
                           G_CALLBACK (connection_disconnected_cb),
-			  source);
+			  page);
 
 	dmap_connection_connect (DMAP_CONNECTION (daap_source->priv->connection),
-				    (DMAPConnectionCallback) rb_daap_source_connection_cb,
-				    source);
+				 (DMAPConnectionCallback) rb_daap_source_connection_cb,
+				 page);
 
-	g_object_unref (G_OBJECT (rdb));
-	g_object_unref (G_OBJECT (shell));
+	g_object_unref (rdb);
+	g_object_unref (shell);
 }
 
 static void
@@ -712,7 +711,7 @@ rb_daap_source_disconnect (RBDAAPSource *daap_source)
 		rb_debug ("destroying DAAP playlist %s", name);
 		g_free (name);
 
-		rb_source_delete_thyself (playlist_source);
+		rb_display_page_delete_thyself (RB_DISPLAY_PAGE (playlist_source));
 	}
 
 	g_slist_free (daap_source->priv->playlist_sources);
@@ -744,9 +743,9 @@ rb_daap_source_disconnect (RBDAAPSource *daap_source)
 }
 
 static gboolean
-rb_daap_source_show_popup (RBSource *source)
+rb_daap_source_show_popup (RBDisplayPage *page)
 {
-	_rb_source_show_popup (RB_SOURCE (source), "/DAAPSourcePopup");
+	_rb_display_page_show_popup (page, "/DAAPSourcePopup");
 	return TRUE;
 }
 
@@ -775,12 +774,12 @@ rb_daap_source_get_paned_key (RBBrowserSource *source)
 }
 
 static void
-rb_daap_source_get_status (RBSource *source,
-			   char    **text,
-			   char    **progress_text,
-			   float    *progress)
+rb_daap_source_get_status (RBDisplayPage *page,
+			   char **text,
+			   char **progress_text,
+			   float *progress)
 {
-	RBDAAPSource *daap_source = RB_DAAP_SOURCE (source);
+	RBDAAPSource *daap_source = RB_DAAP_SOURCE (page);
 
 	if (daap_source->priv->connection_status != NULL) {
 		if (text != NULL) {
@@ -794,6 +793,5 @@ rb_daap_source_get_status (RBSource *source,
 		return;
 	}
 
-	RB_SOURCE_CLASS (rb_daap_source_parent_class)->impl_get_status (source, text, progress_text, progress);
+	RB_DISPLAY_PAGE_CLASS (rb_daap_source_parent_class)->get_status (page, text, progress_text, progress);
 }
-
diff --git a/plugins/daap/rb-dacp-pairing-page.c b/plugins/daap/rb-dacp-pairing-page.c
new file mode 100644
index 0000000..ad1b0b0
--- /dev/null
+++ b/plugins/daap/rb-dacp-pairing-page.c
@@ -0,0 +1,643 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Implementation of DACP (iTunes Remote) pairing page object
+ *
+ *  Copyright (C) 2010 Alexandre Rosenfeld <alexandre rosenfeld gmail com>
+ *
+ *  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 <string.h>
+#include <ctype.h>
+#include <math.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "rhythmdb.h"
+#include "rb-shell.h"
+#include "rb-display-page-group.h"
+#include "eel-gconf-extensions.h"
+#include "rb-stock-icons.h"
+#include "rb-debug.h"
+#include "rb-util.h"
+#include "rb-file-helpers.h"
+#include "rb-builder-helpers.h"
+#include "rb-dialog.h"
+#include "rb-preferences.h"
+#include "rb-playlist-manager.h"
+#include "rb-shell-player.h"
+#include "rb-display-page-model.h"
+#include "rb-rhythmdb-dmap-db-adapter.h"
+#include "rb-dmap-container-db-adapter.h"
+
+#include "rb-daap-plugin.h"
+#include "rb-daap-sharing.h"
+#include "rb-dacp-player.h"
+
+#include <libdmapsharing/dmap.h>
+
+#include "rb-dacp-pairing-page.h"
+
+static void impl_constructed (GObject *object);
+static void impl_dispose (GObject *object);
+static void impl_set_property  (GObject *object,
+				guint prop_id,
+				const GValue *value,
+				GParamSpec *pspec);
+static void impl_get_property  (GObject *object,
+				guint prop_id,
+				GValue *value,
+				GParamSpec *pspec);
+
+static void rb_dacp_pairing_page_connecting (RBDACPPairingPage *page, gboolean connecting);
+static gboolean entry_insert_text_cb (GtkWidget *entry, gchar *text, gint len, gint *position, RBDACPPairingPage *page);
+static gboolean entry_backspace_cb (GtkWidget *entry, RBDACPPairingPage *page);
+static void remote_paired_cb (DACPShare *share, gchar *service_name, gboolean connected, RBDACPPairingPage *page);
+
+static void dacp_remote_added (DACPShare *share, gchar *service_name, gchar *display_name, RBDaapPlugin *plugin);
+static void dacp_remote_removed (DACPShare *share, gchar *service_name, RBDaapPlugin *plugin);
+
+/* DACPShare signals */
+static gboolean dacp_lookup_guid (DACPShare *share, gchar *guid);
+static void     dacp_add_guid    (DACPShare *share, gchar *guid);
+
+static void dacp_player_updated (RBDACPPlayer *player, DACPShare *share);
+
+struct RBDACPPairingPagePrivate
+{
+	char *service_name;
+
+	gboolean done_pairing;
+
+	DACPShare *dacp_share;
+
+	GtkBuilder *builder;
+	GtkWidget *entries[4];
+	GtkWidget *finished_widget;
+	GtkWidget *pairing_widget;
+	GtkWidget *pairing_status_widget;
+};
+
+enum {
+	PROP_0,
+	PROP_SERVICE_NAME
+};
+
+G_DEFINE_TYPE (RBDACPPairingPage, rb_dacp_pairing_page, RB_TYPE_DISPLAY_PAGE)
+
+static gboolean
+entry_insert_text_cb (GtkWidget *entry, gchar *text, gint len, gint *position, RBDACPPairingPage *page)
+{
+	gchar new_char = text[*position];
+	gint entry_pos = 0;
+	gchar passcode[4];
+	int i;
+
+	/* Find out which entry the user just entered text */
+	for (entry_pos = 0; entry_pos < 4; entry_pos++) {
+		if (entry == page->priv->entries[entry_pos]) {
+			break;
+		}
+	}
+
+	if (!isdigit (new_char)) {
+		/* is this a number? If not, don't let it in */
+		g_signal_stop_emission_by_name (entry, "insert-text");
+		return TRUE;
+	}
+	if (entry_pos < 3) {
+		/* Focus the next entry */
+		gtk_widget_grab_focus (page->priv->entries[entry_pos + 1]);
+	} else if (entry_pos == 3) {
+		/* The user entered all 4 characters of the passcode, so let's pair */
+		for (i = 0; i < 3; i++) {
+			const gchar *text = gtk_entry_get_text (GTK_ENTRY (page->priv->entries[i]));
+			passcode[i] = text[0];
+		}
+		/* The last character is still not in the entry */
+		passcode[3] = new_char;
+		rb_dacp_pairing_page_connecting (page, TRUE);
+		/* Let DACPShare do the heavy-lifting */
+		dacp_share_pair (page->priv->dacp_share,
+		                 page->priv->service_name,
+		                 passcode);
+	}
+	/* let the default handler display the number */
+	return FALSE;
+}
+
+static gboolean
+entry_backspace_cb (GtkWidget *entry, RBDACPPairingPage *page)
+{
+	gint entry_pos = 0;
+
+	/* Find out which entry the user just entered text */
+	for (entry_pos = 0; entry_pos < 4; entry_pos++) {
+		if (entry == page->priv->entries[entry_pos]) {
+			break;
+		}
+	}
+
+	if (entry_pos > 0) {
+		gtk_entry_set_text (GTK_ENTRY (page->priv->entries[entry_pos]), "");
+		/* Focus the previous entry */
+		gtk_widget_grab_focus (page->priv->entries[entry_pos - 1]);
+	}
+
+	return FALSE;
+}
+
+static gboolean
+close_pairing_clicked_cb (GtkButton *button, RBDACPPairingPage *page)
+{
+	rb_display_page_delete_thyself (RB_DISPLAY_PAGE (page));
+	return FALSE;
+}
+
+static void
+impl_dispose (GObject *object)
+{
+	RBDACPPairingPage *page = RB_DACP_PAIRING_PAGE (object);
+
+	if (page->priv->builder != NULL) {
+		g_object_unref (page->priv->builder);
+		page->priv->builder = NULL;
+	}
+
+	if (page->priv->dacp_share != NULL) {
+		g_object_unref (page->priv->dacp_share);
+		page->priv->dacp_share = NULL;
+	}
+
+	G_OBJECT_CLASS (rb_dacp_pairing_page_parent_class)->dispose (object);
+}
+
+static void
+impl_finalize (GObject *object)
+{
+	RBDACPPairingPage *page = RB_DACP_PAIRING_PAGE (object);
+
+	g_free (page->priv->service_name);
+
+	G_OBJECT_CLASS (rb_dacp_pairing_page_parent_class)->finalize (object);
+}
+
+static void
+rb_dacp_pairing_page_class_init (RBDACPPairingPageClass *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->get_property = impl_get_property;
+	object_class->set_property = impl_set_property;
+
+	g_object_class_install_property (object_class,
+					 PROP_SERVICE_NAME,
+					 g_param_spec_string ("service-name",
+							      "Service name",
+							      "mDNS/DNS-SD service name of the share",
+							      NULL,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	g_type_class_add_private (klass, sizeof (RBDACPPairingPagePrivate));
+}
+
+static void
+rb_dacp_pairing_page_init (RBDACPPairingPage *page)
+{
+	page->priv = G_TYPE_INSTANCE_GET_PRIVATE (page,
+						  RB_TYPE_DACP_PAIRING_PAGE,
+						  RBDACPPairingPagePrivate);
+}
+
+static void
+impl_constructed (GObject *object)
+{
+	RBDACPPairingPage *page = RB_DACP_PAIRING_PAGE (object);
+	char *builder_filename;
+	GtkWidget *passcode_widget;
+	GtkWidget *close_pairing_button;
+	PangoFontDescription *font;
+	RBPlugin *plugin;
+	int i;
+
+	g_object_get (page, "plugin", &plugin, NULL);
+
+	builder_filename = rb_plugin_find_file (RB_PLUGIN (plugin), "daap-prefs.ui");
+	g_assert (builder_filename != NULL);
+
+	page->priv->builder = rb_builder_load (builder_filename, NULL);
+	g_free (builder_filename);
+
+	passcode_widget = GTK_WIDGET (gtk_builder_get_object (page->priv->builder, "passcode_widget"));
+	gtk_container_add (GTK_CONTAINER (page), passcode_widget);
+
+	close_pairing_button = GTK_WIDGET (gtk_builder_get_object (page->priv->builder, "close_pairing_button"));
+	g_signal_connect_object (close_pairing_button, "clicked", G_CALLBACK (close_pairing_clicked_cb), page, 0);
+
+	page->priv->finished_widget = GTK_WIDGET (gtk_builder_get_object (page->priv->builder, "finished_widget"));
+	page->priv->pairing_widget = GTK_WIDGET (gtk_builder_get_object (page->priv->builder, "pairing_widget"));
+	page->priv->pairing_status_widget = GTK_WIDGET (gtk_builder_get_object (page->priv->builder, "pairing_status_widget"));
+
+	font = pango_font_description_from_string ("normal 28");
+
+	for (i = 0; i < 4; i++) {
+		char *entry_name;
+
+		entry_name = g_strdup_printf ("passcode_entry%d", i + 1);
+		page->priv->entries[i] = GTK_WIDGET (gtk_builder_get_object (page->priv->builder, entry_name));
+		gtk_widget_modify_font (page->priv->entries[i], font);
+		g_signal_connect_object (page->priv->entries[i],
+		                         "insert-text",
+		                         G_CALLBACK (entry_insert_text_cb),
+		                         page,
+		                         0);
+		g_signal_connect_object (page->priv->entries[i],
+		                         "backspace",
+		                         G_CALLBACK (entry_backspace_cb),
+		                         page,
+		                         0);
+		g_free (entry_name);
+	}
+
+	pango_font_description_free (font);
+
+	gtk_widget_show (passcode_widget);
+
+	g_object_unref (plugin);
+}
+
+static void
+impl_set_property (GObject *object,
+		   guint prop_id,
+		   const GValue *value,
+		   GParamSpec *pspec)
+{
+	RBDACPPairingPage *page = RB_DACP_PAIRING_PAGE (object);
+
+	switch (prop_id) {
+	case PROP_SERVICE_NAME:
+		page->priv->service_name = g_value_dup_string (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+impl_get_property (GObject *object,
+		   guint prop_id,
+		   GValue *value,
+		   GParamSpec *pspec)
+{
+	RBDACPPairingPage *page = RB_DACP_PAIRING_PAGE (object);
+
+	switch (prop_id) {
+	case PROP_SERVICE_NAME:
+		g_value_set_string (value, page->priv->service_name);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+RBDACPPairingPage *
+rb_dacp_pairing_page_new (RBPlugin *plugin,
+			  RBShell *shell,
+			  DACPShare *dacp_share,
+			  const char *display_name,
+			  const char *service_name)
+{
+	RBDACPPairingPage *page;
+	char *icon_filename;
+	int icon_size;
+	GdkPixbuf *icon_pixbuf;
+
+	icon_filename = rb_plugin_find_file (plugin, "remote-icon.png");
+	gtk_icon_size_lookup (GTK_ICON_SIZE_LARGE_TOOLBAR, &icon_size, NULL);
+	icon_pixbuf = gdk_pixbuf_new_from_file_at_size (icon_filename, icon_size, icon_size, NULL);
+
+	page = RB_DACP_PAIRING_PAGE (g_object_new (RB_TYPE_DACP_PAIRING_PAGE,
+						   "name", display_name,
+						   "service-name", service_name,
+						   "pixbuf", icon_pixbuf,
+						   "shell", shell,
+						   "plugin", plugin,
+						   NULL));
+
+	g_object_ref (dacp_share);
+	page->priv->dacp_share = dacp_share;
+	/* Retrieve notifications when the remote is finished pairing */
+	g_signal_connect_object (dacp_share, "remote-paired", G_CALLBACK (remote_paired_cb), page, 0);
+
+	g_free (icon_filename);
+	g_object_unref (icon_pixbuf);
+
+	return page;
+}
+
+
+static void
+rb_dacp_pairing_page_reset_passcode (RBDACPPairingPage *page)
+{
+	int i;
+
+	for (i = 0; i < 4; i++) {
+		gtk_entry_set_text (GTK_ENTRY (page->priv->entries[i]), "");
+	}
+	gtk_widget_grab_focus (page->priv->entries [0]);
+}
+
+void
+rb_dacp_pairing_page_remote_found (RBDACPPairingPage *page)
+{
+	if (page->priv->done_pairing) {
+		rb_dacp_pairing_page_reset_passcode (page);
+		gtk_widget_show (page->priv->pairing_widget);
+		gtk_widget_hide (page->priv->pairing_status_widget);
+		gtk_widget_hide (page->priv->finished_widget);
+		page->priv->done_pairing = FALSE;
+	}
+}
+
+void
+rb_dacp_pairing_page_remote_lost (RBDACPPairingPage *page)
+{
+	if (!page->priv->done_pairing) {
+		rb_display_page_delete_thyself (RB_DISPLAY_PAGE (page));
+	}
+}
+
+static void
+rb_dacp_pairing_page_connecting (RBDACPPairingPage *page, gboolean connecting) {
+	int i;
+
+	if (connecting) {
+		gtk_widget_show (page->priv->pairing_status_widget);
+		gtk_label_set_markup (GTK_LABEL (page->priv->pairing_status_widget), _("Connecting..."));
+	} else {
+		gtk_label_set_markup (GTK_LABEL (page->priv->pairing_status_widget), _("Could not pair with this Remote."));
+	}
+
+	for (i = 0; i < 4; i++) {
+		gtk_widget_set_sensitive (page->priv->entries [i], !connecting);
+	}
+}
+
+static void
+remote_paired_cb (DACPShare *share, gchar *service_name, gboolean connected, RBDACPPairingPage *page)
+{
+	/* Check if this remote is the remote paired */
+	if (g_strcmp0 (service_name, page->priv->service_name) != 0)
+		return;
+
+	rb_dacp_pairing_page_connecting (page, FALSE);
+	if (connected) {
+		gtk_widget_hide (page->priv->pairing_widget);
+		gtk_widget_show (page->priv->finished_widget);
+		page->priv->done_pairing = TRUE;
+	} else {
+		gtk_widget_show (page->priv->pairing_status_widget);
+		rb_dacp_pairing_page_reset_passcode (page);
+	}
+}
+
+DACPShare *
+rb_daap_create_dacp_share (RBPlugin *plugin)
+{
+	DACPShare *share;
+	DACPPlayer *player;
+	RhythmDB *rdb;
+	DMAPDb *db;
+	DMAPContainerDb *container_db;
+	RBPlaylistManager *playlist_manager;
+	RBShell *shell;
+	gchar *name;
+
+	g_object_get (plugin, "shell", &shell, NULL);
+
+	g_object_get (shell,
+	              "db", &rdb,
+	              "playlist-manager", &playlist_manager,
+	              NULL);
+	db = DMAP_DB (rb_rhythmdb_dmap_db_adapter_new (rdb, RHYTHMDB_ENTRY_TYPE_SONG));
+	container_db = DMAP_CONTAINER_DB (rb_dmap_container_db_adapter_new (playlist_manager));
+
+	player = DACP_PLAYER (rb_dacp_player_new (shell));
+
+	name = eel_gconf_get_string (CONF_DAAP_SHARE_NAME);
+	if (name == NULL || *name == '\0') {
+		g_free (name);
+		name = rb_daap_sharing_default_share_name ();
+	}
+
+	share = dacp_share_new (name, player, db, container_db);
+
+	g_signal_connect_object (share,
+				 "add-guid",
+				 G_CALLBACK (dacp_add_guid),
+				 RB_DAAP_PLUGIN (plugin),
+				 0);
+	g_signal_connect_object (share,
+				 "lookup-guid",
+				 G_CALLBACK (dacp_lookup_guid),
+				 RB_DAAP_PLUGIN (plugin),
+				 0);
+
+	g_signal_connect_object (share,
+				 "remote-found",
+				 G_CALLBACK (dacp_remote_added),
+				 RB_DAAP_PLUGIN (plugin),
+				 0);
+	g_signal_connect_object (share,
+				 "remote-lost",
+				 G_CALLBACK (dacp_remote_removed),
+				 RB_DAAP_PLUGIN (plugin),
+				 0);
+
+	g_signal_connect_object (player,
+	                         "player-updated",
+	                         G_CALLBACK (dacp_player_updated),
+	                         share,
+	                         0);
+
+	g_object_unref (db);
+	g_object_unref (container_db);
+	g_object_unref (rdb);
+	g_object_unref (playlist_manager);
+	g_object_unref (player);
+
+	return share;
+}
+
+static void
+dacp_player_updated (RBDACPPlayer *player,
+                     DACPShare *share)
+{
+	dacp_share_player_updated (share);
+}
+
+static void
+dacp_add_guid (DACPShare *share,
+               gchar *guid)
+{
+	GSList *known_guids;
+
+	known_guids = eel_gconf_get_string_list (CONF_KNOWN_REMOTES);
+	if (g_slist_find_custom (known_guids, guid, (GCompareFunc) g_strcmp0)) {
+		g_slist_free (known_guids);
+		return;
+	}
+	known_guids = g_slist_insert_sorted (known_guids, guid, (GCompareFunc) g_strcmp0);
+	eel_gconf_set_string_list (CONF_KNOWN_REMOTES, known_guids);
+
+	g_slist_free (known_guids);
+}
+
+static gboolean
+dacp_lookup_guid (DACPShare *share,
+                  gchar *guid)
+{
+	GSList *known_guids;
+	int found;
+
+	known_guids = eel_gconf_get_string_list (CONF_KNOWN_REMOTES);
+	found = g_slist_find_custom (known_guids, guid, (GCompareFunc) g_strcmp0) != NULL;
+
+	g_slist_free (known_guids);
+
+	return found;
+}
+
+typedef struct {
+	const char *name;
+	RBDACPPairingPage *page;
+} FindPage;
+
+static gboolean
+find_dacp_page_foreach (GtkTreeModel *model,
+                        GtkTreePath  *path,
+                        GtkTreeIter  *iter,
+                        FindPage     *fp)
+{
+	gchar *name;
+	RBDisplayPage *page;
+
+	gtk_tree_model_get (model, iter,
+	                    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
+	                    -1);
+	if (page && RB_IS_DACP_PAIRING_PAGE (page)) {
+		g_object_get (page, "service-name", &name, NULL);
+		if (strcmp (name, fp->name) == 0) {
+			fp->page = RB_DACP_PAIRING_PAGE (page);
+		}
+		g_free (name);
+	}
+
+	return (fp->page != NULL);
+}
+
+static RBDACPPairingPage *
+find_dacp_page (RBShell *shell, const gchar *service_name)
+{
+	RBDisplayPageModel *page_model;
+	FindPage find_page;
+
+	find_page.name = service_name;
+
+	g_object_get (shell, "display-page-model", &page_model, NULL);
+
+	gtk_tree_model_foreach (GTK_TREE_MODEL (page_model),
+	                        (GtkTreeModelForeachFunc) find_dacp_page_foreach,
+	                        &find_page);
+
+	return find_page.page;
+}
+
+static void
+dacp_remote_added (DACPShare    *share,
+                   gchar        *service_name,
+                   gchar        *display_name,
+                   RBDaapPlugin *plugin)
+{
+	RBDACPPairingPage *page;
+	RBShell *shell;
+
+	rb_debug ("Remote %s (%s) found", service_name, display_name);
+
+	g_object_get (plugin, "shell", &shell, NULL);
+
+	GDK_THREADS_ENTER ();
+
+	page = find_dacp_page (shell, service_name);
+	if (page == NULL) {
+		RBDisplayPageGroup *page_group;
+
+		page_group = rb_display_page_group_get_by_id ("remotes");
+		if (page_group == NULL) {
+			page_group = rb_display_page_group_new (G_OBJECT (shell),
+								"remotes",
+								_("Remotes"),
+								RB_DISPLAY_PAGE_GROUP_CATEGORY_TRANSIENT);
+			rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (page_group), NULL);
+		}
+
+		page = rb_dacp_pairing_page_new (RB_PLUGIN (plugin), shell, share, display_name, service_name);
+
+		rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (page), RB_DISPLAY_PAGE (page_group));
+	} else {
+		rb_dacp_pairing_page_remote_found (page);
+	}
+
+	GDK_THREADS_LEAVE ();
+}
+
+static void
+dacp_remote_removed (DACPShare       *share,
+                     gchar           *service_name,
+                     RBDaapPlugin    *plugin)
+{
+	RBDACPPairingPage *page;
+	RBShell *shell;
+
+	rb_debug ("Remote '%s' went away", service_name);
+
+	g_object_get (plugin, "shell", &shell, NULL);
+
+	GDK_THREADS_ENTER ();
+
+	page = find_dacp_page (shell, service_name);
+	if (page != NULL) {
+		rb_dacp_pairing_page_remote_lost (page);
+	}
+
+	GDK_THREADS_LEAVE ();
+}
diff --git a/plugins/daap/rb-dacp-source.h b/plugins/daap/rb-dacp-pairing-page.h
similarity index 57%
rename from plugins/daap/rb-dacp-source.h
rename to plugins/daap/rb-dacp-pairing-page.h
index 581d6cf..a8634df 100644
--- a/plugins/daap/rb-dacp-source.h
+++ b/plugins/daap/rb-dacp-pairing-page.h
@@ -1,5 +1,5 @@
 /*
- *  Header for DACP (iTunes Remote) source object
+ *  Header for DACP (iTunes Remote) pairing page object
  *
  *  Copyright (C) 2010 Alexandre Rosenfeld <alexandre rosenfeld gmail com>
  *
@@ -27,11 +27,11 @@
  *
  */
 
-#ifndef __RB_DACP_SOURCE_H
-#define __RB_DACP_SOURCE_H
+#ifndef __RB_DACP_PAIRING_PAGE_H
+#define __RB_DACP_PAIRING_PAGE_H
 
 #include "rb-shell.h"
-#include "rb-source.h"
+#include "rb-display-page.h"
 #include "rb-plugin.h"
 
 #include <libdmapsharing/dmap.h>
@@ -42,38 +42,38 @@ G_BEGIN_DECLS
 #define CONF_DACP_PREFIX  	CONF_PREFIX "/plugins/daap"
 #define CONF_KNOWN_REMOTES 	CONF_DACP_PREFIX "/known_remotes"
 
-#define RB_TYPE_DACP_SOURCE         (rb_dacp_source_get_type ())
-#define RB_DACP_SOURCE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_DACP_SOURCE, RBDACPSource))
-#define RB_DACP_SOURCE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_DACP_SOURCE, RBDACPSourceClass))
-#define RB_IS_DACP_SOURCE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_DACP_SOURCE))
-#define RB_IS_DACP_SOURCE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_DACP_SOURCE))
-#define RB_DACP_SOURCE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_DACP_SOURCE, RBDACPSourceClass))
+#define RB_TYPE_DACP_PAIRING_PAGE         (rb_dacp_pairing_page_get_type ())
+#define RB_DACP_PAIRING_PAGE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_DACP_PAIRING_PAGE, RBDACPPairingPage))
+#define RB_DACP_PAIRING_PAGE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_DACP_PAIRING_PAGE, RBDACPPairingPageClass))
+#define RB_IS_DACP_PAIRING_PAGE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_DACP_PAIRING_PAGE))
+#define RB_IS_DACP_PAIRING_PAGE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_DACP_PAIRING_PAGE))
+#define RB_DACP_PAIRING_PAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_DACP_PAIRING_PAGE, RBDACPPairingPageClass))
 
-typedef struct RBDACPSourcePrivate RBDACPSourcePrivate;
+typedef struct RBDACPPairingPagePrivate RBDACPPairingPagePrivate;
 
 typedef struct {
-	RBSource parent;
+	RBDisplayPage parent;
 
-	RBDACPSourcePrivate *priv;
-} RBDACPSource;
+	RBDACPPairingPagePrivate *priv;
+} RBDACPPairingPage;
 
 typedef struct {
-	RBSourceClass parent;
-} RBDACPSourceClass;
+	RBDisplayPageClass parent;
+} RBDACPPairingPageClass;
 
-GType 		rb_dacp_source_get_type 	(void);
+GType 		rb_dacp_pairing_page_get_type 	(void);
 
-RBDACPSource* rb_dacp_source_new 		(RBPlugin *plugin,
+RBDACPPairingPage *rb_dacp_pairing_page_new 	(RBPlugin *plugin,
 						 RBShell *shell,
 						 DACPShare *dacp_share,
 						 const char *display_name,
 						 const char *service_name);
 
-void           rb_dacp_source_remote_found     (RBDACPSource *source);
-void           rb_dacp_source_remote_lost      (RBDACPSource *source);
+void           rb_dacp_pairing_page_remote_found (RBDACPPairingPage *page);
+void           rb_dacp_pairing_page_remote_lost (RBDACPPairingPage *page);
 
 DACPShare     *rb_daap_create_dacp_share       (RBPlugin *plugin);
 
 G_END_DECLS
 
-#endif /* __RB_DACP_SOURCE_H */
+#endif /* __RB_DACP_PAIRING_PAGE_H */
diff --git a/plugins/fmradio/rb-fm-radio-plugin.c b/plugins/fmradio/rb-fm-radio-plugin.c
index 598a544..3f6b928 100644
--- a/plugins/fmradio/rb-fm-radio-plugin.c
+++ b/plugins/fmradio/rb-fm-radio-plugin.c
@@ -36,6 +36,7 @@
 
 #include "rb-fm-radio-source.h"
 #include "rb-radio-tuner.h"
+#include "rb-display-page-group.h"
 
 #define RB_TYPE_FM_RADIO_PLUGIN         (rb_fm_radio_plugin_get_type ())
 #define RB_FM_RADIO_PLUGIN(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_FM_RADIO_PLUGIN, RBFMRadioPlugin))
@@ -94,7 +95,7 @@ impl_activate (RBPlugin *plugin, RBShell *shell)
 	rb_radio_tuner_set_mute (tuner, TRUE);
 	rb_radio_tuner_update (tuner);
 	pi->source = rb_fm_radio_source_new (shell, tuner);
-	rb_shell_append_source (shell, pi->source, NULL);
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (pi->source), RB_DISPLAY_PAGE_GROUP_LIBRARY);	/* devices? */
 	g_object_unref (tuner);
 
 	filename = rb_plugin_find_file (plugin, "fmradio-ui.xml");
@@ -118,7 +119,7 @@ impl_deactivate (RBPlugin *plugin, RBShell *shell)
 	GtkUIManager *uimanager;
 
 	if (pi->source) {
-		rb_source_delete_thyself (pi->source);
+		rb_display_page_delete_thyself (RB_DISPLAY_PAGE (pi->source));
 		pi->source = NULL;
 	}
 
diff --git a/plugins/fmradio/rb-fm-radio-source.c b/plugins/fmradio/rb-fm-radio-source.c
index 5f488a6..f3c338f 100644
--- a/plugins/fmradio/rb-fm-radio-source.c
+++ b/plugins/fmradio/rb-fm-radio-source.c
@@ -63,8 +63,8 @@ static void playing_entry_changed (RBShellPlayer *player, RhythmDBEntry *entry,
 				   RBFMRadioSource *self);
 
 static void         impl_delete         (RBSource *source);
-static gboolean     impl_show_popup     (RBSource *source);
-static GList       *impl_get_ui_actions (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);
 
 
@@ -93,20 +93,22 @@ static void
 rb_fm_radio_source_class_init (RBFMRadioSourceClass *class)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (class);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (class);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (class);
 
 	object_class->constructed = rb_fm_radio_source_constructed;
 	object_class->dispose = rb_fm_radio_source_dispose;
 
-	g_type_class_add_private (class, sizeof (RBFMRadioSourcePrivate));
+	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;
 	source_class->impl_can_pause = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_delete = impl_delete;
-	source_class->impl_show_popup = impl_show_popup;
 	source_class->impl_get_entry_view = impl_get_entry_view;
-	source_class->impl_get_ui_actions = impl_get_ui_actions;
+
+	g_type_class_add_private (class, sizeof (RBFMRadioSourcePrivate));
 }
 
 static void
@@ -135,8 +137,8 @@ rb_fm_radio_source_constructed (GObject *object)
 		      NULL);
 	g_object_unref (shell);
 
-	self->priv->action_group = _rb_source_register_action_group (
-		RB_SOURCE (self),
+	self->priv->action_group = _rb_display_page_register_action_group (
+		RB_DISPLAY_PAGE (self),
 		"FMRadioActions",
 		rb_fm_radio_source_actions,
 		G_N_ELEMENTS (rb_fm_radio_source_actions),
@@ -280,9 +282,9 @@ rb_fm_radio_source_songs_view_show_popup (RBEntryView *view,
 		return;
 
 	if (over_entry)
-		_rb_source_show_popup (RB_SOURCE (self), "/FMRadioViewPopup");
+		_rb_display_page_show_popup (RB_DISPLAY_PAGE (self), "/FMRadioViewPopup");
 	else
-		_rb_source_show_popup (RB_SOURCE (self), "/FMRadioSourcePopup");
+		_rb_display_page_show_popup (RB_DISPLAY_PAGE (self), "/FMRadioSourcePopup");
 }
 
 void
@@ -410,14 +412,14 @@ impl_delete (RBSource *source)
 }
 
 static gboolean
-impl_show_popup (RBSource *source)
+impl_show_popup (RBDisplayPage *page)
 {
-	_rb_source_show_popup (source, "/FMRadioSourcePopup");
+	_rb_display_page_show_popup (page, "/FMRadioSourcePopup");
 	return TRUE;
 }
 
 static GList *
-impl_get_ui_actions (RBSource *source)
+impl_get_ui_actions (RBDisplayPage *page)
 {
 	return g_list_prepend (NULL, g_strdup ("MusicNewFMRadioStation"));
 }
diff --git a/plugins/generic-player/rb-generic-player-playlist-source.c b/plugins/generic-player/rb-generic-player-playlist-source.c
index db48688..92a330e 100644
--- a/plugins/generic-player/rb-generic-player-playlist-source.c
+++ b/plugins/generic-player/rb-generic-player-playlist-source.c
@@ -436,7 +436,6 @@ rb_generic_player_playlist_source_new (RBShell *shell,
 					  "shell", shell,
 					  "is-local", FALSE,
 					  "entry-type", entry_type,
-					  "source-group", RB_SOURCE_GROUP_DEVICES,
 					  "player-source", player_source,
 					  "playlist-path", playlist_file,
 					  "device-root", device_root,
@@ -550,9 +549,9 @@ impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSp
 }
 
 static gboolean
-impl_show_popup (RBSource *source)
+impl_show_popup (RBDisplayPage *page)
 {
-	_rb_source_show_popup (source, "/GenericPlayerPlaylistSourcePopup");
+	_rb_display_page_show_popup (page, "/GenericPlayerPlaylistSourcePopup");
 	return TRUE;
 }
 
@@ -560,6 +559,7 @@ static void
 rb_generic_player_playlist_source_class_init (RBGenericPlayerPlaylistSourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 	RBPlaylistSourceClass *playlist_class = RB_PLAYLIST_SOURCE_CLASS (klass);
 
@@ -568,7 +568,8 @@ rb_generic_player_playlist_source_class_init (RBGenericPlayerPlaylistSourceClass
 	object_class->get_property = impl_get_property;
 	object_class->set_property = impl_set_property;
 
-	source_class->impl_show_popup = impl_show_popup;
+	page_class->show_popup = impl_show_popup;
+
 	source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_false_function;
 
 	playlist_class->impl_save_contents_to_xml = impl_save_to_xml;
diff --git a/plugins/generic-player/rb-generic-player-plugin.c b/plugins/generic-player/rb-generic-player-plugin.c
index acae670..3462375 100644
--- a/plugins/generic-player/rb-generic-player-plugin.c
+++ b/plugins/generic-player/rb-generic-player-plugin.c
@@ -48,9 +48,9 @@
 #include "rb-generic-player-playlist-source.h"
 #include "rb-file-helpers.h"
 #include "rb-stock-icons.h"
-#include "rb-sourcelist.h"
 #include "rb-nokia770-source.h"
 #include "rb-psp-source.h"
+#include "rb-display-page-tree.h"
 
 
 #define RB_TYPE_GENERIC_PLAYER_PLUGIN		(rb_generic_player_plugin_get_type ())
@@ -147,8 +147,8 @@ static void
 rb_generic_player_plugin_new_playlist (GtkAction *action, RBSource *source)
 {
 	RBShell *shell;
-	RBSourceList *sourcelist;
 	RBSource *playlist;
+	RBDisplayPageTree *page_tree;
 	RhythmDBEntryType *entry_type;
 
 	g_return_if_fail (RB_IS_GENERIC_PLAYER_SOURCE (source));
@@ -164,9 +164,9 @@ rb_generic_player_plugin_new_playlist (GtkAction *action, RBSource *source)
 					       shell,
 					       playlist);
 
-	g_object_get (shell, "sourcelist", &sourcelist, NULL);
-	rb_sourcelist_edit_source_name (sourcelist, playlist);
-	g_object_unref (sourcelist);
+	g_object_get (shell, "display-page-tree", &page_tree, NULL);
+	rb_display_page_tree_edit_source_name (page_tree, playlist);
+	g_object_unref (page_tree);
 
 	g_object_unref (shell);
 }
@@ -177,7 +177,7 @@ rb_generic_player_plugin_delete_playlist (GtkAction *action, RBSource *source)
 	g_return_if_fail (RB_IS_GENERIC_PLAYER_PLAYLIST_SOURCE (source));
 
 	rb_generic_player_playlist_delete_from_player (RB_GENERIC_PLAYER_PLAYLIST_SOURCE (source));
-	rb_source_delete_thyself (source);
+	rb_display_page_delete_thyself (RB_DISPLAY_PAGE (source));
 }
 
 static RBSource *
@@ -196,10 +196,10 @@ create_source_cb (RBRemovableMediaManager *rmm, GMount *mount, MPIDDevice *devic
 		plugin->actions = gtk_action_group_new ("GenericPlayerActions");
 		gtk_action_group_set_translation_domain (plugin->actions, GETTEXT_PACKAGE);
 
-		_rb_action_group_add_source_actions (plugin->actions,
-						     G_OBJECT (plugin->shell),
-						     rb_generic_player_plugin_actions,
-						     G_N_ELEMENTS (rb_generic_player_plugin_actions));
+		_rb_action_group_add_display_page_actions (plugin->actions,
+							   G_OBJECT (plugin->shell),
+							   rb_generic_player_plugin_actions,
+							   G_N_ELEMENTS (rb_generic_player_plugin_actions));
 	}
 
 	if (source) {
@@ -273,7 +273,7 @@ impl_deactivate	(RBPlugin *bplugin,
 
 	g_signal_handlers_disconnect_by_func (G_OBJECT (rmm), create_source_cb, plugin);
 
-	g_list_foreach (plugin->player_sources, (GFunc)rb_source_delete_thyself, NULL);
+	g_list_foreach (plugin->player_sources, (GFunc)rb_display_page_delete_thyself, NULL);
 	g_list_free (plugin->player_sources);
 	plugin->player_sources = NULL;
 
diff --git a/plugins/generic-player/rb-generic-player-source.c b/plugins/generic-player/rb-generic-player-source.c
index 90dfb83..41eeaba 100644
--- a/plugins/generic-player/rb-generic-player-source.c
+++ b/plugins/generic-player/rb-generic-player-source.c
@@ -69,12 +69,13 @@ static void impl_get_property (GObject *object,
 
 static void load_songs (RBGenericPlayerSource *source);
 
-static gboolean impl_show_popup (RBSource *source);
-static void impl_delete_thyself (RBSource *source);
+static gboolean impl_show_popup (RBDisplayPage *page);
+static void impl_delete_thyself (RBDisplayPage *page);
+static void impl_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress);
+
 static gboolean impl_can_paste (RBSource *source);
 static gboolean impl_can_delete (RBSource *source);
 static void impl_delete (RBSource *source);
-static void impl_get_status (RBSource *source, char **text, char **progress_text, float *progress);
 
 static GList* impl_get_mime_types (RBRemovableMediaSource *source);
 static char* impl_build_dest_uri (RBRemovableMediaSource *source,
@@ -138,6 +139,7 @@ static void
 rb_generic_player_source_class_init (RBGenericPlayerSourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 	RBMediaPlayerSourceClass *mps_class = RB_MEDIA_PLAYER_SOURCE_CLASS (klass);
 	RBRemovableMediaSourceClass *rms_class = RB_REMOVABLE_MEDIA_SOURCE_CLASS (klass);
@@ -148,13 +150,14 @@ rb_generic_player_source_class_init (RBGenericPlayerSourceClass *klass)
 	object_class->dispose = impl_dispose;
 	object_class->finalize = impl_finalize;
 
-	source_class->impl_show_popup = impl_show_popup;
-	source_class->impl_delete_thyself = impl_delete_thyself;
+	page_class->show_popup = impl_show_popup;
+	page_class->delete_thyself = impl_delete_thyself;
+	page_class->get_status = impl_get_status;
+
 	source_class->impl_can_delete = impl_can_delete;
 	source_class->impl_delete = impl_delete;
 	source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_paste = impl_can_paste;
-	source_class->impl_get_status = impl_get_status;
 
 	mps_class->impl_get_entries = impl_get_entries;
 	mps_class->impl_get_capacity = impl_get_capacity;
@@ -418,7 +421,6 @@ rb_generic_player_source_new (RBPlugin *plugin, RBShell *shell, GMount *mount, M
 							 "error-entry-type", error_type,
 							 "mount", mount,
 							 "shell", shell,
-							 "source-group", RB_SOURCE_GROUP_DEVICES,
 							 "device-info", device_info,
 							 NULL));
 
@@ -428,28 +430,28 @@ rb_generic_player_source_new (RBPlugin *plugin, RBShell *shell, GMount *mount, M
 }
 
 static void
-impl_delete_thyself (RBSource *source)
+impl_delete_thyself (RBDisplayPage *page)
 {
 	GList *pl;
 	GList *p;
-	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
+	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (page);
 
 	/* take a copy of the list first, as playlist_deleted_cb modifies priv->playlists */
 	pl = g_list_copy (priv->playlists);
 	for (p = pl; p != NULL; p = p->next) {
-		RBSource *playlist = RB_SOURCE (p->data);
-		rb_source_delete_thyself (playlist);
+		RBDisplayPage *playlist_page = RB_DISPLAY_PAGE (p->data);
+		rb_display_page_delete_thyself (playlist_page);
 	}
 	g_list_free (priv->playlists);
 	g_list_free (pl);
 	priv->playlists = NULL;
 
 	if (priv->import_errors != NULL) {
-		rb_source_delete_thyself (priv->import_errors);
+		rb_display_page_delete_thyself (RB_DISPLAY_PAGE (priv->import_errors));
 		priv->import_errors = NULL;
 	}
 
-	RB_SOURCE_CLASS (rb_generic_player_source_parent_class)->impl_delete_thyself (source);
+	RB_DISPLAY_PAGE_CLASS (rb_generic_player_source_parent_class)->delete_thyself (page);
 }
 
 static void
@@ -462,7 +464,7 @@ import_complete_cb (RhythmDBImportJob *job, int total, RBGenericPlayerSource *so
 	GDK_THREADS_ENTER ();
 	
 	g_object_get (source, "shell", &shell, NULL);
-	rb_shell_append_source (shell, priv->import_errors, RB_SOURCE (source));
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (priv->import_errors), RB_DISPLAY_PAGE (source));
 	g_object_unref (shell);
 
 	if (klass->impl_load_playlists)
@@ -471,7 +473,7 @@ import_complete_cb (RhythmDBImportJob *job, int total, RBGenericPlayerSource *so
 	g_object_unref (priv->import_job);
 	priv->import_job = NULL;
 	
-	rb_source_notify_status_changed (RB_SOURCE (source));
+	_rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
 
 	GDK_THREADS_LEAVE ();
 }
@@ -479,7 +481,7 @@ import_complete_cb (RhythmDBImportJob *job, int total, RBGenericPlayerSource *so
 static void
 import_status_changed_cb (RhythmDBImportJob *job, int total, int imported, RBGenericPlayerSource *source)
 {
-	rb_source_notify_status_changed (RB_SOURCE (source));
+	_rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
 }
 
 static void
@@ -577,23 +579,23 @@ rb_generic_player_is_mount_player (GMount *mount, MPIDDevice *device_info)
 }
 
 static gboolean
-impl_show_popup (RBSource *source)
+impl_show_popup (RBDisplayPage *page)
 {
-	_rb_source_show_popup (RB_SOURCE (source), "/GenericPlayerSourcePopup");
+	_rb_display_page_show_popup (page, "/GenericPlayerSourcePopup");
 	return TRUE;
 }
 
 static void
-impl_get_status (RBSource *source, char **text, char **progress_text, float *progress)
+impl_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress)
 {
-	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (source);
+	RBGenericPlayerSourcePrivate *priv = GET_PRIVATE (page);
 
 	/* get default status text first */
-	RB_SOURCE_CLASS (rb_generic_player_source_parent_class)->impl_get_status (source, text, progress_text, progress);
+	RB_DISPLAY_PAGE_CLASS (rb_generic_player_source_parent_class)->get_status (page, text, progress_text, progress);
 
 	/* override with bits of import status */
 	if (priv->import_job != NULL) {
-		_rb_source_set_import_status (source, priv->import_job, progress_text, progress);
+		_rb_source_set_import_status (RB_SOURCE (page), priv->import_job, progress_text, progress);
 	}
 }
 
@@ -623,7 +625,7 @@ rb_generic_player_source_add_playlist (RBGenericPlayerSource *source,
 
 	g_signal_connect_object (playlist, "deleted", G_CALLBACK (playlist_deleted_cb), source, 0);
 
-	rb_shell_append_source (shell, playlist, RB_SOURCE (source));
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (playlist), RB_DISPLAY_PAGE (source));
 }
 
 
@@ -1386,7 +1388,7 @@ impl_remove_playlists (RBMediaPlayerSource *source)
 	for (t = playlists; t != NULL; t = t->next) {
 		RBGenericPlayerPlaylistSource *p = RB_GENERIC_PLAYER_PLAYLIST_SOURCE (t->data);
 		rb_generic_player_playlist_delete_from_player (p);
-		rb_source_delete_thyself (RB_SOURCE (p));
+		rb_display_page_delete_thyself (RB_DISPLAY_PAGE (p));
 	}
 
 	g_list_free (playlists);
diff --git a/plugins/generic-player/rb-nokia770-source.c b/plugins/generic-player/rb-nokia770-source.c
index b0a3198..648dc6c 100644
--- a/plugins/generic-player/rb-nokia770-source.c
+++ b/plugins/generic-player/rb-nokia770-source.c
@@ -112,7 +112,6 @@ rb_nokia770_source_new (RBPlugin *plugin, RBShell *shell, GMount *mount, MPIDDev
 						   "entry-type", entry_type,
 						   "mount", mount,
 						   "shell", shell,
-						   "source-group", RB_SOURCE_GROUP_DEVICES,
 						   "device-info", device_info,
 						   NULL));
 
diff --git a/plugins/generic-player/rb-psp-source.c b/plugins/generic-player/rb-psp-source.c
index 1cdb6c0..eeff992 100644
--- a/plugins/generic-player/rb-psp-source.c
+++ b/plugins/generic-player/rb-psp-source.c
@@ -109,7 +109,6 @@ rb_psp_source_new (RBPlugin *plugin, RBShell *shell, GMount *mount, MPIDDevice *
 					  "entry-type", entry_type,
 					  "mount", mount,
 					  "shell", shell,
-					  "source-group", RB_SOURCE_GROUP_DEVICES,
 					  "device-info", device_info,
 					  NULL));
 
diff --git a/plugins/ipod/rb-ipod-plugin.c b/plugins/ipod/rb-ipod-plugin.c
index 28ff127..b606635 100644
--- a/plugins/ipod/rb-ipod-plugin.c
+++ b/plugins/ipod/rb-ipod-plugin.c
@@ -41,7 +41,6 @@
 #include "rb-ipod-helpers.h"
 #include "rb-removable-media-manager.h"
 #include "rb-media-player-source.h"
-#include "rb-sourcelist.h"
 #include "rb-source.h"
 #include "rb-ipod-source.h"
 #include "rb-ipod-static-playlist-source.h"
@@ -51,6 +50,7 @@
 #include "rb-util.h"
 #include "rb-shell.h"
 #include "rb-stock-icons.h"
+#include "rb-display-page-tree.h"
 
 
 #define RB_TYPE_IPOD_PLUGIN		(rb_ipod_plugin_get_type ())
@@ -173,10 +173,10 @@ impl_activate (RBPlugin *bplugin,
 	plugin->action_group = gtk_action_group_new ("iPodActions");
 	gtk_action_group_set_translation_domain (plugin->action_group,
 						 GETTEXT_PACKAGE);
-	_rb_action_group_add_source_actions (plugin->action_group,
-					     G_OBJECT (shell),
-					     rb_ipod_plugin_actions,
-					     G_N_ELEMENTS (rb_ipod_plugin_actions));
+	_rb_action_group_add_display_page_actions (plugin->action_group,
+						   G_OBJECT (shell),
+						   rb_ipod_plugin_actions,
+						   G_N_ELEMENTS (rb_ipod_plugin_actions));
 	gtk_ui_manager_insert_action_group (uimanager, plugin->action_group, 0);
 	file = rb_plugin_find_file (bplugin, "ipod-ui.xml");
 	plugin->ui_merge_id = gtk_ui_manager_add_ui_from_file (uimanager,
@@ -216,7 +216,7 @@ impl_deactivate	(RBPlugin *bplugin,
 
 	g_signal_handlers_disconnect_by_func (G_OBJECT (rmm), create_source_cb, plugin);
 
-	g_list_foreach (plugin->ipod_sources, (GFunc)rb_source_delete_thyself, NULL);
+	g_list_foreach (plugin->ipod_sources, (GFunc)rb_display_page_delete_thyself, NULL);
 	g_list_free (plugin->ipod_sources);
 	plugin->ipod_sources = NULL;
 
@@ -273,7 +273,7 @@ rb_ipod_plugin_cmd_properties (GtkAction *action, RBSource *source)
 static void
 rb_ipod_plugin_cmd_rename (GtkAction *action, RBSource *source)
 {
-	RBSourceList *sourcelist = NULL;
+	RBDisplayPageTree *page_tree = NULL;
 	RBShell *shell;
 
 	g_return_if_fail (RB_IS_IPOD_SOURCE (source));
@@ -285,28 +285,28 @@ rb_ipod_plugin_cmd_rename (GtkAction *action, RBSource *source)
 	 */
 
 	g_object_get (source, "shell", &shell, NULL);
-	g_object_get (shell, "sourcelist", &sourcelist, NULL);
+	g_object_get (shell, "display-page-tree", &page_tree, NULL);
 
-	rb_sourcelist_edit_source_name (sourcelist, source);
+	rb_display_page_tree_edit_source_name (page_tree, source);
 	/* Once editing is done, notify::name will be fired on the source, and
 	 * we'll catch that in our rename callback
 	 */
-	g_object_unref (sourcelist);
+	g_object_unref (page_tree);
 	g_object_unref (shell);
 }
 
 static void
 rb_ipod_plugin_cmd_playlist_rename (GtkAction *action, RBSource *source)
 {
-	RBSourceList *sourcelist = NULL;
+	RBDisplayPageTree *page_tree = NULL;
 	RBShell *shell;
 
 	g_return_if_fail (RB_IS_IPOD_STATIC_PLAYLIST_SOURCE (source));
 
 	g_object_get (source, "shell", &shell, NULL);
-	g_object_get (shell, "sourcelist", &sourcelist, NULL);
-	rb_sourcelist_edit_source_name (sourcelist, source);
-	g_object_unref (sourcelist);
+	g_object_get (shell, "display-page-tree", &page_tree, NULL);
+	rb_display_page_tree_edit_source_name (page_tree, source);
+	g_object_unref (page_tree);
 	g_object_unref (shell);
 }
 
diff --git a/plugins/ipod/rb-ipod-source.c b/plugins/ipod/rb-ipod-source.c
index 6e70494..5fab96a 100644
--- a/plugins/ipod/rb-ipod-source.c
+++ b/plugins/ipod/rb-ipod-source.c
@@ -66,11 +66,12 @@ static void rb_ipod_source_dispose (GObject *object);
 static char *impl_get_browser_key (RBSource *source);
 static char *impl_get_paned_key (RBBrowserSource *source);
 
-static gboolean impl_show_popup (RBSource *source);
 static void impl_delete (RBSource *asource);
 static void rb_ipod_load_songs (RBiPodSource *source);
-static void impl_delete_thyself (RBSource *source);
-static GList* impl_get_ui_actions (RBSource *source);
+
+static gboolean impl_show_popup (RBDisplayPage *page);
+static void impl_delete_thyself (RBDisplayPage *page);
+static GList* impl_get_ui_actions (RBDisplayPage *page);
 
 static GList * impl_get_mime_types (RBRemovableMediaSource *source);
 static gboolean impl_track_added (RBRemovableMediaSource *source,
@@ -161,6 +162,7 @@ static void
 rb_ipod_source_class_init (RBiPodSourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 	RBMediaPlayerSourceClass *mps_class = RB_MEDIA_PLAYER_SOURCE_CLASS (klass);
 	RBRemovableMediaSourceClass *rms_class = RB_REMOVABLE_MEDIA_SOURCE_CLASS (klass);
@@ -172,13 +174,14 @@ rb_ipod_source_class_init (RBiPodSourceClass *klass)
 	object_class->set_property = rb_ipod_source_set_property;
 	object_class->get_property = rb_ipod_source_get_property;
 
+	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_get_browser_key  = impl_get_browser_key;
-	source_class->impl_show_popup = impl_show_popup;
-	source_class->impl_delete_thyself = impl_delete_thyself;
 	source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_rename = (RBSourceFeatureFunc) rb_true_function;
-	source_class->impl_get_ui_actions = impl_get_ui_actions;
 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_delete = impl_delete;
 
@@ -390,7 +393,6 @@ rb_ipod_source_new (RBPlugin *plugin,
 					       "entry-type", entry_type,
 					       "mount", mount,
 					       "shell", shell,
-					       "source-group", RB_SOURCE_GROUP_DEVICES,
 					       "device-info", device_info,
 					       NULL));
 
@@ -610,8 +612,8 @@ set_podcast_icon (RBIpodStaticPlaylistSource *source)
 					   0, NULL);
 
 	if (pixbuf != NULL) {
-	    rb_source_set_pixbuf (RB_SOURCE (source), pixbuf);
-	    g_object_unref (pixbuf);
+		g_object_set (source, "pixbuf", pixbuf, NULL);
+		g_object_unref (pixbuf);
 	}
 }
 
@@ -674,7 +676,7 @@ add_rb_playlist (RBiPodSource *source, Itdb_Playlist *playlist)
 		priv->podcast_pl = playlist_source;
 		set_podcast_icon (playlist_source);
 	}
-	rb_shell_append_source (shell, RB_SOURCE (playlist_source), RB_SOURCE (source));
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (playlist_source), RB_DISPLAY_PAGE (source));
 	g_object_unref (shell);
 
 	return playlist_source;
@@ -1217,7 +1219,7 @@ rb_ipod_load_songs (RBiPodSource *source)
 }
 
 static GList*
-impl_get_ui_actions (RBSource *source)
+impl_get_ui_actions (RBDisplayPage *page)
 {
 	GList *actions = NULL;
 
@@ -1240,9 +1242,9 @@ impl_get_paned_key (RBBrowserSource *source)
 }
 
 static gboolean
-impl_show_popup (RBSource *source)
+impl_show_popup (RBDisplayPage *page)
 {
-	_rb_source_show_popup (RB_SOURCE (source), "/iPodSourcePopup");
+	_rb_display_page_show_popup (page, "/iPodSourcePopup");
 	return TRUE;
 }
 
@@ -1374,8 +1376,7 @@ impl_remove_playlists (RBMediaPlayerSource *source)
 		    !playlist->is_spl) {
 
 			/* destroy the playlist source */
-			RBSource *rb_playlist = RB_SOURCE (playlist->userdata);
-			rb_source_delete_thyself (rb_playlist);
+			rb_display_page_delete_thyself (RB_DISPLAY_PAGE (playlist->userdata));
 
 			/* remove playlist from ipod */
 			rb_ipod_db_remove_playlist (priv->ipod_db, playlist);
@@ -1898,21 +1899,21 @@ ipod_path_from_unix_path (const gchar *mount_point, const gchar *unix_path)
 }
 
 static void
-impl_delete_thyself (RBSource *source)
+impl_delete_thyself (RBDisplayPage *page)
 {
-	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
+	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (page);
 	RhythmDB *db;
 	GList *p;
 
-        if (priv->ipod_db == NULL) {
-            RB_SOURCE_CLASS (rb_ipod_source_parent_class)->impl_delete_thyself (source);
-            return;
-        }
+	if (priv->ipod_db == NULL) {
+		RB_DISPLAY_PAGE_CLASS (rb_ipod_source_parent_class)->delete_thyself (page);
+		return;
+	}
 
-	db = get_db_for_source (RB_IPOD_SOURCE (source));
+	db = get_db_for_source (RB_IPOD_SOURCE (page));
 	g_signal_handlers_disconnect_by_func (db,
 					      G_CALLBACK (rb_ipod_song_artwork_add_cb),
-					      RB_IPOD_SOURCE (source));
+					      RB_IPOD_SOURCE (page));
 	g_object_unref (db);
 
 	for (p = rb_ipod_db_get_playlists (priv->ipod_db);
@@ -1932,14 +1933,14 @@ impl_delete_thyself (RBSource *source)
 								  RB_IPOD_STATIC_PLAYLIST_SOURCE (rb_playlist));
 
 			g_object_unref (model);
-			rb_source_delete_thyself (rb_playlist);
+			rb_display_page_delete_thyself (RB_DISPLAY_PAGE (rb_playlist));
 		}
 	}
 
 	g_object_unref (G_OBJECT (priv->ipod_db));
 	priv->ipod_db = NULL;
 
-	RB_SOURCE_CLASS (rb_ipod_source_parent_class)->impl_delete_thyself (source);
+	RB_DISPLAY_PAGE_CLASS (rb_ipod_source_parent_class)->delete_thyself (page);
 }
 
 static GList *
@@ -1978,7 +1979,7 @@ rb_ipod_source_remove_playlist (RBiPodSource *ipod_source,
 	RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (ipod_source);
 	RBIpodStaticPlaylistSource *playlist_source = RB_IPOD_STATIC_PLAYLIST_SOURCE (source);
 
-	rb_source_delete_thyself (source);
+	rb_display_page_delete_thyself (RB_DISPLAY_PAGE (source));
 	rb_ipod_db_remove_playlist (priv->ipod_db, rb_ipod_static_playlist_source_get_itdb_playlist (playlist_source));
 }
 
diff --git a/plugins/ipod/rb-ipod-static-playlist-source.c b/plugins/ipod/rb-ipod-static-playlist-source.c
index b3bfed7..e5fd4a0 100644
--- a/plugins/ipod/rb-ipod-static-playlist-source.c
+++ b/plugins/ipod/rb-ipod-static-playlist-source.c
@@ -45,8 +45,8 @@ static void rb_ipod_static_playlist_source_get_property (GObject *object,
 			                  GValue *value,
 			                  GParamSpec *pspec);
 
-static gboolean impl_show_popup (RBSource *source);
-static void impl_delete_thyself (RBSource *source);
+static gboolean impl_show_popup (RBDisplayPage *page);
+static void impl_delete_thyself (RBDisplayPage *page);
 
 static void source_name_changed_cb (RBIpodStaticPlaylistSource *source,
 				    GParamSpec *spec,
@@ -79,6 +79,7 @@ static void
 rb_ipod_static_playlist_source_class_init (RBIpodStaticPlaylistSourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 
 	object_class->constructed = rb_ipod_static_playlist_source_constructed;
@@ -86,8 +87,9 @@ rb_ipod_static_playlist_source_class_init (RBIpodStaticPlaylistSourceClass *klas
 	object_class->get_property = rb_ipod_static_playlist_source_get_property;
 	object_class->set_property = rb_ipod_static_playlist_source_set_property;
 
-	source_class->impl_show_popup = impl_show_popup;
-	source_class->impl_delete_thyself = impl_delete_thyself;
+	page_class->show_popup = impl_show_popup;
+	page_class->delete_thyself = impl_delete_thyself;
+
 	source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
 
@@ -146,9 +148,9 @@ rb_ipod_static_playlist_source_dispose (GObject *object)
 }
 
 static void
-impl_delete_thyself (RBSource *source)
+impl_delete_thyself (RBDisplayPage *page)
 {
-	RBIpodStaticPlaylistSourcePrivate *priv = IPOD_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (source);
+	RBIpodStaticPlaylistSourcePrivate *priv = IPOD_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (page);
 
 	if (priv->ipod_source) {
 		g_object_unref (priv->ipod_source);
@@ -159,7 +161,7 @@ impl_delete_thyself (RBSource *source)
 		priv->ipod_db = NULL;
 	}
 	
-	RB_SOURCE_CLASS (rb_ipod_static_playlist_source_parent_class)->impl_delete_thyself (source);
+	RB_DISPLAY_PAGE_CLASS (rb_ipod_static_playlist_source_parent_class)->delete_thyself (page);
 }
 
 RBIpodStaticPlaylistSource *
@@ -275,9 +277,9 @@ rb_ipod_static_playlist_source_set_was_reordered (RBIpodStaticPlaylistSource *pl
 }
 
 static gboolean
-impl_show_popup (RBSource *source)
+impl_show_popup (RBDisplayPage *page)
 {
-	_rb_source_show_popup (RB_SOURCE (source), "/iPodPlaylistSourcePopup");
+	_rb_display_page_show_popup (page, "/iPodPlaylistSourcePopup");
 	return TRUE;
 }
 
diff --git a/plugins/iradio/rb-iradio-plugin.c b/plugins/iradio/rb-iradio-plugin.c
index 228735d..0bee52e 100644
--- a/plugins/iradio/rb-iradio-plugin.c
+++ b/plugins/iradio/rb-iradio-plugin.c
@@ -44,6 +44,7 @@
 #include "rb-dialog.h"
 #include "rb-iradio-source.h"
 #include "rb-file-helpers.h"
+#include "rb-display-page-group.h"
 
 
 #define RB_TYPE_IRADIO_PLUGIN		(rb_iradio_plugin_get_type ())
@@ -103,7 +104,7 @@ impl_activate (RBPlugin *plugin,
 	char *filename;
 
 	pi->source = rb_iradio_source_new (shell, plugin);
-	rb_shell_append_source (shell, pi->source, NULL);
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (pi->source), RB_DISPLAY_PAGE_GROUP_LIBRARY);
 
 	g_object_get (shell, "ui-manager", &uimanager, NULL);
 	filename = rb_plugin_find_file (plugin, "iradio-ui.xml");
@@ -130,7 +131,7 @@ impl_deactivate	(RBPlugin *plugin,
 	gtk_ui_manager_remove_ui (uimanager, pi->ui_merge_id);
 	g_object_unref (uimanager);
 
-	rb_source_delete_thyself (pi->source);
+	rb_display_page_delete_thyself (RB_DISPLAY_PAGE (pi->source));
 	pi->source = NULL;
 }
 
diff --git a/plugins/iradio/rb-iradio-source.c b/plugins/iradio/rb-iradio-source.c
index 3a8281d..41e40cf 100644
--- a/plugins/iradio/rb-iradio-source.c
+++ b/plugins/iradio/rb-iradio-source.c
@@ -96,15 +96,17 @@ static void genre_selection_reset_cb (RBPropertyView *propview, RBIRadioSource *
 static void rb_iradio_source_songs_view_sort_order_changed_cb (RBEntryView *view, RBIRadioSource *source);
 static char *guess_uri_scheme (const char *uri);
 
+/* 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 */
-static void impl_get_status (RBSource *source, char **text, char **progress_text, float *progress);
 static char *impl_get_browser_key (RBSource *source);
 static RBEntryView *impl_get_entry_view (RBSource *source);
 static void impl_search (RBSource *source, RBSourceSearch *search, const char *cur_text, const char *new_text);
 static void impl_delete (RBSource *source);
 static void impl_song_properties (RBSource *source);
-static gboolean impl_show_popup (RBSource *source);
-static GList *impl_get_ui_actions (RBSource *source);
 static guint impl_want_uri (RBSource *source, const char *uri);
 static void impl_add_uri (RBSource *source,
 			  const char *uri,
@@ -189,6 +191,7 @@ static void
 rb_iradio_source_class_init (RBIRadioSourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 
 	object_class->dispose = rb_iradio_source_dispose;
@@ -197,6 +200,10 @@ rb_iradio_source_class_init (RBIRadioSourceClass *klass)
 	object_class->set_property = rb_iradio_source_set_property;
 	object_class->get_property = rb_iradio_source_get_property;
 
+	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;
@@ -204,10 +211,7 @@ rb_iradio_source_class_init (RBIRadioSourceClass *klass)
 	source_class->impl_delete = impl_delete;
 	source_class->impl_get_browser_key  = impl_get_browser_key;
 	source_class->impl_get_entry_view = impl_get_entry_view;
-	source_class->impl_get_status  = impl_get_status;
-	source_class->impl_get_ui_actions = impl_get_ui_actions;
 	source_class->impl_search = impl_search;
-	source_class->impl_show_popup = impl_show_popup;
 	source_class->impl_song_properties = impl_song_properties;
 	source_class->impl_want_uri = impl_want_uri;
 	source_class->impl_add_uri = impl_add_uri;
@@ -232,7 +236,7 @@ rb_iradio_source_init (RBIRadioSource *source)
 					   IRADIO_SOURCE_ICON,
 					   size,
 					   0, NULL);
-	rb_source_set_pixbuf (RB_SOURCE (source), pixbuf);
+	g_object_set (source, "pixbuf", pixbuf, NULL);
 	if (pixbuf != NULL) {
 		g_object_unref (pixbuf);
 	}
@@ -302,11 +306,11 @@ rb_iradio_source_constructed (GObject *object)
 		      NULL);
 	g_object_unref (shell);
 
-	source->priv->action_group = _rb_source_register_action_group (RB_SOURCE (source),
-								       "IRadioActions",
-								       rb_iradio_source_actions,
-								       G_N_ELEMENTS (rb_iradio_source_actions),
-								       source);
+	source->priv->action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
+									     "IRadioActions",
+									     rb_iradio_source_actions,
+									     G_N_ELEMENTS (rb_iradio_source_actions),
+									     source);
 
 	action = gtk_action_group_get_action (source->priv->action_group,
                                               "MusicNewInternetRadioStation");
@@ -455,7 +459,6 @@ rb_iradio_source_new (RBShell *shell, RBPlugin *plugin)
 					  "name", _("Radio"),
 					  "shell", shell,
 					  "entry-type", entry_type,
-					  "source-group", RB_SOURCE_GROUP_LIBRARY,
 					  "plugin", plugin,
 					  "search-type", RB_SOURCE_SEARCH_INCREMENTAL,
 					  NULL));
@@ -578,16 +581,16 @@ impl_get_entry_view (RBSource *asource)
 }
 
 static void
-impl_get_status (RBSource *asource,
+impl_get_status (RBDisplayPage *page,
 		 char **text,
 		 char **progress_text,
 		 float *progress)
 {
 	RhythmDBQueryModel *model;
 	guint num_entries;
-	RBIRadioSource *source = RB_IRADIO_SOURCE (asource);
+	RBIRadioSource *source = RB_IRADIO_SOURCE (page);
 
-	g_object_get (asource, "query-model", &model, NULL);
+	g_object_get (source, "query-model", &model, NULL);
 	num_entries = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model), NULL);
 	g_object_unref (model);
 
@@ -729,9 +732,9 @@ rb_iradio_source_songs_show_popup_cb (RBEntryView *view,
 	}
 
 	if (over_entry)
-		_rb_source_show_popup (RB_SOURCE (source), "/IRadioViewPopup");
+		_rb_display_page_show_popup (RB_DISPLAY_PAGE (source), "/IRadioViewPopup");
 	else
-		_rb_source_show_popup (RB_SOURCE (source), "/IRadioSourcePopup");
+		_rb_display_page_show_popup (RB_DISPLAY_PAGE (source), "/IRadioSourcePopup");
 }
 
 static void
@@ -983,14 +986,14 @@ stations_view_drag_data_received_cb (GtkWidget *widget,
 }
 
 static gboolean
-impl_show_popup (RBSource *source)
+impl_show_popup (RBDisplayPage *page)
 {
-	_rb_source_show_popup (RB_SOURCE (source), "/IRadioSourcePopup");
+	_rb_display_page_show_popup (page, "/IRadioSourcePopup");
 	return TRUE;
 }
 
 static GList*
-impl_get_ui_actions (RBSource *source)
+impl_get_ui_actions (RBDisplayPage *page)
 {
 	GList *actions = NULL;
 
diff --git a/plugins/jamendo/jamendo/JamendoSource.py b/plugins/jamendo/jamendo/JamendoSource.py
index 007063d..7ff9ffe 100644
--- a/plugins/jamendo/jamendo/JamendoSource.py
+++ b/plugins/jamendo/jamendo/JamendoSource.py
@@ -110,11 +110,11 @@ class JamendoSource(rb.BrowserSource):
 	def do_impl_show_entry_popup(self):
 		self.show_source_popup ("/JamendoSourceViewPopup")
 
-	def do_impl_get_ui_actions(self):
+	def do_get_ui_actions(self):
 		return ["JamendoDownloadAlbum","JamendoDonateArtist"]
 
 
-	def do_impl_get_status(self):
+	def do_get_status(self):
 		if self.__updating:
 			if self.__load_total_size > 0:
 				progress = min (float(self.__load_current_size) / self.__load_total_size, 1.0)
@@ -125,7 +125,7 @@ class JamendoSource(rb.BrowserSource):
 			qm = self.get_property("query-model")
 			return (qm.compute_status_normal("%d song", "%d songs"), None, 2.0)
 
-	def do_impl_activate(self):
+	def do_selected(self):
 		if not self.__activated:
 			shell = self.get_property('shell')
 			self.__db = shell.get_property('db')
@@ -143,9 +143,9 @@ class JamendoSource(rb.BrowserSource):
 				sort_key = "Artist,ascending"
 			self.get_entry_view().set_sorting_type(sort_key)
 
-		rb.BrowserSource.do_impl_activate (self)
+		rb.BrowserSource.do_selected (self)
 
-	def do_impl_delete_thyself(self):
+	def do_delete_thyself(self):
 		if self.__update_id != 0:
 			gobject.source_remove (self.__update_id)
 			self.__update_id = 0
diff --git a/plugins/magnatune/magnatune/MagnatuneSource.py b/plugins/magnatune/magnatune/MagnatuneSource.py
index 00dbbff..f542767 100644
--- a/plugins/magnatune/magnatune/MagnatuneSource.py
+++ b/plugins/magnatune/magnatune/MagnatuneSource.py
@@ -32,7 +32,7 @@ from BuyAlbumHandler import BuyAlbumHandler, MagnatunePurchaseError
 import os
 import gobject, gio
 import gtk
-import gnome, gconf
+import gconf
 import gnomekeyring as keyring
 import xml
 import urllib
@@ -103,7 +103,7 @@ class MagnatuneSource(rb.BrowserSource):
 	def do_impl_show_entry_popup(self):
 		self.show_source_popup("/MagnatuneSourceViewPopup")
 
-	def do_impl_get_status(self):
+	def do_get_status(self):
 		if self.__updating:
 			complete, total = self.__load_progress
 			if total > 0:
@@ -122,12 +122,12 @@ class MagnatuneSource(rb.BrowserSource):
 			qm = self.get_property("query-model")
 			return (qm.compute_status_normal("%d song", "%d songs"), None, 2.0)
 
-	def do_impl_get_ui_actions(self):
+	def do_get_ui_actions(self):
 		return ["MagnatuneDownloadAlbum",
 			"MagnatuneArtistInfo",
 			"MagnatuneCancelDownload"]
 
-	def do_impl_activate(self):
+	def do_selected(self):
 		if not self.__activated:
 			shell = self.get_property('shell')
 			self.__db = shell.get_property('db')
@@ -146,7 +146,7 @@ class MagnatuneSource(rb.BrowserSource):
 
 			self.get_entry_view().set_sorting_type(self.__client.get_string("/apps/rhythmbox/plugins/magnatune/sorting"))
 
-		rb.BrowserSource.do_impl_activate(self)
+		rb.BrowserSource.do_selected(self)
 
 	def do_impl_get_browser_key(self):
 		return "/apps/rhythmbox/plugins/magnatune/show_browser"
@@ -163,7 +163,7 @@ class MagnatuneSource(rb.BrowserSource):
 		self.__paned_box.pack_start(paned)
 
 
-	def do_impl_delete_thyself(self):
+	def do_delete_thyself(self):
 		if self.__update_id != 0:
 			gobject.source_remove(self.__update_id)
 			self.__update_id = 0
diff --git a/plugins/mtpdevice/rb-mtp-plugin.c b/plugins/mtpdevice/rb-mtp-plugin.c
index 94971be..1100007 100644
--- a/plugins/mtpdevice/rb-mtp-plugin.c
+++ b/plugins/mtpdevice/rb-mtp-plugin.c
@@ -49,7 +49,7 @@
 #endif
 
 #include "rb-source.h"
-#include "rb-sourcelist.h"
+#include "rb-display-page-tree.h"
 #include "rb-mtp-source.h"
 #include "rb-plugin.h"
 #include "rb-debug.h"
@@ -187,10 +187,10 @@ impl_activate (RBPlugin *bplugin, RBShell *shell)
 	plugin->action_group = gtk_action_group_new ("MTPActions");
 	gtk_action_group_set_translation_domain (plugin->action_group,
 						 GETTEXT_PACKAGE);
-	_rb_action_group_add_source_actions (plugin->action_group,
-					     G_OBJECT (plugin->shell),
-					     rb_mtp_plugin_actions,
-					     G_N_ELEMENTS (rb_mtp_plugin_actions));
+	_rb_action_group_add_display_page_actions (plugin->action_group,
+						   G_OBJECT (plugin->shell),
+						   rb_mtp_plugin_actions,
+						   G_N_ELEMENTS (rb_mtp_plugin_actions));
 	gtk_ui_manager_insert_action_group (uimanager, plugin->action_group, 0);
 	file = rb_plugin_find_file (bplugin, "mtp-ui.xml");
 	plugin->ui_merge_id = gtk_ui_manager_add_ui_from_file (uimanager, file, NULL);
@@ -256,7 +256,7 @@ impl_deactivate (RBPlugin *bplugin, RBShell *shell)
 	gtk_ui_manager_remove_ui (uimanager, plugin->ui_merge_id);
 	gtk_ui_manager_remove_action_group (uimanager, plugin->action_group);
 
-	g_list_foreach (plugin->mtp_sources, (GFunc)rb_source_delete_thyself, NULL);
+	g_list_foreach (plugin->mtp_sources, (GFunc)rb_display_page_delete_thyself, NULL);
 	g_list_free (plugin->mtp_sources);
 	plugin->mtp_sources = NULL;
 
@@ -288,16 +288,16 @@ static void
 rb_mtp_plugin_rename (GtkAction *action, RBSource *source)
 {
 	RBShell *shell;
-	RBSourceList *sourcelist;
+	RBDisplayPageTree *page_tree;
 
 	g_return_if_fail (RB_IS_MTP_SOURCE (source));
 
 	g_object_get (source, "shell", &shell, NULL);
-	g_object_get (shell, "sourcelist", &sourcelist, NULL);
+	g_object_get (shell, "display-page-tree", &page_tree, NULL);
 
-	rb_sourcelist_edit_source_name (sourcelist, source);
+	rb_display_page_tree_edit_source_name (page_tree, source);
 
-	g_object_unref (sourcelist);
+	g_object_unref (page_tree);
 	g_object_unref (shell);
 }
 
@@ -421,7 +421,7 @@ rb_mtp_plugin_maybe_add_source (RBMtpPlugin *plugin, const char *udi, LIBMTP_raw
 			rb_debug ("device matched, creating a source");
 			source = RB_SOURCE (rb_mtp_source_new (plugin->shell, RB_PLUGIN (plugin), udi, &raw_devices[i]));
 
-			rb_shell_append_source (plugin->shell, source, NULL);
+			rb_shell_append_source (plugin->shell, source, RB_DISPLAY_PAGE_GROUP_DEVICES);
 			plugin->mtp_sources = g_list_prepend (plugin->mtp_sources, source);
 			g_signal_connect_object (source,
 						"deleted", G_CALLBACK (source_deleted_cb),
diff --git a/plugins/mtpdevice/rb-mtp-source.c b/plugins/mtpdevice/rb-mtp-source.c
index 55c4a04..2782920 100644
--- a/plugins/mtpdevice/rb-mtp-source.c
+++ b/plugins/mtpdevice/rb-mtp-source.c
@@ -81,8 +81,8 @@ static char *impl_get_browser_key (RBSource *source);
 static char *impl_get_paned_key (RBBrowserSource *source);
 
 static void impl_delete (RBSource *asource);
-static gboolean impl_show_popup (RBSource *source);
-static GList* impl_get_ui_actions (RBSource *source);
+static gboolean impl_show_popup (RBDisplayPage *page);
+static GList* impl_get_ui_actions (RBDisplayPage *page);
 
 static GList * impl_get_mime_types (RBRemovableMediaSource *source);
 static gboolean impl_track_added (RBRemovableMediaSource *source,
@@ -182,6 +182,7 @@ static void
 rb_mtp_source_class_init (RBMtpSourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 	RBRemovableMediaSourceClass *rms_class = RB_REMOVABLE_MEDIA_SOURCE_CLASS (klass);
 	RBBrowserSourceClass *browser_source_class = RB_BROWSER_SOURCE_CLASS (klass);
@@ -193,6 +194,9 @@ rb_mtp_source_class_init (RBMtpSourceClass *klass)
 	object_class->set_property = rb_mtp_source_set_property;
 	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_get_browser_key = impl_get_browser_key;
 
@@ -202,9 +206,6 @@ rb_mtp_source_class_init (RBMtpSourceClass *klass)
 	source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
-
-	source_class->impl_show_popup = impl_show_popup;
-	source_class->impl_get_ui_actions = impl_get_ui_actions;
 	source_class->impl_delete = impl_delete;
 
 	browser_source_class->impl_get_paned_key = impl_get_paned_key;
@@ -391,7 +392,7 @@ rb_mtp_source_constructed (GObject *object)
 	gtk_icon_size_lookup (GTK_ICON_SIZE_LARGE_TOOLBAR, &size, NULL);
 	pixbuf = gtk_icon_theme_load_icon (theme, "multimedia-player", size, 0, NULL);
 
-	rb_source_set_pixbuf (RB_SOURCE (source), pixbuf);
+	g_object_set (source, "pixbuf", pixbuf, NULL);
 	g_object_unref (pixbuf);
 
 	if (priv->album_art_supported) {
@@ -589,7 +590,6 @@ rb_mtp_source_new (RBShell *shell,
 					      "shell", shell,
 					      "visibility", TRUE,
 					      "volume", NULL,
-					      "source-group", RB_SOURCE_GROUP_DEVICES,
 					      "raw-device", device,
 #if defined(HAVE_GUDEV)
 					      "udev-device", udev_device,
@@ -873,7 +873,7 @@ device_open_failed_idle (RBMtpSource *source)
 			 _("Unable to open the %s %s device"),
 			 priv->raw_device.device_entry.vendor,
 			 priv->raw_device.device_entry.product);
-	rb_source_delete_thyself (RB_SOURCE (source));
+	rb_display_page_delete_thyself (RB_DISPLAY_PAGE (source));
 	g_object_unref (source);
 	return FALSE;
 }
@@ -881,7 +881,7 @@ device_open_failed_idle (RBMtpSource *source)
 static gboolean
 device_open_ignore_idle (DeviceOpenedData *data)
 {
-	rb_source_delete_thyself (RB_SOURCE (data->source));
+	rb_display_page_delete_thyself (RB_DISPLAY_PAGE (data->source));
 	g_object_unref (data->source);
 	free (data->types);
 	g_free (data->name);
@@ -1032,14 +1032,14 @@ impl_delete (RBSource *source)
 }
 
 static gboolean
-impl_show_popup (RBSource *source)
+impl_show_popup (RBDisplayPage *page)
 {
-	_rb_source_show_popup (RB_SOURCE (source), "/MTPSourcePopup");
+	_rb_display_page_show_popup (page, "/MTPSourcePopup");
 	return TRUE;
 }
 
 static GList *
-impl_get_ui_actions (RBSource *source)
+impl_get_ui_actions (RBDisplayPage *page)
 {
 	GList *actions = NULL;
 
@@ -1698,7 +1698,7 @@ impl_can_eject (RBRemovableMediaSource *source)
 static void
 impl_eject (RBRemovableMediaSource *source)
 {
-	rb_source_delete_thyself (RB_SOURCE (source));
+	rb_display_page_delete_thyself (RB_DISPLAY_PAGE (source));
 }
 
 #if defined(HAVE_GUDEV)
diff --git a/plugins/visualizer/rb-visualizer-plugin.c b/plugins/visualizer/rb-visualizer-plugin.c
index cbeacbb..6f9b57e 100644
--- a/plugins/visualizer/rb-visualizer-plugin.c
+++ b/plugins/visualizer/rb-visualizer-plugin.c
@@ -1125,7 +1125,7 @@ update_window (RBVisualizerPlugin *plugin, VisualizerMode mode, int screen, int
 					      plugin->vis_box);
 
 			if (plugin->mode != mode) {
-				rb_shell_notebook_set_page (plugin->shell, NULL);
+				/*rb_shell_notebook_set_page (plugin->shell, NULL);*/
 			}
 			break;
 
@@ -1423,7 +1423,7 @@ enable_visualization (RBVisualizerPlugin *pi)
 	case EMBEDDED:
 		gtk_widget_show_all (pi->vis_shell);
 		gtk_widget_hide (pi->vis_window);
-		rb_shell_notebook_set_page (pi->shell, pi->vis_shell);
+		/*rb_shell_notebook_set_page (pi->shell, pi->vis_shell);*/
 		break;
 	case FULLSCREEN:
 		gtk_widget_hide (pi->vis_shell);
@@ -1451,7 +1451,7 @@ disable_visualization (RBVisualizerPlugin *pi, gboolean set_action)
 	switch (pi->mode) {
 	case EMBEDDED:
 		gtk_widget_hide (pi->vis_box);
-		rb_shell_notebook_set_page (pi->shell, NULL);
+		/*rb_shell_notebook_set_page (pi->shell, NULL);*/
 		break;
 	case FULLSCREEN:
 		gtk_window_unfullscreen (GTK_WINDOW (pi->vis_window));
@@ -1727,7 +1727,7 @@ impl_activate (RBPlugin *plugin,
 
 	if (pi->vis_shell == NULL) {
 		pi->vis_shell = gtk_vbox_new (FALSE, 0);
-		rb_shell_add_widget (pi->shell, pi->vis_shell, RB_SHELL_UI_LOCATION_MAIN_NOTEBOOK, FALSE, TRUE);
+		/* rb_shell_add_widget (pi->shell, pi->vis_shell, RB_SHELL_UI_LOCATION_MAIN_NOTEBOOK, FALSE, TRUE); */
 	}
 
 	if (pi->vis_window == NULL) {
@@ -1778,7 +1778,7 @@ impl_activate (RBPlugin *plugin,
 
 	pi->selected_source_notify_id =
 		g_signal_connect_object (pi->shell,
-					 "notify::selected-source",
+					 "notify::selected-page",
 					 G_CALLBACK (rb_visualizer_plugin_source_selected_cb),
 					 pi, 0);
 	pi->shell_visibility_change_id =
diff --git a/podcast/rb-podcast-main-source.c b/podcast/rb-podcast-main-source.c
index 6741caa..7b7cd4a 100644
--- a/podcast/rb-podcast-main-source.c
+++ b/podcast/rb-podcast-main-source.c
@@ -71,7 +71,6 @@ rb_podcast_main_source_new (RBShell *shell, RBPodcastManager *podcast_manager)
 					  "name", _("Podcasts"),
 					  "shell", shell,
 					  "entry-type", RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
-					  "source-group", RB_SOURCE_GROUP_LIBRARY,
 					  "search-type", RB_SOURCE_SEARCH_INCREMENTAL,
 					  "podcast-manager", podcast_manager,
 					  "base-query", base_query,
@@ -118,7 +117,7 @@ rb_podcast_main_source_add_subsources (RBPodcastMainSource *source)
 						   RB_STOCK_AUTO_PLAYLIST);
 	rhythmdb_query_free (query);
 	rb_source_set_hidden_when_empty (podcast_subsource, TRUE);
-	rb_shell_append_source (shell, podcast_subsource, RB_SOURCE (source));
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (podcast_subsource), RB_DISPLAY_PAGE (source));
 
 	query = rhythmdb_query_parse (db,
 				      RHYTHMDB_QUERY_PROP_EQUALS,
@@ -136,7 +135,7 @@ rb_podcast_main_source_add_subsources (RBPodcastMainSource *source)
 						   RB_STOCK_AUTO_PLAYLIST);
 	rhythmdb_query_free (query);
 	rb_source_set_hidden_when_empty (podcast_subsource, TRUE);
-	rb_shell_append_source (shell, podcast_subsource, RB_SOURCE (source));
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (podcast_subsource), RB_DISPLAY_PAGE (source));
 
 	g_object_unref (db);
 	g_object_unref (shell);
@@ -258,9 +257,9 @@ rb_podcast_main_source_cb_interval_changed_cb (GtkComboBox *box, gpointer cb_dat
 }
 
 static GtkWidget *
-impl_get_config_widget (RBSource *asource, RBShellPreferences *prefs)
+impl_get_config_widget (RBDisplayPage *page, RBShellPreferences *prefs)
 {
-	RBPodcastMainSource *source = RB_PODCAST_MAIN_SOURCE (asource);
+	RBPodcastMainSource *source = RB_PODCAST_MAIN_SOURCE (page);
 	RBPodcastManager *podcast_mgr;
 	GtkBuilder *builder;
 	GtkWidget *cb_update_interval;
@@ -378,7 +377,7 @@ impl_constructed (GObject *object)
 					   0, NULL);
 
 	if (pixbuf != NULL) {
-		rb_source_set_pixbuf (RB_SOURCE (source), pixbuf);
+		g_object_set (source, "pixbuf", pixbuf, NULL);
 		g_object_unref (pixbuf);
 	}
 }
@@ -409,12 +408,14 @@ static void
 rb_podcast_main_source_class_init (RBPodcastMainSourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 
 	object_class->dispose = impl_dispose;
 	object_class->constructed = impl_constructed;
 
-	source_class->impl_get_config_widget = impl_get_config_widget;
+	page_class->get_config_widget = impl_get_config_widget;
+
 	source_class->impl_want_uri = impl_want_uri;
 	source_class->impl_add_uri = impl_add_uri;
 	
diff --git a/podcast/rb-podcast-source.c b/podcast/rb-podcast-source.c
index 63a5989..94c14ad 100644
--- a/podcast/rb-podcast-source.c
+++ b/podcast/rb-podcast-source.c
@@ -204,7 +204,7 @@ podcast_posts_show_popup_cb (RBEntryView *view,
 	if (G_OBJECT (source) == NULL) {
 		return;
 	} else if (!over_entry) {
-		_rb_source_show_popup (RB_SOURCE (source), "/PodcastSourcePopup");
+		_rb_display_page_show_popup (RB_DISPLAY_PAGE (source), "/PodcastSourcePopup");
 	} else {
 		GtkAction* action;
 		GList *lst;
@@ -236,7 +236,7 @@ podcast_posts_show_popup_cb (RBEntryView *view,
 		action = gtk_action_group_get_action (source->priv->action_group, "PodcastSrcCancelDownload");
 		gtk_action_set_sensitive (action, cancellable);
 
-		_rb_source_show_popup (RB_SOURCE (source), "/PodcastViewPopup");
+		_rb_display_page_show_popup (RB_DISPLAY_PAGE (source), "/PodcastViewPopup");
 	}
 }
 
@@ -268,7 +268,7 @@ podcast_feeds_show_popup_cb (RBPropertyView *view,
 			gtk_action_set_visible (act_delete, FALSE);
 		}
 
-		_rb_source_show_popup (RB_SOURCE (source), "/PodcastFeedViewPopup");
+		_rb_display_page_show_popup (RB_DISPLAY_PAGE (source), "/PodcastFeedViewPopup");
 	}
 }
 
@@ -371,7 +371,7 @@ posts_view_drag_data_received_cb (GtkWidget *widget,
 				  guint time,
 				  RBPodcastSource *source)
 {
-	rb_source_receive_drag (RB_SOURCE (source), selection_data);
+	rb_display_page_receive_drag (RB_DISPLAY_PAGE (source), selection_data);
 }
 
 static void
@@ -966,7 +966,6 @@ rb_podcast_source_new (RBShell *shell,
 					  "name", name,
 					  "shell", shell,
 					  "entry-type", RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
-					  "source-group", RB_SOURCE_GROUP_LIBRARY,
 					  "search-type", RB_SOURCE_SEARCH_INCREMENTAL,
 					  "podcast-manager", podcast_manager,
 					  "base-query", base_query,
@@ -983,7 +982,7 @@ rb_podcast_source_new (RBShell *shell,
 						   0, NULL);
 
 		if (pixbuf != NULL) {
-			rb_source_set_pixbuf (source, pixbuf);
+			g_object_set (source, "pixbuf", pixbuf, NULL);
 			g_object_unref (pixbuf);
 		}
 	}
@@ -1147,7 +1146,7 @@ impl_get_search_actions (RBSource *source)
 }
 
 static GList *
-impl_get_ui_actions (RBSource *source)
+impl_get_ui_actions (RBDisplayPage *page)
 {
 	GList *actions = NULL;
 
@@ -1165,10 +1164,10 @@ impl_handle_eos (RBSource *asource)
 
 
 static gboolean
-impl_receive_drag (RBSource *asource, GtkSelectionData *selection_data)
+impl_receive_drag (RBDisplayPage *page, GtkSelectionData *selection_data)
 {
 	GList *list, *i;
-	RBPodcastSource *source = RB_PODCAST_SOURCE (asource);
+	RBPodcastSource *source = RB_PODCAST_SOURCE (page);
 
 	list = rb_uri_list_parse ((const char *) gtk_selection_data_get_data (selection_data));
 
@@ -1209,9 +1208,9 @@ impl_search (RBSource *asource, RBSourceSearch *search, const char *cur_text, co
 }
 
 static gboolean
-impl_show_popup (RBSource *source)
+impl_show_popup (RBDisplayPage *page)
 {
-	_rb_source_show_popup (RB_SOURCE (source), "/PodcastSourcePopup");
+	_rb_display_page_show_popup (page, "/PodcastSourcePopup");
 	return TRUE;
 }
 
@@ -1225,7 +1224,7 @@ impl_song_properties (RBSource *asource)
 }
 
 static void
-impl_get_status (RBSource *source, char **text, char **progress_text, float *progress)
+impl_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress)
 {
 	RhythmDBQueryModel *query_model;
 
@@ -1234,7 +1233,7 @@ impl_get_status (RBSource *source, char **text, char **progress_text, float *pro
 		ngettext ("%d episode", "%d episodes", 0);
 	}
 
-	g_object_get (source, "query-model", &query_model, NULL);
+	g_object_get (page, "query-model", &query_model, NULL);
 	if (query_model != NULL) {
 		*text = rhythmdb_query_model_compute_status_normal (query_model,
 								    "%d episode",
@@ -1307,15 +1306,15 @@ impl_constructed (GObject *object)
 	g_object_get (source, "shell", &shell, NULL);
 	g_object_get (shell, "db", &source->priv->db, NULL);
 
-	source->priv->action_group = _rb_source_register_action_group (RB_SOURCE (source),
-								       "PodcastActions",
-								       NULL, 0,
-								       source);
+	source->priv->action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
+									     "PodcastActions",
+									     NULL, 0,
+									     source);
 
-	_rb_action_group_add_source_actions (source->priv->action_group,
-					     G_OBJECT (shell),
-					     rb_podcast_source_actions,
-					     G_N_ELEMENTS (rb_podcast_source_actions));
+	_rb_action_group_add_display_page_actions (source->priv->action_group,
+						   G_OBJECT (shell),
+						   rb_podcast_source_actions,
+						   G_N_ELEMENTS (rb_podcast_source_actions));
 
 	action = gtk_action_group_get_action (source->priv->action_group,
 					      "MusicNewPodcast");
@@ -1656,6 +1655,7 @@ static void
 rb_podcast_source_class_init (RBPodcastSourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 
 	object_class->dispose = impl_dispose;
@@ -1664,6 +1664,11 @@ rb_podcast_source_class_init (RBPodcastSourceClass *klass)
 	object_class->set_property = impl_set_property;
 	object_class->get_property = impl_get_property;
 
+	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;
@@ -1674,13 +1679,9 @@ rb_podcast_source_class_init (RBPodcastSourceClass *klass)
 	source_class->impl_get_browser_key  = impl_get_browser_key;
 	source_class->impl_get_entry_view = impl_get_entry_view;
 	source_class->impl_get_search_actions = impl_get_search_actions;
-	source_class->impl_get_ui_actions = impl_get_ui_actions;
 	source_class->impl_handle_eos = impl_handle_eos;
-	source_class->impl_receive_drag = impl_receive_drag;
 	source_class->impl_search = impl_search;
-	source_class->impl_show_popup = impl_show_popup;
 	source_class->impl_song_properties = impl_song_properties;
-	source_class->impl_get_status = impl_get_status;
 	source_class->impl_get_delete_action = impl_get_delete_action;
 
 	g_object_class_install_property (object_class,
diff --git a/shell/rb-playlist-manager.c b/shell/rb-playlist-manager.c
index adc079e..c20292c 100644
--- a/shell/rb-playlist-manager.c
+++ b/shell/rb-playlist-manager.c
@@ -50,8 +50,6 @@
 #include "rb-static-playlist-source.h"
 #include "rb-auto-playlist-source.h"
 #include "rb-play-queue-source.h"
-#include "rb-sourcelist.h"
-#include "rb-sourcelist-model.h"
 #include "rb-query-creator.h"
 #include "totem-pl-parser.h"
 
@@ -108,13 +106,12 @@ struct RBPlaylistManagerPrivate
 
 	char *playlists_file;
 
-	RBSourceList *sourcelist;
+	RBDisplayPageModel *page_model;
+	RBDisplayPageTree *display_page_tree;
 
 	GtkActionGroup *actiongroup;
 	GtkUIManager *uimanager;
 
-	GtkWindow *window;
-
 	RBStaticPlaylistSource *loading_playlist;
 
 	gint dirty;
@@ -128,7 +125,8 @@ enum
 	PROP_PLAYLIST_NAME,
 	PROP_SHELL,
 	PROP_SOURCE,
-	PROP_SOURCELIST,
+	PROP_DISPLAY_PAGE_MODEL,
+	PROP_DISPLAY_PAGE_TREE,
 };
 
 enum
@@ -238,11 +236,18 @@ rb_playlist_manager_class_init (RBPlaylistManagerClass *klass)
 							      G_PARAM_READWRITE));
 
 	g_object_class_install_property (object_class,
-					 PROP_SOURCELIST,
-					 g_param_spec_object ("sourcelist",
-							      "RBSourceList",
-							      "RBSourceList",
-							      RB_TYPE_SOURCELIST,
+					 PROP_DISPLAY_PAGE_MODEL,
+					 g_param_spec_object ("display-page-model",
+							      "RBDisplayPageModel",
+							      "RBDisplayPageModel",
+							      RB_TYPE_DISPLAY_PAGE_MODEL,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (object_class,
+					 PROP_DISPLAY_PAGE_TREE,
+					 g_param_spec_object ("display-page-tree",
+							      "RBDisplayPageTree",
+							      "RBDisplayPageTree",
+							      RB_TYPE_DISPLAY_PAGE_TREE,
 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 	/**
 	 * RBPlaylistManager::playlist-added:
@@ -367,9 +372,14 @@ rb_playlist_manager_dispose (GObject *object)
 		mgr->priv->uimanager = NULL;
 	}
 
-	if (mgr->priv->sourcelist != NULL) {
-		g_object_unref (mgr->priv->sourcelist);
-		mgr->priv->sourcelist = NULL;
+	if (mgr->priv->page_model != NULL) {
+		g_object_unref (mgr->priv->page_model);
+		mgr->priv->page_model = NULL;
+	}
+
+	if (mgr->priv->display_page_tree != NULL) {
+		g_object_unref (mgr->priv->display_page_tree);
+		mgr->priv->display_page_tree = NULL;
 	}
 
 	if (mgr->priv->selected_source != NULL) {
@@ -527,10 +537,11 @@ rb_playlist_manager_set_property (GObject *object,
 	case PROP_SHELL:
 		rb_playlist_manager_set_shell_internal (mgr, g_value_get_object (value));
 		break;
-	case PROP_SOURCELIST:
-		mgr->priv->sourcelist = g_value_get_object (value);
-		g_object_ref (mgr->priv->sourcelist);
-		mgr->priv->window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (mgr->priv->sourcelist)));
+	case PROP_DISPLAY_PAGE_MODEL:
+		mgr->priv->page_model = g_value_dup_object (value);
+		break;
+	case PROP_DISPLAY_PAGE_TREE:
+		mgr->priv->display_page_tree = g_value_dup_object (value);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -556,8 +567,11 @@ rb_playlist_manager_get_property (GObject *object,
 	case PROP_SHELL:
 		g_value_set_object (value, mgr->priv->shell);
 		break;
-	case PROP_SOURCELIST:
-		g_value_set_object (value, mgr->priv->sourcelist);
+	case PROP_DISPLAY_PAGE_MODEL:
+		g_value_set_object (value, mgr->priv->page_model);
+		break;
+	case PROP_DISPLAY_PAGE_TREE:
+		g_value_set_object (value, mgr->priv->display_page_tree);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -568,7 +582,8 @@ rb_playlist_manager_get_property (GObject *object,
 /**
  * rb_playlist_manager_new:
  * @shell: the #RBShell
- * @sourcelist: the #RBSourceList
+ * @page_model: the #RBDisplayPageModel
+ * @page_tree: the #RBDisplayPageTree
  * @playlists_file: the full path to the playlist file to load
  *
  * Creates the #RBPlaylistManager instance
@@ -577,12 +592,14 @@ rb_playlist_manager_get_property (GObject *object,
  */
 RBPlaylistManager *
 rb_playlist_manager_new (RBShell *shell,
-			 RBSourceList *sourcelist,
+			 RBDisplayPageModel *page_model,
+			 RBDisplayPageTree *page_tree,
 			 const char *playlists_file)
 {
 	return g_object_new (RB_TYPE_PLAYLIST_MANAGER,
 			     "shell", shell,
-			     "sourcelist", sourcelist,
+			     "display-page-model", page_model,
+			     "display-page-tree", page_tree,
 			     "playlists_file", playlists_file,
 			     NULL);
 }
@@ -797,37 +814,35 @@ _is_dirty_playlist (GtkTreeModel *model,
 		    GtkTreeIter *iter,
 		    gboolean *dirty)
 {
-	RBSource *source;
+	RBDisplayPage *page;
 	gboolean local;
 	gboolean ret;
 
 	gtk_tree_model_get (model,
 			    iter,
-			    RB_SOURCELIST_MODEL_COLUMN_SOURCE,
-			    &source,
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE,
+			    &page,
 			    -1);
-	if (source == NULL) {
+	if (page == NULL) {
 		return FALSE;
 	}
-	if (RB_IS_PLAYLIST_SOURCE (source) == FALSE) {
-		g_object_unref (source);
+	if (RB_IS_PLAYLIST_SOURCE (page) == FALSE) {
+		g_object_unref (page);
 		return FALSE;
 	}
 
-	g_object_get (source,
-		      "is-local", &local,
-		      NULL);
 	ret = FALSE;
+	g_object_get (page, "is-local", &local, NULL);
 	if (local) {
 		gboolean pdirty;
 
-		g_object_get (source, "dirty", &pdirty, NULL);
+		g_object_get (page, "dirty", &pdirty, NULL);
 		if (pdirty) {
 			*dirty = TRUE;
 			ret = TRUE;
 		}
 	}
-	g_object_unref (source);
+	g_object_unref (page);
 
 	return ret;
 }
@@ -837,14 +852,10 @@ static gboolean
 rb_playlist_manager_is_dirty (RBPlaylistManager *mgr)
 {
 	gboolean dirty = FALSE;
-	GtkTreeModel *fmodel;
-	GtkTreeModel *model;
 
-	g_object_get (mgr->priv->sourcelist, "model", &fmodel, NULL);
-	model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (fmodel));
-	g_object_unref (fmodel);
-
-	gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc) _is_dirty_playlist, &dirty);
+	gtk_tree_model_foreach (GTK_TREE_MODEL (mgr->priv->page_model),
+				(GtkTreeModelForeachFunc) _is_dirty_playlist,
+				&dirty);
 
 	if (!dirty)
 		dirty = g_atomic_int_get (&mgr->priv->dirty);
@@ -895,27 +906,27 @@ save_playlist_cb (GtkTreeModel *model,
 		  GtkTreeIter  *iter,
 		  xmlNodePtr    root)
 {
-	RBSource *source;
+	RBDisplayPage *page;
 	gboolean  local;
 
 	gtk_tree_model_get (model,
 			    iter,
-			    RB_SOURCELIST_MODEL_COLUMN_SOURCE, &source,
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
 			    -1);
-	if (source == NULL) {
+	if (page == NULL) {
 		goto out;
 	}
-	if (RB_IS_PLAYLIST_SOURCE (source) == FALSE) {
+	if (RB_IS_PLAYLIST_SOURCE (page) == FALSE) {
 		goto out;
 	}
 
-	g_object_get (source, "is-local", &local, NULL);
+	g_object_get (page, "is-local", &local, NULL);
 	if (local) {
-		rb_playlist_source_save_to_xml (RB_PLAYLIST_SOURCE (source), root);
+		rb_playlist_source_save_to_xml (RB_PLAYLIST_SOURCE (page), root);
 	}
  out:
-	if (source != NULL) {
-		g_object_unref (source);
+	if (page != NULL) {
+		g_object_unref (page);
 	}
 
 	return FALSE;
@@ -939,8 +950,6 @@ rb_playlist_manager_save_playlists (RBPlaylistManager *mgr, gboolean force)
 {
 	xmlNodePtr root;
 	struct RBPlaylistManagerSaveData *data;
-	GtkTreeModel *fmodel;
-	GtkTreeModel *model;
 
 	if (!force && !rb_playlist_manager_is_dirty (mgr)) {
 		/* playlists already in sync, so don't bother */
@@ -960,11 +969,9 @@ rb_playlist_manager_save_playlists (RBPlaylistManager *mgr, gboolean force)
 	root = xmlNewDocNode (data->doc, NULL, RB_PLAYLIST_MGR_PL, NULL);
 	xmlDocSetRootElement (data->doc, root);
 
-	g_object_get (mgr->priv->sourcelist, "model", &fmodel, NULL);
-	model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (fmodel));
-	g_object_unref (fmodel);
-
-	gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc)save_playlist_cb, root);
+	gtk_tree_model_foreach (GTK_TREE_MODEL (mgr->priv->page_model),
+				(GtkTreeModelForeachFunc)save_playlist_cb,
+				root);
 
 	/* mark clean here.  if the save fails, we'll mark it dirty again */
 	rb_playlist_manager_set_dirty (data->mgr, FALSE);
@@ -1005,7 +1012,7 @@ rb_playlist_manager_new_playlist (RBPlaylistManager *mgr,
 							  RHYTHMDB_ENTRY_TYPE_SONG);
 
 	append_new_playlist_source (mgr, RB_PLAYLIST_SOURCE (playlist));
-	rb_sourcelist_edit_source_name (mgr->priv->sourcelist, playlist);
+	rb_display_page_tree_edit_source_name (mgr->priv->display_page_tree, playlist);
 	rb_playlist_manager_set_dirty (mgr, TRUE);
 
 	g_signal_emit (mgr, rb_playlist_manager_signals[PLAYLIST_CREATED], 0,
@@ -1348,7 +1355,8 @@ rb_playlist_manager_cmd_rename_playlist (GtkAction *action,
 {
 	rb_debug ("Renaming playlist %p", mgr->priv->selected_source);
 
-	rb_sourcelist_edit_source_name (mgr->priv->sourcelist, mgr->priv->selected_source);
+	rb_display_page_tree_edit_source_name (mgr->priv->display_page_tree,
+					       mgr->priv->selected_source);
 	rb_playlist_manager_set_dirty (mgr, TRUE);
 }
 
@@ -1358,7 +1366,7 @@ rb_playlist_manager_cmd_delete_playlist (GtkAction *action,
 {
 	rb_debug ("Deleting playlist %p", mgr->priv->selected_source);
 
-	rb_source_delete_thyself (mgr->priv->selected_source);
+	rb_display_page_delete_thyself (RB_DISPLAY_PAGE (mgr->priv->selected_source));
 	rb_playlist_manager_set_dirty (mgr, TRUE);
 }
 
@@ -1396,6 +1404,7 @@ static void
 rb_playlist_manager_cmd_load_playlist (GtkAction *action,
 				       RBPlaylistManager *mgr)
 {
+	GtkWindow *window;
 	GtkWidget *dialog;
 	GtkFileFilter *filter;
 	GtkFileFilter *filter_all;
@@ -1411,9 +1420,10 @@ rb_playlist_manager_cmd_load_playlist (GtkAction *action,
 	gtk_file_filter_set_name (filter_all, _("All Files"));
 	gtk_file_filter_add_pattern (filter_all, "*");
 
+	g_object_get (mgr->priv->shell, "window", &window, NULL);
 
 	dialog = rb_file_chooser_new (_("Load Playlist"),
-				      GTK_WINDOW (mgr->priv->window),
+				      window,
 				      GTK_FILE_CHOOSER_ACTION_OPEN,
 				      FALSE);
 	gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
@@ -1422,6 +1432,8 @@ rb_playlist_manager_cmd_load_playlist (GtkAction *action,
 
 	g_signal_connect_object (dialog, "response",
 				 G_CALLBACK (load_playlist_response_cb), mgr, 0);
+
+	g_object_unref (window);
 }
 
 static void
@@ -1614,23 +1626,22 @@ list_playlists_cb (GtkTreeModel *model,
 		   GtkTreeIter  *iter,
 		   GList **playlists)
 {
-	RBSource *source;
+	RBDisplayPage *page;
 	gboolean  local;
 
 	gtk_tree_model_get (model,
 			    iter,
-			    RB_SOURCELIST_MODEL_COLUMN_SOURCE, &source,
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
 			    -1);
-	if (source != NULL) {
-		if (RB_IS_PLAYLIST_SOURCE (source) && !RB_IS_PLAY_QUEUE_SOURCE (source)) {
-			
-			g_object_get (source, "is-local", &local, NULL);
+	if (page != NULL) {
+		if (RB_IS_PLAYLIST_SOURCE (page) && !RB_IS_PLAY_QUEUE_SOURCE (page)) {
+			g_object_get (page, "is-local", &local, NULL);
 			if (local) {
-				*playlists = g_list_prepend (*playlists, source);
+				*playlists = g_list_prepend (*playlists, RB_SOURCE (page));
 			}
 		}
-		
-		g_object_unref (source);
+
+		g_object_unref (page);
 	}
 
 	return FALSE;
@@ -1649,14 +1660,10 @@ GList *
 rb_playlist_manager_get_playlists (RBPlaylistManager *mgr)
 {
 	GList *playlists = NULL;
-	GtkTreeModel *fmodel;
-	GtkTreeModel *model;
-
-	g_object_get (mgr->priv->sourcelist, "model", &fmodel, NULL);
-	model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (fmodel));
-	g_object_unref (fmodel);
 
-	gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc)list_playlists_cb, &playlists);
+	gtk_tree_model_foreach (GTK_TREE_MODEL (mgr->priv->page_model),
+				(GtkTreeModelForeachFunc)list_playlists_cb,
+				&playlists);
 	return g_list_reverse (playlists);
 }
 
@@ -1679,7 +1686,7 @@ rb_playlist_manager_get_playlist_names (RBPlaylistManager *mgr,
 	GList *pl;
 	GList *t;
 	int i;
-	
+
 	pl = rb_playlist_manager_get_playlists (mgr);
 	*playlists = g_new0 (char *, g_list_length (pl) + 1);
 	if (!*playlists)
@@ -1708,24 +1715,24 @@ find_playlist_by_name_cb (GtkTreeModel *model,
 			  GtkTreeIter  *iter,
 			  FindPlaylistData *data)
 {
-	RBSource *source;
+	RBDisplayPage *page;
 
 	gtk_tree_model_get (model,
 			    iter,
-			    RB_SOURCELIST_MODEL_COLUMN_SOURCE, &source,
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
 			    -1);
-	if (source != NULL) {
-		if (RB_IS_PLAYLIST_SOURCE (source) && !RB_IS_PLAY_QUEUE_SOURCE (source)) {
+	if (page != NULL) {
+		if (RB_IS_PLAYLIST_SOURCE (page) && !RB_IS_PLAY_QUEUE_SOURCE (page)) {
 			char *name;
-			
-			g_object_get (source, "name", &name, NULL);
+
+			g_object_get (page, "name", &name, NULL);
 			if (strcmp (name, data->name) == 0) {
-				data->source = source;
+				data->source = RB_SOURCE (page);
 			}
 			g_free (name);
 		}
-		
-		g_object_unref (source);
+
+		g_object_unref (page);
 	}
 
 	return (data->source != NULL);
@@ -1735,18 +1742,14 @@ static RBSource *
 _get_playlist_by_name (RBPlaylistManager *mgr,
 		       const char *name)
 {
-	GtkTreeModel *fmodel;
-	GtkTreeModel *model;
 	FindPlaylistData d;
 
-	g_object_get (mgr->priv->sourcelist, "model", &fmodel, NULL);
-	model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (fmodel));
-	g_object_unref (fmodel);
-
 	d.name = name;
 	d.source = NULL;
 
-	gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc)find_playlist_by_name_cb, &d);
+	gtk_tree_model_foreach (GTK_TREE_MODEL (mgr->priv->page_model),
+				(GtkTreeModelForeachFunc)find_playlist_by_name_cb,
+				&d);
 	return d.source;
 }
 
@@ -1805,7 +1808,7 @@ rb_playlist_manager_delete_playlist (RBPlaylistManager *mgr,
 			     name);
 		return FALSE;
 	}
-	rb_source_delete_thyself (playlist);
+	rb_display_page_delete_thyself (RB_DISPLAY_PAGE (playlist));
 	rb_playlist_manager_set_dirty (mgr, TRUE);
 	return TRUE;
 }
diff --git a/shell/rb-playlist-manager.h b/shell/rb-playlist-manager.h
index d6d6187..f9e8fa0 100644
--- a/shell/rb-playlist-manager.h
+++ b/shell/rb-playlist-manager.h
@@ -29,7 +29,9 @@
 #define __RB_PLAYLIST_MANAGER_H
 
 #include <sources/rb-source.h>
-#include <sources/rb-sourcelist.h>
+#include <sources/rb-display-page-model.h>
+#include <sources/rb-display-page-tree.h>
+#include <shell/rb-shell.h>
 #include <rhythmdb/rhythmdb.h>
 
 G_BEGIN_DECLS
@@ -86,7 +88,8 @@ typedef enum
 GType			rb_playlist_manager_get_type	(void);
 
 RBPlaylistManager *	rb_playlist_manager_new		(RBShell *shell,
-						 	 RBSourceList *sourcelist,
+							 RBDisplayPageModel *page_model,
+							 RBDisplayPageTree *page_tree,
 							 const char *playlists_file);
 
 void			rb_playlist_manager_shutdown	(RBPlaylistManager *mgr);
diff --git a/shell/rb-removable-media-manager.c b/shell/rb-removable-media-manager.c
index 0c207b4..c3d3a2f 100644
--- a/shell/rb-removable-media-manager.c
+++ b/shell/rb-removable-media-manager.c
@@ -570,7 +570,7 @@ uevent_cb (GUdevClient *client, const char *action, GUdevDevice *device, RBRemov
 		source = g_hash_table_lookup (priv->device_mapping, &devnum);
 		if (source) {
 			rb_debug ("removing the source created for this device");
-			rb_source_delete_thyself (source);
+			rb_display_page_delete_thyself (RB_DISPLAY_PAGE (source));
 		}
 	}
 }
@@ -679,7 +679,7 @@ rb_removable_media_manager_remove_volume (RBRemovableMediaManager *mgr, GVolume
 	rb_debug ("volume removed");
 	source = g_hash_table_lookup (priv->volume_mapping, volume);
 	if (source) {
-		rb_source_delete_thyself (RB_SOURCE (source));
+		rb_display_page_delete_thyself (RB_DISPLAY_PAGE (source));
 	}
 }
 
@@ -756,7 +756,7 @@ rb_removable_media_manager_remove_mount (RBRemovableMediaManager *mgr, GMount *m
 	rb_debug ("mount removed");
 	source = g_hash_table_lookup (priv->mount_mapping, mount);
 	if (source) {
-		rb_source_delete_thyself (RB_SOURCE (source));
+		rb_display_page_delete_thyself (RB_DISPLAY_PAGE (source));
 	}
 }
 
diff --git a/shell/rb-shell-clipboard.c b/shell/rb-shell-clipboard.c
index a298ed4..20201e6 100644
--- a/shell/rb-shell-clipboard.c
+++ b/shell/rb-shell-clipboard.c
@@ -52,7 +52,7 @@
 #include "rb-shell-clipboard.h"
 #include "rb-playlist-manager.h"
 #include "rb-play-queue-source.h"
-#include "rb-sourcelist-model.h"
+#include "rb-display-page-model.h"
 #include "rhythmdb.h"
 #include "rb-debug.h"
 #include "rb-stock-icons.h"
@@ -953,20 +953,20 @@ add_playlist_to_menu (GtkTreeModel *model,
 {
 	RhythmDBEntryType *entry_type;
 	RhythmDBEntryType *source_entry_type;
-	RBSource *source = NULL;
+	RBDisplayPage *page = NULL;
 	char *action_name;
 	GtkAction *action;
 	int i;
 
 	gtk_tree_model_get (GTK_TREE_MODEL (model), iter,
-			    RB_SOURCELIST_MODEL_COLUMN_SOURCE, &source, -1);
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page, -1);
 
-	if (source == NULL) {
+	if (page == NULL) {
 		return FALSE;
 	}
 
-	if (!RB_IS_STATIC_PLAYLIST_SOURCE (source)) {
-		g_object_unref (source);
+	if (RB_IS_STATIC_PLAYLIST_SOURCE (page) == FALSE) {
+		g_object_unref (page);
 		return FALSE;
 	}
 
@@ -975,36 +975,36 @@ add_playlist_to_menu (GtkTreeModel *model,
 	 * the song to the device first), surely?
 	 */
 	g_object_get (clipboard->priv->source, "entry-type", &entry_type, NULL);
-	g_object_get (source, "entry-type", &source_entry_type, NULL);
+	g_object_get (page, "entry-type", &source_entry_type, NULL);
 	if (source_entry_type != entry_type) {
-		g_object_unref (source);
+		g_object_unref (page);
 		g_object_unref (entry_type);
 		g_object_unref (source_entry_type);
 		return FALSE;
 	}
 
-	action_name = generate_action_name (RB_STATIC_PLAYLIST_SOURCE (source), clipboard);
+	action_name = generate_action_name (RB_STATIC_PLAYLIST_SOURCE (page), clipboard);
 	action = gtk_action_group_get_action (clipboard->priv->actiongroup, action_name);
 	if (action == NULL) {
 		char *name;
 
-		g_object_get (source, "name", &name, NULL);
+		g_object_get (page, "name", &name, NULL);
 		action = gtk_action_new (action_name, name, NULL, NULL);
 		gtk_action_group_add_action (clipboard->priv->actiongroup, action);
 		g_free (name);
 
-		g_object_set_data (G_OBJECT (action), "playlist-source", source);
-		g_signal_connect_object (G_OBJECT (action),
+		g_object_set_data (G_OBJECT (action), "playlist-source", page);
+		g_signal_connect_object (action,
 					 "activate", G_CALLBACK (rb_shell_clipboard_playlist_add_cb),
 					 clipboard, 0);
 
-		g_signal_connect_object (source,
+		g_signal_connect_object (page,
 					 "deleted", G_CALLBACK (rb_shell_clipboard_playlist_deleted_cb),
 					 clipboard, 0);
-		g_signal_connect_object (source,
+		g_signal_connect_object (page,
 					 "notify::name", G_CALLBACK (rb_shell_clipboard_playlist_renamed_cb),
 					 clipboard, 0);
-		g_signal_connect_object (source,
+		g_signal_connect_object (page,
 					 "notify::visibility", G_CALLBACK (rb_shell_clipboard_playlist_visible_cb),
 					 clipboard, 0);
 	}
@@ -1019,7 +1019,7 @@ add_playlist_to_menu (GtkTreeModel *model,
 	g_object_unref (source_entry_type);
 	g_object_unref (entry_type);
 	g_free (action_name);
-	g_object_unref (source);
+	g_object_unref (page);
 
 	return FALSE;
 }
@@ -1028,7 +1028,6 @@ static void
 rebuild_playlist_menu (RBShellClipboard *clipboard)
 {
 	GtkTreeModel *model = NULL;
-	GObject *sourcelist = NULL;
 
 	if (clipboard->priv->source == NULL)
 		return;
@@ -1044,12 +1043,7 @@ rebuild_playlist_menu (RBShellClipboard *clipboard)
 	}
 
 	if (clipboard->priv->playlist_manager != NULL) {
-		g_object_get (clipboard->priv->playlist_manager, "sourcelist", &sourcelist, NULL);
-	}
-
-	if (sourcelist != NULL) {
-		g_object_get (sourcelist, "model", &model, NULL);
-		g_object_unref (sourcelist);
+		g_object_get (clipboard->priv->playlist_manager, "display-page-model", &model, NULL);
 	}
 
 	if (model != NULL) {
diff --git a/shell/rb-shell-player.c b/shell/rb-shell-player.c
index a85ea56..f4e40af 100644
--- a/shell/rb-shell-player.c
+++ b/shell/rb-shell-player.c
@@ -1142,7 +1142,7 @@ rb_shell_player_set_source_internal (RBShellPlayer *player,
 		RBSource *source = player->priv->selected_source;
 		if (source == RB_SOURCE (player->priv->queue_source)) {
 			source = NULL;
-		} else {
+		} else if (source != NULL) {
 			g_object_get (source, "play-order", &porder, NULL);
 		}
 
@@ -1419,11 +1419,9 @@ rb_shell_player_set_selected_source (RBShellPlayer *player,
 				     RBSource *source)
 {
 	g_return_if_fail (RB_IS_SHELL_PLAYER (player));
-	g_return_if_fail (RB_IS_SOURCE (source));
+	g_return_if_fail (source == NULL || RB_IS_SOURCE (source));
 
-	g_object_set (G_OBJECT (player),
-		      "source", source,
-		      NULL);
+	g_object_set (player, "source", source, NULL);
 }
 
 /**
@@ -3063,8 +3061,11 @@ rb_shell_player_sync_buttons (RBShellPlayer *player)
         not_small = !eel_gconf_get_boolean (CONF_UI_SMALL_DISPLAY);
 	action = gtk_action_group_get_action (player->priv->actiongroup,
 					      "ViewJumpToPlaying");
-	g_object_set (action,
-		      "sensitive", entry != NULL && not_small, NULL);
+	g_object_set (action, "sensitive", entry != NULL && not_small, NULL);
+
+	action = gtk_action_group_get_action (player->priv->actiongroup,
+					      "ControlPlay");
+	g_object_set (action, "sensitive", entry != NULL || source != NULL, NULL);
 
 	if (source != NULL) {
 		view = rb_source_get_entry_view (source);
@@ -3112,12 +3113,14 @@ actually_set_playing_source (RBShellPlayer *player,
 		if (source == NULL)
 			source = player->priv->selected_source;
 
-		g_object_get (source, "play-order", &porder, NULL);
-		if (porder == NULL)
-			porder = g_object_ref (player->priv->play_order);
+		if (source != NULL) {
+			g_object_get (source, "play-order", &porder, NULL);
+			if (porder == NULL)
+				porder = g_object_ref (player->priv->play_order);
 
-		rb_play_order_playing_source_changed (porder, source);
-		g_object_unref (porder);
+			rb_play_order_playing_source_changed (porder, source);
+			g_object_unref (porder);
+		}
 	}
 
 	rb_shell_player_play_order_update_cb (player->priv->play_order,
diff --git a/shell/rb-shell-preferences.c b/shell/rb-shell-preferences.c
index 47111c2..4918670 100644
--- a/shell/rb-shell-preferences.c
+++ b/shell/rb-shell-preferences.c
@@ -358,14 +358,14 @@ rb_shell_preferences_append_page (RBShellPreferences *prefs,
 static void
 rb_shell_preferences_append_view_page (RBShellPreferences *prefs,
 				       const char *name,
-				       RBSource *source)
+				       RBDisplayPage *page)
 {
 	GtkWidget *widget;
 
 	g_return_if_fail (RB_IS_SHELL_PREFERENCES (prefs));
-	g_return_if_fail (RB_IS_SOURCE (source));
+	g_return_if_fail (RB_IS_DISPLAY_PAGE (page));
 
-	widget = rb_source_get_config_widget (source, prefs);
+	widget = rb_display_page_get_config_widget (page, prefs);
 	if (!widget)
 		return;
 
@@ -396,14 +396,14 @@ rb_shell_preferences_new (GList *views)
 		char *name = NULL;
 		g_object_get (views->data, "name", &name, NULL);
 		if (name == NULL) {
-			g_warning ("Source %p of type %s has no name",
+			g_warning ("Page %p of type %s has no name",
 				   views->data,
 				   G_OBJECT_TYPE_NAME (views->data));
 			continue;
 		}
 		rb_shell_preferences_append_view_page (shell_preferences,
 						       name,
-						       RB_SOURCE (views->data));
+						       RB_DISPLAY_PAGE (views->data));
 		g_free (name);
 	}
 
diff --git a/shell/rb-shell.c b/shell/rb-shell.c
index c490a92..8ced9e9 100644
--- a/shell/rb-shell.c
+++ b/shell/rb-shell.c
@@ -32,7 +32,7 @@
  * @short_description: holds the Rhythmbox main window and everything else
  *
  * RBShell is the main application class in Rhythmbox.  It creates and holds
- * references to the other main objects (#RBShellPlayer, #RhythmDB, #RBSourceList),
+ * references to the other main objects (#RBShellPlayer, #RhythmDB, #RBDisplayPageTree),
  * constructs the main window UI, and provides the basic DBus interface.
  */
 
@@ -62,7 +62,8 @@
 #error "no database specified. configure broken?"
 #endif
 #include "rb-stock-icons.h"
-#include "rb-sourcelist.h"
+#include "rb-display-page-tree.h"
+#include "rb-display-page-group.h"
 #include "rb-file-helpers.h"
 #include "rb-source.h"
 #include "rb-playlist-manager.h"
@@ -87,7 +88,7 @@
 #include "rb-plugins-engine.h"
 #include "rb-plugin-manager.h"
 #include "rb-util.h"
-#include "rb-sourcelist-model.h"
+#include "rb-display-page-model.h"
 #include "rb-song-info.h"
 #include "rb-marshal.h"
 #include "rb-missing-plugins.h"
@@ -127,23 +128,16 @@ static gboolean rb_shell_key_press_event_cb (GtkWidget *win,
 static void rb_shell_sync_window_state (RBShell *shell, gboolean dont_maximise);
 static void rb_shell_sync_paned (RBShell *shell);
 static void rb_shell_sync_party_mode (RBShell *shell);
-static void rb_shell_select_source (RBShell *shell, RBSource *source);
-static void source_selected_cb (RBSourceList *sourcelist,
-				RBSource *source,
-				RBShell *shell);
+static void rb_shell_select_page (RBShell *shell, RBDisplayPage *display_page);
+static void display_page_selected_cb (RBDisplayPageTree *display_page_tree,
+				      RBDisplayPage *page,
+				      RBShell *shell);
 static void rb_shell_playing_source_changed_cb (RBShellPlayer *player,
 						RBSource *source,
 						RBShell *shell);
 static void rb_shell_playing_from_queue_cb (RBShellPlayer *player,
 					    GParamSpec *arg,
 					    RBShell *shell);
-static void source_activated_cb (RBSourceList *sourcelist,
-				 RBSource *source,
-				 RBShell *shell);
-static gboolean rb_shell_activate_source (RBShell *shell,
-					  RBSource *source,
-					  guint play,
-					  GError **error);
 static void rb_shell_db_save_error_cb (RhythmDB *db,
 				       const char *uri, const GError *error,
 				       RBShell *shell);
@@ -151,7 +145,7 @@ static void rb_shell_db_save_error_cb (RhythmDB *db,
 static void rb_shell_playlist_added_cb (RBPlaylistManager *mgr, RBSource *source, RBShell *shell);
 static void rb_shell_playlist_created_cb (RBPlaylistManager *mgr, RBSource *source, RBShell *shell);
 static void rb_shell_medium_added_cb (RBRemovableMediaManager *mgr, RBSource *source, RBShell *shell);
-static void rb_shell_source_deleted_cb (RBSource *source, RBShell *shell);
+static void rb_shell_display_page_deleted_cb (RBDisplayPage *page, RBShell *shell);
 static void rb_shell_set_window_title (RBShell *shell, const char *window_title);
 static void rb_shell_player_window_title_changed_cb (RBShellPlayer *player,
 					             const char *window_title,
@@ -178,7 +172,7 @@ static void rb_shell_jump_to_entry_with_source (RBShell *shell, RBSource *source
 						RhythmDBEntry *entry);
 static void rb_shell_play_entry (RBShell *shell, RhythmDBEntry *entry);
 static void rb_shell_cmd_view_all (GtkAction *action,
- 				   RBShell *shell);
+				   RBShell *shell);
 static void rb_shell_view_sidepane_changed_cb (GtkAction *action,
 						 RBShell *shell);
 static void rb_shell_view_toolbar_changed_cb (GtkAction *action,
@@ -212,13 +206,10 @@ static void smalldisplay_changed_cb (GConfClient *client,
 				     guint cnxn_id,
 				     GConfEntry *entry,
 				     RBShell *shell);
-static void sourcelist_drag_received_cb (RBSourceList *sourcelist,
-					 RBSource *source,
-					 GtkSelectionData *data,
-					 RBShell *shell);
-static gboolean rb_shell_show_popup_cb (RBSourceList *sourcelist,
-					RBSource *target,
-					RBShell *shell);
+static void display_page_tree_drag_received_cb (RBDisplayPageTree *display_page_tree,
+						RBDisplayPage *page,
+						GtkSelectionData *data,
+						RBShell *shell);
 
 static void paned_size_allocate_cb (GtkWidget *widget,
 				    GtkAllocation *allocation,
@@ -245,7 +236,7 @@ enum
 	PROP_DRY_RUN,
 	PROP_RHYTHMDB_FILE,
 	PROP_PLAYLISTS_FILE,
-	PROP_SELECTED_SOURCE,
+	PROP_SELECTED_PAGE,
 	PROP_DB,
 	PROP_UI_MANAGER,
 	PROP_CLIPBOARD,
@@ -257,8 +248,8 @@ enum
 	PROP_QUEUE_SOURCE,
 	PROP_PROXY_CONFIG,
 	PROP_LIBRARY_SOURCE,
-	PROP_SOURCELIST_MODEL,
-	PROP_SOURCELIST,
+	PROP_DISPLAY_PAGE_MODEL,
+	PROP_DISPLAY_PAGE_TREE,
 	PROP_SOURCE_HEADER,
 	PROP_VISIBILITY,
 	PROP_TRACK_TRANSFER_QUEUE,
@@ -305,7 +296,7 @@ struct _RBShellPrivate
 	GtkWidget *main_vbox;
 	GtkWidget *paned;
 	GtkWidget *right_paned;
-	GtkWidget *sourcelist;
+	RBDisplayPageTree *display_page_tree;
 	GtkWidget *notebook;
 	GtkWidget *queue_paned;
 	GtkWidget *queue_sidebar;
@@ -316,8 +307,9 @@ struct _RBShellPrivate
 	GtkBox *bottom_container;
 	guint right_sidebar_widget_count;
 
-	GList *sources;
-	GHashTable *sources_hash;
+	RBDisplayPageModel *display_page_model;
+	GList *sources;				/* kill? */
+	GHashTable *sources_hash;		/* kill? */
 
 	guint async_state_save_id;
 	guint save_playlist_id;
@@ -350,7 +342,7 @@ struct _RBShellPrivate
 	RBSource *missing_files_source;
 	RBSource *import_errors_source;
 
-	RBSource *selected_source;
+	RBDisplayPage *selected_page;
 
 	GtkWidget *prefs;
 	GtkWidget *plugins;
@@ -384,7 +376,7 @@ struct _RBShellPrivate
 	gint window_y;
 	gint paned_position;
 	gint right_paned_position;
-	gint sourcelist_height;
+	gint display_page_tree_height;
 };
 
 #define RB_SHELL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_SHELL, RBShellPrivate))
@@ -523,24 +515,24 @@ rb_shell_class_init (RBShellClass *klass)
 	 */
 	g_object_class_install_property (object_class,
 					 PROP_PLAYLISTS_FILE,
-					 g_param_spec_string ("playlists-file", 
-							      "playlists-file", 
-							      "The playlists file to use", 
+					 g_param_spec_string ("playlists-file",
+							      "playlists-file",
+							      "The playlists file to use",
 							      "playlists.xml",
 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
 
 	/**
-	 * RBShell:selected-source:
+	 * RBShell:selected-page:
 	 *
-	 * The currently selected source object
+	 * The currently selected display page
 	 */
 	g_object_class_install_property (object_class,
-					 PROP_SELECTED_SOURCE,
-					 g_param_spec_object ("selected-source",
-							      "selected-source",
-							      "Source which is currently selected",
-							      RB_TYPE_SOURCE,
+					 PROP_SELECTED_PAGE,
+					 g_param_spec_object ("selected-page",
+							      "selected-page",
+							      "Display page which is currently selected",
+							      RB_TYPE_DISPLAY_PAGE,
 							      G_PARAM_READABLE));
 	/**
 	 * RBShell:db:
@@ -574,7 +566,7 @@ rb_shell_class_init (RBShellClass *klass)
 	g_object_class_install_property (object_class,
 					 PROP_CLIPBOARD,
 					 g_param_spec_object ("clipboard",
-						 	      "RBShellClipboard",
+							      "RBShellClipboard",
 							      "RBShellClipboard object",
 							      RB_TYPE_SHELL_CLIPBOARD,
 							      G_PARAM_READABLE));
@@ -586,7 +578,7 @@ rb_shell_class_init (RBShellClass *klass)
 	g_object_class_install_property (object_class,
 					 PROP_PLAYLIST_MANAGER,
 					 g_param_spec_object ("playlist-manager",
-						 	      "RBPlaylistManager",
+							      "RBPlaylistManager",
 							      "RBPlaylistManager object",
 							      RB_TYPE_PLAYLIST_MANAGER,
 							      G_PARAM_READABLE));
@@ -598,7 +590,7 @@ rb_shell_class_init (RBShellClass *klass)
 	g_object_class_install_property (object_class,
 					 PROP_SHELL_PLAYER,
 					 g_param_spec_object ("shell-player",
-						 	      "RBShellPlayer",
+							      "RBShellPlayer",
 							      "RBShellPlayer object",
 							      RB_TYPE_SHELL_PLAYER,
 							      G_PARAM_READABLE));
@@ -610,7 +602,7 @@ rb_shell_class_init (RBShellClass *klass)
 	g_object_class_install_property (object_class,
 					 PROP_REMOVABLE_MEDIA_MANAGER,
 					 g_param_spec_object ("removable-media-manager",
-						 	      "RBRemovableMediaManager",
+							      "RBRemovableMediaManager",
 							      "RBRemovableMediaManager object",
 							      RB_TYPE_REMOVABLE_MEDIA_MANAGER,
 							      G_PARAM_READABLE));
@@ -663,29 +655,29 @@ rb_shell_class_init (RBShellClass *klass)
 							      RB_TYPE_LIBRARY_SOURCE,
 							      G_PARAM_READABLE));
 	/**
-	 * RBShell:sourcelist-model:
+	 * RBShell:display-page-model:
 	 *
-	 * The tree model underlying the source list.
+	 * The model underlying the display page tree
 	 */
 	g_object_class_install_property (object_class,
-					 PROP_SOURCELIST_MODEL,
-					 g_param_spec_object ("sourcelist-model",
-							      "sourcelist-model",
-							      "RBSourcelistModel",
-							      RB_TYPE_SOURCELIST_MODEL,
+					 PROP_DISPLAY_PAGE_MODEL,
+					 g_param_spec_object ("display-page-model",
+							      "display-page-model",
+							      "RBDisplayPageModel",
+							      RB_TYPE_DISPLAY_PAGE_MODEL,
 							      G_PARAM_READABLE));
 
 	/**
-	 * RBShell:sourcelist:
+	 * RBShell:display-page-tree:
 	 *
-	 * The #RBSourceList instance
+	 * The #RBDisplayPageTree instance
 	 */
 	g_object_class_install_property (object_class,
-					 PROP_SOURCELIST,
-					 g_param_spec_object ("sourcelist",
-							      "sourcelist",
-							      "RBSourceList",
-							      RB_TYPE_SOURCELIST,
+					 PROP_DISPLAY_PAGE_TREE,
+					 g_param_spec_object ("display-page-tree",
+							      "display-page-tree",
+							      "RBDisplayPageTree",
+							      RB_TYPE_DISPLAY_PAGE_TREE,
 							      G_PARAM_READABLE));
 
 	/**
@@ -973,8 +965,8 @@ rb_shell_get_property (GObject *object,
 	case PROP_REMOVABLE_MEDIA_MANAGER:
 		g_value_set_object (value, shell->priv->removable_media_manager);
 		break;
-	case PROP_SELECTED_SOURCE:
-		g_value_set_object (value, shell->priv->selected_source);
+	case PROP_SELECTED_PAGE:
+		g_value_set_object (value, shell->priv->selected_page);
 		break;
 	case PROP_WINDOW:
 		g_value_set_object (value, shell->priv->window);
@@ -996,21 +988,15 @@ rb_shell_get_property (GObject *object,
 	case PROP_QUEUE_SOURCE:
 		g_value_set_object (value, shell->priv->queue_source);
 		break;
- 	case PROP_LIBRARY_SOURCE:
- 		g_value_set_object (value, shell->priv->library_source);
- 		break;
- 	case PROP_SOURCELIST_MODEL:
-		{
-			GtkTreeModel *model = NULL;
-
-			g_object_get (shell->priv->sourcelist, "model", &model, NULL);
- 			g_value_set_object (value, model);
-			g_object_unref (model);
-		}
- 		break;
- 	case PROP_SOURCELIST:
- 		g_value_set_object (value, shell->priv->sourcelist);
- 		break;
+	case PROP_LIBRARY_SOURCE:
+		g_value_set_object (value, shell->priv->library_source);
+		break;
+	case PROP_DISPLAY_PAGE_MODEL:
+		g_value_set_object (value, shell->priv->display_page_model);
+		break;
+	case PROP_DISPLAY_PAGE_TREE:
+		g_value_set_object (value, shell->priv->display_page_tree);
+		break;
 	case PROP_VISIBILITY:
 		g_value_set_boolean (value, rb_shell_get_visibility (shell));
 		break;
@@ -1043,7 +1029,7 @@ rb_shell_sync_state (RBShell *shell)
 	}
 
 	rb_debug ("saving playlists");
-	rb_playlist_manager_save_playlists (shell->priv->playlist_manager, 
+	rb_playlist_manager_save_playlists (shell->priv->playlist_manager,
 					    TRUE);
 
 	rb_debug ("saving db");
@@ -1062,10 +1048,10 @@ idle_save_rhythmdb (RBShell *shell)
 }
 
 static gboolean
-idle_save_playlist_manager (RBShell *shell) 
+idle_save_playlist_manager (RBShell *shell)
 {
 	GDK_THREADS_ENTER ();
-	rb_playlist_manager_save_playlists (shell->priv->playlist_manager, 
+	rb_playlist_manager_save_playlists (shell->priv->playlist_manager,
 					    FALSE);
 	GDK_THREADS_LEAVE ();
 
@@ -1186,7 +1172,7 @@ rb_shell_new (gboolean no_registration,
 	return g_object_new (RB_TYPE_SHELL,
 			  "no-registration", no_registration,
 			  "no-update", no_update,
-			  "dry-run", dry_run, "rhythmdb-file", rhythmdb, 
+			  "dry-run", dry_run, "rhythmdb-file", rhythmdb,
 			  "playlists-file", playlists,
 			  "autostarted", autostarted,
 			  NULL);
@@ -1232,9 +1218,9 @@ construct_db (RBShell *shell)
 	g_free (pathname);
 
 	if (shell->priv->dry_run)
-		g_object_set (G_OBJECT (shell->priv->db), "dry-run", TRUE, NULL);
+		g_object_set (shell->priv->db, "dry-run", TRUE, NULL);
 	if (shell->priv->no_update)
-		g_object_set (G_OBJECT (shell->priv->db), "no-update", TRUE, NULL);
+		g_object_set (shell->priv->db, "no-update", TRUE, NULL);
 
 	g_signal_connect_object (G_OBJECT (shell->priv->db), "load-complete",
 				 G_CALLBACK (rb_shell_load_complete_cb), shell,
@@ -1305,14 +1291,12 @@ construct_widgets (RBShell *shell)
 							   shell->priv->actiongroup);
 	gtk_widget_show_all (GTK_WIDGET (shell->priv->source_header));
 
-	shell->priv->sourcelist = rb_sourcelist_new (shell);
-	gtk_widget_show_all (shell->priv->sourcelist);
-	g_signal_connect_object (G_OBJECT (shell->priv->sourcelist), "drop_received",
-				 G_CALLBACK (sourcelist_drag_received_cb), shell, 0);
-	g_signal_connect_object (G_OBJECT (shell->priv->sourcelist), "source_activated",
-				 G_CALLBACK (source_activated_cb), shell, 0);
-	g_signal_connect_object (G_OBJECT (shell->priv->sourcelist), "show_popup",
-				 G_CALLBACK (rb_shell_show_popup_cb), shell, 0);
+	shell->priv->display_page_tree = rb_display_page_tree_new (shell);
+	gtk_widget_show_all (GTK_WIDGET (shell->priv->display_page_tree));
+	g_signal_connect_object (shell->priv->display_page_tree, "drop-received",
+				 G_CALLBACK (display_page_tree_drag_received_cb), shell, 0);
+	g_object_get (shell->priv->display_page_tree, "model", &shell->priv->display_page_model, NULL);
+	rb_display_page_group_add_core_groups (G_OBJECT (shell), shell->priv->display_page_model);
 
 	shell->priv->statusbar = rb_statusbar_new (shell->priv->db,
 						   shell->priv->ui_manager,
@@ -1320,22 +1304,22 @@ construct_widgets (RBShell *shell)
 	g_object_set (shell->priv->player_shell, "statusbar", shell->priv->statusbar, NULL);
 	gtk_widget_show (GTK_WIDGET (shell->priv->statusbar));
 
-	g_signal_connect_object (G_OBJECT (shell->priv->sourcelist), "selected",
-				 G_CALLBACK (source_selected_cb), shell, 0);
+	g_signal_connect_object (shell->priv->display_page_tree, "selected",
+				 G_CALLBACK (display_page_selected_cb), shell, 0);
 
 	shell->priv->notebook = gtk_notebook_new ();
 	gtk_widget_show (shell->priv->notebook);
 	gtk_notebook_set_show_tabs (GTK_NOTEBOOK (shell->priv->notebook), FALSE);
 	gtk_notebook_set_show_border (GTK_NOTEBOOK (shell->priv->notebook), FALSE);
-	g_signal_connect_object (G_OBJECT (shell->priv->sourcelist),
+	g_signal_connect_object (shell->priv->display_page_tree,
 				 "size-allocate",
 				 G_CALLBACK (paned_size_allocate_cb),
 				 shell, 0);
 
 	shell->priv->queue_source = RB_PLAYLIST_SOURCE (rb_play_queue_source_new (shell));
-	g_object_set (G_OBJECT(shell->priv->player_shell), "queue-source", shell->priv->queue_source, NULL);
-	g_object_set (G_OBJECT(shell->priv->clipboard_shell), "queue-source", shell->priv->queue_source, NULL);
-	rb_shell_append_source (shell, RB_SOURCE (shell->priv->queue_source), NULL);
+	g_object_set (shell->priv->player_shell, "queue-source", shell->priv->queue_source, NULL);
+	g_object_set (shell->priv->clipboard_shell, "queue-source", shell->priv->queue_source, NULL);
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->queue_source), RB_DISPLAY_PAGE_GROUP_LIBRARY);
 	g_object_get (shell->priv->queue_source, "sidebar", &shell->priv->queue_sidebar, NULL);
 	gtk_widget_show_all (shell->priv->queue_sidebar);
 	gtk_widget_set_no_show_all (shell->priv->queue_sidebar, TRUE);
@@ -1360,13 +1344,13 @@ construct_widgets (RBShell *shell)
 
 		shell->priv->queue_paned = gtk_vpaned_new ();
 		gtk_paned_pack1 (GTK_PANED (shell->priv->queue_paned),
-				 shell->priv->sourcelist,
+				 GTK_WIDGET (shell->priv->display_page_tree),
 				 FALSE, TRUE);
 		gtk_paned_pack2 (GTK_PANED (shell->priv->queue_paned),
 				 shell->priv->queue_sidebar,
 				 TRUE, TRUE);
 		gtk_container_child_set (GTK_CONTAINER (shell->priv->queue_paned),
-					 GTK_WIDGET (shell->priv->sourcelist),
+					 GTK_WIDGET (shell->priv->display_page_tree),
 					 "resize", FALSE,
 					 NULL);
 
@@ -1407,7 +1391,7 @@ construct_widgets (RBShell *shell)
 
 	shell->priv->main_vbox = gtk_vbox_new (FALSE, 0);
 	gtk_container_set_border_width (GTK_CONTAINER (shell->priv->main_vbox), 0);
- 	gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), GTK_WIDGET (shell->priv->player_shell), FALSE, TRUE, 6);
+	gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), GTK_WIDGET (shell->priv->player_shell), FALSE, TRUE, 6);
 	gtk_widget_show (GTK_WIDGET (shell->priv->player_shell));
 
 	gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), GTK_WIDGET (shell->priv->top_container), FALSE, TRUE, 0);
@@ -1423,22 +1407,26 @@ construct_widgets (RBShell *shell)
 static void
 construct_sources (RBShell *shell)
 {
+	RBDisplayPage *page_group;
 	GError *error = NULL;
 	char *pathname;
 
 	rb_profile_start ("constructing sources");
 
+	page_group = RB_DISPLAY_PAGE_GROUP_LIBRARY;
 	shell->priv->library_source = RB_LIBRARY_SOURCE (rb_library_source_new (shell));
-	rb_shell_append_source (shell, RB_SOURCE (shell->priv->library_source), NULL);
 	shell->priv->podcast_source = RB_PODCAST_SOURCE (rb_podcast_main_source_new (shell, shell->priv->podcast_manager));
-	rb_shell_append_source (shell, RB_SOURCE (shell->priv->podcast_source), NULL);
 	shell->priv->missing_files_source = rb_missing_files_source_new (shell, shell->priv->library_source);
-	rb_shell_append_source (shell, shell->priv->missing_files_source, NULL);
+
 	shell->priv->import_errors_source = rb_import_errors_source_new (shell,
 									 RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR,
 									 RHYTHMDB_ENTRY_TYPE_SONG,
 									 RHYTHMDB_ENTRY_TYPE_IGNORE);
-	rb_shell_append_source (shell, shell->priv->import_errors_source, NULL);
+
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source), page_group);
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->podcast_source), page_group);
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->missing_files_source), page_group);
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (shell->priv->import_errors_source), page_group);
 
 	rb_podcast_main_source_add_subsources (RB_PODCAST_MAIN_SOURCE (shell->priv->podcast_source));
 
@@ -1458,7 +1446,9 @@ construct_sources (RBShell *shell)
 	/* Initialize playlist manager */
 	rb_debug ("shell: creating playlist manager");
 	shell->priv->playlist_manager = rb_playlist_manager_new (shell,
-								 RB_SOURCELIST (shell->priv->sourcelist), pathname);
+								 shell->priv->display_page_model,
+								 shell->priv->display_page_tree,
+								 pathname);
 
 	g_object_set (shell->priv->clipboard_shell,
 		      "playlist-manager", shell->priv->playlist_manager,
@@ -1487,8 +1477,8 @@ construct_load_ui (RBShell *shell)
 {
 	GtkWidget *menubar;
 	GtkWidget *toolbar;
- 	GtkWidget *hbox;
- 	GtkToolItem *tool_item;
+	GtkWidget *hbox;
+	GtkToolItem *tool_item;
 	GError *error = NULL;
 
 	rb_debug ("shell: loading ui");
@@ -1581,7 +1571,6 @@ rb_shell_constructed (GObject *object)
 					     shell);
 
 	construct_db (shell);
-	rb_source_group_init ();
 
 	/* initialize shell services */
 	construct_widgets (shell);
@@ -1616,7 +1605,7 @@ rb_shell_constructed (GObject *object)
 	shell->priv->window_y = eel_gconf_get_integer (CONF_STATE_WINDOW_Y_POSITION);
 	shell->priv->paned_position = eel_gconf_get_integer (CONF_STATE_PANED_POSITION);
 	shell->priv->right_paned_position = eel_gconf_get_integer (CONF_STATE_RIGHT_PANED_POSITION);
-	shell->priv->sourcelist_height = eel_gconf_get_integer (CONF_STATE_SOURCELIST_HEIGHT);
+	shell->priv->display_page_tree_height = eel_gconf_get_integer (CONF_STATE_SOURCELIST_HEIGHT);
 	shell->priv->statusbar_hidden = eel_gconf_get_boolean (CONF_UI_STATUSBAR_HIDDEN);
 
 	rb_debug ("shell: syncing with gconf");
@@ -1635,10 +1624,16 @@ rb_shell_constructed (GObject *object)
 	rb_shell_sync_party_mode (shell);
 	rb_shell_sync_toolbar_state (shell);
 
-	rb_shell_select_source (shell, RB_SOURCE (shell->priv->library_source));
+	rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source));
 
 	rb_plugins_engine_init (shell);
 
+	/* by now we've added the built in sources and any sources from plugins,
+	 * so we can consider the fixed page groups loaded
+	 */
+	rb_display_page_group_loaded (RB_DISPLAY_PAGE_GROUP (RB_DISPLAY_PAGE_GROUP_LIBRARY));
+	rb_display_page_group_loaded (RB_DISPLAY_PAGE_GROUP (RB_DISPLAY_PAGE_GROUP_STORES));
+
 	rb_missing_plugins_init (GTK_WINDOW (shell->priv->window));
 
 	g_idle_add ((GSourceFunc)_scan_idle, shell);
@@ -1897,25 +1892,15 @@ rb_shell_sync_window_state (RBShell *shell,
 }
 
 static void
-source_selected_cb (RBSourceList *sourcelist,
-		    RBSource *source,
-		    RBShell *shell)
-{
-	rb_debug ("source selected");
-	rb_shell_select_source (shell, source);
-}
-
-static void
-source_activated_cb (RBSourceList *sourcelist,
-		     RBSource *source,
-		     RBShell *shell)
+display_page_selected_cb (RBDisplayPageTree *display_page_tree,
+			  RBDisplayPage *page,
+			  RBShell *shell)
 {
-	rb_debug ("source activated");
-
-	rb_shell_activate_source (shell, source, 2, NULL);
+	rb_debug ("page selected");
+	rb_shell_select_page (shell, page);
 }
 
-static gboolean
+gboolean
 rb_shell_activate_source (RBShell *shell, RBSource *source, guint play, GError **error)
 {
 	RhythmDBEntry *entry;
@@ -1929,20 +1914,20 @@ rb_shell_activate_source (RBShell *shell, RBSource *source, guint play, GError *
 	 */
 
 	/* Select the new one, and optionally start it playing */
-	rb_shell_select_source (shell, source);
+	rb_shell_select_page (shell, RB_DISPLAY_PAGE (source));
 
 	switch (play) {
-	case 0:
+	case RB_SHELL_ACTIVATION_SELECT:
 		return TRUE;
 
-	case 1:
+	case RB_SHELL_ACTIVATION_PLAY:
 		entry = rb_shell_player_get_playing_entry (shell->priv->player_shell);
 		if (entry != NULL) {
 			rhythmdb_entry_unref (entry);
 			return TRUE;
 		}
 		/* fall through */
-	case 2:
+	case RB_SHELL_ACTIVATION_ALWAYS_PLAY:
 		rb_shell_player_set_playing_source (shell->priv->player_shell, source);
 		return rb_shell_player_playpause (shell->priv->player_shell, FALSE, error);
 
@@ -1954,7 +1939,7 @@ rb_shell_activate_source (RBShell *shell, RBSource *source, guint play, GError *
 static void
 rb_shell_db_save_error_cb (RhythmDB *db,
 			   const char *uri, const GError *error,
-		  	   RBShell *shell)
+			   RBShell *shell)
 {
 	rb_error_dialog (GTK_WINDOW (shell->priv->window),
 			 _("Error while saving song information"),
@@ -2003,32 +1988,29 @@ rb_shell_register_entry_type_for_source (RBShell *shell,
 }
 
 /**
- * rb_shell_append_source:
+ * rb_shell_append_display_page:
  * @shell: the #RBShell
- * @source: the new #RBSource
- * @parent: the parent source for the new source (optional)
+ * @page: the new #RBDisplayPage
+ * @parent: the parent page for the new page (optional)
  *
- * Registers a new source with the shell.  All sources must be
- * registered.
+ * Adds a new display page to the shell.
  */
 void
-rb_shell_append_source (RBShell *shell,
-			RBSource *source,
-			RBSource *parent)
+rb_shell_append_display_page (RBShell *shell, RBDisplayPage *page, RBDisplayPage *parent)
 {
-	shell->priv->sources
-		= g_list_append (shell->priv->sources, source);
+	if (RB_IS_SOURCE (page)) {
+		shell->priv->sources = g_list_append (shell->priv->sources, RB_SOURCE (page));
+	}
 
-	g_signal_connect_object (G_OBJECT (source), "deleted",
-				 G_CALLBACK (rb_shell_source_deleted_cb), shell, 0);
+	g_signal_connect_object (G_OBJECT (page), "deleted",
+				 G_CALLBACK (rb_shell_display_page_deleted_cb), shell, 0);
 
 	gtk_notebook_append_page (GTK_NOTEBOOK (shell->priv->notebook),
-				  GTK_WIDGET (source),
+				  GTK_WIDGET (page),
 				  gtk_label_new (""));
-	gtk_widget_show (GTK_WIDGET (source));
+	gtk_widget_show (GTK_WIDGET (page));
 
-	rb_sourcelist_append (RB_SOURCELIST (shell->priv->sourcelist),
-			      source, parent);
+	rb_display_page_model_add_page (shell->priv->display_page_model, page, parent);
 }
 
 static void
@@ -2036,7 +2018,7 @@ rb_shell_playlist_added_cb (RBPlaylistManager *mgr,
 			    RBSource *source,
 			    RBShell *shell)
 {
-	rb_shell_append_source (shell, source, NULL);
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (source), RB_DISPLAY_PAGE_GROUP_PLAYLISTS);
 }
 
 static void
@@ -2056,43 +2038,46 @@ rb_shell_medium_added_cb (RBRemovableMediaManager *mgr,
 			  RBSource *source,
 			  RBShell *shell)
 {
-	rb_shell_append_source (shell, source, NULL);
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (source), RB_DISPLAY_PAGE_GROUP_DEVICES);
 }
 
 static void
-rb_shell_source_deleted_cb (RBSource *source,
-			    RBShell *shell)
+rb_shell_display_page_deleted_cb (RBDisplayPage *page, RBShell *shell)
 {
-	RhythmDBEntryType *entry_type;
 
-	rb_debug ("source deleted");
+	rb_debug ("display page deleted");
 
-	/* remove from the map if the source owns the type */
-	g_object_get (source, "entry-type", &entry_type, NULL);
-	if (rb_shell_get_source_by_entry_type (shell, entry_type) == source) {
-		g_hash_table_remove (shell->priv->sources_hash, entry_type);
-	}
-	g_object_unref (entry_type);
+	if (RB_IS_SOURCE (page)) {
+		RhythmDBEntryType *entry_type;
+		RBSource *source = RB_SOURCE (page);
 
+		/* remove from the map if the source owns the type */
+		g_object_get (source, "entry-type", &entry_type, NULL);
+		if (rb_shell_get_source_by_entry_type (shell, entry_type) == source) {
+			g_hash_table_remove (shell->priv->sources_hash, entry_type);
+		}
+		g_object_unref (entry_type);
 
-	if (source == rb_shell_player_get_playing_source (shell->priv->player_shell) ||
-	    source == rb_shell_player_get_active_source (shell->priv->player_shell)) {
-		rb_shell_player_stop (shell->priv->player_shell);
-	}
-	if (source == shell->priv->selected_source) {
-		if (source != RB_SOURCE (shell->priv->library_source))
-			rb_shell_select_source (shell, RB_SOURCE (shell->priv->library_source));
-		else
-			rb_shell_select_source (shell, NULL);
-	}
+		if (source == rb_shell_player_get_playing_source (shell->priv->player_shell) ||
+		    source == rb_shell_player_get_active_source (shell->priv->player_shell)) {
+			rb_shell_player_stop (shell->priv->player_shell);
+		}
 
-	shell->priv->sources = g_list_remove (shell->priv->sources, source);
+		shell->priv->sources = g_list_remove (shell->priv->sources, source);
+	}
 
-	rb_sourcelist_remove (RB_SOURCELIST (shell->priv->sourcelist), source);
+	if (page == shell->priv->selected_page) {
+		if (page != RB_DISPLAY_PAGE (shell->priv->library_source)) {
+			rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source));
+		} else {
+			rb_shell_select_page (shell, NULL);
+		}
+	}
 
+	rb_display_page_model_remove_page (shell->priv->display_page_model, page);
 	gtk_notebook_remove_page (GTK_NOTEBOOK (shell->priv->notebook),
 				  gtk_notebook_page_num (GTK_NOTEBOOK (shell->priv->notebook),
-							 GTK_WIDGET (source)));
+							 GTK_WIDGET (page)));
 }
 
 static void
@@ -2101,22 +2086,23 @@ rb_shell_playing_source_changed_cb (RBShellPlayer *player,
 				    RBShell *shell)
 {
 	rb_debug ("playing source changed");
-	if (source != RB_SOURCE (shell->priv->queue_source))
-		rb_sourcelist_set_playing_source (RB_SOURCELIST (shell->priv->sourcelist),
-						  source);
+	if (source != RB_SOURCE (shell->priv->queue_source)) {
+		rb_display_page_model_set_playing_source (shell->priv->display_page_model, RB_DISPLAY_PAGE (source));
+	}
 }
 
 static void
 rb_shell_playing_from_queue_cb (RBShellPlayer *player,
-			 	GParamSpec *param,
+				GParamSpec *param,
 				RBShell *shell)
 {
 	gboolean from_queue;
 
 	g_object_get (player, "playing-from-queue", &from_queue, NULL);
 	if (!shell->priv->queue_as_sidebar) {
-		rb_sourcelist_set_playing_source (RB_SOURCELIST (shell->priv->sourcelist),
-						  rb_shell_player_get_playing_source (shell->priv->player_shell));
+		RBSource *source;
+		source = rb_shell_player_get_playing_source (shell->priv->player_shell);
+		rb_display_page_model_set_playing_source (shell->priv->display_page_model, RB_DISPLAY_PAGE (source));
 	} else {
 		RBSource *source;
 		RhythmDBEntry *entry;
@@ -2144,8 +2130,8 @@ rb_shell_playing_from_queue_cb (RBShellPlayer *player,
 		}
 		rhythmdb_entry_unref (entry);
 
-		rb_sourcelist_set_playing_source (RB_SOURCELIST (shell->priv->sourcelist),
-						  rb_shell_player_get_active_source (shell->priv->player_shell));
+		source = rb_shell_player_get_active_source (shell->priv->player_shell);
+		rb_display_page_model_set_playing_source (shell->priv->display_page_model, RB_DISPLAY_PAGE (source));
 	}
 }
 
@@ -2163,51 +2149,61 @@ merge_source_ui_cb (const char *action,
 }
 
 static void
-rb_shell_select_source (RBShell *shell,
-			RBSource *source)
+rb_shell_select_page (RBShell *shell, RBDisplayPage *page)
 {
 	GList *actions;
+	int pagenum;
 
-	if (shell->priv->selected_source == source)
+	if (shell->priv->selected_page == page)
 		return;
 
-	rb_debug ("selecting source %p", source);
+	rb_debug ("selecting page %p", page);
 
-	if (shell->priv->selected_source) {
-		rb_source_deactivate (shell->priv->selected_source);
+	if (shell->priv->selected_page) {
+		rb_display_page_deselected (shell->priv->selected_page);
 		gtk_ui_manager_remove_ui (shell->priv->ui_manager, shell->priv->source_ui_merge_id);
 	}
 
-	shell->priv->selected_source = source;
-	rb_source_activate (shell->priv->selected_source);
+	shell->priv->selected_page = page;
+	rb_display_page_selected (shell->priv->selected_page);
 
-	/* show source */
-	gtk_notebook_set_current_page (GTK_NOTEBOOK (shell->priv->notebook),
-				       gtk_notebook_page_num (GTK_NOTEBOOK (shell->priv->notebook), GTK_WIDGET (source)));
+	/* show page */
+	pagenum = gtk_notebook_page_num (GTK_NOTEBOOK (shell->priv->notebook),
+					 GTK_WIDGET (page));
+	gtk_notebook_set_current_page (GTK_NOTEBOOK (shell->priv->notebook), pagenum);
 
-	g_signal_handlers_block_by_func (G_OBJECT (shell->priv->sourcelist),
-					 G_CALLBACK (source_selected_cb),
+	g_signal_handlers_block_by_func (shell->priv->display_page_tree,
+					 G_CALLBACK (display_page_selected_cb),
 					 shell);
-	rb_sourcelist_select (RB_SOURCELIST (shell->priv->sourcelist),
-			      source);
-	g_signal_handlers_unblock_by_func (G_OBJECT (shell->priv->sourcelist),
-					   G_CALLBACK (source_selected_cb),
+	rb_display_page_tree_select (shell->priv->display_page_tree, page);
+	g_signal_handlers_unblock_by_func (shell->priv->display_page_tree,
+					   G_CALLBACK (display_page_selected_cb),
 					   shell);
 
 	/* update services */
-	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);
-	rb_statusbar_set_source (shell->priv->statusbar, source);
-	g_object_set (G_OBJECT (shell->priv->playlist_manager), "source", source, NULL);
-	g_object_set (G_OBJECT (shell->priv->removable_media_manager), "source", source, NULL);
-
-	/* merge the source-specific UI */
-	actions = rb_source_get_ui_actions (source);
+	if (RB_IS_SOURCE (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-source");
+	g_object_notify (G_OBJECT (shell), "selected-page");
 }
 
 static void
@@ -2311,9 +2307,9 @@ rb_shell_view_queue_as_sidebar_changed_cb (GtkAction *action,
 	eel_gconf_set_boolean (CONF_UI_QUEUE_AS_SIDEBAR, shell->priv->queue_as_sidebar);
 
 	if (shell->priv->queue_as_sidebar &&
-	    shell->priv->selected_source == RB_SOURCE (shell->priv->queue_source)) {
+	    shell->priv->selected_page == RB_DISPLAY_PAGE (shell->priv->queue_source)) {
 		/* queue no longer exists as a source, so change to the library */
-		rb_shell_select_source (shell, RB_SOURCE (shell->priv->library_source));
+		rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source));
 	}
 
 	rb_shell_playing_from_queue_cb (shell->priv->player_shell, NULL, shell);
@@ -2487,7 +2483,7 @@ rb_shell_cmd_plugins (GtkAction *action,
 								    GTK_RESPONSE_CLOSE,
 								    NULL);
 		content_area = gtk_dialog_get_content_area (GTK_DIALOG (shell->priv->plugins));
-	    	gtk_container_set_border_width (GTK_CONTAINER (shell->priv->plugins), 5);
+		gtk_container_set_border_width (GTK_CONTAINER (shell->priv->plugins), 5);
 		gtk_box_set_spacing (GTK_BOX (content_area), 2);
 
 		g_signal_connect_object (G_OBJECT (shell->priv->plugins),
@@ -2638,6 +2634,7 @@ idle_handle_load_complete (RBShell *shell)
 	rb_debug ("load complete");
 
 	rb_playlist_manager_load_playlists (shell->priv->playlist_manager);
+	rb_display_page_group_loaded (RB_DISPLAY_PAGE_GROUP (RB_DISPLAY_PAGE_GROUP_PLAYLISTS));
 	shell->priv->load_complete = TRUE;
 	shell->priv->save_playlist_id = g_timeout_add_seconds (10, (GSourceFunc) idle_save_playlist_manager, shell);
 
@@ -2683,7 +2680,7 @@ rb_shell_sync_pane_visibility (RBShell *shell)
 	GtkAction *action;
 
 	if (shell->priv->queue_source != NULL) {
-		g_object_set (G_OBJECT (shell->priv->queue_source), "visibility", !shell->priv->queue_as_sidebar, NULL);
+		g_object_set (shell->priv->queue_source, "visibility", !shell->priv->queue_as_sidebar, NULL);
 	}
 
 	if (shell->priv->queue_as_sidebar) {
@@ -2774,18 +2771,19 @@ rb_shell_sync_party_mode (RBShell *shell)
 
 	/* disable/enable quit action */
 	action = gtk_action_group_get_action (shell->priv->actiongroup, "MusicQuit");
-	g_object_set (G_OBJECT (action), "sensitive", !shell->priv->party_mode, NULL);
+	g_object_set (action, "sensitive", !shell->priv->party_mode, NULL);
 	action = gtk_action_group_get_action (shell->priv->actiongroup, "ViewSmallDisplay");
-	g_object_set (G_OBJECT (action), "sensitive", !shell->priv->party_mode, NULL);
+	g_object_set (action, "sensitive", !shell->priv->party_mode, NULL);
 
 	/* show/hide queue as sidebar ? */
 
 	g_object_set (shell->priv->player_shell, "queue-only", shell->priv->party_mode, NULL);
 
 	/* Set playlist manager source to the current source to update properties */
-	if (shell->priv->selected_source) {
-		g_object_set (G_OBJECT (shell->priv->playlist_manager), "source", shell->priv->selected_source, NULL);
-		rb_shell_clipboard_set_source (shell->priv->clipboard_shell, shell->priv->selected_source);
+	if (shell->priv->selected_page && RB_IS_SOURCE (shell->priv->selected_page)) {
+		RBSource *source = RB_SOURCE (shell->priv->selected_page);
+		g_object_set (shell->priv->playlist_manager, "source", source, NULL);
+		rb_shell_clipboard_set_source (shell->priv->clipboard_shell, source);
 	}
 
 	gtk_window_set_keep_above (GTK_WINDOW (shell->priv->window), shell->priv->party_mode);
@@ -2823,21 +2821,21 @@ rb_shell_sync_smalldisplay (RBShell *shell)
 	toolbar = gtk_ui_manager_get_widget (shell->priv->ui_manager, "/ToolBar");
 
 	if (shell->priv->window_small) {
-		g_object_set (G_OBJECT (action), "sensitive", FALSE, NULL);
-		g_object_set (G_OBJECT (queue_action), "sensitive", FALSE, NULL);
-		g_object_set (G_OBJECT (party_mode_action), "sensitive", FALSE, NULL);
-		g_object_set (G_OBJECT (jump_to_playing_action), "sensitive", FALSE, NULL);
+		g_object_set (action, "sensitive", FALSE, NULL);
+		g_object_set (queue_action, "sensitive", FALSE, NULL);
+		g_object_set (party_mode_action, "sensitive", FALSE, NULL);
+		g_object_set (jump_to_playing_action, "sensitive", FALSE, NULL);
 
 		gtk_widget_hide (GTK_WIDGET (shell->priv->paned));
 	} else {
 		RhythmDBEntry *playing;
 
-		g_object_set (G_OBJECT (action), "sensitive", TRUE, NULL);
-		g_object_set (G_OBJECT (queue_action), "sensitive", TRUE, NULL);
-		g_object_set (G_OBJECT (party_mode_action), "sensitive", TRUE, NULL);
+		g_object_set (action, "sensitive", TRUE, NULL);
+		g_object_set (queue_action, "sensitive", TRUE, NULL);
+		g_object_set (party_mode_action, "sensitive", TRUE, NULL);
 
 		playing = rb_shell_player_get_playing_entry (shell->priv->player_shell);
-		g_object_set (G_OBJECT (jump_to_playing_action), "sensitive", playing != NULL, NULL);
+		g_object_set (jump_to_playing_action, "sensitive", playing != NULL, NULL);
 		if (playing)
 			rhythmdb_entry_unref (playing);
 
@@ -2908,7 +2906,7 @@ rb_shell_sync_paned (RBShell *shell)
 	gtk_paned_set_position (GTK_PANED (shell->priv->paned),
 				shell->priv->paned_position);
 	gtk_paned_set_position (GTK_PANED (shell->priv->queue_paned),
-				shell->priv->sourcelist_height);
+				shell->priv->display_page_tree_height);
 }
 
 static void
@@ -2929,24 +2927,26 @@ sidebar_paned_size_allocate_cb (GtkWidget *widget,
 				GtkAllocation *allocation,
 				RBShell *shell)
 {
-	shell->priv->sourcelist_height = gtk_paned_get_position (GTK_PANED (shell->priv->queue_paned));
-	rb_debug ("sidebar paned position %d", shell->priv->sourcelist_height);
-	eel_gconf_set_integer (CONF_STATE_SOURCELIST_HEIGHT, shell->priv->sourcelist_height);
+	shell->priv->display_page_tree_height = gtk_paned_get_position (GTK_PANED (shell->priv->queue_paned));
+	rb_debug ("sidebar paned position %d", shell->priv->display_page_tree_height);
+	eel_gconf_set_integer (CONF_STATE_SOURCELIST_HEIGHT, shell->priv->display_page_tree_height);
 }
 
 static void
-sourcelist_drag_received_cb (RBSourceList *sourcelist,
-			     RBSource *source,
-			     GtkSelectionData *data,
-			     RBShell *shell)
+display_page_tree_drag_received_cb (RBDisplayPageTree *display_page_tree,
+				    RBDisplayPage *page,
+				    GtkSelectionData *data,
+				    RBShell *shell)
 {
-        if (source == NULL) {
+        if (page == NULL) {
+		RBSource *source;
 		source = rb_playlist_manager_new_playlist_from_selection_data (shell->priv->playlist_manager,
 									       data);
+		page = RB_DISPLAY_PAGE (source);
         }
 
-        if (source != NULL) {
-                rb_source_receive_drag (source, data);
+        if (page != NULL) {
+                rb_display_page_receive_drag (page, data);
         }
 
 }
@@ -2964,9 +2964,10 @@ static void
 rb_shell_cmd_view_all (GtkAction *action,
 		       RBShell *shell)
 {
+	RBSource *source = RB_SOURCE (shell->priv->selected_page);
 	rb_debug ("view all");
 
-	rb_source_reset_filters (shell->priv->selected_source);
+	rb_source_reset_filters (source);
 	rb_source_header_clear_search (shell->priv->source_header);
 	rb_source_header_focus_search_box (shell->priv->source_header);
 }
@@ -2991,7 +2992,7 @@ rb_shell_jump_to_entry_with_source (RBShell *shell,
 		return;
 
 	songs = rb_source_get_entry_view (source);
-	rb_shell_select_source (shell, source);
+	rb_shell_select_page (shell, RB_DISPLAY_PAGE (source));
 
 	if (songs != NULL) {
 		rb_entry_view_scroll_to_entry (songs, entry);
@@ -3024,15 +3025,6 @@ rb_shell_jump_to_current (RBShell *shell)
 	rhythmdb_entry_unref (playing);
 }
 
-static gboolean
-rb_shell_show_popup_cb (RBSourceList *sourcelist,
-			RBSource *target,
-			RBShell *shell)
-{
-	rb_debug ("popup");
-	return rb_source_show_popup (target);
-}
-
 void
 rb_shell_notify_custom (RBShell *shell,
 			guint timeout,
@@ -3253,8 +3245,7 @@ load_uri_finish (RBShell *shell, RBSource *entry_source, RhythmDBEntry *entry, g
 		GError *error = NULL;
 
 		g_object_get (entry_source, "name", &name, NULL);
-		/* play type 2: we don't have an entry to play, so just play something */
-		if (rb_shell_activate_source (shell, entry_source, 2, &error) == FALSE) {
+		if (rb_shell_activate_source (shell, entry_source, RB_SHELL_ACTIVATION_ALWAYS_PLAY, &error) == FALSE) {
 			rb_debug ("couldn't activate source %s: %s", name, error->message);
 			g_clear_error (&error);
 		} else {
@@ -3382,7 +3373,7 @@ rb_shell_load_uri (RBShell *shell,
 	 * the Podcast source */
 	if (rb_uri_could_be_podcast (uri, NULL)) {
 		rb_podcast_manager_subscribe_feed (shell->priv->podcast_manager, uri, FALSE);
-		rb_shell_select_source (shell, RB_SOURCE (shell->priv->podcast_source));
+		rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->podcast_source));
 		return TRUE;
 	}
 
@@ -3835,11 +3826,6 @@ rb_shell_add_widget (RBShell *shell, GtkWidget *widget, RBShellUILocation locati
 	GtkBox *box;
 
 	switch (location) {
-	case RB_SHELL_UI_LOCATION_MAIN_NOTEBOOK:
-		gtk_notebook_append_page (GTK_NOTEBOOK (shell->priv->notebook),
-					  widget,
-					  gtk_label_new (""));
-		break;
 	case RB_SHELL_UI_LOCATION_RIGHT_SIDEBAR:
 		if (!shell->priv->right_sidebar_widget_count)
 			gtk_widget_show (GTK_WIDGET (shell->priv->right_sidebar_container));
@@ -3865,16 +3851,8 @@ void
 rb_shell_remove_widget (RBShell *shell, GtkWidget *widget, RBShellUILocation location)
 {
 	GtkBox *box;
-	gint page_num;
 
 	switch (location) {
-	case RB_SHELL_UI_LOCATION_MAIN_NOTEBOOK:
-		page_num = gtk_notebook_page_num (GTK_NOTEBOOK (shell->priv->notebook),
-						  widget);
-		g_return_if_fail (page_num != -1);
-		gtk_notebook_remove_page (GTK_NOTEBOOK (shell->priv->notebook),
-					  page_num);
-		break;
 	case RB_SHELL_UI_LOCATION_RIGHT_SIDEBAR:
 		shell->priv->right_sidebar_widget_count--;
 		if (!shell->priv->right_sidebar_widget_count)
@@ -3888,40 +3866,28 @@ rb_shell_remove_widget (RBShell *shell, GtkWidget *widget, RBShellUILocation loc
 	}
 }
 
-/**
- * rb_shell_notebook_set_page:
- * @shell: the #RBShell
- * @widget: #GtkWidget for the page to display
- *
- * Changes the visible page in the main window notebook widget.  Use this to
- * display widgets added to the #RB_SHELL_UI_LOCATION_MAIN_NOTEBOOK location.
- */
-void
-rb_shell_notebook_set_page (RBShell *shell, GtkWidget *widget)
-{
-	gint page = 0;
+/* This should really be standard. */
+#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
 
-	/* if no widget specified, use the selected source */
-	if (widget == NULL && shell->priv->selected_source)
-		widget = GTK_WIDGET (shell->priv->selected_source);
+GType
+rb_shell_activation_type_get_type (void)
+{
+	static GType etype = 0;
 
-	if (widget)
-		page = gtk_notebook_page_num (GTK_NOTEBOOK (shell->priv->notebook), widget);
+	if (etype == 0)	{
+		static const GEnumValue values[] = {
+			ENUM_ENTRY (RB_SHELL_ACTIVATION_SELECT, "select"),
+			ENUM_ENTRY (RB_SHELL_ACTIVATION_PLAY, "play"),
+			ENUM_ENTRY (RB_SHELL_ACTIVATION_ALWAYS_PLAY, "always-play"),
+			{ 0, 0, 0 }
+		};
 
-	if (RB_IS_SOURCE (widget)) {
-		rb_source_header_set_source (shell->priv->source_header, RB_SOURCE (widget));
-		rb_shell_clipboard_set_source (shell->priv->clipboard_shell, RB_SOURCE (widget));
-	} else {
-		rb_source_header_set_source (shell->priv->source_header, NULL);
-		rb_shell_clipboard_set_source (shell->priv->clipboard_shell, NULL);
+		etype = g_enum_register_static ("RBShellActivationType", values);
 	}
 
-	gtk_notebook_set_current_page (GTK_NOTEBOOK (shell->priv->notebook), page);
+	return etype;
 }
 
-/* This should really be standard. */
-#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
-
 GType
 rb_shell_ui_location_get_type (void)
 {
@@ -3933,7 +3899,6 @@ rb_shell_ui_location_get_type (void)
 			ENUM_ENTRY (RB_SHELL_UI_LOCATION_RIGHT_SIDEBAR, "right-sidebar"),
 			ENUM_ENTRY (RB_SHELL_UI_LOCATION_MAIN_TOP, "main-top"),
 			ENUM_ENTRY (RB_SHELL_UI_LOCATION_MAIN_BOTTOM, "main-bottom"),
-			ENUM_ENTRY (RB_SHELL_UI_LOCATION_MAIN_NOTEBOOK, "main-notebook"),
 			{ 0, 0, 0 }
 		};
 
diff --git a/shell/rb-shell.h b/shell/rb-shell.h
index 0c6079d..2c6debe 100644
--- a/shell/rb-shell.h
+++ b/shell/rb-shell.h
@@ -44,6 +44,16 @@ G_BEGIN_DECLS
 
 typedef enum
 {
+	RB_SHELL_ACTIVATION_SELECT = 0,
+	RB_SHELL_ACTIVATION_PLAY = 1,
+	RB_SHELL_ACTIVATION_ALWAYS_PLAY = 2
+} RBShellActivationType;
+
+GType rb_shell_activation_type_get_type (void);
+#define RB_TYPE_SHELL_ACTIVATION_TYPE (rb_shell_activation_type_get_type ())
+
+typedef enum
+{
 	RB_SHELL_ERROR_NO_SUCH_URI,
 	RB_SHELL_ERROR_NO_SUCH_PROPERTY,
 	RB_SHELL_ERROR_IMMUTABLE_PROPERTY,
@@ -62,8 +72,7 @@ typedef enum
 	RB_SHELL_UI_LOCATION_SIDEBAR,
 	RB_SHELL_UI_LOCATION_RIGHT_SIDEBAR,
 	RB_SHELL_UI_LOCATION_MAIN_TOP,
-	RB_SHELL_UI_LOCATION_MAIN_BOTTOM,
-	RB_SHELL_UI_LOCATION_MAIN_NOTEBOOK
+	RB_SHELL_UI_LOCATION_MAIN_BOTTOM
 } RBShellUILocation;
 
 GType rb_shell_ui_location_get_type (void);
@@ -150,6 +159,10 @@ gboolean	rb_shell_activate_source_by_uri (RBShell *shell,
 						 const char *source_uri,
 						 guint play,
 						 GError **error);
+gboolean	rb_shell_activate_source (RBShell *shell,
+					  RBSource *source,
+					  guint play,
+					  GError **error);
 
 void            rb_shell_notify_custom  (RBShell *shell,
 					 guint timeout,
@@ -169,11 +182,10 @@ RBSource * rb_shell_get_source_by_entry_type (RBShell *shell,
 
 gboolean        rb_shell_get_party_mode (RBShell *shell);
 
-void rb_shell_append_source (RBShell *shell, RBSource *source, RBSource *parent);
+void 		rb_shell_append_display_page (RBShell *shell, RBDisplayPage *page, RBDisplayPage *parent);
 
 void 		rb_shell_add_widget (RBShell *shell, GtkWidget *widget, RBShellUILocation location, gboolean expand, gboolean fill);
 void 		rb_shell_remove_widget (RBShell *shell, GtkWidget *widget, RBShellUILocation location);
-void		rb_shell_notebook_set_page (RBShell *shell, GtkWidget *widget);
 
 G_END_DECLS
 
diff --git a/shell/rb-statusbar.c b/shell/rb-statusbar.c
index ac74a90..82ce135 100644
--- a/shell/rb-statusbar.c
+++ b/shell/rb-statusbar.c
@@ -47,15 +47,15 @@
  * The status bar is displayed at the bottom of the main window.  It consists of some
  * status text and a progress bar.
  *
- * The status text usually comes from the selected source, and typically shows the number
+ * The status text usually comes from the selected page, and typically shows the number
  * of songs, the total duration and the total file size.  When a menu is open, however, 
  * the status text shows the description of the currently selected menu item.
  *
- * The progress bar shows progress information from a variety of sources.  The source that
- * is currently selected in the source list can provide progress information, such as buffering
- * feedback, track transfer status, or progress for updating a song catalog.  If the source
- * does not provide status information and the database is busy (loading the database from disk,
- * processing a query, etc.) the progress bar will be pulsed periodically.
+ * The progress bar shows progress information from a variety of sources.  The page that
+ * is currently selected in the display page tree can provide progress information, such as
+ * buffering feedback, track transfer status, or progress for updating a song catalog.
+ * If the page does not provide status information and the database is busy (loading the
+ * database from disk, processing a query, etc.) the progress bar will be pulsed periodically.
  */
 
 #define EPSILON		(0.00001)
@@ -75,7 +75,7 @@ static void rb_statusbar_get_property (GObject *object,
 
 static gboolean poll_status (RBStatusbar *status);
 static void rb_statusbar_sync_status (RBStatusbar *status);
-static void rb_statusbar_source_status_changed_cb (RBSource *source,
+static void rb_statusbar_page_status_changed_cb (RBDisplayPage *page,
 						   RBStatusbar *statusbar);
 static void rb_statusbar_transfer_progress_cb (RBTrackTransferQueue *queue,
 					       int done,
@@ -86,7 +86,7 @@ static void rb_statusbar_transfer_progress_cb (RBTrackTransferQueue *queue,
 
 struct RBStatusbarPrivate
 {
-        RBSource *selected_source;
+        RBDisplayPage *selected_page;
 	RBTrackTransferQueue *transfer_queue;
 
         RhythmDB *db;
@@ -103,7 +103,7 @@ enum
         PROP_0,
         PROP_DB,
         PROP_UI_MANAGER,
-        PROP_SOURCE,
+        PROP_PAGE,
 	PROP_TRANSFER_QUEUE
 };
 
@@ -133,16 +133,16 @@ rb_statusbar_class_init (RBStatusbarClass *klass)
                                                               RHYTHMDB_TYPE,
                                                               G_PARAM_READWRITE));
 	/**
-	 * RBStatusbar:source:
+	 * RBStatusbar:page:
 	 *
-	 * The currently selected #RBSource
+	 * The currently selected #RBDisplayPage
 	 */
         g_object_class_install_property (object_class,
-                                         PROP_SOURCE,
-                                         g_param_spec_object ("source",
-                                                              "RBSource",
-                                                              "RBSource object",
-                                                              RB_TYPE_SOURCE,
+                                         PROP_PAGE,
+                                         g_param_spec_object ("page",
+                                                              "RBDisplayPage",
+                                                              "RBDisplayPage object",
+                                                              RB_TYPE_DISPLAY_PAGE,
                                                               G_PARAM_READWRITE));
 	/**
 	 * RBStatusbar:ui-manager:
@@ -219,9 +219,9 @@ rb_statusbar_dispose (GObject *object)
 		statusbar->priv->ui_manager = NULL;
 	}
 
-	if (statusbar->priv->selected_source != NULL) {
-		g_object_unref (statusbar->priv->selected_source);
-		statusbar->priv->selected_source = NULL;
+	if (statusbar->priv->selected_page != NULL) {
+		g_object_unref (statusbar->priv->selected_page);
+		statusbar->priv->selected_page = NULL;
 	}
 
 	if (statusbar->priv->transfer_queue != NULL) {
@@ -329,22 +329,21 @@ rb_statusbar_set_property (GObject *object,
                 statusbar->priv->status_poll_id
                         = g_idle_add ((GSourceFunc) poll_status, statusbar);
                 break;
-        case PROP_SOURCE:
-                if (statusbar->priv->selected_source != NULL) {
-			g_signal_handlers_disconnect_by_func (G_OBJECT (statusbar->priv->selected_source),
-							      G_CALLBACK (rb_statusbar_source_status_changed_cb),
+        case PROP_PAGE:
+                if (statusbar->priv->selected_page != NULL) {
+			g_signal_handlers_disconnect_by_func (G_OBJECT (statusbar->priv->selected_page),
+							      G_CALLBACK (rb_statusbar_page_status_changed_cb),
 							      statusbar);
-			g_object_unref (statusbar->priv->selected_source);
+			g_object_unref (statusbar->priv->selected_page);
                 }
 
-                statusbar->priv->selected_source = g_value_get_object (value);
-                rb_debug ("selected source %p", statusbar->priv->selected_source);
-		g_object_ref (statusbar->priv->selected_source);
+                statusbar->priv->selected_page = g_value_dup_object (value);
+                rb_debug ("selected page %p", statusbar->priv->selected_page);
 
-                if (statusbar->priv->selected_source != NULL) {
-			g_signal_connect_object (G_OBJECT (statusbar->priv->selected_source),
+                if (statusbar->priv->selected_page != NULL) {
+			g_signal_connect_object (G_OBJECT (statusbar->priv->selected_page),
 						 "status-changed",
-						 G_CALLBACK (rb_statusbar_source_status_changed_cb),
+						 G_CALLBACK (rb_statusbar_page_status_changed_cb),
 						 statusbar, 0);
                 }
 		rb_statusbar_sync_status (statusbar);
@@ -393,8 +392,8 @@ rb_statusbar_get_property (GObject *object,
         case PROP_DB:
                 g_value_set_object (value, statusbar->priv->db);
                 break;
-        case PROP_SOURCE:
-                g_value_set_object (value, statusbar->priv->selected_source);
+        case PROP_PAGE:
+                g_value_set_object (value, statusbar->priv->selected_page);
                 break;
         case PROP_UI_MANAGER:
                 g_value_set_object (value, statusbar->priv->ui_manager);
@@ -409,21 +408,19 @@ rb_statusbar_get_property (GObject *object,
 }
 
 /**
- * rb_statusbar_set_source:
+ * rb_statusbar_set_page:
  * @statusbar: the #RBStatusbar
- * @source: the new selected #RBSource
+ * @page: the new selected #RBDisplayPage
  *
- * Updates the status bar for a newly selected source.
+ * Updates the status bar for a newly selected page.
  */
 void
-rb_statusbar_set_source (RBStatusbar *statusbar, RBSource *source)
+rb_statusbar_set_page (RBStatusbar *statusbar, RBDisplayPage *page)
 {
         g_return_if_fail (RB_IS_STATUSBAR (statusbar));
-        g_return_if_fail (RB_IS_SOURCE (source));
+        g_return_if_fail (RB_IS_DISPLAY_PAGE (page));
 
-        g_object_set (G_OBJECT (statusbar),
-                      "source", source,
-                      NULL);
+        g_object_set (statusbar, "page", page, NULL);
 }
 
 static gboolean
@@ -450,9 +447,9 @@ rb_statusbar_sync_status (RBStatusbar *status)
 
         /*
          * Behaviour of status bar:
-	 * - use source's status text
-	 * - use source's progress value and text, unless transfer queue provides something
-	 * - if no source progress value or transfer progress value and library is busy,
+	 * - use page's status text
+	 * - use page's progress value and text, unless transfer queue provides something
+	 * - if no page progress value or transfer progress value and library is busy,
 	 *    pulse the progress bar
          */
         
@@ -465,9 +462,9 @@ rb_statusbar_sync_status (RBStatusbar *status)
 		changed = TRUE;
         }
 
-	/* get source details */
-        if (status->priv->selected_source) {
-		rb_source_get_status (status->priv->selected_source, &status_text, &progress_text, &progress);
+	/* get page details */
+        if (status->priv->selected_page) {
+		rb_display_page_get_status (status->priv->selected_page, &status_text, &progress_text, &progress);
 		rb_debug ("updating status with: '%s', '%s', %f",
 			status_text ? status_text : "", progress_text ? progress_text : "", progress);
 	}
@@ -544,7 +541,7 @@ add_status_poll (RBStatusbar *statusbar)
 }
 
 static void
-rb_statusbar_source_status_changed_cb (RBSource *source, RBStatusbar *statusbar)
+rb_statusbar_page_status_changed_cb (RBDisplayPage *page, RBStatusbar *statusbar)
 {
 	rb_debug ("source status changed");
 	add_status_poll (statusbar);
diff --git a/shell/rb-statusbar.h b/shell/rb-statusbar.h
index ffda4a7..935f055 100644
--- a/shell/rb-statusbar.h
+++ b/shell/rb-statusbar.h
@@ -27,7 +27,7 @@
 
 #include <gtk/gtk.h>
 
-#include <sources/rb-source.h>
+#include <sources/rb-display-page.h>
 #include <rhythmdb/rhythmdb.h>
 #include <shell/rb-track-transfer-queue.h>
 
@@ -66,8 +66,8 @@ RBStatusbar *		rb_statusbar_new	(RhythmDB *db,
 						 GtkUIManager *ui_manager,
 						 RBTrackTransferQueue *transfer_queue);
 
-void			rb_statusbar_set_source	(RBStatusbar *statusbar,
-						 RBSource *source);
+void			rb_statusbar_set_page	(RBStatusbar *statusbar,
+						 RBDisplayPage *page);
 
 G_END_DECLS
 
diff --git a/sources/Makefile.am b/sources/Makefile.am
index 4cc2b4f..eb9e217 100644
--- a/sources/Makefile.am
+++ b/sources/Makefile.am
@@ -8,10 +8,11 @@ sourceincludedir = $(includedir)/rhythmbox/sources
 sourceinclude_HEADERS = 		\
 	rb-source.h			\
 	rb-streaming-source.h		\
-	rb-source-group.h		\
 	rb-source-search.h		\
-	rb-sourcelist.h			\
-	rb-sourcelist-model.h		\
+	rb-display-page.h		\
+	rb-display-page-group.h		\
+	rb-display-page-tree.h		\
+	rb-display-page-model.h		\
 	rb-browser-source.h		\
 	rb-removable-media-source.h	\
 	rb-media-player-source.h	\
@@ -25,10 +26,11 @@ libsources_la_SOURCES = 		\
 	$(sourceinclude_HEADERS)	\
 	rb-source.c 			\
 	rb-streaming-source.c		\
-	rb-source-group.c		\
 	rb-source-search.c		\
-	rb-sourcelist.c			\
-	rb-sourcelist-model.c		\
+	rb-display-page.c		\
+	rb-display-page-group.c		\
+	rb-display-page-tree.c		\
+	rb-display-page-model.c		\
 	rb-browser-source.c		\
 	rb-library-source.c		\
 	rb-library-source.h		\
diff --git a/sources/rb-auto-playlist-source.c b/sources/rb-auto-playlist-source.c
index b724fc7..91bfbbe 100644
--- a/sources/rb-auto-playlist-source.c
+++ b/sources/rb-auto-playlist-source.c
@@ -75,8 +75,8 @@ static void rb_auto_playlist_source_get_property (GObject *object,
 						  GParamSpec *pspec);
 
 /* source methods */
-static gboolean impl_show_popup (RBSource *source);
-static gboolean impl_receive_drag (RBSource *asource, GtkSelectionData *data);
+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 void impl_browser_toggled (RBSource *source, gboolean enabled);
@@ -147,6 +147,7 @@ static void
 rb_auto_playlist_source_class_init (RBAutoPlaylistSourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 	RBPlaylistSourceClass *playlist_class = RB_PLAYLIST_SOURCE_CLASS (klass);
 
@@ -156,10 +157,11 @@ rb_auto_playlist_source_class_init (RBAutoPlaylistSourceClass *klass)
 	object_class->set_property = rb_auto_playlist_source_set_property;
 	object_class->get_property = rb_auto_playlist_source_get_property;
 
+	page_class->show_popup = impl_show_popup;
+	page_class->receive_drag = impl_receive_drag;
+
 	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_false_function;
-	source_class->impl_receive_drag = impl_receive_drag;
-	source_class->impl_show_popup = impl_show_popup;
 	source_class->impl_can_browse = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_browser_toggled = impl_browser_toggled;
 	source_class->impl_search = impl_search;
@@ -187,13 +189,11 @@ rb_auto_playlist_source_init (RBAutoPlaylistSource *source)
 			g_object_add_weak_pointer (playlist_pixbuf,
 						   (gpointer *) &playlist_pixbuf);
 
-			rb_source_set_pixbuf (RB_SOURCE (source), playlist_pixbuf);
-
-			/* drop the initial reference to the icon */
+			g_object_set (source, "pixbuf", playlist_pixbuf, NULL);
 			g_object_unref (playlist_pixbuf);
 		}
 	} else {
-		rb_source_set_pixbuf (RB_SOURCE (source), playlist_pixbuf);
+		g_object_set (source, "pixbuf", playlist_pixbuf, NULL);
 	}
 
 }
@@ -272,10 +272,10 @@ rb_auto_playlist_source_constructed (GObject *object)
 				 source, 0);
 
 	g_object_get (source, "shell", &shell, NULL);
-	priv->action_group = _rb_source_register_action_group (RB_SOURCE (source),
-							       "AutoPlaylistActions",
-							       NULL, 0,
-							       shell);
+	priv->action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
+								     "AutoPlaylistActions",
+								     NULL, 0,
+								     shell);
 	if (gtk_action_group_get_action (priv->action_group,
 					 rb_auto_playlist_source_radio_actions[0].name) == NULL) {
 		gtk_action_group_add_radio_actions (priv->action_group,
@@ -323,7 +323,6 @@ rb_auto_playlist_source_new (RBShell *shell, const char *name, gboolean local)
 					"shell", shell,
 					"is-local", local,
 					"entry-type", RHYTHMDB_ENTRY_TYPE_SONG,
-					"source-group", RB_SOURCE_GROUP_PLAYLISTS,
 					"search-type", RB_SOURCE_SEARCH_INCREMENTAL,
 					NULL));
 }
@@ -465,9 +464,9 @@ rb_auto_playlist_source_new_from_xml (RBShell *shell, xmlNodePtr node)
 }
 
 static gboolean
-impl_show_popup (RBSource *source)
+impl_show_popup (RBDisplayPage *page)
 {
-	_rb_source_show_popup (source, AUTO_PLAYLIST_SOURCE_POPUP_PATH);
+	_rb_display_page_show_popup (page, AUTO_PLAYLIST_SOURCE_POPUP_PATH);
 	return TRUE;
 }
 
@@ -566,9 +565,9 @@ impl_browser_toggled (RBSource *source, gboolean enabled)
 }
 
 static gboolean
-impl_receive_drag (RBSource *asource, GtkSelectionData *data)
+impl_receive_drag (RBDisplayPage *page, GtkSelectionData *data)
 {
-	RBAutoPlaylistSource *source = RB_AUTO_PLAYLIST_SOURCE (asource);
+	RBAutoPlaylistSource *source = RB_AUTO_PLAYLIST_SOURCE (page);
 
 	GdkAtom type;
 	GPtrArray *subquery = NULL;
@@ -587,7 +586,7 @@ impl_receive_drag (RBSource *asource, GtkSelectionData *data)
 	names = g_strsplit ((char *) gtk_selection_data_get_data (data), "\r\n", 0);
 	propid = rb_auto_playlist_source_drag_atom_to_prop (type);
 
-	g_object_get (asource, "db", &db, NULL);
+	g_object_get (page, "db", &db, NULL);
 
 	for (i = 0; names[i]; i++) {
 		if (subquery == NULL) {
diff --git a/sources/rb-browser-source.c b/sources/rb-browser-source.c
index a31609a..04e5a0d 100644
--- a/sources/rb-browser-source.c
+++ b/sources/rb-browser-source.c
@@ -323,14 +323,14 @@ rb_browser_source_songs_show_popup_cb (RBEntryView *view,
 
 		klass->impl_show_entry_popup (source);
 	} else {
-		rb_source_show_popup (RB_SOURCE (source));
+		rb_display_page_show_popup (RB_DISPLAY_PAGE (source));
 	}
 }
 
 static void
 default_show_entry_popup (RBBrowserSource *source)
 {
-	_rb_source_show_popup (RB_SOURCE (source), "/BrowserSourceViewPopup");
+	_rb_display_page_show_popup (RB_DISPLAY_PAGE (source), "/BrowserSourceViewPopup");
 }
 
 static void
@@ -355,13 +355,13 @@ rb_browser_source_constructed (GObject *object)
 	g_object_get (shell, "db", &source->priv->db, NULL);
 	shell_player = rb_shell_get_player (shell);
 
-	source->priv->action_group = _rb_source_register_action_group (RB_SOURCE (source),
-								       "BrowserSourceActions",
-								       NULL, 0, NULL);
-	_rb_action_group_add_source_actions (source->priv->action_group,
-					     G_OBJECT (shell),
-					     rb_browser_source_actions,
-					     G_N_ELEMENTS (rb_browser_source_actions));
+	source->priv->action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
+									     "BrowserSourceActions",
+									     NULL, 0, NULL);
+	_rb_action_group_add_display_page_actions (source->priv->action_group,
+						   G_OBJECT (shell),
+						   rb_browser_source_actions,
+						   G_N_ELEMENTS (rb_browser_source_actions));
 
 	/* only add the actions if we haven't already */
 	if (gtk_action_group_get_action (source->priv->action_group,
@@ -846,7 +846,7 @@ songs_view_drag_data_received_cb (GtkWidget *widget,
 				  RBBrowserSource *source)
 {
 	rb_debug ("data dropped on the library source song view");
-	rb_source_receive_drag (RB_SOURCE (source), selection_data);
+	rb_display_page_receive_drag (RB_DISPLAY_PAGE (source), selection_data);
 }
 
 static void
diff --git a/sources/rb-display-page-group.c b/sources/rb-display-page-group.c
new file mode 100644
index 0000000..d8b22e1
--- /dev/null
+++ b/sources/rb-display-page-group.c
@@ -0,0 +1,351 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2010  Jonathan Matthew <jonathan d14n org>
+ *  Copyright (C) 2006  William Jon McCann <mccann jhu edu>
+ *
+ *  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 <string.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "rb-util.h"
+
+#include "rb-display-page-group.h"
+#include "rb-display-page-tree.h"
+
+/**
+ * SECTION:rb-display-page-group
+ * @short_description: Display page grouping
+ *
+ * Page groups define sections of the display page tree.  A page group
+ * consists of an internal name, a display name, and a category.
+ * The internal name can be used to locate a registered page group.
+ * The category is used to sort the page groups.
+ *
+ * While #RBDisplayPageGroup is a subclass of #RBDisplayPage, by default page
+ * groups are never selectable so they have no content.
+ */
+
+G_LOCK_DEFINE_STATIC (display_page_groups);
+
+enum {
+	PROP_0,
+	PROP_ID,
+	PROP_CATEGORY,
+	PROP_LOADED
+};
+
+struct _RBDisplayPageGroupPrivate
+{
+	char *id;
+	RBDisplayPageGroupCategory category;
+	gboolean loaded;
+};
+
+G_DEFINE_TYPE (RBDisplayPageGroup, rb_display_page_group, RB_TYPE_DISPLAY_PAGE)
+
+static GHashTable *display_page_groups_map;
+
+/**
+ * rb_display_page_group_add_core_groups:
+ * @shell: the #RBShell
+ * @page_model: the #RBDisplayPageModel
+ *
+ * Registers core page groups.
+ */
+void
+rb_display_page_group_add_core_groups (GObject *shell, RBDisplayPageModel *page_model)
+{
+	RBDisplayPageGroup *group;
+
+	group = rb_display_page_group_new (shell, "library", _("Library"), RB_DISPLAY_PAGE_GROUP_CATEGORY_FIXED);
+	rb_display_page_model_add_page (page_model, RB_DISPLAY_PAGE (group), NULL);
+
+	group = rb_display_page_group_new (shell, "stores", _("Stores"), RB_DISPLAY_PAGE_GROUP_CATEGORY_FIXED);
+	rb_display_page_model_add_page (page_model, RB_DISPLAY_PAGE (group), NULL);
+
+	group = rb_display_page_group_new (shell, "playlists", _("Playlists"), RB_DISPLAY_PAGE_GROUP_CATEGORY_PERSISTENT);
+	rb_display_page_model_add_page (page_model, RB_DISPLAY_PAGE (group), NULL);
+
+	group = rb_display_page_group_new (shell, "devices", _("Devices"), RB_DISPLAY_PAGE_GROUP_CATEGORY_REMOVABLE);
+	rb_display_page_model_add_page (page_model, RB_DISPLAY_PAGE (group), NULL);
+	rb_display_page_group_loaded (group);
+
+	group = rb_display_page_group_new (shell, "shared", _("Shared"), RB_DISPLAY_PAGE_GROUP_CATEGORY_TRANSIENT);
+	rb_display_page_model_add_page (page_model, RB_DISPLAY_PAGE (group), NULL);
+	rb_display_page_group_loaded (group);
+}
+
+/**
+ * rb_display_page_group_get_by_id:
+ * @id: name of page group to find
+ *
+ * Locates a page group by name.  If the page group has not been registered yet,
+ * returns NULL instead.
+ *
+ * Return value: existing page group, or NULL.
+ */
+RBDisplayPageGroup *
+rb_display_page_group_get_by_id (const char *id)
+{
+	RBDisplayPageGroup *group;
+
+	group = NULL;
+
+	G_LOCK (display_page_groups);
+	if (display_page_groups_map) {
+		group = g_hash_table_lookup (display_page_groups_map, id);
+	}
+	G_UNLOCK (display_page_groups);
+
+	return group;
+}
+
+/**
+ * rb_display_page_group_loaded:
+ * @group: a #RBDisplayPageGroup
+ *
+ * Called when the page group is fully loaded, that is, all initial pages have
+ * been added.
+ */
+void
+rb_display_page_group_loaded (RBDisplayPageGroup *group)
+{
+	group->priv->loaded = TRUE;
+	g_object_notify (G_OBJECT (group), "loaded");
+}
+
+/**
+ * rb_display_page_group_new:
+ * @shell: the #RBShell
+ * @id: name of the page group (untranslated, used in code)
+ * @name: display name of the page group (translated)
+ * @category: category for the page group
+ *
+ * Creates a new page group object.  The group will be registered
+ * before it is returned.
+ *
+ * Return value: new page group
+ */
+RBDisplayPageGroup *
+rb_display_page_group_new (GObject *shell,
+			   const char *id,
+			   const char *name,
+			   RBDisplayPageGroupCategory category)
+{
+	return g_object_new (RB_TYPE_DISPLAY_PAGE_GROUP,
+			     "shell", shell,
+			     "id", id,
+			     "name", name,
+			     "category", category,
+			     NULL);
+}
+
+static gboolean
+impl_selectable (RBDisplayPage *page)
+{
+	return FALSE;
+}
+
+static void
+impl_activate (RBDisplayPage *page)
+{
+	RBDisplayPageTree *display_page_tree;
+	RBShell *shell;
+
+	g_object_get (page, "shell", &shell, NULL);
+	g_object_get (shell, "display-page-tree", &display_page_tree, NULL);
+	rb_display_page_tree_toggle_expanded (display_page_tree, page);
+	g_object_unref (display_page_tree);
+	g_object_unref (shell);
+}
+
+/**
+ * RBDisplayPageGroupType:
+ * @RB_DISPLAY_PAGE_GROUP_CATEGORY_FIXED: Fixed single instance sources (e.g., library)
+ * @RB_DISPLAY_PAGE_GROUP_CATEGORY_PERSISTENT: Persistent multiple-instance sources (e.g. playlists)
+ * @RB_DISPLAY_PAGE_GROUP_CATEGORY_REMOVABLE: Sources representing removable devices
+ * @RB_DISPLAY_PAGE_GROUP_CATEGORY_TRANSIENT: Transient sources (e.g. network shares)
+ *
+ * Predefined categories of page group. The order they're defined here is the order they
+ * appear in the page tree.
+ */
+
+#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
+GType
+rb_display_page_group_category_get_type (void)
+{
+	static GType etype = 0;
+
+	if (etype == 0) {
+		static const GEnumValue values[] = {
+			ENUM_ENTRY (RB_DISPLAY_PAGE_GROUP_CATEGORY_FIXED, "fixed"),
+			ENUM_ENTRY (RB_DISPLAY_PAGE_GROUP_CATEGORY_PERSISTENT, "persistent"),
+			ENUM_ENTRY (RB_DISPLAY_PAGE_GROUP_CATEGORY_REMOVABLE, "removable"),
+			ENUM_ENTRY (RB_DISPLAY_PAGE_GROUP_CATEGORY_TRANSIENT, "transient"),
+			{ 0, 0, 0 }
+		};
+
+		etype = g_enum_register_static ("RBDisplayPageGroupType", values);
+	}
+
+	return etype;
+}
+
+static void
+impl_finalize (GObject *object)
+{
+	RBDisplayPageGroup *group = RB_DISPLAY_PAGE_GROUP (object);
+
+	g_free (group->priv->id);
+	/* remove from group map?  can this ever happen? */
+
+	G_OBJECT_CLASS (rb_display_page_group_parent_class)->finalize (object);
+}
+
+static void
+impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	RBDisplayPageGroup *group = RB_DISPLAY_PAGE_GROUP (object);
+	switch (prop_id) {
+	case PROP_ID:
+		g_value_set_string (value, group->priv->id);
+		break;
+	case PROP_CATEGORY:
+		g_value_set_enum (value, group->priv->category);
+		break;
+	case PROP_LOADED:
+		g_value_set_boolean (value, group->priv->loaded);
+		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)
+{
+	RBDisplayPageGroup *group = RB_DISPLAY_PAGE_GROUP (object);
+	switch (prop_id) {
+	case PROP_ID:
+		group->priv->id = g_value_dup_string (value);
+		break;
+	case PROP_CATEGORY:
+		group->priv->category = g_value_get_enum (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+impl_constructed (GObject *object)
+{
+	RBDisplayPageGroup *group;
+
+	RB_CHAIN_GOBJECT_METHOD (rb_display_page_group_parent_class, constructed, object);
+
+	group = RB_DISPLAY_PAGE_GROUP (object);
+
+	/* register the new group */
+	G_LOCK (display_page_groups);
+	g_assert (g_hash_table_lookup (display_page_groups_map, group->priv->id) == NULL);
+	g_hash_table_insert (display_page_groups_map, g_strdup (group->priv->id), group);
+	G_UNLOCK (display_page_groups);
+}
+
+static void
+rb_display_page_group_init (RBDisplayPageGroup *group)
+{
+	group->priv = G_TYPE_INSTANCE_GET_PRIVATE (group,
+						   RB_TYPE_DISPLAY_PAGE_GROUP,
+						   RBDisplayPageGroupPrivate);
+}
+
+static void
+rb_display_page_group_class_init (RBDisplayPageGroupClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
+
+	G_LOCK (display_page_groups);
+	if (display_page_groups_map == NULL) {
+		display_page_groups_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+	}
+	G_UNLOCK (display_page_groups);
+
+	object_class->constructed = impl_constructed;
+	object_class->finalize = impl_finalize;
+	object_class->set_property = impl_set_property;
+	object_class->get_property = impl_get_property;
+
+	page_class->selectable = impl_selectable;
+	page_class->activate = impl_activate;
+
+	/**
+	 * RBDisplayPageGroup:id:
+	 *
+	 * Internal (untranslated) name for the page group
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_ID,
+					 g_param_spec_string ("id",
+							      "identifier",
+							      "identifier",
+							      NULL,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	/**
+	 * RBDisplayPageGroup:category:
+	 *
+	 * Page group category that the group falls into
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_CATEGORY,
+					 g_param_spec_enum ("category",
+							    "category",
+							    "page group category",
+							    RB_TYPE_DISPLAY_PAGE_GROUP_CATEGORY,
+							    RB_DISPLAY_PAGE_GROUP_CATEGORY_FIXED,
+							    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	/**
+	 * RBDisplayPageProperty:loaded:
+	 *
+	 * Set to %TRUE once the initial set of pages have been added to the group
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_LOADED,
+					 g_param_spec_boolean ("loaded",
+							       "loaded",
+							       "Whether the group is loaded",
+							       FALSE,
+							       G_PARAM_READABLE));
+
+	g_type_class_add_private (klass, sizeof (RBDisplayPageGroupPrivate));
+}
diff --git a/sources/rb-display-page-group.h b/sources/rb-display-page-group.h
new file mode 100644
index 0000000..7683bf0
--- /dev/null
+++ b/sources/rb-display-page-group.h
@@ -0,0 +1,95 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2010  Jonathan Matthew <jonathan d14n org>
+ *  Copyright (C) 2006  William Jon McCann <mccann jhu edu>
+ *
+ *  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_DISPLAY_PAGE_GROUP_H
+#define __RB_DISPLAY_PAGE_GROUP_H
+
+#include <sources/rb-display-page.h>
+#include <sources/rb-display-page-model.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+	RB_DISPLAY_PAGE_GROUP_CATEGORY_FIXED = 0,
+	RB_DISPLAY_PAGE_GROUP_CATEGORY_REMOVABLE,
+	RB_DISPLAY_PAGE_GROUP_CATEGORY_PERSISTENT,
+	RB_DISPLAY_PAGE_GROUP_CATEGORY_TRANSIENT,
+	RB_DISPLAY_PAGE_GROUP_CATEGORY_LAST
+} RBDisplayPageGroupCategory;
+
+GType rb_display_page_group_category_get_type (void);
+
+#define RB_TYPE_DISPLAY_PAGE_GROUP_CATEGORY (rb_display_page_group_category_get_type())
+
+typedef struct _RBDisplayPageGroup RBDisplayPageGroup;
+typedef struct _RBDisplayPageGroupClass RBDisplayPageGroupClass;
+typedef struct _RBDisplayPageGroupPrivate RBDisplayPageGroupPrivate;
+
+struct _RBDisplayPageGroup
+{
+	RBDisplayPage parent;
+
+	RBDisplayPageGroupPrivate *priv;
+};
+
+struct _RBDisplayPageGroupClass
+{
+	RBDisplayPageClass parent_class;
+};
+
+
+GType          rb_display_page_group_get_type    (void);
+
+#define RB_TYPE_DISPLAY_PAGE_GROUP	(rb_display_page_group_get_type ())
+#define RB_DISPLAY_PAGE_GROUP(o)	(G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_DISPLAY_PAGE_GROUP, RBDisplayPageGroup))
+#define RB_IS_DISPLAY_PAGE_GROUP(o)	(G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_DISPLAY_PAGE_GROUP))
+#define RB_DISPLAY_PAGE_GROUP_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_DISPLAY_PAGE_GROUP, RBDisplayPageGroupClass))
+#define RB_IS_DISPLAY_PAGE_GROUP_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_DISPLAY_PAGE_GROUP))
+#define RB_DISPLAY_PAGE_GROUP_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_DISPLAY_PAGE_GROUP, RBDisplayPageGroupClass))
+
+#define RB_DISPLAY_PAGE_GROUP_LIBRARY           (RB_DISPLAY_PAGE (rb_display_page_group_get_by_id ("library")))
+#define RB_DISPLAY_PAGE_GROUP_PLAYLISTS         (RB_DISPLAY_PAGE (rb_display_page_group_get_by_id ("playlists")))
+#define RB_DISPLAY_PAGE_GROUP_DEVICES           (RB_DISPLAY_PAGE (rb_display_page_group_get_by_id ("devices")))
+#define RB_DISPLAY_PAGE_GROUP_SHARED            (RB_DISPLAY_PAGE (rb_display_page_group_get_by_id ("shared")))
+#define RB_DISPLAY_PAGE_GROUP_STORES            (RB_DISPLAY_PAGE (rb_display_page_group_get_by_id ("stores")))
+
+void                rb_display_page_group_add_core_groups       (GObject *shell,
+								 RBDisplayPageModel *page_model);
+
+RBDisplayPageGroup *rb_display_page_group_get_by_id        	(const char *id);
+RBDisplayPageGroup *rb_display_page_group_new              	(GObject *shell,
+								 const char *id,
+								 const char *name,
+								 RBDisplayPageGroupCategory category);
+
+void                rb_display_page_group_loaded		(RBDisplayPageGroup *group);
+
+G_END_DECLS
+
+#endif /* __RB_DISPLAY_PAGE_GROUP_H */
diff --git a/sources/rb-display-page-model.c b/sources/rb-display-page-model.c
new file mode 100644
index 0000000..00126c2
--- /dev/null
+++ b/sources/rb-display-page-model.c
@@ -0,0 +1,868 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2003 Colin Walters <walters verbum 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 <string.h>
+
+#include <glib/gi18n.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#include "rb-display-page-group.h"
+#include "rb-display-page-model.h"
+#include "rb-tree-dnd.h"
+#include "rb-debug.h"
+#include "rb-marshal.h"
+#include "rb-playlist-source.h"
+#include "rb-auto-playlist-source.h"
+#include "rb-static-playlist-source.h"
+
+#include "gseal-gtk-compat.h"
+
+/**
+ * SECTION:rb-display-page-model
+ * @short_description: models backing the display page tree
+ *
+ * The #RBDisplayPageTree widget is backed by a #GtkTreeStore containing
+ * the sources and a set of attributes used to structure and display
+ * them, and a #GtkTreeModelFilter that hides sources with the
+ * visibility property set to FALSE.  This class implements the filter
+ * model and also creates the actual model.
+ *
+ * The display page model supports drag and drop in a variety of formats.
+ * The simplest of these are text/uri-list and application/x-rhythmbox-entry,
+ * which convey URIs and IDs of existing database entries.  When dragged
+ * to an existing source, these just add the URIs or entries to the target
+ * source.  When dragged to an empty space in the tree widget, this results
+ * in the creation of a static playlist.
+ *
+ * text/x-rhythmbox-artist, text/x-rhythmbox-album, and text/x-rhythmbox-genre
+ * are used when dragging items from the library browser.  When dragged to
+ * the display page tree, these result in the creation of a new auto playlist with
+ * the dragged items as criteria.
+ */
+
+enum
+{
+	DROP_RECEIVED,
+	LAST_SIGNAL
+};
+
+static guint rb_display_page_model_signals[LAST_SIGNAL] = { 0 };
+
+enum {
+	TARGET_PROPERTY,
+	TARGET_SOURCE,
+	TARGET_URIS,
+	TARGET_ENTRIES,
+	TARGET_DELETE
+};
+
+static const GtkTargetEntry dnd_targets[] = {
+	{ "text/x-rhythmbox-album", 0, TARGET_PROPERTY },
+	{ "text/x-rhythmbox-artist", 0, TARGET_PROPERTY },
+	{ "text/x-rhythmbox-genre", 0, TARGET_PROPERTY },
+	{ "application/x-rhythmbox-source", 0, TARGET_SOURCE },
+	{ "application/x-rhythmbox-entry", 0, TARGET_ENTRIES },
+	{ "text/uri-list", 0, TARGET_URIS },
+	{ "application/x-delete-me", 0, TARGET_DELETE }
+};
+
+static GtkTargetList *drag_target_list = NULL;
+
+static void rb_display_page_model_drag_dest_init (RbTreeDragDestIface *iface);
+static void rb_display_page_model_drag_source_init (RbTreeDragSourceIface *iface);
+
+G_DEFINE_TYPE_EXTENDED (RBDisplayPageModel,
+                        rb_display_page_model,
+                        GTK_TYPE_TREE_MODEL_FILTER,
+                        0,
+                        G_IMPLEMENT_INTERFACE (RB_TYPE_TREE_DRAG_SOURCE,
+                                               rb_display_page_model_drag_source_init)
+                        G_IMPLEMENT_INTERFACE (RB_TYPE_TREE_DRAG_DEST,
+                                               rb_display_page_model_drag_dest_init));
+
+static gboolean
+rb_display_page_model_drag_data_received (RbTreeDragDest *drag_dest,
+					  GtkTreePath *dest,
+					  GtkTreeViewDropPosition pos,
+					  GtkSelectionData *selection_data)
+{
+	RBDisplayPageModel *model;
+	GdkAtom type;
+
+	g_return_val_if_fail (RB_IS_DISPLAY_PAGE_MODEL (drag_dest), FALSE);
+	model = RB_DISPLAY_PAGE_MODEL (drag_dest);
+	type = gtk_selection_data_get_data_type (selection_data);
+
+	if (type == gdk_atom_intern ("text/uri-list", TRUE) ||
+	    type == gdk_atom_intern ("application/x-rhythmbox-entry", TRUE)) {
+		GtkTreeIter iter;
+		RBDisplayPage *target = NULL;
+
+		rb_debug ("text/uri-list or application/x-rhythmbox-entry drag data received");
+
+		if (dest != NULL && gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, dest)) {
+			gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
+					    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &target, -1);
+		}
+
+		g_signal_emit (G_OBJECT (model), rb_display_page_model_signals[DROP_RECEIVED],
+			       0, target, pos, selection_data);
+
+		if (target != NULL) {
+			g_object_unref (target);
+		}
+
+		return TRUE;
+	}
+
+        /* if artist, album or genre, only allow new playlists */
+        if (type == gdk_atom_intern ("text/x-rhythmbox-album", TRUE) ||
+            type == gdk_atom_intern ("text/x-rhythmbox-artist", TRUE) ||
+            type == gdk_atom_intern ("text/x-rhythmbox-genre", TRUE)) {
+                rb_debug ("text/x-rhythmbox-(album|artist|genre) drag data received");
+                g_signal_emit (G_OBJECT (model), rb_display_page_model_signals[DROP_RECEIVED],
+                               0, NULL, pos, selection_data);
+                return TRUE;
+        }
+
+	if (type == gdk_atom_intern ("application/x-rhythmbox-source", TRUE)) {
+		/* don't support dnd of sources */
+		return FALSE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+rb_display_page_model_row_drop_possible (RbTreeDragDest *drag_dest,
+					 GtkTreePath *dest,
+					 GtkTreeViewDropPosition pos,
+					 GtkSelectionData *selection_data)
+{
+	RBDisplayPageModel *model;
+
+	rb_debug ("row drop possible");
+	g_return_val_if_fail (RB_IS_DISPLAY_PAGE_MODEL (drag_dest), FALSE);
+
+	model = RB_DISPLAY_PAGE_MODEL (drag_dest);
+
+	if (!dest)
+		return TRUE;
+
+	/* Call the superclass method */
+	return gtk_tree_drag_dest_row_drop_possible (GTK_TREE_DRAG_DEST (GTK_TREE_STORE (model)),
+						     dest, selection_data);
+}
+
+static gboolean
+path_is_droppable (RBDisplayPageModel *model,
+		   GtkTreePath *dest)
+{
+	GtkTreeIter iter;
+	gboolean res;
+
+	res = FALSE;
+
+	if (gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, dest)) {
+		RBDisplayPage *page;
+
+		gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
+				    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page, -1);
+
+		if (page != NULL) {
+			if (RB_IS_SOURCE (page)) {
+				res = rb_source_can_paste (RB_SOURCE (page));
+			}
+			g_object_unref (page);
+		}
+	}
+
+	return res;
+}
+
+static gboolean
+rb_display_page_model_row_drop_position (RbTreeDragDest   *drag_dest,
+					 GtkTreePath       *dest_path,
+					 GList *targets,
+					 GtkTreeViewDropPosition *pos)
+{
+	GtkTreeModel *model = GTK_TREE_MODEL (drag_dest);
+
+	if (g_list_find (targets, gdk_atom_intern ("application/x-rhythmbox-source", TRUE)) && dest_path) {
+		rb_debug ("application/x-rhythmbox-source type");
+		return FALSE;
+	}
+
+	if (g_list_find (targets, gdk_atom_intern ("text/uri-list", TRUE)) ||
+	    g_list_find (targets, gdk_atom_intern ("application/x-rhythmbox-entry", TRUE))) {
+		rb_debug ("text/uri-list or application/x-rhythmbox-entry type");
+		if (dest_path && !path_is_droppable (RB_DISPLAY_PAGE_MODEL (model), dest_path))
+			return FALSE;
+
+		*pos = GTK_TREE_VIEW_DROP_INTO_OR_BEFORE;
+		return TRUE;
+	}
+
+	if ((g_list_find (targets, gdk_atom_intern ("text/x-rhythmbox-artist", TRUE))
+	     || g_list_find (targets, gdk_atom_intern ("text/x-rhythmbox-album", TRUE))
+	     || g_list_find (targets, gdk_atom_intern ("text/x-rhythmbox-genre", TRUE)))
+	    && !g_list_find (targets, gdk_atom_intern ("application/x-rhythmbox-source", TRUE))) {
+		rb_debug ("genre, album, or artist type");
+		*pos = GTK_TREE_VIEW_DROP_AFTER;
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static GdkAtom
+rb_display_page_model_get_drag_target (RbTreeDragDest *drag_dest,
+				       GtkWidget *widget,
+				       GdkDragContext *context,
+				       GtkTreePath *path,
+				       GtkTargetList *target_list)
+{
+	if (g_list_find (gdk_drag_context_list_targets (context),
+	    gdk_atom_intern ("application/x-rhythmbox-source", TRUE))) {
+		/* always accept rb source path if offered */
+		return gdk_atom_intern ("application/x-rhythmbox-source", TRUE);
+	}
+
+	if (path) {
+		/* only accept text/uri-list or application/x-rhythmbox-entry drops into existing sources */
+		GdkAtom entry_atom;
+
+		entry_atom = gdk_atom_intern ("application/x-rhythmbox-entry", FALSE);
+		if (g_list_find (gdk_drag_context_list_targets (context), entry_atom))
+			return entry_atom;
+
+		return gdk_atom_intern ("text/uri-list", FALSE);
+	}
+
+	return gtk_drag_dest_find_target (widget, context, target_list);
+}
+
+static gboolean
+rb_display_page_model_row_draggable (RbTreeDragSource *drag_source, GList *path_list)
+{
+	return FALSE;
+}
+
+static gboolean
+rb_display_page_model_drag_data_get (RbTreeDragSource *drag_source,
+				     GList *path_list,
+				     GtkSelectionData *selection_data)
+{
+	char *path_str;
+	GtkTreePath *path;
+	GdkAtom selection_data_target;
+	guint target;
+
+	selection_data_target = gtk_selection_data_get_target (selection_data);
+	path = gtk_tree_row_reference_get_path (path_list->data);
+	if (path == NULL)
+		return FALSE;
+
+	if (!gtk_target_list_find (drag_target_list,
+				   selection_data_target,
+				   &target)) {
+		return FALSE;
+	}
+
+	switch (target) {
+	case TARGET_SOURCE:
+		rb_debug ("getting drag data as rb display page path");
+		path_str = gtk_tree_path_to_string (path);
+		gtk_selection_data_set (selection_data,
+					selection_data_target,
+					8, (guchar *) path_str,
+					strlen (path_str));
+		g_free (path_str);
+		gtk_tree_path_free (path);
+		return TRUE;
+	case TARGET_URIS:
+	case TARGET_ENTRIES:
+	{
+		RBDisplayPage *page;
+		RhythmDBQueryModel *query_model;
+		GtkTreeIter iter;
+		GString *data;
+		gboolean first = TRUE;
+
+		rb_debug ("getting drag data as uri list");
+		if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source), &iter, path))
+			return FALSE;
+
+		data = g_string_new ("");
+		gtk_tree_model_get (GTK_TREE_MODEL (drag_source),
+				    &iter,
+				    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
+				    -1);
+		if (RB_IS_SOURCE (page) == FALSE) {
+			g_object_unref (page);
+			return FALSE;
+		}
+		g_object_get (page, "query-model", &query_model, NULL);
+		g_object_unref (page);
+
+		if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (query_model), &iter)) {
+			g_object_unref (query_model);
+			return FALSE;
+		}
+
+		do {
+			RhythmDBEntry *entry;
+
+			if (first) {
+				g_string_append(data, "\r\n");
+				first = FALSE;
+			}
+
+			entry = rhythmdb_query_model_iter_to_entry (query_model, &iter);
+			if (target == TARGET_URIS) {
+				g_string_append (data, rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
+			} else {
+				g_string_append_printf (data,
+							"%lu",
+							rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_ENTRY_ID));
+			}
+
+			rhythmdb_entry_unref (entry);
+
+		} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (query_model), &iter));
+
+		g_object_unref (query_model);
+
+		gtk_selection_data_set (selection_data,
+					selection_data_target,
+					8, (guchar *) data->str,
+					data->len);
+
+		g_string_free (data, TRUE);
+		return TRUE;
+	}
+	default:
+		/* unsupported target */
+		return FALSE;
+	}
+}
+
+static gboolean
+rb_display_page_model_drag_data_delete (RbTreeDragSource *drag_source,
+					GList *paths)
+{
+	return TRUE;
+}
+
+typedef struct _DisplayPageIter {
+	RBDisplayPage *page;
+	GtkTreeIter iter;
+	gboolean found;
+} DisplayPageIter;
+
+static gboolean
+match_page_to_iter (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, DisplayPageIter *dpi)
+{
+	RBDisplayPage *target = NULL;
+	gboolean res;
+
+	gtk_tree_model_get (model, iter, RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &target, -1);
+
+	res = FALSE;
+	if (target == dpi->page) {
+		dpi->iter = *iter;
+		dpi->found = TRUE;
+	}
+
+	if (target != NULL) {
+		g_object_unref (target);
+	}
+
+	return dpi->found;
+}
+
+static gboolean
+find_in_real_model (RBDisplayPageModel *page_model, RBDisplayPage *page, GtkTreeIter *iter)
+{
+	GtkTreeModel *model;
+	DisplayPageIter dpi = {0, };
+	dpi.page = page;
+
+	model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (page_model));
+	gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc) match_page_to_iter, &dpi);
+	if (dpi.found) {
+		*iter = dpi.iter;
+		return TRUE;
+	} else {
+		return FALSE;
+	}
+}
+
+static int
+compare_rows (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, RBDisplayPageModel *page_model)
+{
+	RBDisplayPage *a_page;
+	RBDisplayPage *b_page;
+	char *a_name;
+	char *b_name;
+	int ret;
+
+	gtk_tree_model_get (model, a, RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &a_page, -1);
+	gtk_tree_model_get (model, b, RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &b_page, -1);
+
+	g_object_get (a_page, "name", &a_name, NULL);
+	g_object_get (b_page, "name", &b_name, NULL);
+
+	if (RB_IS_DISPLAY_PAGE_GROUP (a_page) && RB_IS_DISPLAY_PAGE_GROUP (b_page)) {
+		RBDisplayPageGroupCategory a_cat;
+		RBDisplayPageGroupCategory b_cat;
+		g_object_get (a_page, "category", &a_cat, NULL);
+		g_object_get (b_page, "category", &b_cat, NULL);
+		if (a_cat < b_cat) {
+			ret = -1;
+		} else if (a_cat > b_cat) {
+			ret = 1;
+		} else {
+			ret = g_utf8_collate (a_name, b_name);
+		}
+	} else {
+		/* walk up the tree until we find the group, then get its category
+		 * to figure out how to sort the pages
+		 */
+		GtkTreeIter walk_iter;
+		GtkTreeIter group_iter;
+		RBDisplayPage *group_page;
+		RBDisplayPageGroupCategory category;
+
+		walk_iter = *a;
+		do {
+			group_iter = walk_iter;
+		} while (gtk_tree_model_iter_parent (model, &walk_iter, &group_iter));
+		gtk_tree_model_get (model, &group_iter, RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &group_page, -1);
+		g_object_get (group_page, "category", &category, NULL);
+		g_object_unref (group_page);
+
+		/* sort mostly by name */
+		switch (category) {
+		case RB_DISPLAY_PAGE_GROUP_CATEGORY_FIXED:
+			/* fixed sources go in order of appearance */
+			ret = -1;
+			break;
+		case RB_DISPLAY_PAGE_GROUP_CATEGORY_PERSISTENT:
+			/* sort auto and static playlists separately */
+			if (RB_IS_AUTO_PLAYLIST_SOURCE (a_page)
+			    && RB_IS_AUTO_PLAYLIST_SOURCE (b_page)) {
+				ret = g_utf8_collate (a_name, b_name);
+			} else if (RB_IS_STATIC_PLAYLIST_SOURCE (a_page)
+				   && RB_IS_STATIC_PLAYLIST_SOURCE (b_page)) {
+				ret = g_utf8_collate (a_name, b_name);
+			} else if (RB_IS_AUTO_PLAYLIST_SOURCE (a_page)) {
+				ret = -1;
+			} else {
+				ret = 1;
+			}
+
+			break;
+		case RB_DISPLAY_PAGE_GROUP_CATEGORY_REMOVABLE:
+		case RB_DISPLAY_PAGE_GROUP_CATEGORY_TRANSIENT:
+			ret = g_utf8_collate (a_name, b_name);
+			break;
+		default:
+			g_assert_not_reached ();
+			break;
+		}
+	}
+
+	g_object_unref (a_page);
+	g_object_unref (b_page);
+	g_free (a_name);
+	g_free (b_name);
+
+	return ret;
+}
+
+static gboolean
+rb_display_page_model_is_row_visible (GtkTreeModel *model,
+				      GtkTreeIter *iter,
+				      RBDisplayPageModel *page_model)
+{
+	RBDisplayPage *page = NULL;
+	gboolean visibility = FALSE;
+
+	gtk_tree_model_get (model, iter,
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
+			    -1);
+	if (page != NULL) {
+		g_object_get (page, "visibility", &visibility, NULL);
+		g_object_unref (page);
+	}
+
+	return visibility;
+}
+
+static void
+update_group_visibility_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, RBDisplayPageModel *page_model)
+{
+	RBDisplayPage *page;
+
+	gtk_tree_model_get (model, iter,
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
+			    -1);
+	if (RB_IS_DISPLAY_PAGE_GROUP (page)) {
+		g_object_set (page, "visibility", gtk_tree_model_iter_has_child (model, iter), NULL);
+	}
+	g_object_unref (page);
+}
+
+
+/**
+ * rb_display_page_model_set_dnd_targets:
+ * @sourcelist: the #RBDisplayPageModel
+ * @treeview: the sourcel ist #GtkTreeView
+ *
+ * Sets up the drag and drop targets for the display page tree.
+ */
+void
+rb_display_page_model_set_dnd_targets (RBDisplayPageModel *display_page_model,
+				       GtkTreeView *treeview)
+{
+	int n_targets = G_N_ELEMENTS (dnd_targets);
+
+	rb_tree_dnd_add_drag_dest_support (treeview,
+					   (RB_TREE_DEST_EMPTY_VIEW_DROP | RB_TREE_DEST_SELECT_ON_DRAG_TIMEOUT),
+					   dnd_targets, n_targets,
+					   GDK_ACTION_LINK);
+
+	rb_tree_dnd_add_drag_source_support (treeview,
+					     GDK_BUTTON1_MASK,
+					     dnd_targets, n_targets,
+					     GDK_ACTION_COPY);
+}
+
+
+static void
+page_notify_cb (GObject *object,
+		GParamSpec *pspec,
+		RBDisplayPageModel *page_model)
+{
+	RBDisplayPage *page = RB_DISPLAY_PAGE (object);
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+	GtkTreePath *path;
+
+	if (find_in_real_model (page_model, page, &iter) == FALSE) {
+		return;
+	}
+
+	model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (page_model));
+	path = gtk_tree_model_get_path (model, &iter);
+	gtk_tree_model_row_changed (model, path, &iter);
+	gtk_tree_path_free (path);
+}
+
+
+/**
+ * rb_display_page_model_add_page:
+ * @page_model: the #RBDisplayPageModel
+ * @page: the #RBDisplayPage to add
+ * @parent: the parent under which to add @page
+ *
+ * Adds a page to the model, either below a specified page (if it's a source or
+ * something else) or at the top level (if it's a group)
+ */
+void
+rb_display_page_model_add_page (RBDisplayPageModel *page_model, RBDisplayPage *page, RBDisplayPage *parent)
+{
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	char *name;
+
+	g_return_if_fail (RB_IS_DISPLAY_PAGE_MODEL (page_model));
+	g_return_if_fail (RB_IS_DISPLAY_PAGE (page));
+
+	g_object_get (page, "name", &name, NULL);
+
+	model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (page_model));
+	if (parent != NULL) {
+		GtkTreeIter parent_iter;
+
+		rb_debug ("inserting source %s with parent %p", name, parent);
+		g_assert (find_in_real_model (page_model, parent, &parent_iter));
+		gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &parent_iter);
+	} else {
+		rb_debug ("appending page %s with no parent", name);
+		g_object_set (page, "visibility", FALSE, NULL);	/* hide until it has some content */
+		gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL);
+	}
+	g_free (name);
+
+	gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING, FALSE,
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, page,
+			    -1);
+
+	g_signal_connect_object (page, "notify::name", G_CALLBACK (page_notify_cb), page_model, 0);
+	g_signal_connect_object (page, "notify::visibility", G_CALLBACK (page_notify_cb), page_model, 0);
+	g_signal_connect_object (page, "notify::pixbuf", G_CALLBACK (page_notify_cb), page_model, 0);
+}
+
+/**
+ * rb_display_page_model_remove_page:
+ * @page_model: the #RBDisplayPageModel
+ * @page: the #RBDisplayPage to remove
+ *
+ * Removes a page from the model.
+ */
+void
+rb_display_page_model_remove_page (RBDisplayPageModel *page_model,
+				   RBDisplayPage *page)
+{
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+
+	g_assert (find_in_real_model (page_model, page, &iter));
+
+	model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (page_model));
+
+	gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
+	g_signal_handlers_disconnect_by_func (page, G_CALLBACK (page_notify_cb), page_model);
+}
+
+
+
+/**
+ * rb_display_page_model_find_page:
+ * @page_model: the #RBDisplayPageModel
+ * @page: the #RBDisplayPage to find
+ * @iter: returns a #GtkTreeIter for the page
+ *
+ * Finds a #GtkTreeIter for a specified page in the model.  This will only
+ * find pages that are currently visible.  The returned #GtkTreeIter can be used
+ * with the #RBDisplayPageModel.
+ *
+ * Return value: %TRUE if the page was found
+ */
+gboolean
+rb_display_page_model_find_page (RBDisplayPageModel *page_model, RBDisplayPage *page, GtkTreeIter *iter)
+{
+	DisplayPageIter dpi = {0, };
+	dpi.page = page;
+
+	gtk_tree_model_foreach (GTK_TREE_MODEL (page_model), (GtkTreeModelForeachFunc) match_page_to_iter, &dpi);
+	if (dpi.found) {
+		*iter = dpi.iter;
+		return TRUE;
+	} else {
+		return FALSE;
+	}
+}
+
+static gboolean
+set_playing_flag (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, RBDisplayPage *source)
+{
+	RBDisplayPage *page;
+	gboolean old_playing;
+
+	gtk_tree_model_get (model,
+			    iter,
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING, &old_playing,
+			    -1);
+	if (RB_IS_SOURCE (page)) {
+		gboolean new_playing = (page == source);
+		if (old_playing || new_playing) {
+			gtk_tree_store_set (GTK_TREE_STORE (model),
+					    iter,
+					    RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING, new_playing,
+					    -1);
+		}
+	}
+	g_object_unref (page);
+
+	return FALSE;
+}
+
+/**
+ * rb_display_page_model_set_playing_source:
+ * @page_model: the #RBDisplayPageModel
+ * @source: the new playing #RBSource (as a #RBDisplayPage)
+ *
+ * Updates the model with the new playing source.
+ */
+void
+rb_display_page_model_set_playing_source (RBDisplayPageModel *page_model, RBDisplayPage *source)
+{
+	GtkTreeModel *model;
+	model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (page_model));
+	gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc) set_playing_flag, source);
+}
+
+/**
+ * rb_display_page_model_new:
+ *
+ * This constructs both the GtkTreeStore holding the display page
+ * data and the filter model that hides invisible pages.
+ *
+ * Return value: the #RBDisplayPageModel
+ */
+RBDisplayPageModel *
+rb_display_page_model_new (void)
+{
+	RBDisplayPageModel *model;
+	GtkTreeStore *store;
+	GType *column_types = g_new (GType, RB_DISPLAY_PAGE_MODEL_N_COLUMNS);
+
+	column_types[RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING] = G_TYPE_BOOLEAN;
+	column_types[RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE] = RB_TYPE_DISPLAY_PAGE;
+	store = gtk_tree_store_newv (RB_DISPLAY_PAGE_MODEL_N_COLUMNS,
+				     column_types);
+	gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
+					 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE,
+					 (GtkTreeIterCompareFunc) compare_rows,
+					 NULL, NULL);
+	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
+					      RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE,
+					      GTK_SORT_ASCENDING);
+
+	model = RB_DISPLAY_PAGE_MODEL (g_object_new (RB_TYPE_DISPLAY_PAGE_MODEL,
+						     "child-model", store,
+						     "virtual-root", NULL,
+						     NULL));
+
+	/* hide groups when they're empty */
+	g_signal_connect_object (store, "row-has-child-toggled", G_CALLBACK (update_group_visibility_cb), model, 0);
+	g_object_unref (store);
+
+	gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (model),
+						(GtkTreeModelFilterVisibleFunc) rb_display_page_model_is_row_visible,
+						model, NULL);
+
+	g_free (column_types);
+
+	return model;
+}
+
+
+static void
+rb_display_page_model_init (RBDisplayPageModel *model)
+{
+}
+
+static void
+rb_display_page_model_finalize (GObject *object)
+{
+	RBDisplayPageModel *model;
+
+	g_return_if_fail (RB_IS_DISPLAY_PAGE_MODEL (object));
+	model = RB_DISPLAY_PAGE_MODEL (object);
+
+	G_OBJECT_CLASS (rb_display_page_model_parent_class)->finalize (object);
+}
+
+static void
+rb_display_page_model_drag_dest_init (RbTreeDragDestIface *iface)
+{
+	iface->rb_drag_data_received = rb_display_page_model_drag_data_received;
+	iface->rb_row_drop_possible = rb_display_page_model_row_drop_possible;
+	iface->rb_row_drop_position = rb_display_page_model_row_drop_position;
+	iface->rb_get_drag_target = rb_display_page_model_get_drag_target;
+}
+
+static void
+rb_display_page_model_drag_source_init (RbTreeDragSourceIface *iface)
+{
+	iface->rb_row_draggable = rb_display_page_model_row_draggable;
+	iface->rb_drag_data_get = rb_display_page_model_drag_data_get;
+	iface->rb_drag_data_delete = rb_display_page_model_drag_data_delete;
+}
+
+static void
+rb_display_page_model_class_init (RBDisplayPageModelClass *klass)
+{
+	GObjectClass   *object_class;
+
+	object_class = G_OBJECT_CLASS (klass);
+	object_class->finalize = rb_display_page_model_finalize;
+
+	/**
+	 * RBDisplayPageModel::drop-received:
+	 * @model: the #RBDisplayPageModel
+	 * @target: the #RBSource receiving the drop
+	 * @pos: the drop position
+	 * @data: the drop data
+	 *
+	 * Emitted when a drag and drop operation to the display page tree completes.
+	 */
+	rb_display_page_model_signals[DROP_RECEIVED] =
+		g_signal_new ("drop-received",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBDisplayPageModelClass, drop_received),
+			      NULL, NULL,
+			      rb_marshal_VOID__OBJECT_INT_POINTER,
+			      G_TYPE_NONE,
+			      3,
+			      RB_TYPE_DISPLAY_PAGE, G_TYPE_INT, G_TYPE_POINTER);
+
+	if (!drag_target_list) {
+		drag_target_list = gtk_target_list_new (dnd_targets, G_N_ELEMENTS (dnd_targets));
+	}
+}
+
+/**
+ * RBDisplayPageModelColumn:
+ * @RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING: TRUE if the page is the playing source
+ * @RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE: the #RBDisplayPage object
+ * @RB_DISPLAY_PAGE_MODEL_N_COLUMNS: the number of columns
+ *
+ * Columns present in the display page model.
+ */
+
+#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
+
+GType
+rb_display_page_model_column_get_type (void)
+{
+	static GType etype = 0;
+
+	if (etype == 0)	{
+		static const GEnumValue values[] = {
+			ENUM_ENTRY (RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING, "playing"),
+			ENUM_ENTRY (RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, "page"),
+			{ 0, 0, 0 }
+		};
+
+		etype = g_enum_register_static ("RBDisplayPageModelColumn", values);
+	}
+
+	return etype;
+}
diff --git a/sources/rb-display-page-model.h b/sources/rb-display-page-model.h
new file mode 100644
index 0000000..29e26b0
--- /dev/null
+++ b/sources/rb-display-page-model.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2003 Colin Walters <walters verbum 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_DISPLAY_PAGE_MODEL_H
+#define RB_DISPLAY_PAGE_MODEL_H
+
+#include <gtk/gtk.h>
+
+#include <sources/rb-display-page.h>
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_DISPLAY_PAGE_MODEL		(rb_display_page_model_get_type ())
+#define RB_DISPLAY_PAGE_MODEL(obj)		(G_TYPE_CHECK_INSTANCE_CAST ((obj), RB_TYPE_DISPLAY_PAGE_MODEL, RBDisplayPageModel))
+#define RB_DISPLAY_PAGE_MODEL_CLASS(klass)	(G_TYPE_CHECK_CLASS_CAST ((klass), RB_TYPE_DISPLAY_PAGE_MODEL, RBDisplayPageModelClass))
+#define RB_IS_DISPLAY_PAGE_MODEL(obj)		(G_TYPE_CHECK_INSTANCE_TYPE ((obj), RB_TYPE_DISPLAY_PAGE_MODEL))
+#define RB_IS_DISPLAY_PAGE_MODEL_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE ((obj), RB_TYPE_DISPLAY_PAGE_MODEL))
+#define RB_DISPLAY_PAGE_MODEL_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), RB_TYPE_DISPLAY_PAGE_MODEL, RBDisplayPageModelClass))
+
+typedef enum {
+	RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING = 0,
+	RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE,
+	RB_DISPLAY_PAGE_MODEL_N_COLUMNS
+} RBDisplayPageModelColumn;
+
+GType rb_display_page_model_column_get_type (void);
+#define RB_TYPE_DISPLAY_PAGE_MODEL_COLUMN (rb_display_page_model_column_get_type ())
+
+typedef struct _RBDisplayPageModel RBDisplayPageModel;
+typedef struct _RBDisplayPageModelClass RBDisplayPageModelClass;
+
+struct _RBDisplayPageModel
+{
+	GtkTreeModelFilter parent;
+};
+
+struct _RBDisplayPageModelClass
+{
+	GtkTreeModelFilterClass parent_class;
+
+	void (*drop_received) (RBDisplayPageModel *model,
+			       RBDisplayPage *target,
+			       GtkTreeViewDropPosition pos,
+			       GtkSelectionData *data);
+};
+
+GType		rb_display_page_model_get_type	(void);
+
+RBDisplayPageModel *rb_display_page_model_new		(void);
+
+void		rb_display_page_model_set_playing_source (RBDisplayPageModel *page_model,
+							  RBDisplayPage *source);
+
+void		rb_display_page_model_add_page (RBDisplayPageModel *page_model,
+						RBDisplayPage *page,
+						RBDisplayPage *parent);
+void		rb_display_page_model_remove_page (RBDisplayPageModel *page_model,
+						   RBDisplayPage *page);
+gboolean	rb_display_page_model_find_page (RBDisplayPageModel *page_model,
+						 RBDisplayPage *page,
+						 GtkTreeIter *iter);
+
+void		rb_display_page_model_set_dnd_targets (RBDisplayPageModel *page_model,
+						       GtkTreeView *treeview);
+
+G_END_DECLS
+
+#endif /* RB_DISPLAY_PAGE_MODEL */
diff --git a/sources/rb-display-page-tree.c b/sources/rb-display-page-tree.c
new file mode 100644
index 0000000..792fa9c
--- /dev/null
+++ b/sources/rb-display-page-tree.c
@@ -0,0 +1,1096 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2003,2004 Colin Walters <walters verbum org>
+ * Copyright (C) 2010 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 <unistd.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+
+#include "eel-gconf-extensions.h"
+#include "rb-display-page-group.h"
+#include "rb-display-page-tree.h"
+#include "rb-display-page-model.h"
+#include "rb-debug.h"
+#include "rb-stock-icons.h"
+#include "rb-marshal.h"
+#include "rb-cell-renderer-pixbuf.h"
+#include "gossip-cell-renderer-expander.h"
+#include "rb-tree-dnd.h"
+#include "rb-util.h"
+#include "rb-auto-playlist-source.h"
+#include "rb-static-playlist-source.h"
+
+/**
+ * SECTION:rb-display-page-tree
+ * @short_description: widget displaying the tree of #RBDisplayPage instances
+ *
+ * The display page tree widget is a GtkTreeView backed by a GtkListStore
+ * containing the display page instances (sources and other things).  Display
+ * pages include sources, such as the library and playlists, and other things
+ * like the visualization display.
+ *
+ * Display pages are shown in the list with an icon and the name.  The current
+ * playing source is displayed in bold.
+ *
+ * Sources are divided into groups - library, stores, playlists, devices,
+ * network shares.  Groups are displayed as headers, with expanders for hiding
+ * and showing the sources in the group.  Sources themselves may also have
+ * child sources, such as playlists on portable audio players.
+ */
+
+struct _RBDisplayPageTreePrivate
+{
+	GtkWidget *treeview;
+	GtkCellRenderer *title_renderer;
+	GtkCellRenderer *expander_renderer;
+
+	RBDisplayPageModel *page_model;
+	GtkTreeSelection *selection;
+
+	int child_source_count;
+	GtkTreeViewColumn *main_column;
+
+	RBShell *shell;
+
+	GList *expand_rows;
+	GtkTreeRowReference *expand_select_row;
+	guint expand_rows_id;
+};
+
+
+enum
+{
+	PROP_0,
+	PROP_SHELL,
+	PROP_MODEL
+};
+
+enum
+{
+	SELECTED,
+	DROP_RECEIVED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (RBDisplayPageTree, rb_display_page_tree, GTK_TYPE_SCROLLED_WINDOW)
+
+
+static char *
+expander_state_gconf_key (RBDisplayPageGroup *group)
+{
+	char *id;
+	char *gconf_key;
+
+	g_object_get (group, "id", &id, NULL);
+	gconf_key = g_strconcat (CONF_UI_DIR "/page-groups/", id, NULL);
+	g_free (id);
+
+	return gconf_key;
+}
+
+static gboolean
+retrieve_expander_state (RBDisplayPageGroup *group)
+{
+	gboolean state;
+	char *gconf_key = expander_state_gconf_key (group);
+	state = eel_gconf_get_boolean (gconf_key);
+	g_free (gconf_key);
+
+	return (state == FALSE);
+}
+
+static void
+store_expander_state (RBDisplayPageGroup *group, gboolean expanded)
+{
+	char *gconf_key = expander_state_gconf_key (group);
+	eel_gconf_set_boolean (gconf_key, (expanded == FALSE));
+	g_free (gconf_key);
+}
+
+static void
+set_cell_background (RBDisplayPageTree  *display_page_tree,
+		     GtkCellRenderer    *cell,
+		     gboolean            is_group,
+		     gboolean            is_active)
+{
+	GdkColor  color;
+	GtkStyle *style;
+
+	g_return_if_fail (display_page_tree != NULL);
+	g_return_if_fail (cell != NULL);
+
+	style = gtk_widget_get_style (GTK_WIDGET (display_page_tree));
+
+	if (!is_group) {
+		if (is_active) {
+			color = style->bg[GTK_STATE_SELECTED];
+
+			/* Here we take the current theme colour and add it to
+			 * the colour for white and average the two. This
+			 * gives a colour which is inline with the theme but
+			 * slightly whiter.
+			 */
+			color.red = (color.red + (style->white).red) / 2;
+			color.green = (color.green + (style->white).green) / 2;
+			color.blue = (color.blue + (style->white).blue) / 2;
+
+			g_object_set (cell,
+				      "cell-background-gdk", &color,
+				      NULL);
+		} else {
+			g_object_set (cell,
+				      "cell-background-gdk", NULL,
+				      NULL);
+		}
+	} else {
+		/* don't set background for group heading */
+	}
+}
+
+static void
+indent_level1_cell_data_func (GtkTreeViewColumn *tree_column,
+			      GtkCellRenderer   *cell,
+			      GtkTreeModel      *model,
+			      GtkTreeIter       *iter,
+			      RBDisplayPageTree *display_page_tree)
+{
+	GtkTreePath *path;
+	int          depth;
+
+	path = gtk_tree_model_get_path (model, iter);
+	depth = gtk_tree_path_get_depth (path);
+	gtk_tree_path_free (path);
+	g_object_set (cell,
+		      "text", "    ",
+		      "visible", depth > 1,
+		      NULL);
+}
+
+static void
+indent_level2_cell_data_func (GtkTreeViewColumn *tree_column,
+			      GtkCellRenderer   *cell,
+			      GtkTreeModel      *model,
+			      GtkTreeIter       *iter,
+			      RBDisplayPageTree *display_page_tree)
+{
+	GtkTreePath *path;
+	int          depth;
+
+	path = gtk_tree_model_get_path (model, iter);
+	depth = gtk_tree_path_get_depth (path);
+	gtk_tree_path_free (path);
+	g_object_set (cell,
+		      "text", "    ",
+		      "visible", depth > 2,
+		      NULL);
+}
+
+static void
+pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
+		       GtkCellRenderer   *cell,
+		       GtkTreeModel      *model,
+		       GtkTreeIter       *iter,
+		       RBDisplayPageTree *display_page_tree)
+{
+	RBDisplayPage *page;
+	GdkPixbuf *pixbuf;
+
+	gtk_tree_model_get (model, iter,
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
+			    -1);
+	g_object_get (page, "pixbuf", &pixbuf, NULL);
+
+	if (pixbuf == NULL) {
+		g_object_set (cell,
+			      "visible", FALSE,
+			      "pixbuf", NULL,
+			      NULL);
+	} else {
+		g_object_set (cell,
+			      "visible", TRUE,
+			      "pixbuf", pixbuf,
+			      NULL);
+		g_object_unref (pixbuf);
+	}
+
+	set_cell_background (display_page_tree, cell, RB_IS_DISPLAY_PAGE_GROUP (page), FALSE);
+	g_object_unref (page);
+}
+
+static void
+title_cell_data_func (GtkTreeViewColumn *column,
+		      GtkCellRenderer   *renderer,
+		      GtkTreeModel      *tree_model,
+		      GtkTreeIter       *iter,
+		      RBDisplayPageTree *display_page_tree)
+{
+	RBDisplayPage *page;
+	char    *name;
+	gboolean playing;
+
+	gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model), iter,
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING, &playing,
+			    -1);
+
+	g_object_get (page, "name", &name, NULL);
+
+	g_object_set (renderer,
+		      "text", name,
+		      "weight", playing ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
+		      NULL);
+
+	set_cell_background (display_page_tree, renderer, RB_IS_DISPLAY_PAGE_GROUP (page), FALSE);
+
+	g_free (name);
+	g_object_unref (page);
+}
+
+static void
+expander_cell_data_func (GtkTreeViewColumn *column,
+			 GtkCellRenderer   *cell,
+			 GtkTreeModel      *model,
+			 GtkTreeIter       *iter,
+			 RBDisplayPageTree *display_page_tree)
+{
+	RBDisplayPage *page;
+
+	if (gtk_tree_model_iter_has_child (model, iter)) {
+		GtkTreePath *path;
+		gboolean     row_expanded;
+
+		path = gtk_tree_model_get_path (model, iter);
+		row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)),
+							   path);
+		gtk_tree_path_free (path);
+
+		g_object_set (cell,
+			      "visible", TRUE,
+			      "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
+			      NULL);
+	} else {
+		g_object_set (cell, "visible", FALSE, NULL);
+	}
+
+	gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model), iter,
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
+			    -1);
+	set_cell_background (display_page_tree, cell, RB_IS_DISPLAY_PAGE_GROUP (page), FALSE);
+	g_object_unref (page);
+}
+
+static void
+row_activated_cb (GtkTreeView       *treeview,
+		  GtkTreePath       *path,
+		  GtkTreeViewColumn *column,
+		  RBDisplayPageTree *display_page_tree)
+{
+	GtkTreeModel *model;
+	GtkTreeIter   iter;
+	RBDisplayPage *page;
+
+	model = gtk_tree_view_get_model (treeview);
+
+	g_return_if_fail (gtk_tree_model_get_iter (model, &iter, path));
+
+	gtk_tree_model_get (model, &iter,
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
+			    -1);
+
+	if (page != NULL) {
+		rb_debug ("page %p activated", page);
+		rb_display_page_activate (page);
+		g_object_unref (page);
+	}
+}
+
+static void
+update_expanded_state (RBDisplayPageTree *display_page_tree,
+		       GtkTreeIter *iter,
+		       gboolean expanded)
+{
+	GtkTreeModel *model;
+	RBDisplayPage *page;
+
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (display_page_tree->priv->treeview));
+	gtk_tree_model_get (model, iter,
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
+			    -1);
+	if (RB_IS_DISPLAY_PAGE_GROUP (page)) {
+		store_expander_state (RB_DISPLAY_PAGE_GROUP (page), expanded);
+	}
+}
+
+static void
+row_expanded_cb (GtkTreeView *treeview,
+		 GtkTreeIter *iter,
+		 GtkTreePath *path,
+		 RBDisplayPageTree *display_page_tree)
+{
+	update_expanded_state (display_page_tree, iter, TRUE);
+}
+
+static void
+row_collapsed_cb (GtkTreeView *treeview,
+		  GtkTreeIter *iter,
+		  GtkTreePath *path,
+		  RBDisplayPageTree *display_page_tree)
+{
+	update_expanded_state (display_page_tree, iter, FALSE);
+}
+
+static void
+drop_received_cb (RBDisplayPageModel     *model,
+		  RBDisplayPage          *page,
+		  GtkTreeViewDropPosition pos,
+		  GtkSelectionData       *data,
+		  RBDisplayPageTree      *display_page_tree)
+{
+	rb_debug ("drop received");
+	g_signal_emit (display_page_tree, signals[DROP_RECEIVED], 0, page, data);
+}
+
+static gboolean
+expand_rows_cb (RBDisplayPageTree *display_page_tree)
+{
+	GList *l;
+	rb_debug ("expanding %d rows", g_list_length (display_page_tree->priv->expand_rows));
+	display_page_tree->priv->expand_rows_id = 0;
+
+	for (l = display_page_tree->priv->expand_rows; l != NULL; l = l->next) {
+		GtkTreePath *path;
+		path = gtk_tree_row_reference_get_path (l->data);
+		if (path != NULL) {
+			gtk_tree_view_expand_to_path (GTK_TREE_VIEW (display_page_tree->priv->treeview), path);
+			if (l->data == display_page_tree->priv->expand_select_row) {
+				GtkTreeSelection *selection;
+				GtkTreeIter iter;
+
+				selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (display_page_tree->priv->treeview));
+				if (gtk_tree_model_get_iter (GTK_TREE_MODEL (display_page_tree->priv->page_model), &iter, path)) {
+					rb_debug ("selecting one of the expanded rows");
+					gtk_tree_selection_select_iter (selection, &iter);
+				}
+			}
+			gtk_tree_path_free (path);
+		}
+	}
+
+	rb_list_destroy_free (display_page_tree->priv->expand_rows, (GDestroyNotify) gtk_tree_row_reference_free);
+	display_page_tree->priv->expand_rows = NULL;
+	return FALSE;
+}
+
+static void
+model_row_inserted_cb (GtkTreeModel *model,
+		       GtkTreePath *path,
+		       GtkTreeIter *iter,
+		       RBDisplayPageTree *display_page_tree)
+{
+	gboolean expand = FALSE;
+	if (gtk_tree_path_get_depth (path) == 2) {
+		GtkTreeIter group_iter;
+		expand = TRUE;
+		if (gtk_tree_model_iter_parent (model, &group_iter, iter)) {
+			gboolean loaded;
+			RBDisplayPage *page;
+			RBDisplayPageGroupCategory category;
+
+			gtk_tree_model_get (model, &group_iter,
+					    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
+					    -1);
+			g_object_get (page, "loaded", &loaded, "category", &category, NULL);
+			if (category == RB_DISPLAY_PAGE_GROUP_CATEGORY_TRANSIENT || loaded == FALSE) {
+				expand = retrieve_expander_state (RB_DISPLAY_PAGE_GROUP (page));
+			}
+		}
+	}
+
+	if (expand) {
+		display_page_tree->priv->expand_rows = g_list_append (display_page_tree->priv->expand_rows,
+								      gtk_tree_row_reference_new (model, path));
+		if (display_page_tree->priv->expand_rows_id == 0) {
+			display_page_tree->priv->expand_rows_id = g_idle_add ((GSourceFunc)expand_rows_cb, display_page_tree);
+		}
+	}
+
+	gtk_tree_view_columns_autosize (GTK_TREE_VIEW (display_page_tree->priv->treeview));
+}
+
+static gboolean
+emit_show_popup (GtkTreeView *treeview,
+		 RBDisplayPageTree *display_page_tree)
+{
+	GtkTreeIter iter;
+	RBDisplayPage *page;
+	gboolean ret;
+
+	if (!gtk_tree_selection_get_selected (gtk_tree_view_get_selection (treeview),
+					      NULL, &iter))
+		return FALSE;
+
+	gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model),
+			    &iter,
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
+			    -1);
+	if (page == NULL)
+		return FALSE;
+
+	g_return_val_if_fail (RB_IS_DISPLAY_PAGE (page), FALSE);
+
+	rb_display_page_show_popup (page);
+	g_object_unref (page);
+	return ret;
+}
+
+static gboolean
+button_press_cb (GtkTreeView *treeview,
+		 GdkEventButton *event,
+		 RBDisplayPageTree *display_page_tree)
+{
+	GtkTreeIter  iter;
+	GtkTreePath *path;
+	gboolean     res;
+
+	if (event->button != 3) {
+		return FALSE;
+	}
+
+	res = gtk_tree_view_get_path_at_pos (treeview,
+					     event->x,
+					     event->y,
+					     &path,
+					     NULL,
+					     NULL,
+					     NULL);
+	if (! res) {
+		/* pointer is over empty space */
+		GtkUIManager *uimanager;
+		g_object_get (display_page_tree->priv->shell, "ui-manager", &uimanager, NULL);
+		rb_gtk_action_popup_menu (uimanager, "/DisplayPageTreePopup");
+		g_object_unref (uimanager);
+		return TRUE;
+	}
+
+	res = gtk_tree_model_get_iter (GTK_TREE_MODEL (display_page_tree->priv->page_model),
+				       &iter,
+				       path);
+	gtk_tree_path_free (path);
+	if (res) {
+		gtk_tree_selection_select_iter (gtk_tree_view_get_selection (treeview), &iter);
+	}
+
+	return emit_show_popup (treeview, display_page_tree);
+}
+
+static gboolean
+key_release_cb (GtkTreeView *treeview,
+		GdkEventKey *event,
+		RBDisplayPageTree *display_page_tree)
+{
+	GtkTreeIter iter;
+	RBDisplayPage *page;
+	gboolean res;
+
+	/* F2 = rename playlist */
+	if (event->keyval != GDK_F2) {
+		return FALSE;
+	}
+
+	if (!gtk_tree_selection_get_selected (display_page_tree->priv->selection, NULL, &iter)) {
+		return FALSE;
+	}
+
+	gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model),
+			    &iter,
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
+			    -1);
+	if (page == NULL || RB_IS_SOURCE (page) == FALSE) {
+		return FALSE;
+	}
+
+	res = FALSE;
+	if (rb_source_can_rename (RB_SOURCE (page))) {
+		rb_display_page_tree_edit_source_name (display_page_tree, RB_SOURCE (page));
+		res = TRUE;
+	}
+
+	g_object_unref (page);
+	return res;
+}
+
+static gboolean
+popup_menu_cb (GtkTreeView *treeview,
+	       RBDisplayPageTree *display_page_tree)
+{
+	return emit_show_popup (treeview, display_page_tree);
+}
+
+
+/**
+ * rb_display_page_tree_edit_source_name:
+ * @display_page_tree: the #RBDisplayPageTree
+ * @source: the #RBSource to edit
+ *
+ * Initiates editing of the name of the specified source.  The row for the source
+ * is selected and given input focus, allowing the user to edit the name.
+ * source_name_edited_cb is called when the user finishes editing.
+ */
+void
+rb_display_page_tree_edit_source_name (RBDisplayPageTree *display_page_tree,
+				       RBSource *source)
+{
+	GtkTreeIter  iter;
+	GtkTreePath *path;
+
+	g_assert (rb_display_page_model_find_page (display_page_tree->priv->page_model,
+						   RB_DISPLAY_PAGE (source),
+						   &iter));
+	path = gtk_tree_model_get_path (GTK_TREE_MODEL (display_page_tree->priv->page_model),
+					&iter);
+	gtk_tree_view_expand_to_path (GTK_TREE_VIEW (display_page_tree->priv->treeview), path);
+
+	/* Make cell editable just for the moment.
+	   We'll turn it off once editing is done. */
+	g_object_set (display_page_tree->priv->title_renderer, "editable", TRUE, NULL);
+
+	gtk_tree_view_set_cursor_on_cell (GTK_TREE_VIEW (display_page_tree->priv->treeview),
+					  path, display_page_tree->priv->main_column,
+					  display_page_tree->priv->title_renderer,
+					  TRUE);
+	gtk_tree_path_free (path);
+}
+
+/**
+ * rb_display_page_tree_select:
+ * @display_page_tree: the #RBDisplayPageTree
+ * @page: the #RBDisplayPage to select
+ *
+ * Selects the specified page in the tree.  This will result in the 'selected'
+ * signal being emitted.
+ */
+void
+rb_display_page_tree_select (RBDisplayPageTree *display_page_tree,
+			     RBDisplayPage *page)
+{
+	GtkTreeIter iter;
+	GtkTreePath *path;
+	GList *l;
+	gboolean defer = FALSE;
+
+	g_assert (rb_display_page_model_find_page (display_page_tree->priv->page_model,
+						   page,
+						   &iter));
+
+	/* if this is a path we're trying to expand to, wait until we've done that first */
+	path = gtk_tree_model_get_path (GTK_TREE_MODEL (display_page_tree->priv->page_model), &iter);
+	for (l = display_page_tree->priv->expand_rows; l != NULL; l = l->next) {
+		GtkTreePath *expand_path;
+
+		expand_path = gtk_tree_row_reference_get_path (l->data);
+		if (expand_path != NULL) {
+			defer = (gtk_tree_path_compare (expand_path, path) == 0);
+			gtk_tree_path_free (expand_path);
+		}
+
+		if (defer) {
+			display_page_tree->priv->expand_select_row = l->data;
+			break;
+		}
+	}
+
+	if (defer == FALSE) {
+		gtk_tree_selection_select_iter (display_page_tree->priv->selection, &iter);
+	}
+
+	gtk_tree_path_free (path);
+}
+
+/**
+ * rb_display_page_tree_toggle_expanded:
+ * @display_page_tree: the #RBDisplayPageTree
+ * @page: the #RBDisplayPage to toggle
+ *
+ * If @page is expanded (children visible), collapses it, otherwise expands it.
+ */
+void
+rb_display_page_tree_toggle_expanded (RBDisplayPageTree *display_page_tree,
+				      RBDisplayPage *page)
+{
+	GtkTreeIter iter;
+	GtkTreePath *path;
+	gboolean expanding;
+
+	g_assert (rb_display_page_model_find_page (display_page_tree->priv->page_model,
+						   page,
+						   &iter));
+	path = gtk_tree_model_get_path (GTK_TREE_MODEL (display_page_tree->priv->page_model),
+					&iter);
+	if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (display_page_tree->priv->treeview), path)) {
+		rb_debug ("collapsing page %p", page);
+		gtk_tree_view_collapse_row (GTK_TREE_VIEW (display_page_tree->priv->treeview), path);
+		expanding = FALSE;
+	} else {
+		rb_debug ("expanding page %p", page);
+		gtk_tree_view_expand_row (GTK_TREE_VIEW (display_page_tree->priv->treeview), path, FALSE);
+		expanding = TRUE;
+	}
+	gossip_cell_renderer_expander_start_animation (GOSSIP_CELL_RENDERER_EXPANDER (display_page_tree->priv->expander_renderer),
+						       GTK_TREE_VIEW (display_page_tree->priv->treeview),
+						       path,
+						       expanding);
+	gtk_tree_path_free (path);
+}
+
+static gboolean
+selection_check_cb (GtkTreeSelection *selection,
+		    GtkTreeModel *model,
+		    GtkTreePath *path,
+		    gboolean currently_selected,
+		    RBDisplayPageTree *display_page_tree)
+{
+	GtkTreeIter iter;
+	gboolean result = TRUE;
+
+	if (currently_selected) {
+		/* do anything? */
+	} else if (gtk_tree_model_get_iter (model, &iter, path)) {
+		RBDisplayPage *page;
+		gtk_tree_model_get (model, &iter, RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page, -1);
+
+		/* figure out if page can be selected */
+		result = rb_display_page_selectable (page);
+
+		g_object_unref (page);
+	}
+	return result;
+}
+
+static void
+selection_changed_cb (GtkTreeSelection *selection,
+		      RBDisplayPageTree *display_page_tree)
+{
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+	RBDisplayPage *page;
+
+	if (!gtk_tree_selection_get_selected (display_page_tree->priv->selection, &model, &iter))
+		return;
+
+	gtk_tree_model_get (model,
+			    &iter,
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
+			    -1);
+	if (page == NULL)
+		return;
+	g_signal_emit (display_page_tree, signals[SELECTED], 0, page);
+	g_object_unref (page);
+}
+
+static void
+source_name_edited_cb (GtkCellRendererText *renderer,
+		       const char          *pathstr,
+		       const char          *text,
+		       RBDisplayPageTree   *display_page_tree)
+{
+	GtkTreePath *path;
+	GtkTreeIter iter;
+	RBDisplayPage *page;
+
+	if (text[0] == '\0')
+		return;
+
+	path = gtk_tree_path_new_from_string (pathstr);
+	g_return_if_fail (gtk_tree_model_get_iter (GTK_TREE_MODEL (display_page_tree->priv->page_model), &iter, path));
+	gtk_tree_path_free (path);
+
+	gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model),
+			    &iter,
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
+			    -1);
+	if (page == NULL || RB_IS_SOURCE (page) == FALSE) {
+		g_object_set (renderer, "editable", FALSE, NULL);
+		return;
+	}
+
+	g_object_set (page, "name", text, NULL);
+	g_object_unref (page);
+}
+
+static gboolean
+display_page_search_equal_func (GtkTreeModel *model,
+				gint column,
+				const char *key,
+				GtkTreeIter *iter,
+				RBDisplayPageTree *display_page_tree)
+{
+	RBDisplayPage *page;
+	gboolean result = TRUE;
+	char *folded_key;
+	char *name;
+	char *folded_name;
+
+	gtk_tree_model_get (model,
+			    iter,
+			    RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
+			    -1);
+	g_object_get (page, "name", &name, NULL);
+
+	folded_key = rb_search_fold (key);
+	folded_name = rb_search_fold (name);
+
+	if (folded_key != NULL && folded_name != NULL) {
+		result = (strncmp (folded_key, folded_name, strlen (folded_key)) != 0);
+	}
+
+	g_free (folded_key);
+	g_free (folded_name);
+	g_free (name);
+	g_object_unref (page);
+	return result;
+}
+
+/**
+ * rb_display_page_tree_new:
+ * @shell: the #RBShell instance
+ *
+ * Creates the display page tree widget.
+ *
+ * Return value: the display page tree widget.
+ */
+RBDisplayPageTree *
+rb_display_page_tree_new (RBShell *shell)
+{
+	return RB_DISPLAY_PAGE_TREE (g_object_new (RB_TYPE_DISPLAY_PAGE_TREE,
+						   "hadjustment", NULL,
+						   "vadjustment", NULL,
+						   "hscrollbar_policy", GTK_POLICY_AUTOMATIC,
+						   "vscrollbar_policy", GTK_POLICY_AUTOMATIC,
+						   "shadow_type", GTK_SHADOW_IN,
+						   "shell", shell,
+						   NULL));
+}
+
+static void
+impl_set_property (GObject      *object,
+		   guint         prop_id,
+		   const GValue *value,
+		   GParamSpec   *pspec)
+{
+	RBDisplayPageTree *display_page_tree = RB_DISPLAY_PAGE_TREE (object);
+	switch (prop_id)
+	{
+	case PROP_SHELL:
+		display_page_tree->priv->shell = g_value_get_object (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+impl_get_property (GObject    *object,
+		   guint       prop_id,
+		   GValue     *value,
+		   GParamSpec *pspec)
+{
+	RBDisplayPageTree *display_page_tree = RB_DISPLAY_PAGE_TREE (object);
+	switch (prop_id)
+	{
+	case PROP_SHELL:
+		g_value_set_object (value, display_page_tree->priv->shell);
+		break;
+	case PROP_MODEL:
+		g_value_set_object (value, display_page_tree->priv->page_model);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+impl_finalize (GObject *object)
+{
+	RBDisplayPageTree *display_page_tree = RB_DISPLAY_PAGE_TREE (object);
+
+	g_object_unref (display_page_tree->priv->page_model);
+
+	if (display_page_tree->priv->expand_rows_id != 0) {
+		g_source_remove (display_page_tree->priv->expand_rows_id);
+		display_page_tree->priv->expand_rows_id = 0;
+	}
+
+	rb_list_destroy_free (display_page_tree->priv->expand_rows, (GDestroyNotify) gtk_tree_row_reference_free);
+
+	G_OBJECT_CLASS (rb_display_page_tree_parent_class)->finalize (object);
+}
+
+static void
+impl_constructed (GObject *object)
+{
+	RBDisplayPageTree *display_page_tree;
+
+	RB_CHAIN_GOBJECT_METHOD (rb_display_page_tree_parent_class, constructed, object);
+	display_page_tree = RB_DISPLAY_PAGE_TREE (object);
+
+	gtk_container_add (GTK_CONTAINER (display_page_tree), display_page_tree->priv->treeview);
+}
+
+static void
+rb_display_page_tree_init (RBDisplayPageTree *display_page_tree)
+{
+	GtkCellRenderer *renderer;
+
+	display_page_tree->priv =
+		G_TYPE_INSTANCE_GET_PRIVATE (display_page_tree,
+					     RB_TYPE_DISPLAY_PAGE_TREE,
+					     RBDisplayPageTreePrivate);
+
+	display_page_tree->priv->page_model = rb_display_page_model_new ();
+	g_signal_connect_object (display_page_tree->priv->page_model,
+				 "drop-received",
+				 G_CALLBACK (drop_received_cb),
+				 display_page_tree, 0);
+	g_signal_connect_object (display_page_tree->priv->page_model,
+				 "row-inserted",
+				 G_CALLBACK (model_row_inserted_cb),
+				 display_page_tree, 0);
+
+	display_page_tree->priv->treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (display_page_tree->priv->page_model));
+
+	g_object_set (display_page_tree->priv->treeview,
+		      "headers-visible", FALSE,
+		      "reorderable", TRUE,
+		      "enable-search", TRUE,
+		      "search-column", RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE,
+		      NULL);
+	gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (display_page_tree->priv->treeview),
+					     (GtkTreeViewSearchEqualFunc) display_page_search_equal_func,
+					     display_page_tree,
+					     NULL);
+
+	rb_display_page_model_set_dnd_targets (display_page_tree->priv->page_model,
+					       GTK_TREE_VIEW (display_page_tree->priv->treeview));
+
+	g_signal_connect_object (display_page_tree->priv->treeview,
+				 "row_activated",
+				 G_CALLBACK (row_activated_cb),
+				 display_page_tree, 0);
+	g_signal_connect_object (display_page_tree->priv->treeview,
+				 "row-collapsed",
+				 G_CALLBACK (row_collapsed_cb),
+				 display_page_tree, 0);
+	g_signal_connect_object (display_page_tree->priv->treeview,
+				 "row-expanded",
+				 G_CALLBACK (row_expanded_cb),
+				 display_page_tree, 0);
+	g_signal_connect_object (display_page_tree->priv->treeview,
+				 "button_press_event",
+				 G_CALLBACK (button_press_cb),
+				 display_page_tree, 0);
+	g_signal_connect_object (display_page_tree->priv->treeview,
+				 "key_release_event",
+				 G_CALLBACK (key_release_cb),
+				 display_page_tree, 0);
+
+	g_signal_connect_object (display_page_tree->priv->treeview,
+				 "popup_menu",
+				 G_CALLBACK (popup_menu_cb),
+				 display_page_tree, 0);
+
+	display_page_tree->priv->main_column = gtk_tree_view_column_new ();
+	gtk_tree_view_column_set_clickable (display_page_tree->priv->main_column, FALSE);
+
+	gtk_tree_view_append_column (GTK_TREE_VIEW (display_page_tree->priv->treeview),
+				     display_page_tree->priv->main_column);
+
+	/* Set up the indent level1 column */
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, FALSE);
+	gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
+						 renderer,
+						 (GtkTreeCellDataFunc) indent_level1_cell_data_func,
+						 display_page_tree,
+						 NULL);
+	g_object_set (renderer,
+		      "xpad", 0,
+		      "visible", FALSE,
+		      NULL);
+
+	/* Set up the indent level2 column */
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, FALSE);
+	gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
+						 renderer,
+						 (GtkTreeCellDataFunc) indent_level2_cell_data_func,
+						 display_page_tree,
+						 NULL);
+	g_object_set (renderer,
+		      "xpad", 0,
+		      "visible", FALSE,
+		      NULL);
+
+	/* Set up the pixbuf column */
+	renderer = gtk_cell_renderer_pixbuf_new ();
+	gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, FALSE);
+	gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
+						 renderer,
+						 (GtkTreeCellDataFunc) pixbuf_cell_data_func,
+						 display_page_tree,
+						 NULL);
+
+	g_object_set (renderer,
+		      "xpad", 8,
+		      "ypad", 1,
+		      "visible", FALSE,
+		      NULL);
+
+
+	/* Set up the name column */
+	renderer = gtk_cell_renderer_text_new ();
+	g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+	gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, TRUE);
+	gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
+						 renderer,
+						 (GtkTreeCellDataFunc) title_cell_data_func,
+						 display_page_tree,
+						 NULL);
+	g_signal_connect_object (renderer, "edited", G_CALLBACK (source_name_edited_cb), display_page_tree, 0);
+
+	g_object_set (display_page_tree->priv->treeview, "show-expanders", FALSE, NULL);
+	display_page_tree->priv->title_renderer = renderer;
+
+	/* Expander */
+	renderer = gossip_cell_renderer_expander_new ();
+	gtk_tree_view_column_pack_end (display_page_tree->priv->main_column, renderer, FALSE);
+	gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
+						 renderer,
+						 (GtkTreeCellDataFunc) expander_cell_data_func,
+						 display_page_tree,
+						 NULL);
+	display_page_tree->priv->expander_renderer = renderer;
+
+	display_page_tree->priv->selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (display_page_tree->priv->treeview));
+	g_signal_connect_object (display_page_tree->priv->selection,
+			         "changed",
+			         G_CALLBACK (selection_changed_cb),
+			         display_page_tree,
+				 0);
+	gtk_tree_selection_set_select_function (display_page_tree->priv->selection,
+						(GtkTreeSelectionFunc) selection_check_cb,
+						display_page_tree,
+						NULL);
+}
+
+static void
+rb_display_page_tree_class_init (RBDisplayPageTreeClass *class)
+{
+	GObjectClass   *o_class;
+
+	o_class = (GObjectClass *) class;
+
+	o_class->constructed = impl_constructed;
+	o_class->finalize = impl_finalize;
+	o_class->set_property = impl_set_property;
+	o_class->get_property = impl_get_property;
+
+	/**
+	 * RBDisplayPageTree:shell:
+	 *
+	 * The #RBShell instance
+	 */
+	g_object_class_install_property (o_class,
+					 PROP_SHELL,
+					 g_param_spec_object ("shell",
+							      "RBShell",
+							      "RBShell object",
+							      RB_TYPE_SHELL,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+	/**
+	 * RBDisplayPageTree:model:
+	 *
+	 * The #GtkTreeModel for the display page tree
+	 */
+	g_object_class_install_property (o_class,
+					 PROP_MODEL,
+					 g_param_spec_object ("model",
+							      "GtkTreeModel",
+							      "GtkTreeModel object",
+							      GTK_TYPE_TREE_MODEL,
+							      G_PARAM_READABLE));
+	/**
+	 * RBDisplayPageTree::selected:
+	 * @tree: the #RBDisplayPageTree
+	 * @page: the newly selected #RBDisplayPage
+	 *
+	 * Emitted when a page is selected from the tree
+	 */
+	signals[SELECTED] =
+		g_signal_new ("selected",
+			      G_OBJECT_CLASS_TYPE (o_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBDisplayPageTreeClass, selected),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__OBJECT,
+			      G_TYPE_NONE,
+			      1,
+			      G_TYPE_OBJECT);
+
+	/**
+	 * RBDisplayPageTree::drop-received:
+	 * @tree: the #RBDisplayPageTree
+	 * @page: the #RBDisplagePage receiving the drop
+	 * @data: the drop data
+	 *
+	 * Emitted when a drag and drop to the tree completes.
+	 */
+	signals[DROP_RECEIVED] =
+		g_signal_new ("drop-received",
+			      G_OBJECT_CLASS_TYPE (o_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBDisplayPageTreeClass, drop_received),
+			      NULL, NULL,
+			      rb_marshal_VOID__POINTER_POINTER,
+			      G_TYPE_NONE,
+			      2,
+			      G_TYPE_POINTER, G_TYPE_POINTER);
+
+	g_type_class_add_private (class, sizeof (RBDisplayPageTreePrivate));
+}
diff --git a/sources/rb-display-page-tree.h b/sources/rb-display-page-tree.h
new file mode 100644
index 0000000..91fafa9
--- /dev/null
+++ b/sources/rb-display-page-tree.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010 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_DISPLAY_PAGE_TREE_H
+#define RB_DISPLAY_PAGE_TREE_H
+
+#include <gtk/gtk.h>
+
+#include <sources/rb-display-page.h>
+#include <shell/rb-shell.h>
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_DISPLAY_PAGE_TREE	      (rb_display_page_tree_get_type ())
+#define RB_DISPLAY_PAGE_TREE(obj)	      (G_TYPE_CHECK_INSTANCE_CAST ((obj), RB_TYPE_DISPLAY_PAGE_TREE, RBDisplayPageTree))
+#define RB_DISPLAY_PAGE_TREE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), RB_TYPE_DISPLAY_PAGE_TREE, RBDisplayPageTreeClass))
+#define RB_IS_DISPLAY_PAGE_TREE(obj)	      (G_TYPE_CHECK_INSTANCE_TYPE ((obj), RB_TYPE_DISPLAY_PAGE_TREE))
+#define RB_IS_DISPLAY_PAGE_TREE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), RB_TYPE_DISPLAY_PAGE_TREE))
+#define RB_DISPLAY_PAGE_TREE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), RB_TYPE_DISPLAY_PAGE_TREE, RBDisplayPageTreeClass))
+
+typedef struct _RBDisplayPageTree RBDisplayPageTree;
+typedef struct _RBDisplayPageTreeClass RBDisplayPageTreeClass;
+typedef struct _RBDisplayPageTreePrivate RBDisplayPageTreePrivate;
+
+struct _RBDisplayPageTree
+{
+	GtkScrolledWindow    parent;
+
+	RBDisplayPageTreePrivate *priv;
+};
+
+struct _RBDisplayPageTreeClass
+{
+	GtkScrolledWindowClass parent_class;
+
+	/* signals */
+	void (*selected) (RBDisplayPageTree *tree, RBDisplayPage *page);
+	void (*drop_received) (RBDisplayPageTree *tree, RBDisplayPage *page, GtkSelectionData *data);
+};
+
+GType		rb_display_page_tree_get_type		(void);
+
+RBDisplayPageTree *rb_display_page_tree_new		(RBShell *shell);
+
+void		rb_display_page_tree_edit_source_name	(RBDisplayPageTree *display_page_tree,
+							 RBSource *source);
+
+void		rb_display_page_tree_select		(RBDisplayPageTree *display_page_tree,
+							 RBDisplayPage *page);
+
+void		rb_display_page_tree_toggle_expanded	(RBDisplayPageTree *display_page_tree,
+							 RBDisplayPage *page);
+
+G_END_DECLS
+
+#endif /* RB_DISPLAY_PAGE_TREE_H */
diff --git a/sources/rb-display-page.c b/sources/rb-display-page.c
new file mode 100644
index 0000000..158cf54
--- /dev/null
+++ b/sources/rb-display-page.c
@@ -0,0 +1,718 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2010 Jonathan Matthew <jonathan d14n org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#include "config.h"
+
+#include "rb-display-page.h"
+#include "rb-plugin.h"
+#include "rb-debug.h"
+#include "rb-util.h"
+
+G_DEFINE_ABSTRACT_TYPE (RBDisplayPage, rb_display_page, GTK_TYPE_HBOX)
+
+/**
+ * SECTION:rb-display-page
+ * @short_description: base class for items that appear in the display page tree
+ *
+ * This is the base class for items that appear in the display page tree and can
+ * occupy the main display area.  Sources and source groups are display pages.
+ * Other types of display, such as music visualization, could be implemented as
+ * display pages too.
+ *
+ * The display page object itself is the widget shown in the main display area.
+ * The pixbuf and name properties control its appearance in the display page
+ * tree, and its location is determined by its parent display page, the sorting
+ * rules for its source group (if any), and insertion order.  The visibility property
+ * controls whether the display page is actually shown in the display page tree at all.
+ */
+
+struct _RBDisplayPagePrivate
+{
+	char *name;
+	gboolean visible;
+	GdkPixbuf *pixbuf;
+	RBDisplayPage *parent;
+
+	RBPlugin *plugin;
+	RBShell *shell;
+
+	gboolean deleted;
+};
+
+enum
+{
+	PROP_0,
+	PROP_SHELL,
+	PROP_UI_MANAGER,
+	PROP_NAME,
+	PROP_PIXBUF,
+	PROP_VISIBLE,
+	PROP_PARENT,
+	PROP_PLUGIN,
+};
+
+enum
+{
+	STATUS_CHANGED,
+	DELETED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+/**
+ * rb_display_age_receive_drag:
+ * @page: a #RBDisplayPage
+ * @data: the selection data
+ *
+ * This is called when the user drags something to the page.
+ * Depending on the drag data type, the data might be a list of
+ * #RhythmDBEntry objects, a list of URIs, or a list of album
+ * or artist or genre names.
+ *
+ * Return value: TRUE if the page accepted the drag data
+ */
+gboolean
+rb_display_page_receive_drag (RBDisplayPage *page, GtkSelectionData *data)
+{
+	RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
+
+	if (klass->receive_drag)
+		return klass->receive_drag (page, data);
+	else
+		return FALSE;
+}
+
+/**
+ * rb_display_page_show_popup:
+ * @page: a #RBDisplayPage
+ *
+ * Called when the user performs an action (such as right-clicking)
+ * that should result in a popup menu being displayed for the page.
+ *
+ * Return value: TRUE if the page managed to display a popup
+ */
+gboolean
+rb_display_page_show_popup (RBDisplayPage *page)
+{
+	RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
+
+	if (klass->show_popup)
+		return klass->show_popup (page);
+	else
+		return FALSE;
+}
+
+/**
+ * rb_display_page_delete_thyself:
+ * @page: a #RBDisplayPage
+ *
+ * This is called when the page should delete itself.
+ * The 'deleted' signal will be emitted, which removes the page
+ * from the page model.  This will not actually dispose of the
+ * page object, so reference counting must still be handled
+ * correctly.
+ */
+void
+rb_display_page_delete_thyself (RBDisplayPage *page)
+{
+	RBDisplayPageClass *klass;
+
+	g_return_if_fail (page != NULL);
+	if (page->priv->deleted) {
+		rb_debug ("source has already been deleted");
+		return;
+	}
+	page->priv->deleted = TRUE;
+
+	klass = RB_DISPLAY_PAGE_GET_CLASS (page);
+	if (klass->delete_thyself) {
+		klass->delete_thyself (page);
+	}
+	g_signal_emit (G_OBJECT (page), signals[DELETED], 0);
+}
+
+/**
+ * rb_display_page_selectable:
+ * @page: a #RBDisplayPage
+ *
+ * Checks if @page can be selected
+ */
+gboolean
+rb_display_page_selectable (RBDisplayPage *page)
+{
+	RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
+	if (klass->selectable)
+		return klass->selectable (page);
+	else
+		return TRUE;
+}
+
+/**
+ * rb_display_page_selected:
+ * @page: a #RBDisplayPage
+ *
+ * Called when the page is selected in the page tree.
+ */
+void
+rb_display_page_selected (RBDisplayPage *page)
+{
+	RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
+
+	if (klass->selected)
+		klass->selected (page);
+}
+
+/**
+ * rb_display_page_deselected:
+ * @page: a #RBDisplayPage
+ *
+ * Called when the page is deselected in the page tree.
+ */
+void
+rb_display_page_deselected (RBDisplayPage *page)
+{
+	RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
+
+	if (klass->deselected)
+		klass->deselected (page);
+}
+
+/**
+ * rb_display_page_activate:
+ * @page: a #RBDisplayPage
+ *
+ * Called when the page is activated (double clicked, etc.) in the page tree.
+ */
+void
+rb_display_page_activate (RBDisplayPage *page)
+{
+	RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
+
+	if (klass->activate)
+		klass->activate (page);
+}
+
+
+/**
+ * rb_display_page_get_config_widget:
+ * @page: a #RBDisplayPage
+ * @prefs: the #RBShellPreferences object
+ *
+ * Source implementations can use this to return an optional
+ * configuration widget. The widget will be displayed in a
+ * page in the preferences dialog.
+ *
+ * Return value: configuration widget
+ */
+GtkWidget *
+rb_display_page_get_config_widget (RBDisplayPage *page,
+				   RBShellPreferences *prefs)
+{
+	RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
+
+	if (klass->get_config_widget) {
+		return klass->get_config_widget (page, prefs);
+	} else {
+		return NULL;
+	}
+}
+
+/**
+ * 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: 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: holds the returned status text (allocated)
+ * @progress_text: holds the returned text for the progress bar (allocated)
+ * @progress: holds the progress value
+ *
+ * Retrieves the details to display in the status bar for the page.
+ * If the progress value returned is less than zero, the progress bar
+ * will pulse.  If the progress value is greater than or equal to 1,
+ * the progress bar will be hidden.
+ **/
+void
+rb_display_page_get_status (RBDisplayPage *page,
+			    char **text,
+			    char **progress_text,
+			    float *progress)
+{
+	RBDisplayPageClass *klass = RB_DISPLAY_PAGE_GET_CLASS (page);
+
+	if (klass->get_status)
+		klass->get_status (page, text, progress_text, progress);
+}
+
+/**
+ * _rb_display_page_notify_status_changed:
+ * @page: a #RBDisplayPage
+ *
+ * Page implementations call this when their status bar information
+ * changes.
+ */
+void
+_rb_display_page_notify_status_changed (RBDisplayPage *page)
+{
+	g_signal_emit (G_OBJECT (page), signals[STATUS_CHANGED], 0);
+}
+
+/**
+ * _rb_display_page_show_popup:
+ * @page: a #RBDisplayPage
+ * @ui_path: UI path to the popup to show
+ *
+ * Page implementations can use this as a shortcut to
+ * display a popup that has been loaded into the UI manager.
+ */
+void
+_rb_display_page_show_popup (RBDisplayPage *page, const char *ui_path)
+{
+	GtkUIManager *uimanager;
+
+	g_object_get (page->priv->shell, "ui-manager", &uimanager, NULL);
+	rb_gtk_action_popup_menu (uimanager, ui_path);
+	g_object_unref (uimanager);
+}
+
+static GtkActionGroup *
+find_action_group (GtkUIManager *uimanager, const char *group_name)
+{
+	GList *actiongroups;
+	GList *i;
+	actiongroups = gtk_ui_manager_get_action_groups (uimanager);
+
+	/* Don't create the action group if it's already registered */
+	for (i = actiongroups; i != NULL; i = i->next) {
+		const char *name;
+
+		name = gtk_action_group_get_name (GTK_ACTION_GROUP (i->data));
+		if (g_strcmp0 (name, group_name) == 0) {
+			return GTK_ACTION_GROUP (i->data);
+		}
+	}
+
+	return NULL;
+}
+
+/**
+ * _rb_display_page_register_action_group:
+ * @page: a #RBDisplayPage
+ * @group_name: action group name
+ * @actions: array of GtkActionEntry structures for the action group
+ * @num_actions: number of actions in the @actions array
+ * @user_data: user data to use for action signal handlers
+ *
+ * Creates and registers a GtkActionGroup for the page.
+ *
+ * Return value: the created action group
+ */
+GtkActionGroup *
+_rb_display_page_register_action_group (RBDisplayPage *page,
+					const char *group_name,
+					GtkActionEntry *actions,
+					int num_actions,
+					gpointer user_data)
+{
+	GtkUIManager *uimanager;
+	GtkActionGroup *group;
+
+	g_return_val_if_fail (group_name != NULL, NULL);
+
+	g_object_get (page, "ui-manager", &uimanager, NULL);
+	group = find_action_group (uimanager, group_name);
+	if (group == NULL) {
+		group = gtk_action_group_new (group_name);
+		gtk_action_group_set_translation_domain (group,
+							 GETTEXT_PACKAGE);
+		if (actions != NULL) {
+			gtk_action_group_add_actions (group,
+						      actions, num_actions,
+						      user_data);
+		}
+		gtk_ui_manager_insert_action_group (uimanager, group, 0);
+	} else {
+		g_object_ref (group);
+	}
+	g_object_unref (uimanager);
+
+	return group;
+}
+
+typedef void (*DisplayPageActionCallback) (GtkAction *action, RBDisplayPage *page);
+
+typedef struct {
+	DisplayPageActionCallback callback;
+	gpointer shell;
+} DisplayPageActionData;
+
+static void
+display_page_action_data_destroy (DisplayPageActionData *data)
+{
+	if (data->shell != NULL) {
+		g_object_remove_weak_pointer (G_OBJECT (data->shell), &data->shell);
+	}
+	g_slice_free (DisplayPageActionData, data);
+}
+
+static void
+display_page_action_cb (GtkAction *action, DisplayPageActionData *data)
+{
+	RBDisplayPage *page;
+
+	if (data->shell == NULL) {
+		return;
+	}
+
+	/* get current page */
+	g_object_get (data->shell, "selected-page", &page, NULL);
+	if (page != NULL) {
+		data->callback (action, page);
+		g_object_unref (page);
+	}
+}
+
+/**
+ * _rb_action_group_add_display_page_actions:
+ * @group: a #GtkActionGroup
+ * @shell: the #RBShell
+ * @actions: array of GtkActionEntry structures for the action group
+ * @num_actions: number of actions in the @actions array
+ *
+ * Adds actions to an action group where the action callback is
+ * called with the current selected display page.  This can safely be called
+ * multiple times on the same action group.
+ */
+void
+_rb_action_group_add_display_page_actions (GtkActionGroup *group,
+					   GObject *shell,
+					   GtkActionEntry *actions,
+					   int num_actions)
+{
+	int i;
+	for (i = 0; i < num_actions; i++) {
+		GtkAction *action;
+		const char *label;
+		const char *tooltip;
+		DisplayPageActionData *page_action_data;
+
+		if (gtk_action_group_get_action (group, actions[i].name) != NULL) {
+			/* action was already added */
+			continue;
+		}
+
+		label = gtk_action_group_translate_string (group, actions[i].label);
+		tooltip = gtk_action_group_translate_string (group, actions[i].tooltip);
+
+		action = gtk_action_new (actions[i].name, label, tooltip, NULL);
+		if (actions[i].stock_id != NULL) {
+			g_object_set (action, "stock-id", actions[i].stock_id, NULL);
+			if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
+						     actions[i].stock_id)) {
+				g_object_set (action, "icon-name", actions[i].stock_id, NULL);
+			}
+		}
+
+		if (actions[i].callback) {
+			GClosure *closure;
+			page_action_data = g_slice_new0 (DisplayPageActionData);
+			page_action_data->callback = (DisplayPageActionCallback) actions[i].callback;
+			page_action_data->shell = shell;
+			g_object_add_weak_pointer (shell, &page_action_data->shell);
+
+			closure = g_cclosure_new (G_CALLBACK (display_page_action_cb),
+						  page_action_data,
+						  (GClosureNotify) display_page_action_data_destroy);
+			g_signal_connect_closure (action, "activate", closure, FALSE);
+		}
+
+		gtk_action_group_add_action_with_accel (group, action, actions[i].accelerator);
+		g_object_unref (action);
+	}
+}
+
+static void
+impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+	RBDisplayPage *page = RB_DISPLAY_PAGE (object);
+
+	switch (prop_id) {
+	case PROP_SHELL:
+		g_value_set_object (value, page->priv->shell);
+		break;
+	case PROP_UI_MANAGER:
+		{
+			GtkUIManager *manager;
+			g_object_get (page->priv->shell, "ui-manager", &manager, NULL);
+			g_value_set_object (value, manager);
+			g_object_unref (manager);
+			break;
+		}
+	case PROP_NAME:
+		g_value_set_string (value, page->priv->name);
+		break;
+	case PROP_PIXBUF:
+		g_value_set_object (value, page->priv->pixbuf);
+		break;
+	case PROP_VISIBLE:
+		g_value_set_boolean (value, page->priv->visible);
+		break;
+	case PROP_PARENT:
+		g_value_set_object (value, page->priv->parent);
+		break;
+	case PROP_PLUGIN:
+		g_value_set_object (value, page->priv->plugin);
+		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)
+{
+	RBDisplayPage *page = RB_DISPLAY_PAGE (object);
+
+	switch (prop_id) {
+	case PROP_SHELL:
+		page->priv->shell = g_value_get_object (value);
+		break;
+	case PROP_NAME:
+		g_free (page->priv->name);
+		page->priv->name = g_value_dup_string (value);
+		break;
+	case PROP_PIXBUF:
+		if (page->priv->pixbuf) {
+			g_object_unref (page->priv->pixbuf);
+		}
+		page->priv->pixbuf = g_value_dup_object (value);
+		break;
+	case PROP_VISIBLE:
+		page->priv->visible = g_value_get_boolean (value);
+		break;
+	case PROP_PARENT:
+		page->priv->parent = g_value_get_object (value);
+		break;
+	case PROP_PLUGIN:
+		page->priv->plugin = g_value_get_object (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		break;
+	}
+}
+
+static void
+impl_dispose (GObject *object)
+{
+	RBDisplayPage *page;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (RB_IS_DISPLAY_PAGE (object));
+	page = RB_DISPLAY_PAGE (object);
+
+	rb_debug ("Disposing page %s", page->priv->name);
+	if (page->priv->pixbuf != NULL) {
+		g_object_unref (page->priv->pixbuf);
+		page->priv->pixbuf = NULL;
+	}
+
+	G_OBJECT_CLASS (rb_display_page_parent_class)->dispose (object);
+}
+
+static void
+impl_finalize (GObject *object)
+{
+	RBDisplayPage *page;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (RB_IS_DISPLAY_PAGE (object));
+	page = RB_DISPLAY_PAGE (object);
+
+	rb_debug ("finalizing page %s", page->priv->name);
+
+	g_free (page->priv->name);
+
+	G_OBJECT_CLASS (rb_display_page_parent_class)->finalize (object);
+}
+
+static void
+rb_display_page_init (RBDisplayPage *page)
+{
+	page->priv = G_TYPE_INSTANCE_GET_PRIVATE (page, RB_TYPE_DISPLAY_PAGE, RBDisplayPagePrivate);
+
+	page->priv->visible = TRUE;
+}
+
+static void
+rb_display_page_class_init (RBDisplayPageClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	object_class->dispose = impl_dispose;
+	object_class->finalize = impl_finalize;
+
+	object_class->set_property = impl_set_property;
+	object_class->get_property = impl_get_property;
+
+	/**
+	 * RBDisplayPage:shell:
+	 *
+	 * The rhythmbox shell object
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_SHELL,
+					 g_param_spec_object ("shell",
+							      "RBShell",
+							      "RBShell object",
+							      RB_TYPE_SHELL,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	/**
+	 * RBDisplayPage:ui-manager:
+	 *
+	 * The Gtk UIManager object
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_UI_MANAGER,
+					 g_param_spec_object ("ui-manager",
+							      "GtkUIManager",
+							      "GtkUIManager object",
+							      GTK_TYPE_UI_MANAGER,
+							      G_PARAM_READABLE));
+	/**
+	 * RBDisplayPage:name:
+	 *
+	 * Page name as displayed in the tree
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_NAME,
+					 g_param_spec_string ("name",
+							      "UI name",
+							      "Interface name",
+							      NULL,
+							      G_PARAM_READWRITE));
+	/**
+	 * RBDisplayPage:pixbuf:
+	 *
+	 * Pixbuf to display in the page tree
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_PIXBUF,
+					 g_param_spec_object ("pixbuf",
+							      "Pixbuf",
+							      "Page pixbuf",
+							      GDK_TYPE_PIXBUF,
+							      G_PARAM_READWRITE));
+	/**
+	 * RBDisplayPage:visibility:
+	 *
+	 * If FALSE, the page will not be displayed in the tree
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_VISIBLE,
+					 g_param_spec_boolean ("visibility",
+							       "visibility",
+							       "Whether the page should be displayed in the tree",
+							       TRUE,
+							       G_PARAM_READWRITE));
+	/**
+	 * RBDisplayPage:parent:
+	 *
+	 * The parent page in the tree (may be NULL)
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_PARENT,
+					 g_param_spec_object ("parent",
+							      "Parent",
+							      "Parent page",
+							      RB_TYPE_DISPLAY_PAGE,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+	/**
+	 * RBDisplayPage:plugin:
+	 *
+	 * The plugin that created this page.
+	 */
+	g_object_class_install_property (object_class,
+					 PROP_PLUGIN,
+					 g_param_spec_object ("plugin",
+							      "RBPlugin",
+							      "RBPlugin instance for the plugin that created the page",
+							      RB_TYPE_PLUGIN,
+							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+	/**
+	 * RBDisplayPage::deleted:
+	 * @page: the #RBDisplayPage
+	 *
+	 * Emitted when the page is being deleted.
+	 */
+	signals[DELETED] =
+		g_signal_new ("deleted",
+			      RB_TYPE_DISPLAY_PAGE,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBDisplayPageClass, deleted),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
+	/**
+	 * RBDisplayPage::status-changed:
+	 * @page: the #RBDisplayPage
+	 *
+	 * Emitted when the page's status changes.
+	 */
+	signals[STATUS_CHANGED] =
+		g_signal_new ("status_changed",
+			      RB_TYPE_DISPLAY_PAGE,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (RBDisplayPageClass, status_changed),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE,
+			      0);
+
+	g_type_class_add_private (object_class, sizeof (RBDisplayPagePrivate));
+}
diff --git a/sources/rb-display-page.h b/sources/rb-display-page.h
new file mode 100644
index 0000000..c760fdf
--- /dev/null
+++ b/sources/rb-display-page.h
@@ -0,0 +1,112 @@
+/*
+ *  Copyright (C) 2010 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_DISPLAY_PAGE_H
+#define RB_DISPLAY_PAGE_H
+
+#include <gtk/gtk.h>
+
+#include <shell/rb-shell-preferences.h>
+
+G_BEGIN_DECLS
+
+typedef struct _RBDisplayPage		RBDisplayPage;
+typedef struct _RBDisplayPageClass	RBDisplayPageClass;
+typedef struct _RBDisplayPagePrivate	RBDisplayPagePrivate;
+
+#define RB_TYPE_DISPLAY_PAGE (rb_display_page_get_type ())
+#define RB_DISPLAY_PAGE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_DISPLAY_PAGE, RBDisplayPage))
+#define RB_DISPLAY_PAGE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_DISPLAY_PAGE, RBDisplayPageClass))
+#define RB_IS_DISPLAY_PAGE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_DISPLAY_PAGE))
+#define RB_IS_DISPLAY_PAGE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_DISPLAY_PAGE))
+#define RB_DISPLAY_PAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_DISPLAY_PAGE, RBDisplayPageClass))
+
+#define RB_DISPLAY_PAGE_ICON_SIZE	GTK_ICON_SIZE_LARGE_TOOLBAR
+
+struct _RBDisplayPage
+{
+	GtkHBox parent;
+
+	RBDisplayPagePrivate *priv;
+};
+
+struct _RBDisplayPageClass
+{
+	GtkHBoxClass parent_class;
+
+	/* signals */
+	void	(*status_changed)	(RBDisplayPage *page);
+	void	(*deleted)		(RBDisplayPage *page);
+
+	/* methods */
+	gboolean (*selectable)		(RBDisplayPage *page);
+	void	(*selected)		(RBDisplayPage *page);
+	void	(*deselected)		(RBDisplayPage *page);
+	void	(*activate)		(RBDisplayPage *page);
+
+	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);
+};
+
+GType		rb_display_page_get_type		(void);
+
+gboolean	rb_display_page_receive_drag		(RBDisplayPage *page, GtkSelectionData *data);
+gboolean	rb_display_page_show_popup		(RBDisplayPage *page);
+
+gboolean	rb_display_page_selectable		(RBDisplayPage *page);
+void		rb_display_page_selected		(RBDisplayPage *page);
+void		rb_display_page_deselected		(RBDisplayPage *page);
+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 */
+
+void		_rb_display_page_notify_status_changed	(RBDisplayPage *page);
+void		_rb_display_page_show_popup		(RBDisplayPage *page, const char *ui_path);
+
+GtkActionGroup *_rb_display_page_register_action_group	(RBDisplayPage *page,
+							 const char *group_name,
+							 GtkActionEntry *actions,
+							 int num_actions,
+							 gpointer user_data);
+void		_rb_action_group_add_display_page_actions (GtkActionGroup *group,
+							 GObject *shell,
+							 GtkActionEntry *actions,
+							 int num_actions);
+
+#endif /* RB_DISPLAY_PAGE_H */
diff --git a/sources/rb-import-errors-source.c b/sources/rb-import-errors-source.c
index 9bc6e1a..0a442cd 100644
--- a/sources/rb-import-errors-source.c
+++ b/sources/rb-import-errors-source.c
@@ -46,7 +46,7 @@ static void impl_set_property (GObject *object, guint prop_id, const GValue *val
 
 static RBEntryView *impl_get_entry_view (RBSource *source);
 static void impl_delete (RBSource *source);
-static void impl_get_status (RBSource *source, char **text, char **progress_text, float *progress);
+static void impl_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress);
 
 static void rb_import_errors_source_songs_show_popup_cb (RBEntryView *view,
 							 gboolean over_entry,
@@ -108,6 +108,7 @@ static void
 rb_import_errors_source_class_init (RBImportErrorsSourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 
 	object_class->dispose = rb_import_errors_source_dispose;
@@ -115,6 +116,8 @@ rb_import_errors_source_class_init (RBImportErrorsSourceClass *klass)
 	object_class->get_property = impl_get_property;
 	object_class->set_property = impl_set_property;
 
+	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;
@@ -130,8 +133,6 @@ rb_import_errors_source_class_init (RBImportErrorsSourceClass *klass)
 	source_class->impl_try_playlist = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_pause = (RBSourceFeatureFunc) rb_false_function;
 
-	source_class->impl_get_status = impl_get_status;
-
 	g_object_class_install_property (object_class,
 					 PROP_NORMAL_ENTRY_TYPE,
 					 g_param_spec_object ("normal-entry-type",
@@ -163,7 +164,7 @@ rb_import_errors_source_init (RBImportErrorsSource *source)
 					   "dialog-error",
 					   size,
 					   0, NULL);
-	rb_source_set_pixbuf (RB_SOURCE (source), pixbuf);
+	g_object_set (source, "pixbuf", pixbuf, NULL);
 	if (pixbuf != NULL) {
 		g_object_unref (pixbuf);
 	}
@@ -351,7 +352,6 @@ rb_import_errors_source_new (RBShell *shell,
 					  "shell", shell,
 					  "visibility", FALSE,
 					  "hidden-when-empty", TRUE,
-					  "source-group", RB_SOURCE_GROUP_LIBRARY,
 					  "entry-type", entry_type,
 					  "normal-entry-type", normal_entry_type,
 					  "ignore-entry-type", ignore_entry_type,
@@ -376,12 +376,12 @@ impl_delete (RBSource *asource)
 }
 
 static void
-impl_get_status (RBSource *asource, char **text, char **progress_text, float *progress)
+impl_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress)
 {
 	RhythmDBQueryModel *model;
 	gint count;
 
-	g_object_get (asource, "query-model", &model, NULL);
+	g_object_get (page, "query-model", &model, NULL);
 	count = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model), NULL);
 	g_object_unref (model);
 
@@ -394,7 +394,7 @@ rb_import_errors_source_songs_show_popup_cb (RBEntryView *view,
 					     gboolean over_entry,
 					     RBImportErrorsSource *source)
 {
-	_rb_source_show_popup (RB_SOURCE (source), "/ImportErrorsViewPopup");
+	_rb_display_page_show_popup (RB_DISPLAY_PAGE (source), "/ImportErrorsViewPopup");
 }
 
 static void
diff --git a/sources/rb-library-source.c b/sources/rb-library-source.c
index ed7044e..9238907 100644
--- a/sources/rb-library-source.c
+++ b/sources/rb-library-source.c
@@ -76,12 +76,13 @@ static void rb_library_source_constructed (GObject *object);
 static void rb_library_source_dispose (GObject *object);
 static void rb_library_source_finalize (GObject *object);
 
-/* RBSource implementations */
-static gboolean impl_show_popup (RBSource *source);
-static GtkWidget *impl_get_config_widget (RBSource *source, RBShellPreferences *prefs);
+static gboolean impl_show_popup (RBDisplayPage *source);
+static GtkWidget *impl_get_config_widget (RBDisplayPage *source, RBShellPreferences *prefs);
+static gboolean impl_receive_drag (RBDisplayPage *source, GtkSelectionData *data);
+static void impl_get_status (RBDisplayPage *source, char **text, char **progress_text, float *progress);
+
 static char *impl_get_browser_key (RBSource *source);
 static char *impl_get_paned_key (RBBrowserSource *source);
-static gboolean impl_receive_drag (RBSource *source, GtkSelectionData *data);
 static gboolean impl_can_paste (RBSource *asource);
 static RBTrackTransferBatch *impl_paste (RBSource *source, GList *entries);
 static guint impl_want_uri (RBSource *source, const char *uri);
@@ -92,7 +93,6 @@ static void impl_add_uri (RBSource *source,
 			  RBSourceAddCallback callback,
 			  gpointer data,
 			  GDestroyNotify destroy_data);
-static void impl_get_status (RBSource *source, char **text, char **progress_text, float *progress);
 
 static void rb_library_source_ui_prefs_sync (RBLibrarySource *source);
 static void rb_library_source_preferences_sync (RBLibrarySource *source);
@@ -194,6 +194,7 @@ static void
 rb_library_source_class_init (RBLibrarySourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 	RBBrowserSourceClass *browser_source_class = RB_BROWSER_SOURCE_CLASS (klass);
 
@@ -201,16 +202,17 @@ rb_library_source_class_init (RBLibrarySourceClass *klass)
 	object_class->finalize = rb_library_source_finalize;
 	object_class->constructed = rb_library_source_constructed;
 
-	source_class->impl_show_popup = impl_show_popup;
-	source_class->impl_get_config_widget = impl_get_config_widget;
+	page_class->show_popup = impl_show_popup;
+	page_class->get_config_widget = impl_get_config_widget;
+	page_class->receive_drag = impl_receive_drag;
+	page_class->get_status = impl_get_status;
+
 	source_class->impl_get_browser_key = impl_get_browser_key;
-	source_class->impl_receive_drag = impl_receive_drag;
 	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_can_paste = (RBSourceFeatureFunc) impl_can_paste;
 	source_class->impl_paste = impl_paste;
 	source_class->impl_want_uri = impl_want_uri;
 	source_class->impl_add_uri = impl_add_uri;
-	source_class->impl_get_status = impl_get_status;
 
 	browser_source_class->impl_get_paned_key = impl_get_paned_key;
 	browser_source_class->impl_has_drop_support = (RBBrowserSourceFeatureFunc) rb_true_function;
@@ -409,10 +411,9 @@ rb_library_source_new (RBShell *shell)
 	source = RB_SOURCE (g_object_new (RB_TYPE_LIBRARY_SOURCE,
 					  "name", _("Music"),
 					  "entry-type", RHYTHMDB_ENTRY_TYPE_SONG,
-					  "source-group", RB_SOURCE_GROUP_LIBRARY,
 					  "sorting-key", CONF_STATE_LIBRARY_SORTING,
 					  "shell", shell,
-					  "icon", icon,
+					  "pixbuf", icon,
 					  "populate", FALSE,		/* wait until the database is loaded */
 					  NULL));
 	if (icon != NULL) {
@@ -464,7 +465,7 @@ rb_library_source_location_button_clicked_cb (GtkButton *button, RBLibrarySource
 }
 
 static GtkWidget *
-impl_get_config_widget (RBSource *asource, RBShellPreferences *prefs)
+impl_get_config_widget (RBDisplayPage *asource, RBShellPreferences *prefs)
 {
 	RBLibrarySource *source = RB_LIBRARY_SOURCE (asource);
 	GtkBuilder *builder;
@@ -475,7 +476,7 @@ impl_get_config_widget (RBSource *asource, RBShellPreferences *prefs)
 	if (source->priv->config_widget)
 		return source->priv->config_widget;
 
-	g_object_ref (G_OBJECT (prefs));
+	g_object_ref (prefs);
 	source->priv->shell_prefs = prefs;
 
 	builder = rb_builder_load ("library-prefs.ui", source);
@@ -700,7 +701,7 @@ impl_get_paned_key (RBBrowserSource *status)
 }
 
 static gboolean
-impl_receive_drag (RBSource *asource, GtkSelectionData *data)
+impl_receive_drag (RBDisplayPage *asource, GtkSelectionData *data)
 {
 	RBLibrarySource *source = RB_LIBRARY_SOURCE (asource);
 	GList *list, *i;
@@ -734,8 +735,8 @@ impl_receive_drag (RBSource *asource, GtkSelectionData *data)
 
 	if (entries) {
 		entries = g_list_reverse (entries);
-		if (rb_source_can_paste (asource))
-			rb_source_paste (asource, entries);
+		if (rb_source_can_paste (RB_SOURCE (asource)))
+			rb_source_paste (RB_SOURCE (asource), entries);
 		g_list_free (entries);
 	}
 
@@ -744,9 +745,9 @@ impl_receive_drag (RBSource *asource, GtkSelectionData *data)
 }
 
 static gboolean
-impl_show_popup (RBSource *source)
+impl_show_popup (RBDisplayPage *source)
 {
-	_rb_source_show_popup (source, "/LibrarySourcePopup");
+	_rb_display_page_show_popup (source, "/LibrarySourcePopup");
 	return TRUE;
 }
 
@@ -1366,7 +1367,7 @@ import_job_status_changed_cb (RhythmDBImportJob *job, int total, int imported, R
 {
 	RhythmDBImportJob *head = RHYTHMDB_IMPORT_JOB (source->priv->import_jobs->data);
 	if (job == head) {		/* it was inevitable */
-		rb_source_notify_status_changed (RB_SOURCE (source));
+		_rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
 	}
 }
 
@@ -1521,13 +1522,13 @@ rb_library_source_add_child_source (const char *path, RBLibrarySource *library_s
 	rhythmdb_query_free (query);
 	g_free (sort_column);
 
-	g_object_get (library_source, "icon", &icon, NULL);
-	g_object_set (source, "icon", icon, NULL);
+	g_object_get (library_source, "pixbuf", &icon, NULL);
+	g_object_set (source, "pixbuf", icon, NULL);
 	if (icon != NULL) {
 		g_object_unref (icon);
 	}
 
-	rb_shell_append_source (shell, source, RB_SOURCE (library_source));
+	rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (source), RB_DISPLAY_PAGE (library_source));
 	library_source->priv->child_sources = g_list_prepend (library_source->priv->child_sources, source);
 
 	g_object_unref (entry_type);
@@ -1543,7 +1544,7 @@ rb_library_source_sync_child_sources (RBLibrarySource *source)
 	list = eel_gconf_get_string_list (CONF_LIBRARY_LOCATION);
 
 	/* FIXME: don't delete and re-create sources that are still there */
-	g_list_foreach (source->priv->child_sources, (GFunc)rb_source_delete_thyself, NULL);
+	g_list_foreach (source->priv->child_sources, (GFunc)rb_display_page_delete_thyself, NULL);
 	g_list_free (source->priv->child_sources);
 	source->priv->child_sources = NULL;
 
@@ -1553,13 +1554,13 @@ rb_library_source_sync_child_sources (RBLibrarySource *source)
 }
 
 static void
-impl_get_status (RBSource *source, char **text, char **progress_text, float *progress)
+impl_get_status (RBDisplayPage *source, char **text, char **progress_text, float *progress)
 {
-	RB_SOURCE_CLASS (rb_library_source_parent_class)->impl_get_status (source, text, progress_text, progress);
+	RB_DISPLAY_PAGE_CLASS (rb_library_source_parent_class)->get_status (source, text, progress_text, progress);
 	RBLibrarySource *lsource = RB_LIBRARY_SOURCE (source);
 
 	if (lsource->priv->import_jobs != NULL) {
 		RhythmDBImportJob *job = RHYTHMDB_IMPORT_JOB (lsource->priv->import_jobs->data);
-		_rb_source_set_import_status (source, job, progress_text, progress);
+		_rb_source_set_import_status (RB_SOURCE (source), job, progress_text, progress);
 	}
 }
diff --git a/sources/rb-media-player-source.c b/sources/rb-media-player-source.c
index b548fac..cc77963 100644
--- a/sources/rb-media-player-source.c
+++ b/sources/rb-media-player-source.c
@@ -120,10 +120,10 @@ rb_media_player_source_init_actions (RBShell *shell)
 	gtk_ui_manager_insert_action_group (uimanager, action_group, 0);
 	g_object_unref (uimanager);
 
-	_rb_action_group_add_source_actions (action_group,
-					     G_OBJECT (shell),
-					     rb_media_player_source_actions,
-					     G_N_ELEMENTS (rb_media_player_source_actions));
+	_rb_action_group_add_display_page_actions (action_group,
+						   G_OBJECT (shell),
+						   rb_media_player_source_actions,
+						   G_N_ELEMENTS (rb_media_player_source_actions));
 }
 
 static void
diff --git a/sources/rb-missing-files-source.c b/sources/rb-missing-files-source.c
index bd94c13..f96ca33 100644
--- a/sources/rb-missing-files-source.c
+++ b/sources/rb-missing-files-source.c
@@ -67,7 +67,7 @@ static void rb_missing_files_source_get_property (GObject *object,
 static RBEntryView *impl_get_entry_view (RBSource *source);
 static void impl_song_properties (RBSource *source);
 static void impl_delete (RBSource *source);
-static void impl_get_status (RBSource *source, char **text, char **progress_text, float *progress);
+static void impl_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress);
 
 static void rb_missing_files_source_songs_show_popup_cb (RBEntryView *view,
 							 gboolean over_entry,
@@ -89,6 +89,7 @@ static void
 rb_missing_files_source_class_init (RBMissingFilesSourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 
 	object_class->dispose = rb_missing_files_source_dispose;
@@ -97,6 +98,8 @@ rb_missing_files_source_class_init (RBMissingFilesSourceClass *klass)
 	object_class->set_property = rb_missing_files_source_set_property;
 	object_class->get_property = rb_missing_files_source_get_property;
 
+	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;
@@ -113,8 +116,6 @@ rb_missing_files_source_class_init (RBMissingFilesSourceClass *klass)
 	source_class->impl_try_playlist = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_pause = (RBSourceFeatureFunc) rb_false_function;
 
-	source_class->impl_get_status = impl_get_status;
-
 	g_type_class_add_private (klass, sizeof (RBMissingFilesSourcePrivate));
 }
 
@@ -131,7 +132,7 @@ rb_missing_files_source_init (RBMissingFilesSource *source)
 					   "dialog-warning",
 					   size,
 					   0, NULL);
-	rb_source_set_pixbuf (RB_SOURCE (source), pixbuf);
+	g_object_set (source, "pixbuf", pixbuf, NULL);
 	if (pixbuf != NULL) {
 		g_object_unref (pixbuf);
 	}
@@ -283,7 +284,6 @@ rb_missing_files_source_new (RBShell *shell,
 					  "shell", shell,
 					  "visibility", FALSE,
 					  "hidden-when-empty", TRUE,
-					  "source-group", RB_SOURCE_GROUP_LIBRARY,
 					  NULL));
 	g_object_unref (entry_type);
 	return source;
@@ -295,7 +295,7 @@ rb_missing_files_source_songs_show_popup_cb (RBEntryView *view,
 					     RBMissingFilesSource *source)
 {
 	if (over_entry)
-		_rb_source_show_popup (RB_SOURCE (source), MISSING_FILES_SOURCE_SONGS_POPUP_PATH);
+		_rb_display_page_show_popup (RB_DISPLAY_PAGE (source), MISSING_FILES_SOURCE_SONGS_POPUP_PATH);
 }
 
 static void
@@ -337,12 +337,12 @@ rb_missing_files_source_songs_sort_order_changed_cb (RBEntryView *view,
 }
 
 static void
-impl_get_status (RBSource *asource, char **text, char **progress_text, float *progress)
+impl_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress)
 {
 	RhythmDBQueryModel *model;
 	gint count;
 
-	g_object_get (asource, "query-model", &model, NULL);
+	g_object_get (page, "query-model", &model, NULL);
 	count = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model), NULL);
 	g_object_unref (model);
 
diff --git a/sources/rb-play-queue-source.c b/sources/rb-play-queue-source.c
index e3c15a8..726943a 100644
--- a/sources/rb-play-queue-source.c
+++ b/sources/rb-play-queue-source.c
@@ -82,8 +82,8 @@ 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 (RBSource *source);
-static gboolean impl_show_popup (RBSource *asource);
+static GList *impl_get_ui_actions (RBDisplayPage *page);
+static gboolean impl_show_popup (RBDisplayPage *page);
 
 #define PLAY_QUEUE_SOURCE_SONGS_POPUP_PATH "/QueuePlaylistViewPopup"
 #define PLAY_QUEUE_SOURCE_SIDEBAR_POPUP_PATH "/QueueSidebarViewPopup"
@@ -160,6 +160,7 @@ static void
 rb_play_queue_source_class_init (RBPlayQueueSourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 	RBPlaylistSourceClass *playlist_class = RB_PLAYLIST_SOURCE_CLASS (klass);
 
@@ -168,11 +169,12 @@ 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;
-	source_class->impl_get_ui_actions = impl_get_ui_actions;
-	source_class->impl_show_popup = impl_show_popup;
 
 	playlist_class->impl_show_entry_view_popup = impl_show_entry_view_popup;
 	playlist_class->impl_save_contents_to_xml = impl_save_contents_to_xml;
@@ -231,11 +233,11 @@ rb_play_queue_source_constructed (GObject *object)
 
 	priv->queue_play_order = rb_queue_play_order_new (RB_SHELL_PLAYER (shell_player));
 
-	priv->action_group = _rb_source_register_action_group (RB_SOURCE (source),
-							       "PlayQueueActions",
-							       rb_play_queue_source_actions,
-							       G_N_ELEMENTS (rb_play_queue_source_actions),
-							       source);
+	priv->action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
+								     "PlayQueueActions",
+								     rb_play_queue_source_actions,
+								     G_N_ELEMENTS (rb_play_queue_source_actions),
+								     source);
 	action = gtk_action_group_get_action (priv->action_group,
 					      "ClearQueue");
 	/* Translators: this is the toolbutton label for Clear Queue action */
@@ -318,7 +320,6 @@ rb_play_queue_source_new (RBShell *shell)
 					"shell", shell,
 					"is-local", TRUE,
 					"entry-type", NULL,
-					"source-group", RB_SOURCE_GROUP_LIBRARY,
 					NULL));
 }
 
@@ -400,7 +401,7 @@ impl_show_entry_view_popup (RBPlaylistSource *source,
 		popup = PLAY_QUEUE_SOURCE_SIDEBAR_POPUP_PATH;
 	else if (!over_entry)
 		return;
-	_rb_source_show_popup (RB_SOURCE (source), popup);
+	_rb_display_page_show_popup (RB_DISPLAY_PAGE (source), popup);
 }
 
 static void
@@ -504,14 +505,14 @@ rb_play_queue_source_cmd_shuffle (GtkAction *action,
 }
 
 static gboolean
-impl_show_popup (RBSource *asource)
+impl_show_popup (RBDisplayPage *page)
 {
-	_rb_source_show_popup (asource, PLAY_QUEUE_SOURCE_POPUP_PATH);
+	_rb_display_page_show_popup (page, PLAY_QUEUE_SOURCE_POPUP_PATH);
 	return TRUE;
 }
 
 static GList *
-impl_get_ui_actions (RBSource *source)
+impl_get_ui_actions (RBDisplayPage *page)
 {
 	GList *actions = NULL;
 
diff --git a/sources/rb-playlist-source.c b/sources/rb-playlist-source.c
index f7cb118..1c41718 100644
--- a/sources/rb-playlist-source.c
+++ b/sources/rb-playlist-source.c
@@ -88,7 +88,7 @@ static void rb_playlist_source_get_property (GObject *object,
 static char *impl_get_browser_key (RBSource *source);
 static RBEntryView *impl_get_entry_view (RBSource *source);
 static void impl_song_properties (RBSource *source);
-static gboolean impl_show_popup (RBSource *source);
+static gboolean impl_show_popup (RBDisplayPage *page);
 
 static void rb_playlist_source_songs_show_popup_cb (RBEntryView *view,
 						    gboolean over_entry,
@@ -171,15 +171,17 @@ static void
 rb_playlist_source_class_init (RBPlaylistSourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 
 	object_class->dispose = rb_playlist_source_dispose;
 	object_class->finalize = rb_playlist_source_finalize;
 	object_class->constructed = rb_playlist_source_constructed;
-
 	object_class->set_property = rb_playlist_source_set_property;
 	object_class->get_property = rb_playlist_source_get_property;
 
+	page_class->show_popup = impl_show_popup;
+
 	source_class->impl_get_browser_key = impl_get_browser_key;
 	source_class->impl_get_entry_view = impl_get_entry_view;
 	source_class->impl_can_rename = (RBSourceFeatureFunc) rb_true_function;
@@ -189,7 +191,6 @@ rb_playlist_source_class_init (RBPlaylistSourceClass *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_song_properties = impl_song_properties;
-	source_class->impl_show_popup = impl_show_popup;
 	source_class->impl_get_delete_action = impl_get_delete_action;
 
 	klass->impl_show_entry_view_popup = default_show_entry_view_popup;
@@ -298,14 +299,14 @@ rb_playlist_source_constructed (GObject *object)
 	rb_playlist_source_set_db (source, db);
 	g_object_unref (db);
 
-	source->priv->action_group = _rb_source_register_action_group (RB_SOURCE (source),
-								       "PlaylistActions",
-								       NULL, 0,
-								       shell);
-	_rb_action_group_add_source_actions (source->priv->action_group,
-					     G_OBJECT (shell),
-					     rb_playlist_source_actions,
-					     G_N_ELEMENTS (rb_playlist_source_actions));
+	source->priv->action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
+									     "PlaylistActions",
+									     NULL, 0,
+									     shell);
+	_rb_action_group_add_display_page_actions (source->priv->action_group,
+						   G_OBJECT (shell),
+						   rb_playlist_source_actions,
+						   G_N_ELEMENTS (rb_playlist_source_actions));
 
 	g_object_unref (shell);
 
@@ -492,8 +493,9 @@ default_show_entry_view_popup (RBPlaylistSource *source,
 			       RBEntryView *view,
 			       gboolean over_entry)
 {
-	if (over_entry)
-		_rb_source_show_popup (RB_SOURCE (source), PLAYLIST_SOURCE_SONGS_POPUP_PATH);
+	if (over_entry) {
+		_rb_display_page_show_popup (RB_DISPLAY_PAGE (source), PLAYLIST_SOURCE_SONGS_POPUP_PATH);
+	}
 }
 
 static void
@@ -536,9 +538,9 @@ impl_song_properties (RBSource *asource)
 }
 
 static gboolean
-impl_show_popup (RBSource *asource)
+impl_show_popup (RBDisplayPage *page)
 {
-	_rb_source_show_popup (asource, PLAYLIST_SOURCE_POPUP_PATH);
+	_rb_display_page_show_popup (page, PLAYLIST_SOURCE_POPUP_PATH);
 	return TRUE;
 }
 
@@ -563,7 +565,7 @@ rb_playlist_source_drop_cb (GtkWidget *widget,
 	if (target == GDK_NONE)
 		return;
 
-	rb_source_receive_drag (RB_SOURCE (source), data);
+	rb_display_page_receive_drag (RB_DISPLAY_PAGE (source), data);
 
 	gtk_drag_finish (context, TRUE, FALSE, time);
 }
diff --git a/sources/rb-removable-media-source.c b/sources/rb-removable-media-source.c
index 54f6d64..4c8ea37 100644
--- a/sources/rb-removable-media-source.c
+++ b/sources/rb-removable-media-source.c
@@ -84,9 +84,9 @@ static void rb_removable_media_source_get_property (GObject *object,
 			                  GValue *value,
 			                  GParamSpec *pspec);
 
-static void impl_delete_thyself (RBSource *source);
+static void impl_delete_thyself (RBDisplayPage *page);
 static RBTrackTransferBatch *impl_paste (RBSource *source, GList *entries);
-static gboolean impl_receive_drag (RBSource *asource, GtkSelectionData *data);
+static gboolean impl_receive_drag (RBDisplayPage *page, GtkSelectionData *data);
 static gboolean impl_should_paste (RBRemovableMediaSource *source,
 				   RhythmDBEntry *entry);
 static guint impl_want_uri (RBSource *source, const char *uri);
@@ -115,6 +115,7 @@ static void
 rb_removable_media_source_class_init (RBRemovableMediaSourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 	RBBrowserSourceClass *browser_source_class = RB_BROWSER_SOURCE_CLASS (klass);
 
@@ -123,17 +124,16 @@ rb_removable_media_source_class_init (RBRemovableMediaSourceClass *klass)
 	object_class->set_property = rb_removable_media_source_set_property;
 	object_class->get_property = rb_removable_media_source_get_property;
 
-	source_class->impl_delete_thyself = impl_delete_thyself;
+	page_class->receive_drag = impl_receive_drag;
+	page_class->delete_thyself = impl_delete_thyself;
+
 	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_copy = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_can_paste = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_false_function;
   	source_class->impl_paste = impl_paste;
-  	source_class->impl_receive_drag = impl_receive_drag;
 	source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_delete = NULL;
-	source_class->impl_get_config_widget = NULL;
-	source_class->impl_show_popup = (RBSourceFeatureFunc) rb_false_function;
 	source_class->impl_want_uri = impl_want_uri;
 	source_class->impl_uri_is_source = impl_uri_is_source;
 	source_class->impl_get_delete_action = impl_get_delete_action;
@@ -240,8 +240,8 @@ rb_removable_media_source_constructed (GObject *object)
 		pixbuf = NULL;
 	}
 
-	rb_source_set_pixbuf (RB_SOURCE (object), pixbuf);
 	if (pixbuf != NULL) {
+		g_object_set (object, "pixbuf", pixbuf, NULL);
 		g_object_unref (pixbuf);
 	}
 	if (mount != NULL) {
@@ -322,17 +322,17 @@ rb_removable_media_source_get_property (GObject *object,
 }
 
 static void
-impl_delete_thyself (RBSource *source)
+impl_delete_thyself (RBDisplayPage *page)
 {
 	RhythmDB *db;
 	RBShell *shell;
 	RhythmDBEntryType *entry_type;
 
-	g_object_get (source, "shell", &shell, NULL);
+	g_object_get (page, "shell", &shell, NULL);
 	g_object_get (shell, "db", &db, NULL);
 	g_object_unref (shell);
 
-	g_object_get (source, "entry-type", &entry_type, NULL);
+	g_object_get (page, "entry-type", &entry_type, NULL);
 	rb_debug ("deleting all entries of type '%s'", rhythmdb_entry_type_get_name (entry_type));
 	rhythmdb_entry_delete_by_type (db, entry_type);
 	g_object_unref (entry_type);
@@ -533,7 +533,7 @@ get_db_for_source (RBSource *source)
 }
 
 static gboolean
-impl_receive_drag (RBSource *asource, GtkSelectionData *data)
+impl_receive_drag (RBDisplayPage *page, GtkSelectionData *data)
 {
 	GList *entries;
 	RhythmDB *db;
@@ -541,7 +541,7 @@ impl_receive_drag (RBSource *asource, GtkSelectionData *data)
 
 	entries = NULL;
 	type = gdk_atom_name (gtk_selection_data_get_data_type (data));
-        db = get_db_for_source (asource);
+        db = get_db_for_source (RB_SOURCE (page));
 
 	if (strcmp (type, "text/uri-list") == 0) {
 		GList *list;
@@ -595,9 +595,10 @@ impl_receive_drag (RBSource *asource, GtkSelectionData *data)
 	g_free (type);
 
 	if (entries) {
+		RBSource *source = RB_SOURCE (page);
 		entries = g_list_reverse (entries);
-		if (rb_source_can_paste (asource))
-			rb_source_paste (asource, entries);
+		if (rb_source_can_paste (source))
+			rb_source_paste (source, entries);
 		g_list_free (entries);
 	}
 
diff --git a/sources/rb-source.c b/sources/rb-source.c
index c0360a3..aef06d0 100644
--- a/sources/rb-source.c
+++ b/sources/rb-source.c
@@ -35,6 +35,7 @@
 #include <glib/gi18n.h>
 #include <gtk/gtk.h>
 
+#include "rb-source.h"
 #include "rb-cut-and-paste-code.h"
 #include "rb-debug.h"
 #include "rb-dialog.h"
@@ -42,7 +43,6 @@
 #include "rb-source.h"
 #include "rb-util.h"
 #include "rb-static-playlist-source.h"
-#include "rb-source-group.h"
 #include "rb-plugin.h"
 #include "rb-play-order.h"
 
@@ -59,6 +59,8 @@ static void rb_source_get_property (GObject *object,
 					GValue *value,
 					GParamSpec *pspec);
 
+static void default_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress);
+static void default_activate (RBDisplayPage *page);
 static char * default_get_browser_key (RBSource *source);
 static GList *default_get_property_views (RBSource *source);
 static gboolean default_can_rename (RBSource *source);
@@ -66,15 +68,9 @@ static GList *default_copy (RBSource *source);
 static void default_reset_filters (RBSource *source);
 static gboolean default_try_playlist (RBSource *source);
 static RBSourceEOFType default_handle_eos (RBSource *source);
-static gboolean default_show_popup  (RBSource *source);
-static void default_delete_thyself (RBSource *source);
 static RBEntryView *default_get_entry_view (RBSource *source);
-static void default_activate (RBSource *source);
-static void default_deactivate (RBSource *source);
 static void default_add_to_queue (RBSource *source, RBSource *queue);
-static void default_get_status (RBSource *source, char **text, char **progress_text, float *progress);
 static void default_move_to_trash (RBSource *source);
-static GList * default_get_ui_actions (RBSource *source);
 static GList * default_get_search_actions (RBSource *source);
 static char *default_get_delete_action (RBSource *source);
 
@@ -85,8 +81,8 @@ static void rb_source_row_inserted_cb (GtkTreeModel *model,
 				       GtkTreePath *path,
 				       GtkTreeIter *iter,
 				       RBSource *source);
-G_DEFINE_ABSTRACT_TYPE (RBSource, rb_source, GTK_TYPE_HBOX)
-#define RB_SOURCE_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), RB_TYPE_SOURCE, RBSourcePrivate))
+
+G_DEFINE_ABSTRACT_TYPE (RBSource, rb_source, RB_TYPE_DISPLAY_PAGE)
 
 /**
  * SECTION:rb-source
@@ -110,36 +106,20 @@ G_DEFINE_ABSTRACT_TYPE (RBSource, rb_source, GTK_TYPE_HBOX)
 
 struct _RBSourcePrivate
 {
-	char *name;
-
-	RBShell *shell;
-	gboolean visible;
 	RhythmDBQueryModel *query_model;
-	GdkPixbuf *pixbuf;
 	guint hidden_when_empty : 1;
 	guint update_visibility_id;
 	guint update_status_id;
 	RhythmDBEntryType *entry_type;
-	RBSourceGroup *source_group;
-	RBPlugin *plugin;
 	RBSourceSearchType search_type;
-
-	gboolean deleted;
 };
 
 enum
 {
 	PROP_0,
-	PROP_NAME,
-	PROP_ICON,
-	PROP_SHELL,
-	PROP_UI_MANAGER,
-	PROP_VISIBLE,
 	PROP_QUERY_MODEL,
 	PROP_HIDDEN_WHEN_EMPTY,
-	PROP_SOURCE_GROUP,
 	PROP_ENTRY_TYPE,
-	PROP_PLUGIN,
 	PROP_BASE_QUERY_MODEL,
 	PROP_PLAY_ORDER,
 	PROP_SEARCH_TYPE
@@ -147,9 +127,7 @@ enum
 
 enum
 {
-	STATUS_CHANGED,
 	FILTER_CHANGED,
-	DELETED,
 	LAST_SIGNAL
 };
 
@@ -159,13 +137,16 @@ static void
 rb_source_class_init (RBSourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 
 	object_class->dispose = rb_source_dispose;
 	object_class->finalize = rb_source_finalize;
-
 	object_class->set_property = rb_source_set_property;
 	object_class->get_property = rb_source_get_property;
 
+	page_class->activate = default_activate;
+	page_class->get_status = default_get_status;
+
 	klass->impl_can_browse = (RBSourceFeatureFunc) rb_false_function;
 	klass->impl_get_browser_key = default_get_browser_key;
 	klass->impl_browser_toggled = NULL;
@@ -182,90 +163,13 @@ rb_source_class_init (RBSourceClass *klass)
 	klass->impl_copy = default_copy;
 	klass->impl_reset_filters = default_reset_filters;
 	klass->impl_handle_eos = default_handle_eos;
-	klass->impl_get_config_widget = NULL;
-	klass->impl_receive_drag = NULL;
-	klass->impl_show_popup = default_show_popup;
-	klass->impl_delete_thyself = default_delete_thyself;
-	klass->impl_activate = default_activate;
-	klass->impl_deactivate = default_deactivate;
 	klass->impl_try_playlist = default_try_playlist;
 	klass->impl_add_to_queue = default_add_to_queue;
-	klass->impl_get_status = default_get_status;
-	klass->impl_get_ui_actions = default_get_ui_actions;
 	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;
 
 	/**
-	 * RBSource:name:
-	 *
-	 * Source name as displayed in the source list
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_NAME,
-					 g_param_spec_string ("name",
-							      "UI name",
-							      "Interface name",
-							      NULL,
-							      G_PARAM_READWRITE));
-	/**
-	 * RBSource:icon:
-	 *
-	 * Icon to display in the source list
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_ICON,
-					 g_param_spec_object ("icon",
-							      "Icon",
-							      "Source Icon",
-							      GDK_TYPE_PIXBUF,
-							      G_PARAM_READWRITE));
-
-	/**
-	 * RBSource:shell:
-	 *
-	 * The rhythmbox shell object
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_SHELL,
-					 g_param_spec_object ("shell",
-							       "RBShell",
-							       "RBShell object",
-							      RB_TYPE_SHELL,
-							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-	/**
-	 * RBSource:ui-manager:
-	 *
-	 * The Gtk UIManager object
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_UI_MANAGER,
-					 g_param_spec_object ("ui-manager",
-							       "GtkUIManager",
-							       "GtkUIManager object",
-							      GTK_TYPE_UI_MANAGER,
-							      G_PARAM_READABLE));
-
-	/**
-	 * RBSource:visibility:
-	 *
-	 * If FALSE, the source will not be displayed in the source list
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_VISIBLE,
-					 /* FIXME: This property could probably
-					  * be named better, there's already
-					  * a GtkWidget 'visible' property,
-					  * since RBSource derives from
-					  * GtkWidget, this can be confusing
-					  */
-					 g_param_spec_boolean ("visibility",
-							       "visibility",
-							       "Whether the source should be displayed in the source list",
-							       TRUE,
-							       G_PARAM_READWRITE));
-
-	/**
 	 * RBSource:hidden-when-empty:
 	 *
 	 * If TRUE, the source will not be displayed in the source list
@@ -294,18 +198,6 @@ rb_source_class_init (RBSourceClass *klass)
 							      RHYTHMDB_TYPE_QUERY_MODEL,
 							      G_PARAM_READWRITE));
 	/**
-	 * RBSource:source-group:
-	 *
-	 * Source group in which to display the source
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_SOURCE_GROUP,
-					 g_param_spec_boxed ("source-group",
-							     "Source group",
-							     "Source group",
-							     RB_TYPE_SOURCE_GROUP,
-							     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-	/**
 	 * RBSource:entry-type:
 	 *
 	 * Entry type for entries in this source.
@@ -318,18 +210,6 @@ rb_source_class_init (RBSourceClass *klass)
 							      RHYTHMDB_TYPE_ENTRY_TYPE,
 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 	/**
-	 * RBSource:plugin:
-	 *
-	 * The plugin that created this source.
-	 */
-	g_object_class_install_property (object_class,
-					 PROP_PLUGIN,
-					 g_param_spec_object ("plugin",
-							      "RBPlugin",
-							      "RBPlugin instance for the plugin that created the source",
-							      RB_TYPE_PLUGIN,
-							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
-	/**
 	 * RBSource:base-query-model:
 	 *
 	 * The unfiltered query model for the source, containing all entries in the source.
@@ -371,38 +251,6 @@ rb_source_class_init (RBSourceClass *klass)
 							    RB_SOURCE_SEARCH_NONE,
 							    G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 	/**
-	 * RBSource::deleted:
-	 * @source: the #RBSource
-	 *
-	 * Emitted when the source is being deleted.
-	 */
-	rb_source_signals[DELETED] =
-		g_signal_new ("deleted",
-			      RB_TYPE_SOURCE,
-			      G_SIGNAL_RUN_LAST,
-			      G_STRUCT_OFFSET (RBSourceClass, deleted),
-			      NULL, NULL,
-			      g_cclosure_marshal_VOID__VOID,
-			      G_TYPE_NONE,
-			      0);
-
-	/**
-	 * RBSource::status-changed:
-	 * @source: the #RBSource
-	 *
-	 * Emitted when the source's status changes.
-	 */
-	rb_source_signals[STATUS_CHANGED] =
-		g_signal_new ("status_changed",
-			      RB_TYPE_SOURCE,
-			      G_SIGNAL_RUN_LAST,
-			      G_STRUCT_OFFSET (RBSourceClass, status_changed),
-			      NULL, NULL,
-			      g_cclosure_marshal_VOID__VOID,
-			      G_TYPE_NONE,
-			      0);
-
-	/**
 	 * RBSource::filter-changed:
 	 * @source: the #RBSource
 	 *
@@ -426,31 +274,26 @@ rb_source_class_init (RBSourceClass *klass)
 static void
 rb_source_init (RBSource *source)
 {
-	RB_SOURCE_GET_PRIVATE (source)->visible = TRUE;
+	source->priv = G_TYPE_INSTANCE_GET_PRIVATE (source, RB_TYPE_SOURCE, RBSourcePrivate);
 }
 
 static void
 rb_source_dispose (GObject *object)
 {
 	RBSource *source;
-	RBSourcePrivate *priv;
 
 	g_return_if_fail (object != NULL);
 	g_return_if_fail (RB_IS_SOURCE (object));
 
 	source = RB_SOURCE (object);
-	priv = RB_SOURCE_GET_PRIVATE (source);
-	g_return_if_fail (priv != NULL);
-
-	rb_debug ("Disposing source %p: '%s'", source, priv->name);
 
-	if (priv->update_visibility_id != 0) {
-		g_source_remove (priv->update_visibility_id);
-		priv->update_visibility_id = 0;
+	if (source->priv->update_visibility_id != 0) {
+		g_source_remove (source->priv->update_visibility_id);
+		source->priv->update_visibility_id = 0;
 	}
-	if (priv->update_status_id != 0) {
-		g_source_remove (priv->update_status_id);
-		priv->update_status_id = 0;
+	if (source->priv->update_status_id != 0) {
+		g_source_remove (source->priv->update_status_id);
+		source->priv->update_status_id = 0;
 	}
 
 	G_OBJECT_CLASS (rb_source_parent_class)->dispose (object);
@@ -460,74 +303,33 @@ static void
 rb_source_finalize (GObject *object)
 {
 	RBSource *source;
-	RBSourcePrivate *priv;
 
 	g_return_if_fail (object != NULL);
 	g_return_if_fail (RB_IS_SOURCE (object));
 
 	source = RB_SOURCE (object);
-	priv = RB_SOURCE_GET_PRIVATE (source);
-	g_return_if_fail (priv != NULL);
-
-	rb_debug ("Finalizing source %p: '%s'", source, priv->name);
-
-	if (priv->query_model != NULL) {
-		rb_debug ("Unreffing model %p count: %d", priv->query_model, G_OBJECT (priv->query_model)->ref_count);
-		g_object_unref (priv->query_model);
-	}
 
-	if (priv->pixbuf != NULL) {
-		g_object_unref (priv->pixbuf);
+	if (source->priv->query_model != NULL) {
+		rb_debug ("Unreffing model %p count: %d",
+			  source->priv->query_model,
+			  G_OBJECT (source->priv->query_model)->ref_count);
+		g_object_unref (source->priv->query_model);
 	}
 
-	g_free (priv->name);
-
 	G_OBJECT_CLASS (rb_source_parent_class)->finalize (object);
 }
 
-/**
- * rb_source_set_pixbuf:
- * @source: a #RBSource
- * @pixbuf: new GdkPixbuf for the source
- *
- * Sets the pixbuf for the source.
- */
-void
-rb_source_set_pixbuf (RBSource  *source,
-		      GdkPixbuf *pixbuf)
-{
-	RBSourcePrivate *priv = RB_SOURCE_GET_PRIVATE (source);
-
-	g_return_if_fail (RB_IS_SOURCE (source));
-
-	if (priv->pixbuf != NULL) {
-		g_object_unref (priv->pixbuf);
-	}
-
-	priv->pixbuf = pixbuf;
-
-	if (priv->pixbuf != NULL) {
-		g_object_ref (priv->pixbuf);
-	}
-}
-
 static gboolean
 update_visibility_idle (RBSource *source)
 {
-	RBSourcePrivate *priv = RB_SOURCE_GET_PRIVATE (source);
-	gboolean visibility;
+	gint count;
 
 	GDK_THREADS_ENTER ();
 
-	gint count = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (priv->query_model), NULL);
-
-	visibility = (count > 0);
-
-	if (visibility != priv->visible) {
-		g_object_set (G_OBJECT (source), "visibility", visibility, NULL);
-	}
+	count = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (source->priv->query_model), NULL);
+	g_object_set (source, "visibility", (count > 0), NULL);
 
-	priv->update_visibility_id = 0;
+	source->priv->update_visibility_id = 0;
 	GDK_THREADS_LEAVE ();
 	return FALSE;
 }
@@ -535,12 +337,10 @@ update_visibility_idle (RBSource *source)
 static void
 queue_update_visibility (RBSource *source)
 {
-	RBSourcePrivate *priv = RB_SOURCE_GET_PRIVATE (source);
-
-	if (priv->update_visibility_id != 0) {
-		g_source_remove (priv->update_visibility_id);
+	if (source->priv->update_visibility_id != 0) {
+		g_source_remove (source->priv->update_visibility_id);
 	}
-	priv->update_visibility_id = g_idle_add ((GSourceFunc) update_visibility_idle, source);
+	source->priv->update_visibility_id = g_idle_add ((GSourceFunc) update_visibility_idle, source);
 }
 
 /**
@@ -555,12 +355,10 @@ void
 rb_source_set_hidden_when_empty (RBSource *source,
 				 gboolean  hidden)
 {
-	RBSourcePrivate *priv = RB_SOURCE_GET_PRIVATE (source);
-
 	g_return_if_fail (RB_IS_SOURCE (source));
 
-	if (priv->hidden_when_empty != hidden) {
-		priv->hidden_when_empty = hidden;
+	if (source->priv->hidden_when_empty != hidden) {
+		source->priv->hidden_when_empty = hidden;
 		queue_update_visibility (source);
 	}
 }
@@ -569,25 +367,23 @@ static void
 rb_source_set_query_model_internal (RBSource *source,
 				    RhythmDBQueryModel *model)
 {
-	RBSourcePrivate *priv = RB_SOURCE_GET_PRIVATE (source);
-
-	if (priv->query_model == model) {
+	if (source->priv->query_model == model) {
 		return;
 	}
 
-	if (priv->query_model != NULL) {
-		g_signal_handlers_disconnect_by_func (priv->query_model,
+	if (source->priv->query_model != NULL) {
+		g_signal_handlers_disconnect_by_func (source->priv->query_model,
 						      G_CALLBACK (rb_source_post_entry_deleted_cb),
 						      source);
-		g_signal_handlers_disconnect_by_func (priv->query_model,
+		g_signal_handlers_disconnect_by_func (source->priv->query_model,
 						      G_CALLBACK (rb_source_row_inserted_cb),
 						      source);
-		g_object_unref (priv->query_model);
+		g_object_unref (source->priv->query_model);
 	}
 
-	priv->query_model = model;
-	if (priv->query_model != NULL) {
-		g_object_ref (priv->query_model);
+	source->priv->query_model = model;
+	if (source->priv->query_model != NULL) {
+		g_object_ref (source->priv->query_model);
 		g_signal_connect_object (model, "post-entry-delete",
 					 G_CALLBACK (rb_source_post_entry_deleted_cb),
 					 source, 0);
@@ -596,8 +392,7 @@ rb_source_set_query_model_internal (RBSource *source,
 					 source, 0);
 	}
 
-	/* g_object_notify (G_OBJECT (source), "query-model"); */
-	rb_source_notify_status_changed (source);
+	_rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
 }
 
 static void
@@ -606,43 +401,20 @@ rb_source_set_property (GObject *object,
 			const GValue *value,
 			GParamSpec *pspec)
 {
-	RBSourcePrivate *priv = RB_SOURCE_GET_PRIVATE (object);
 	RBSource *source = RB_SOURCE (object);
 
 	switch (prop_id) {
-	case PROP_NAME:
-		g_free (priv->name);
-		priv->name = g_strdup (g_value_get_string (value));
-		break;
-	case PROP_ICON:
-		rb_source_set_pixbuf (source, g_value_get_object (value));
-		break;
-	case PROP_SHELL:
-		priv->shell = g_value_get_object (value);
-		break;
-	case PROP_VISIBLE:
-		priv->visible = g_value_get_boolean (value);
-		rb_debug ("Setting %s visibility to %u",
-			  priv->name,
-			  priv->visible);
-		break;
 	case PROP_HIDDEN_WHEN_EMPTY:
 		rb_source_set_hidden_when_empty (source, g_value_get_boolean (value));
 		break;
 	case PROP_QUERY_MODEL:
 		rb_source_set_query_model_internal (source, g_value_get_object (value));
 		break;
-	case PROP_SOURCE_GROUP:
-		priv->source_group = g_value_get_boxed (value);
-		break;
 	case PROP_ENTRY_TYPE:
-		priv->entry_type = g_value_get_object (value);
-		break;
-	case PROP_PLUGIN:
-		priv->plugin = g_value_get_object (value);
+		source->priv->entry_type = g_value_get_object (value);
 		break;
 	case PROP_SEARCH_TYPE:
-		priv->search_type = g_value_get_enum (value);
+		source->priv->search_type = g_value_get_enum (value);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -656,54 +428,26 @@ rb_source_get_property (GObject *object,
 			GValue *value,
 			GParamSpec *pspec)
 {
-	RBSourcePrivate *priv = RB_SOURCE_GET_PRIVATE (object);
+	RBSource *source = RB_SOURCE (object);
 
 	switch (prop_id) {
-	case PROP_NAME:
-		g_value_set_string (value, priv->name);
-		break;
-	case PROP_ICON:
-		g_value_set_object (value, priv->pixbuf);
-		break;
-	case PROP_SHELL:
-		g_value_set_object (value, priv->shell);
-		break;
-	case PROP_VISIBLE:
-		g_value_set_boolean (value, priv->visible);
-		break;
-	case PROP_UI_MANAGER:
-	{
-		GtkUIManager *manager;
-		g_object_get (priv->shell,
-			      "ui-manager", &manager,
-			      NULL);
-		g_value_set_object (value, manager);
-		g_object_unref (manager);
-		break;
-	}
 	case PROP_QUERY_MODEL:
-		g_value_set_object (value, priv->query_model);
-		break;
-	case PROP_SOURCE_GROUP:
-		g_value_set_boxed (value, priv->source_group);
+		g_value_set_object (value, source->priv->query_model);
 		break;
 	case PROP_ENTRY_TYPE:
-		g_value_set_object (value, priv->entry_type);
-		break;
-	case PROP_PLUGIN:
-		g_value_set_object (value, priv->plugin);
+		g_value_set_object (value, source->priv->entry_type);
 		break;
 	case PROP_BASE_QUERY_MODEL:
 		/* unless the subclass overrides it, just assume the
 		 * current query model is the base model.
 		 */
-		g_value_set_object (value, priv->query_model);
+		g_value_set_object (value, source->priv->query_model);
 		break;
 	case PROP_PLAY_ORDER:
 		g_value_set_object (value, NULL);		/* ? */
 		break;
 	case PROP_SEARCH_TYPE:
-		g_value_set_enum (value, priv->search_type);
+		g_value_set_enum (value, source->priv->search_type);
 		break;
 	default:
 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -712,52 +456,41 @@ rb_source_get_property (GObject *object,
 }
 
 static void
-default_get_status (RBSource *source,
+default_activate (RBDisplayPage *page)
+{
+	RBShell *shell;
+
+	g_object_get (page, "shell", &shell, NULL);
+	rb_shell_activate_source (shell,
+				  RB_SOURCE (page),
+				  RB_SHELL_ACTIVATION_ALWAYS_PLAY,
+				  NULL);
+}
+
+static void
+default_get_status (RBDisplayPage *page,
 		    char **text,
 		    char **progress_text,
 		    float *progress)
 {
-	RBSourcePrivate *priv = RB_SOURCE_GET_PRIVATE (source);
-
+	RBSource *source = RB_SOURCE (page);
 	/* hack to get these strings marked for translation */
 	if (0) {
 		ngettext ("%d song", "%d songs", 0);
 	}
 
-	if (priv->query_model) {
-		*text = rhythmdb_query_model_compute_status_normal (priv->query_model,
+	if (source->priv->query_model) {
+		*text = rhythmdb_query_model_compute_status_normal (source->priv->query_model,
 								    "%d song",
 								    "%d songs");
-		if (rhythmdb_query_model_has_pending_changes (priv->query_model))
+		if (rhythmdb_query_model_has_pending_changes (source->priv->query_model)) {
 			*progress = -1.0f;
+		}
 	} else {
 		*text = g_strdup ("");
 	}
 }
 
-/**
- * rb_source_get_status:
- * @source: a #RBSource
- * @text: holds the returned status text (allocated)
- * @progress_text: holds the returned text for the progress bar (allocated)
- * @progress: holds the progress value
- *
- * Retrieves the details to display in the status bar for the source.
- * If the progress value returned is less than zero, the progress bar
- * will pulse.  If the progress value is greater than or equal to 1,
- * the progress bar will be hidden.
- **/
-void
-rb_source_get_status (RBSource *source,
-		      char **text,
-		      char **progress_text,
-		      float *progress)
-{
-	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
-
-	klass->impl_get_status (source, text, progress_text, progress);
-}
-
 static char *
 default_get_browser_key (RBSource *source)
 {
@@ -814,19 +547,6 @@ rb_source_browser_toggled (RBSource *source,
 }
 
 /**
- * rb_source_notify_status_changed:
- * @source: a #RBSource
- *
- * Source implementations call this when their status bar information
- * changes.
- */
-void
-rb_source_notify_status_changed (RBSource *source)
-{
-	g_signal_emit (G_OBJECT (source), rb_source_signals[STATUS_CHANGED], 0);
-}
-
-/**
  * rb_source_notify_filter_changed:
  * @source: a #RBSource
  *
@@ -923,6 +643,19 @@ default_can_rename (RBSource *source)
 	return FALSE;
 }
 
+static gboolean
+is_party_mode (RBSource *source)
+{
+	gboolean result = FALSE;
+	RBShell *shell;
+
+	g_object_get (source, "shell", &shell, NULL);
+	result = rb_shell_get_party_mode (shell);
+	g_object_unref (shell);
+
+	return result;
+}
+
 /**
  * rb_source_can_rename:
  * @source: a #RBSource.
@@ -935,13 +668,12 @@ gboolean
 rb_source_can_rename (RBSource *source)
 {
 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
-	RBSourcePrivate *priv = RB_SOURCE_GET_PRIVATE (source);
 
-	if (rb_shell_get_party_mode (priv->shell)) {
+	if (is_party_mode (source)) {
 		return FALSE;
+	} else {
+		return klass->impl_can_rename (source);
 	}
-
-	return klass->impl_can_rename (source);
 }
 
 /**
@@ -964,34 +696,10 @@ rb_source_search (RBSource *source,
 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
 	g_assert (new_text != NULL);
 
-	/* several sources don't have a search ability */
 	if (klass->impl_search != NULL)
 		klass->impl_search (source, search, cur_text, new_text);
 }
 
-/**
- * rb_source_get_config_widget:
- * @source: a #RBSource
- * @prefs: the #RBShellPreferences object
- *
- * Source implementations can use this to return an optional
- * configuration widget. The widget will be displayed in a 
- * page in the preferences dialog.
- *
- * Return value: configuration widget
- */
-GtkWidget *
-rb_source_get_config_widget (RBSource *source,
-			     RBShellPreferences *prefs)
-{
-	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
-
-	if (klass->impl_get_config_widget) {
-		return klass->impl_get_config_widget (source, prefs);
-	} else {
-		return NULL;
-	}
-}
 
 /**
  * rb_source_can_cut:
@@ -1039,13 +747,11 @@ gboolean
 rb_source_can_delete (RBSource *source)
 {
 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
-	RBSourcePrivate *priv = RB_SOURCE_GET_PRIVATE (source);
-
-	if (rb_shell_get_party_mode (priv->shell)) {
+	if (is_party_mode (source)) {
 		return FALSE;
+	} else {
+		return klass->impl_can_delete (source);
 	}
-
-	return klass->impl_can_delete (source);
 }
 
 /**
@@ -1061,13 +767,11 @@ gboolean
 rb_source_can_move_to_trash (RBSource *source)
 {
 	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
-	RBSourcePrivate *priv = RB_SOURCE_GET_PRIVATE (source);
-
-	if (rb_shell_get_party_mode (priv->shell)) {
+	if (is_party_mode (source)) {
 		return FALSE;
+	} else {
+		return klass->impl_can_move_to_trash (source);
 	}
-
-	return klass->impl_can_move_to_trash (source);
 }
 
 /**
@@ -1234,9 +938,8 @@ default_move_to_trash (RBSource *source)
 	GList *sel, *tem;
 	RBEntryView *entry_view;
 	RhythmDB *db;
-	RBSourcePrivate *priv = RB_SOURCE_GET_PRIVATE (source);
 
-	g_object_get (priv->shell, "db", &db, NULL);
+	g_object_get (source->priv->query_model, "db", &db, NULL);
 
 	sel = NULL;
 	entry_view = rb_source_get_entry_view (source);
@@ -1454,176 +1157,12 @@ rb_source_handle_eos (RBSource *source)
 	return klass->impl_handle_eos (source);
 }
 
-/**
- * rb_source_receive_drag:
- * @source: a #RBSource
- * @data: the selection data
- *
- * This is called when the user drags something to the source.
- * Depending on the drag data type, the data might be a list of
- * #RhythmDBEntry objects, a list of URIs, or a list of album
- * or artist or genre names.
- *
- * Return value: TRUE if the source accepted the drag data
- */
-gboolean
-rb_source_receive_drag (RBSource *source,
-			GtkSelectionData *data)
-{
-	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
-
-	if (klass->impl_receive_drag)
-		return klass->impl_receive_drag (source, data);
-	else
-		return FALSE;
-}
-
-/**
- * _rb_source_show_popup:
- * @source: a #RBSource
- * @ui_path: UI path to the popup to show
- *
- * Source implementations can use this as a shortcut to
- * display a popup that has been loaded into the UI manager.
- */
-void
-_rb_source_show_popup (RBSource *source, const char *ui_path)
-{
-	GtkUIManager *uimanager;
-
-	g_object_get (RB_SOURCE_GET_PRIVATE (source)->shell,
-		      "ui-manager", &uimanager, NULL);
-	rb_gtk_action_popup_menu (uimanager, ui_path);
-	g_object_unref (uimanager);
-
-}
-
-static gboolean
-default_show_popup  (RBSource *source)
-{
-	return FALSE;
-}
-
-/**
- * rb_source_show_popup:
- * @source: a #RBSource
- *
- * Called when the user performs an action (such as right-clicking)
- * that should result in a popup menu being displayed for the source.
- *
- * Return value: TRUE if the source managed to display a popup
- */
-gboolean
-rb_source_show_popup (RBSource *source)
-{
-	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
-
-	return klass->impl_show_popup (source);
-}
-
-static void
-default_delete_thyself (RBSource *source)
-{
-}
-
-/**
- * rb_source_delete_thyself:
- * @source: a #RBSource
- *
- * This is called when the source should delete itself.
- * The 'deleted' signal will be emitted, which removes the source
- * from the source list.  This will not actually dispose of the
- * source object, so reference counting must still be handled
- * correctly.
- */
-void
-rb_source_delete_thyself (RBSource *source)
-{
-	RBSourceClass *klass;
-	RBSourcePrivate *priv;
-
-	g_return_if_fail (source != NULL);
-	priv = RB_SOURCE_GET_PRIVATE (source);
-	if (priv->deleted) {
-		rb_debug ("source has already been deleted");
-		return;
-	}
-	priv->deleted = TRUE;
-
-	klass = RB_SOURCE_GET_CLASS (source);
-	klass->impl_delete_thyself (source);
-	g_signal_emit (G_OBJECT (source), rb_source_signals[DELETED], 0);
-}
-
 static RBEntryView*
 default_get_entry_view (RBSource *source)
 {
 	return NULL;
 }
 
-static void
-default_activate (RBSource *source)
-{
-	return;
-}
-
-static void
-default_deactivate (RBSource *source)
-{
-	return;
-}
-
-/**
- * rb_source_activate:
- * @source: a #RBSource
- *
- * Called when the source is selected in the source list.
- */
-void
-rb_source_activate (RBSource *source)
-{
-	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
-
-	klass->impl_activate (source);
-}
-
-/**
- * rb_source_deactivate:
- * @source: a #RBSource
- *
- * Called when the source is deselected in the source list.
- */
-void
-rb_source_deactivate (RBSource *source)
-{
-	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
-
-	klass->impl_deactivate (source);
-}
-
-static GList *
-default_get_ui_actions (RBSource *source)
-{
-	return NULL;
-}
-
-/**
- * rb_source_get_ui_actions:
- * @source: a #RBSource
- *
- * Returns a list of UI action names.  Items for
- * these actions will be added to the toolbar.
- *
- * Return value: list of action names
- */
-GList *
-rb_source_get_ui_actions (RBSource *source)
-{
-	RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
-
-	return klass->impl_get_ui_actions (source);
-}
-
 static GList *
 default_get_search_actions (RBSource *source)
 {
@@ -1675,14 +1214,12 @@ rb_source_get_delete_action (RBSource *source)
 static gboolean
 _update_status_idle (RBSource *source)
 {
-	RBSourcePrivate *priv = RB_SOURCE_GET_PRIVATE (source);
+	_rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
 
-	rb_source_notify_status_changed (source);
-
-	if (priv->hidden_when_empty)
+	if (source->priv->hidden_when_empty)
 		update_visibility_idle (source);
 
-	priv->update_status_id = 0;
+	source->priv->update_status_id = 0;
 	return FALSE;
 }
 
@@ -1692,10 +1229,8 @@ rb_source_row_inserted_cb (GtkTreeModel *model,
 			   GtkTreeIter *iter,
 			   RBSource *source)
 {
-	RBSourcePrivate *priv = RB_SOURCE_GET_PRIVATE (source);
-
-	if (priv->update_status_id == 0)
-		priv->update_status_id = g_idle_add ((GSourceFunc)_update_status_idle, source);
+	if (source->priv->update_status_id == 0)
+		source->priv->update_status_id = g_idle_add ((GSourceFunc)_update_status_idle, source);
 }
 
 static void
@@ -1703,10 +1238,8 @@ rb_source_post_entry_deleted_cb (GtkTreeModel *model,
 				 RhythmDBEntry *entry,
 				 RBSource *source)
 {
-	RBSourcePrivate *priv = RB_SOURCE_GET_PRIVATE (source);
-
-	if (priv->update_status_id == 0)
-		priv->update_status_id = g_idle_add ((GSourceFunc)_update_status_idle, source);
+	if (source->priv->update_status_id == 0)
+		source->priv->update_status_id = g_idle_add ((GSourceFunc)_update_status_idle, source);
 }
 
 static void
@@ -1760,163 +1293,6 @@ rb_source_gather_selected_properties (RBSource *source,
 	return tem;
 }
 
-static GtkActionGroup *
-find_action_group (GtkUIManager *uimanager, const char *group_name)
-{
-	GList *actiongroups;
-	GList *i;
-	actiongroups = gtk_ui_manager_get_action_groups (uimanager);
-
-	/* Don't create the action group if it's already registered */
-	for (i = actiongroups; i != NULL; i = i->next) {
-		const char *name;
-
-		name = gtk_action_group_get_name (GTK_ACTION_GROUP (i->data));
-		if (name != NULL && strcmp (name, group_name) == 0) {
-			return GTK_ACTION_GROUP (i->data);
-		}
-	}
-
-	return NULL;
-}
-
-/**
- * _rb_source_register_action_group:
- * @source: a #RBSource
- * @group_name: action group name
- * @actions: array of GtkActionEntry structures for the action group
- * @num_actions: number of actions in the @actions array
- * @user_data: user data to use for action signal handlers
- *
- * Creates and registers a GtkActionGroup for the source.
- *
- * Return value: the created action group
- */
-GtkActionGroup *
-_rb_source_register_action_group (RBSource *source,
-				  const char *group_name,
-				  GtkActionEntry *actions,
-				  int num_actions,
-				  gpointer user_data)
-{
-	GtkUIManager *uimanager;
-	GtkActionGroup *group;
-
-	g_return_val_if_fail (group_name != NULL, NULL);
-
-	g_object_get (source, "ui-manager", &uimanager, NULL);
-	group = find_action_group (uimanager, group_name);
-	if (group == NULL) {
-		group = gtk_action_group_new (group_name);
-		gtk_action_group_set_translation_domain (group,
-							 GETTEXT_PACKAGE);
-		if (actions != NULL) {
-			gtk_action_group_add_actions (group,
-						      actions, num_actions,
-						      user_data);
-		}
-		gtk_ui_manager_insert_action_group (uimanager, group, 0);
-	} else {
-		g_object_ref (group);
-	}
-	g_object_unref (uimanager);
-
-	return group;
-}
-
-typedef void (*SourceActionCallback) (GtkAction *action, RBSource *source);
-
-typedef struct {
-	SourceActionCallback callback;
-	/*RBShell *shell;*/
-	gpointer shell;
-} SourceActionData;
-
-static void
-source_action_data_destroy (SourceActionData *data)
-{
-	if (data->shell != NULL) {
-		g_object_remove_weak_pointer (G_OBJECT (data->shell), &data->shell);
-	}
-	g_slice_free (SourceActionData, data);
-}
-
-static void
-source_action_cb (GtkAction *action, SourceActionData *data)
-{
-	RBSource *source;
-
-	if (data->shell == NULL) {
-		return;
-	}
-
-	/* get current source */
-	g_object_get (G_OBJECT (data->shell), "selected-source", &source, NULL);
-	if (source != NULL) {
-		data->callback (action, source);
-		g_object_unref (source);
-	}
-}
-
-/**
- * _rb_action_group_add_source_actions:
- * @group: a #GtkActionGroup
- * @shell: the #RBShell
- * @actions: array of GtkActionEntry structures for the action group
- * @num_actions: number of actions in the @actions array
- *
- * Adds actions to an action group where the action callback is
- * called with the current selected source.  This can safely be called
- * multiple times on the same action group.
- */
-void
-_rb_action_group_add_source_actions (GtkActionGroup *group,
-				     GObject *shell,
-				     GtkActionEntry *actions,
-				     int num_actions)
-{
-	int i;
-	for (i = 0; i < num_actions; i++) {
-		GtkAction *action;
-		const char *label;
-		const char *tooltip;
-		SourceActionData *source_action_data;
-
-		if (gtk_action_group_get_action (group, actions[i].name) != NULL) {
-			/* action was already added */
-			continue;
-		}
-
-		label = gtk_action_group_translate_string (group, actions[i].label);
-		tooltip = gtk_action_group_translate_string (group, actions[i].tooltip);
-
-		action = gtk_action_new (actions[i].name, label, tooltip, NULL);
-		if (actions[i].stock_id != NULL) {
-			g_object_set (action, "stock-id", actions[i].stock_id, NULL);
-			if (gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
-						     actions[i].stock_id)) {
-				g_object_set (action, "icon-name", actions[i].stock_id, NULL);
-			}
-		}
-
-		if (actions[i].callback) {
-			GClosure *closure;
-			source_action_data = g_slice_new0 (SourceActionData);
-			source_action_data->callback = (SourceActionCallback) actions[i].callback;
-			source_action_data->shell = shell;
-			g_object_add_weak_pointer (shell, &source_action_data->shell);
-
-			closure = g_cclosure_new (G_CALLBACK (source_action_cb),
-						  source_action_data,
-						  (GClosureNotify) source_action_data_destroy);
-			g_signal_connect_closure (action, "activate", closure, FALSE);
-		}
-
-		gtk_action_group_add_action_with_accel (group, action, actions[i].accelerator);
-		g_object_unref (action);
-	}
-}
-
 /**
  * _rb_source_check_entry_type:
  * @source: a #RBSource
@@ -2007,4 +1383,3 @@ rb_source_search_type_get_type (void)
 
 	return etype;
 }
-
diff --git a/sources/rb-source.h b/sources/rb-source.h
index 6a035ee..f7d0ff8 100644
--- a/sources/rb-source.h
+++ b/sources/rb-source.h
@@ -31,7 +31,7 @@
 
 #include <gtk/gtk.h>
 
-#include <sources/rb-source-group.h>
+#include <sources/rb-display-page.h>
 #include <sources/rb-source-search.h>
 #include <widgets/rb-entry-view.h>
 #include <shell/rb-shell-preferences.h>
@@ -80,21 +80,18 @@ typedef void (*RBSourceAddCallback) (RBSource *source, const char *uri, gpointer
 
 struct _RBSource
 {
-	GtkHBox parent;
+	RBDisplayPage parent;
+	RBSourcePrivate *priv;
 };
 
 struct _RBSourceClass
 {
-	GtkHBoxClass parent;
+	RBDisplayPageClass parent;
 
 	/* signals */
-	void (*status_changed)	(RBSource *source);
 	void (*filter_changed)	(RBSource *source);
-	void (*deleted)		(RBSource *source);
-	void (*artistalbum_changed)	(RBSource *source);
 
 	/* methods */
-	void		(*impl_get_status)	(RBSource *source, char **text, char **progress_text, float *progress);
 
 	gboolean	(*impl_can_browse)	(RBSource *source);
 	char *		(*impl_get_browser_key)	(RBSource *source);
@@ -107,7 +104,6 @@ struct _RBSourceClass
 
 	void		(*impl_search)		(RBSource *source, RBSourceSearch *search, const char *cur_text, const char *new_text);
 	void		(*impl_reset_filters)	(RBSource *source);
-	GtkWidget *	(*impl_get_config_widget)(RBSource *source, RBShellPreferences *prefs);
 
 	gboolean	(*impl_can_cut)		(RBSource *source);
 	gboolean	(*impl_can_delete)	(RBSource *source);
@@ -140,13 +136,8 @@ struct _RBSourceClass
 	RBSourceEOFType	(*impl_handle_eos)	(RBSource *source);
 
 	gboolean	(*impl_have_url)	(RBSource *source);
-	gboolean	(*impl_receive_drag)	(RBSource *source, GtkSelectionData *data);
-	gboolean	(*impl_show_popup)	(RBSource *source);
 
 	void		(*impl_delete_thyself)	(RBSource *source);
-	void		(*impl_activate)	(RBSource *source);
-	void		(*impl_deactivate)	(RBSource *source);
-	GList *		(*impl_get_ui_actions)	(RBSource *source);
 	GList *		(*impl_get_search_actions) (RBSource *source);
 	char *		(*impl_get_delete_action) (RBSource *source);
 };
@@ -155,14 +146,10 @@ GType		rb_source_get_type		(void);
 
 void		rb_source_notify_filter_changed	(RBSource *source);
 
-void		rb_source_notify_status_changed (RBSource *source);
-
 void		rb_source_update_play_statistics(RBSource *source, RhythmDB *db,
 						 RhythmDBEntry *entry);
 
 /* general interface */
-void	        rb_source_set_pixbuf		(RBSource *source, GdkPixbuf *pixbuf);
-void	        rb_source_get_status		(RBSource *source, char **text, char **progress_text, float *progress);
 
 gboolean	rb_source_can_browse		(RBSource *source);
 char *		rb_source_get_browser_key	(RBSource *source);
@@ -181,8 +168,6 @@ void		rb_source_search		(RBSource *source,
 
 void		rb_source_reset_filters		(RBSource *source);
 
-GtkWidget *	rb_source_get_config_widget	(RBSource *source, RBShellPreferences *prefs);
-
 gboolean	rb_source_can_cut		(RBSource *source);
 gboolean	rb_source_can_delete		(RBSource *source);
 gboolean	rb_source_can_move_to_trash	(RBSource *source);
@@ -214,16 +199,6 @@ void		rb_source_add_uri		(RBSource *source,
 gboolean	rb_source_can_pause		(RBSource *source);
 RBSourceEOFType	rb_source_handle_eos		(RBSource *source);
 
-gboolean	rb_source_receive_drag		(RBSource *source, GtkSelectionData *data);
-
-gboolean	rb_source_show_popup		(RBSource *source);
-
-void		rb_source_delete_thyself	(RBSource *source);
-
-void		rb_source_activate		(RBSource *source);
-void		rb_source_deactivate		(RBSource *source);
-
-GList *		rb_source_get_ui_actions	(RBSource *source);
 GList *		rb_source_get_search_actions	(RBSource *source);
 char *		rb_source_get_delete_action	(RBSource *source);
 
@@ -233,17 +208,6 @@ void            rb_source_set_hidden_when_empty (RBSource *source,
                                                  gboolean  hidden);
 
 /* Protected methods, should only be used by objects inheriting from RBSource */
-void            _rb_source_show_popup           (RBSource *source,
-						 const char *ui_path);
-GtkActionGroup *_rb_source_register_action_group (RBSource *source,
-						  const char *group_name,
-						  GtkActionEntry *actions,
-						  int num_actions,
-						  gpointer user_data);
-void		_rb_action_group_add_source_actions (GtkActionGroup *group,
-						     GObject *shell,
-						     GtkActionEntry *actions,
-						     int num_actions);
 
 gboolean	_rb_source_check_entry_type	(RBSource *source,
 						 RhythmDBEntry *entry);
diff --git a/sources/rb-static-playlist-source.c b/sources/rb-static-playlist-source.c
index 21ff166..aafe50b 100644
--- a/sources/rb-static-playlist-source.c
+++ b/sources/rb-static-playlist-source.c
@@ -78,7 +78,7 @@ 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_browser_toggled (RBSource *source, gboolean enabled);
 static void impl_reset_filters (RBSource *asource);
-static gboolean impl_receive_drag (RBSource *source, GtkSelectionData *data);
+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);
 
@@ -160,6 +160,7 @@ static void
 rb_static_playlist_source_class_init (RBStaticPlaylistSourceClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
 	RBPlaylistSourceClass *playlist_class = RB_PLAYLIST_SOURCE_CLASS (klass);
 
@@ -169,13 +170,14 @@ rb_static_playlist_source_class_init (RBStaticPlaylistSourceClass *klass)
 	object_class->set_property = rb_static_playlist_source_set_property;
 	object_class->get_property = rb_static_playlist_source_get_property;
 
+	page_class->receive_drag = impl_receive_drag;
+
 	source_class->impl_can_cut = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_can_paste = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
 	source_class->impl_cut = impl_cut;
 	source_class->impl_paste = impl_paste;
 	source_class->impl_delete = impl_delete;
-	source_class->impl_receive_drag = impl_receive_drag;
 	source_class->impl_search = impl_search;
 	source_class->impl_reset_filters = impl_reset_filters;
 	source_class->impl_can_browse = (RBSourceFeatureFunc) rb_true_function;
@@ -207,13 +209,13 @@ rb_static_playlist_source_init (RBStaticPlaylistSource *source)
 			g_object_add_weak_pointer (playlist_pixbuf,
 					   (gpointer *) &playlist_pixbuf);
 
-			rb_source_set_pixbuf (RB_SOURCE (source), playlist_pixbuf);
+			g_object_set (source, "pixbuf", playlist_pixbuf, NULL);
 
 			/* drop the initial reference to the icon */
 			g_object_unref (playlist_pixbuf);
 		}
 	} else {
-		rb_source_set_pixbuf (RB_SOURCE (source), playlist_pixbuf);
+		g_object_set (source, "pixbuf", playlist_pixbuf, NULL);
 	}
 }
 
@@ -297,10 +299,10 @@ rb_static_playlist_source_constructed (GObject *object)
 	priv->paned = gtk_vpaned_new ();
 
 	g_object_get (source, "shell", &shell, NULL);
-	priv->action_group = _rb_source_register_action_group (RB_SOURCE (source),
-							       "StaticPlaylistActions",
-							       NULL, 0,
-							       shell);
+	priv->action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
+								     "StaticPlaylistActions",
+								     NULL, 0,
+								     shell);
 	if (gtk_action_group_get_action (priv->action_group,
 					 rb_static_playlist_source_radio_actions[0].name) == NULL) {
 		gtk_action_group_add_radio_actions (priv->action_group,
@@ -381,7 +383,6 @@ rb_static_playlist_source_new (RBShell *shell, const char *name, const char *sor
 					"shell", shell,
 					"is-local", local,
 					"entry-type", entry_type,
-					"source-group", RB_SOURCE_GROUP_PLAYLISTS,
 					"search-type", RB_SOURCE_SEARCH_INCREMENTAL,
 					NULL));
 }
@@ -635,11 +636,11 @@ rb_static_playlist_source_browser_changed_cb (RBLibraryBrowser *browser,
 }
 
 static gboolean
-impl_receive_drag (RBSource *asource, GtkSelectionData *data)
+impl_receive_drag (RBDisplayPage *page, GtkSelectionData *data)
 {
 	GdkAtom type;
 	GList *list;
-	RBStaticPlaylistSource *source = RB_STATIC_PLAYLIST_SOURCE (asource);
+	RBStaticPlaylistSource *source = RB_STATIC_PLAYLIST_SOURCE (page);
 
 	type = gtk_selection_data_get_data_type (data);
 
diff --git a/sources/rb-streaming-source.c b/sources/rb-streaming-source.c
index 4bbee36..46c400b 100644
--- a/sources/rb-streaming-source.c
+++ b/sources/rb-streaming-source.c
@@ -234,7 +234,7 @@ buffering_cb (GObject *backend, gpointer whatever, guint progress, RBStreamingSo
 
 	GDK_THREADS_ENTER ();
 	source->priv->buffering = progress;
-	rb_source_notify_status_changed (RB_SOURCE (source));
+	_rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
 	GDK_THREADS_LEAVE ();
 }
 
@@ -485,7 +485,7 @@ playing_entry_changed_cb (RBShellPlayer *player,
 						     source->priv->buffering_id);
 			source->priv->buffering_id = 0;
 
-			rb_source_notify_status_changed (RB_SOURCE (source));
+			_rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
 		}
 	} else {
 		rb_debug ("playing new stream; resetting buffering");
@@ -499,7 +499,7 @@ playing_entry_changed_cb (RBShellPlayer *player,
 		source->priv->buffering = -1;
 
 		source->priv->playing_stream = rhythmdb_entry_ref (entry);
-		rb_source_notify_status_changed (RB_SOURCE (source));
+		_rb_display_page_notify_status_changed (RB_DISPLAY_PAGE (source));
 	}
 
 	g_object_unref (backend);
diff --git a/widgets/gossip-cell-renderer-expander.c b/widgets/gossip-cell-renderer-expander.c
index 0258d29..ce96e56 100644
--- a/widgets/gossip-cell-renderer-expander.c
+++ b/widgets/gossip-cell-renderer-expander.c
@@ -417,17 +417,30 @@ animation_timeout (gpointer data)
 	return retval;
 }
 
-static void
+void
 gossip_cell_renderer_expander_start_animation (GossipCellRendererExpander *expander,
 					       GtkTreeView                *tree_view,
 					       GtkTreePath                *path,
-					       gboolean                    expanding,
-					       GdkRectangle               *background_area)
+					       gboolean                    expanding)
 {
 	GossipCellRendererExpanderPriv *priv;
+	GtkSettings                    *settings;
+	gboolean                        animate;
 
-	priv = GET_PRIV (expander);
+	settings = gtk_widget_get_settings (GTK_WIDGET (tree_view));
+	if (g_object_class_find_property (G_OBJECT_GET_CLASS (settings), "gtk-enable-animations")) {
+		g_object_get (settings,
+			      "gtk-enable-animations", &animate,
+			      NULL);
+	} else {
+		animate = FALSE;
+	}
+
+	if (animate == FALSE) {
+		return;
+	}
 
+	priv = GET_PRIV (expander);
 	if (expanding) {
 		priv->animation_style = GTK_EXPANDER_SEMI_COLLAPSED;
 	} else {
@@ -436,6 +449,11 @@ gossip_cell_renderer_expander_start_animation (GossipCellRendererExpander *expan
 
 	invalidate_node (tree_view, path);
 
+	if (priv->animation_timeout != 0) {
+		g_source_remove (priv->animation_timeout);
+		gtk_tree_row_reference_free (priv->animation_node);
+	}
+
 	priv->animation_expanding = expanding;
 	priv->animation_view = tree_view;
 	priv->animation_node = gtk_tree_row_reference_new (gtk_tree_view_get_model (tree_view), path);
@@ -454,8 +472,6 @@ gossip_cell_renderer_expander_activate (GtkCellRenderer      *cell,
 	GossipCellRendererExpander     *expander;
 	GossipCellRendererExpanderPriv *priv;
 	GtkTreePath                    *path;
-	GtkSettings                    *settings;
-	gboolean                        animate;
 	gboolean                        expanding;
 	gboolean                        in_cell;
 	int                             mouse_x;
@@ -483,6 +499,7 @@ gossip_cell_renderer_expander_activate (GtkCellRenderer      *cell,
 	}
 
 	if (! in_cell) {
+		gtk_tree_path_free (path);
 		return FALSE;
 	}
 
@@ -493,15 +510,6 @@ gossip_cell_renderer_expander_activate (GtkCellRenderer      *cell,
 	}
 #endif
 
-	settings = gtk_widget_get_settings (GTK_WIDGET (widget));
-	if (g_object_class_find_property (G_OBJECT_GET_CLASS (settings), "gtk-enable-animations")) {
-		g_object_get (settings,
-			      "gtk-enable-animations", &animate,
-			      NULL);
-	} else {
-		animate = FALSE;
-	}
-
 	if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
 		gtk_tree_view_collapse_row (GTK_TREE_VIEW (widget), path);
 		expanding = FALSE;
@@ -510,14 +518,10 @@ gossip_cell_renderer_expander_activate (GtkCellRenderer      *cell,
 		expanding = TRUE;
 	}
 
-	if (animate) {
-		gossip_cell_renderer_expander_start_animation (expander,
-							       GTK_TREE_VIEW (widget),
-							       path,
-							       expanding,
-							       background_area);
-	}
-
+	gossip_cell_renderer_expander_start_animation (expander,
+						       GTK_TREE_VIEW (widget),
+						       path,
+						       expanding);
 	gtk_tree_path_free (path);
 
 	return TRUE;
diff --git a/widgets/gossip-cell-renderer-expander.h b/widgets/gossip-cell-renderer-expander.h
index 79cdbcf..692bffb 100644
--- a/widgets/gossip-cell-renderer-expander.h
+++ b/widgets/gossip-cell-renderer-expander.h
@@ -49,8 +49,13 @@ struct _GossipCellRendererExpanderClass {
   void (*_gtk_reserved4) (void);
 };
 
-GType            gossip_cell_renderer_expander_get_type (void) G_GNUC_CONST;
-GtkCellRenderer *gossip_cell_renderer_expander_new      (void);
+GType            gossip_cell_renderer_expander_get_type 	(void) G_GNUC_CONST;
+GtkCellRenderer *gossip_cell_renderer_expander_new      	(void);
+
+void		 gossip_cell_renderer_expander_start_animation  (GossipCellRendererExpander *expander,
+								 GtkTreeView *widget,
+								 GtkTreePath *path,
+								 gboolean expanding);
 
 G_END_DECLS
 



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