[rhythmbox] Convert everything to GMenu and add an app menu



commit ce7fa7ff226b67d37094d1167d3cd0cc883d3103
Author: Jonathan Matthew <jonathan d14n org>
Date:   Tue Feb 26 08:05:49 2013 +1000

    Convert everything to GMenu and add an app menu
    
    Most significantly, this removes the menu bar, scattering its contents
    into an application menu, additional menus added to source toolbars,
    and a toolbar in the source list.
    
    The playlist related parts of the music menu are now in the source list
    toolbar.  The rest formed the basis of the app menu.
    
    Most sources now have an edit menu that corresponds roughly to the
    edit menu from the menu bar.
    
    The view and tools menus are now part of the app menu.
    
    The control menu is gone, since it didn't do anything that wasn't already
    represented in the controls in the main toolbar.
    
    The help menu is now part of the app menu.

 bindings/gi/Makefile.am                            |    4 +
 configure.ac                                       |    2 +-
 data/org.gnome.rhythmbox.gschema.xml               |   14 +-
 data/ui/Makefile.am                                |   22 +-
 data/ui/app-menu.ui                                |   80 +
 data/ui/browser-popup.ui                           |   57 +
 data/ui/display-page-add-menu.ui                   |   31 +
 data/ui/edit-menu.ui                               |   57 +
 data/ui/import-errors-popup.ui                     |   15 +
 data/ui/library-toolbar.ui                         |   28 +
 data/ui/main-toolbar.ui                            |  114 +
 data/ui/missing-files-popup.ui                     |   15 +
 data/ui/playlist-menu.ui                           |   34 +
 data/ui/playlist-popup.ui                          |   57 +
 data/ui/playlist-toolbar.ui                        |   27 +
 data/ui/podcast-add-dialog.ui                      |    3 -
 data/ui/podcast-popups.ui                          |   65 +
 data/ui/podcast-toolbar.ui                         |   31 +
 data/ui/queue-popups.ui                            |   57 +
 data/ui/queue-toolbar.ui                           |   26 +
 data/ui/rhythmbox-ui.xml                           |  326 --
 doc/reference/Makefile.am                          |    2 -
 doc/reference/rhythmbox-docs.sgml                  |    1 +
 doc/reference/rhythmbox.types                      |    2 +
 help/C/index.docbook                               |   33 -
 lib/Makefile.am                                    |    4 -
 lib/eggsmclient-private.h                          |   53 -
 lib/eggsmclient-xsmp.c                             | 1371 -----
 lib/eggsmclient.c                                  |  604 --
 lib/eggsmclient.h                                  |  117 -
 lib/rb-builder-helpers.c                           |   28 +-
 lib/rb-builder-helpers.h                           |    2 +
 lib/rb-util.c                                      |   67 +-
 lib/rb-util.h                                      |    4 +-
 plugins/audiocd/Makefile.am                        |    8 +-
 plugins/audiocd/audiocd-toolbar.ui                 |   26 +
 plugins/audiocd/audiocd-ui.xml                     |   14 -
 plugins/audiocd/rb-audiocd-plugin.c                |   26 -
 plugins/audiocd/rb-audiocd-source.c                |   86 +-
 .../audioscrobbler/audioscrobbler-profile-ui.xml   |    5 -
 plugins/audioscrobbler/audioscrobbler-radio-ui.xml |    6 -
 .../rb-audioscrobbler-profile-page.c               |  224 +-
 .../rb-audioscrobbler-radio-source.c               |   97 +-
 .../rb-disc-recorder-plugin.c                      |  156 +-
 plugins/context/ContextView.py                     |   48 +-
 plugins/daap/Makefile.am                           |    5 +-
 plugins/daap/daap-toolbar.ui                       |   24 +
 plugins/daap/daap-ui.xml                           |   25 -
 plugins/daap/rb-daap-plugin.c                      |   84 +-
 plugins/daap/rb-daap-source.c                      |   43 +-
 plugins/fmradio/Makefile.am                        |    6 +-
 plugins/fmradio/fmradio-popup.ui                   |   17 +
 plugins/fmradio/fmradio-toolbar.ui                 |   15 +
 plugins/fmradio/fmradio-ui.xml                     |   23 -
 plugins/fmradio/rb-fm-radio-plugin.c               |   29 +-
 plugins/fmradio/rb-fm-radio-source.c               |   99 +-
 plugins/fmradio/rb-fm-radio-source.h               |    3 +-
 plugins/generic-player/Makefile.am                 |    7 +-
 plugins/generic-player/generic-player-toolbar.ui   |   32 +
 plugins/generic-player/generic-player-ui.xml       |   26 -
 .../rb-generic-player-playlist-source.c            |   31 +-
 .../rb-generic-player-playlist-source.h            |    4 +-
 plugins/generic-player/rb-generic-player-plugin.c  |   97 -
 plugins/generic-player/rb-generic-player-source.c  |  116 +-
 plugins/ipod/Makefile.am                           |    6 +-
 plugins/ipod/ipod-toolbar.ui                       |   32 +
 plugins/ipod/ipod-ui.xml                           |   23 -
 plugins/ipod/rb-ipod-plugin.c                      |  119 -
 plugins/ipod/rb-ipod-source.c                      |  154 +-
 plugins/ipod/rb-ipod-source.h                      |    1 -
 plugins/ipod/rb-ipod-static-playlist-source.c      |   29 +-
 plugins/ipod/rb-ipod-static-playlist-source.h      |    3 +-
 plugins/iradio/Makefile.am                         |    7 +-
 plugins/iradio/iradio-popup.ui                     |   17 +
 plugins/iradio/iradio-toolbar.ui                   |   28 +
 plugins/iradio/iradio-ui.xml                       |   25 -
 plugins/iradio/rb-iradio-plugin.c                  |   26 -
 plugins/iradio/rb-iradio-source.c                  |   99 +-
 plugins/lyrics/lyrics.py                           |   45 +-
 plugins/magnatune/MagnatuneSource.py               |   37 +-
 plugins/magnatune/Makefile.am                      |    2 +
 plugins/magnatune/magnatune-popup.ui               |   43 +
 plugins/magnatune/magnatune-toolbar.ui             |   28 +
 plugins/magnatune/magnatune.py                     |   88 +-
 plugins/mtpdevice/Makefile.am                      |    8 +-
 plugins/mtpdevice/mtp-toolbar.ui                   |   32 +
 plugins/mtpdevice/mtp-ui.xml                       |   18 -
 plugins/mtpdevice/rb-mtp-plugin.c                  |  104 +-
 plugins/mtpdevice/rb-mtp-source.c                  |   21 +-
 plugins/power-manager/rb-power-manager-plugin.c    |  149 +-
 plugins/pythonconsole/pythonconsole.py             |   72 +-
 plugins/sendto/sendto.py                           |   55 +-
 plugins/visualizer/rb-visualizer-menu.c            |  111 +-
 plugins/visualizer/rb-visualizer-menu.h            |    2 +-
 plugins/visualizer/rb-visualizer-page.c            |   31 +-
 plugins/visualizer/rb-visualizer-page.h            |    8 +-
 plugins/visualizer/rb-visualizer-plugin.c          |   10 +-
 plugins/visualizer/visualizer-ui.xml               |   13 -
 po/POTFILES.in                                     |   28 +-
 podcast/rb-podcast-main-source.c                   |   10 +-
 podcast/rb-podcast-source.c                        |  319 +-
 remote/dbus/rb-client.c                            |   16 +-
 rhythmdb/rhythmdb-entry-type.c                     |   22 +-
 rhythmdb/rhythmdb-song-entry-types.c               |    1 -
 shell/Makefile.am                                  |    2 +
 shell/main.c                                       |   31 +-
 shell/rb-application.c                             |  824 +++
 shell/rb-application.h                             |   73 +
 shell/rb-playlist-manager.c                        |  396 +-
 shell/rb-playlist-manager.h                        |    4 +-
 shell/rb-removable-media-manager.c                 |  160 +-
 shell/rb-shell-clipboard.c                         |  725 +--
 shell/rb-shell-clipboard.h                         |    4 +-
 shell/rb-shell-player.c                            | 5814 ++++++++++----------
 shell/rb-shell-player.h                            |    4 +-
 shell/rb-shell.c                                   | 1085 +---
 shell/rb-shell.h                                   |    2 -
 shell/rb-statusbar.c                               |  109 -
 shell/rb-statusbar.h                               |    1 -
 sources/Makefile.am                                |    2 +
 sources/rb-auto-playlist-source.c                  |  119 +-
 sources/rb-auto-playlist-source.h                  |    4 -
 sources/rb-browser-source.c                        |  182 +-
 sources/rb-display-page-menu.c                     |  459 ++
 sources/rb-display-page-menu.h                     |   71 +
 sources/rb-display-page-tree.c                     |  294 +-
 sources/rb-display-page-tree.h                     |    4 +-
 sources/rb-display-page.c                          |  261 +-
 sources/rb-display-page.h                          |   22 +-
 sources/rb-import-errors-source.c                  |   26 +-
 sources/rb-library-source.c                        |   58 +-
 sources/rb-media-player-source.c                   |  120 +-
 sources/rb-media-player-source.h                   |    2 -
 sources/rb-missing-files-source.c                  |   28 +-
 sources/rb-play-queue-source.c                     |  237 +-
 sources/rb-play-queue-source.h                     |    3 +-
 sources/rb-playlist-source.c                       |   95 +-
 sources/rb-source-search-basic.c                   |  167 +-
 sources/rb-source-search-basic.h                   |   15 +-
 sources/rb-source-search.c                         |   91 +-
 sources/rb-source-search.h                         |   12 +
 sources/rb-source.c                                |   86 +-
 sources/rb-source.h                                |    6 +-
 sources/rb-static-playlist-source.c                |  130 +-
 sources/rb-static-playlist-source.h                |    4 -
 widgets/Makefile.am                                |    6 +-
 widgets/rb-button-bar.c                            |  376 ++
 widgets/rb-button-bar.h                            |   67 +
 widgets/rb-header.c                                |   54 +
 widgets/rb-import-dialog.c                         |    2 +-
 widgets/rb-source-toolbar.c                        |  357 +-
 widgets/rb-source-toolbar.h                        |    9 +-
 152 files changed, 8752 insertions(+), 10428 deletions(-)
---
diff --git a/bindings/gi/Makefile.am b/bindings/gi/Makefile.am
index 065907a..7194c61 100644
--- a/bindings/gi/Makefile.am
+++ b/bindings/gi/Makefile.am
@@ -79,6 +79,8 @@ rb_introspection_sources = \
                rhythmdb/rhythmdb-song-entry-types.c \
                rhythmdb/rb-refstring.h \
                rhythmdb/rb-refstring.c \
+               shell/rb-application.h \
+               shell/rb-application.c \
                shell/rb-shell.h \
                shell/rb-shell.c \
                shell/rb-shell-player.h \
@@ -128,6 +130,8 @@ rb_introspection_sources = \
                sources/rb-device-source.c \
                sources/rb-transfer-target.h \
                sources/rb-transfer-target.c \
+               widgets/rb-button-bar.h \
+               widgets/rb-button-bar.c \
                widgets/rb-entry-view.h \
                widgets/rb-entry-view.c \
                widgets/rb-property-view.h \
diff --git a/configure.ac b/configure.ac
index 9e27d1d..f739a19 100644
--- a/configure.ac
+++ b/configure.ac
@@ -44,7 +44,7 @@ m4_ifdef([LT_OUTPUT], [LT_OUTPUT])
 AC_C_BIGENDIAN
 AC_CHECK_SIZEOF(long)
 
-GTK_REQS=3.4.0
+GTK_REQS=3.6.0
 
 GST_REQS=0.11.92
 GDK_PIXBUF_REQS=2.18.0
diff --git a/data/org.gnome.rhythmbox.gschema.xml b/data/org.gnome.rhythmbox.gschema.xml
index 9c1e4bf..649ffc6 100644
--- a/data/org.gnome.rhythmbox.gschema.xml
+++ b/data/org.gnome.rhythmbox.gschema.xml
@@ -16,6 +16,11 @@
       <summary>Position of browser pane (if it exists)</summary>
       <description>Position of browser pane.</description>
     </key>
+    <key name="search-type" type="s">
+      <default>'search-match'</default>
+      <summary>Selected search type</summary>
+      <description>The currently selected search type for the source.</description>
+    </key>
   </schema>
 
   <schema id="org.gnome.rhythmbox.encoding-settings">
@@ -70,10 +75,10 @@
       <summary>Position of the right pane</summary>
       <description>Position of the right pane</description>
     </key>
-    <key name="statusbar-hidden" type="b">
-      <default>false</default>
+    <key name="statusbar-visible" type="b">
+      <default>true</default>
       <summary>Statusbar visibility</summary>
-      <description>If true, the statusbar is hidden.</description>
+      <description>If true, the statusbar is visible.</description>
     </key>
     <key name="queue-as-sidebar" type="b">
       <default>false</default>
@@ -174,6 +179,7 @@
     <override name="sorting">('Feed',true)</override>
     <override name="paned-position">180</override>
     <override name="show-browser">true</override>
+    <override name="search-type">'search-match'</override>
   </schema>
   <schema id="org.gnome.rhythmbox.podcast" path="/org/gnome/rhythmbox/podcast/">
 
@@ -409,7 +415,7 @@
       <summary>GStreamer element to use for visual effects</summary>
       <description>The name of the GStreamer element to use for visual effects.</description>
     </key>
-    <key name="quality" enum="org.gnome.rhythmbox.plugins.visualizer.quality">
+    <key name="vis-quality" enum="org.gnome.rhythmbox.plugins.visualizer.quality">
       <default>'medium'</default>
       <summary>The frame rate and size to use for visual effects</summary>
       <description>The frame rate and size to use for visual effects</description>
diff --git a/data/ui/Makefile.am b/data/ui/Makefile.am
index 75bfa00..c4727de 100644
--- a/data/ui/Makefile.am
+++ b/data/ui/Makefile.am
@@ -1,19 +1,31 @@
 
-
-UI_XML_FILES = rhythmbox-ui.xml
-
 GTK_BUILDER_FILES =                                    \
+       app-menu.ui                                     \
+       browser-popup.ui                                \
        create-playlist.ui                              \
+       display-page-add-menu.ui                        \
+       edit-menu.ui                                    \
        general-prefs.ui                                \
        import-dialog.ui                                \
+       import-errors-popup.ui                          \
        library-prefs.ui                                \
+       library-toolbar.ui                              \
+       main-toolbar.ui                                 \
        media-player-properties.ui                      \
+       missing-files-popup.ui                          \
        playback-prefs.ui                               \
+       playlist-menu.ui                                \
+       playlist-popup.ui                               \
        playlist-save.ui                                \
+       playlist-toolbar.ui                             \
        podcast-add-dialog.ui                           \
        podcast-feed-properties.ui                      \
+       podcast-popups.ui                               \
        podcast-prefs.ui                                \
        podcast-properties.ui                           \
+       podcast-toolbar.ui                              \
+       queue-popups.ui                                 \
+       queue-toolbar.ui                                \
        song-info.ui                                    \
        song-info-multiple.ui                           \
        sync-dialog.ui                                  \
@@ -21,7 +33,7 @@ GTK_BUILDER_FILES =                                   \
        uri-new.ui
 
 uidir = $(pkgdatadir)
-ui_DATA = $(UI_XML_FILES) $(GTK_BUILDER_FILES)
+ui_DATA = $(GTK_BUILDER_FILES)
 
-EXTRA_DIST = $(UI_XML_FILES) $(GTK_BUILDER_FILES)
+EXTRA_DIST = $(GTK_BUILDER_FILES)
 
diff --git a/data/ui/app-menu.ui b/data/ui/app-menu.ui
new file mode 100644
index 0000000..cdbfeca
--- /dev/null
+++ b/data/ui/app-menu.ui
@@ -0,0 +1,80 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="app-menu">
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">_Add Music</attribute>
+       <attribute name="action">app.library-import</attribute>
+      </item>
+    </section>
+    <section>
+      <submenu>
+       <attribute name="label" translatable="yes">_View</attribute>
+       <section>
+         <item>
+           <attribute name="label" translatable="yes">P_arty Mode</attribute>
+           <attribute name="action">win.party-mode</attribute>
+           <attribute name="accel">F11</attribute>
+         </item>
+         <item>
+           <attribute name="label" translatable="yes">Side Pane</attribute>
+           <attribute name="action">win.display-page-tree-visible</attribute>
+           <attribute name="accel">F9</attribute>
+         </item>
+         <item>
+           <attribute name="label" translatable="yes">Play Queue in Side Pane</attribute>
+           <attribute name="action">win.queue-as-sidebar</attribute>
+           <attribute name="accel">&lt;Primary&gt;k</attribute>
+         </item>
+         <item>
+           <attribute name="label" translatable="yes">Status Bar</attribute>
+           <attribute name="action">win.statusbar-visible</attribute>
+         </item>
+         <item>
+           <attribute name="label" translatable="yes">Song Position Slider</attribute>
+           <attribute name="action">win.show-song-position-slider</attribute>
+         </item>
+         <item>
+           <attribute name="label" translatable="yes">Album Art</attribute>
+           <attribute name="action">win.show-album-art</attribute>
+         </item>
+       </section>
+       <section>
+         <attribute name="rb-plugin-menu-link">view</attribute>
+       </section>
+      </submenu>
+      <submenu>
+       <attribute name="label" translatable="yes">_Tools</attribute>
+       <attribute name="rb-plugin-menu-link">tools</attribute>
+      </submenu>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">P_lugins</attribute>
+       <attribute name="action">app.plugins</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">_Preferences</attribute>
+       <attribute name="action">app.preferences</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">_Help</attribute>
+       <attribute name="action">app.help</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">_About</attribute>
+       <attribute name="action">app.about</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">_Quit</attribute>
+       <attribute name="action">app.quit</attribute>
+       <attribute name="accel">&lt;Primary&gt;q</attribute>
+      </item>
+    </section>
+  </menu>
+
+</interface>
diff --git a/data/ui/browser-popup.ui b/data/ui/browser-popup.ui
new file mode 100644
index 0000000..c3079ee
--- /dev/null
+++ b/data/ui/browser-popup.ui
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="browser-popup">
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Add to Queue</attribute>
+       <attribute name="action">app.clipboard-add-to-queue</attribute>
+      </item>
+      <submenu>
+       <attribute name="label" translatable="yes">Add to Playlist</attribute>
+       <attribute name="rb-playlist-menu-link"></attribute>
+      </submenu>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Copy</attribute>
+       <attribute name="action">app.clipboard-copy</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Cut</attribute>
+       <attribute name="action">app.clipboard-cut</attribute>
+      </item>
+    </section>
+    <section>
+      <section>
+       <attribute name="rb-menu-link">delete-menu</attribute>
+      </section>
+      <item>
+       <attribute name="label" translatable="yes">_Move to Trash</attribute>
+       <attribute name="action">app.clipboard-trash</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Browse this Genre</attribute>
+       <attribute name="action">app.browser-select-genre</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Browse this Artist</attribute>
+       <attribute name="action">app.browser-select-artist</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Browse this Album</attribute>
+       <attribute name="action">app.browser-select-album</attribute>
+      </item>
+    </section>
+    <section>
+      <attribute name="rb-plugin-menu-link">browser-popup</attribute>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Pr_operties</attribute>
+       <attribute name="action">app.clipboard-properties</attribute>
+      </item>
+    </section>
+  </menu>
+</interface>
diff --git a/data/ui/display-page-add-menu.ui b/data/ui/display-page-add-menu.ui
new file mode 100644
index 0000000..ff5c613
--- /dev/null
+++ b/data/ui/display-page-add-menu.ui
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="display-page-add-menu">
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">_New Playlist</attribute>
+       <attribute name="action">app.playlist-new</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">New _Automatic Playlist</attribute>
+       <attribute name="action">app.playlist-new-auto</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">_Load from File</attribute>
+       <attribute name="action">app.playlist-load</attribute>
+      </item>
+    </section>
+    <section>
+      <attribute name="rb-plugin-menu-link">display-page-add-playlist</attribute>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">_Check for New Devices</attribute>
+       <attribute name="action">app.check-devices</attribute>
+      </item>
+    </section>
+    <section>
+      <attribute name="rb-plugin-menu-link">display-page-add</attribute>
+    </section>
+  </menu>
+</interface>
diff --git a/data/ui/edit-menu.ui b/data/ui/edit-menu.ui
new file mode 100644
index 0000000..575295f
--- /dev/null
+++ b/data/ui/edit-menu.ui
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="edit-menu">
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Cu_t</attribute>
+       <attribute name="action">app.clipboard-cut</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">_Copy</attribute>
+       <attribute name="action">app.clipboard-copy</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">_Paste</attribute>
+       <attribute name="action">app.clipboard-paste</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Select _All</attribute>
+       <attribute name="action">app.clipboard-select-all</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">D_eselect All</attribute>
+       <attribute name="action">app.clipboard-select-none</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Add to Play Queue</attribute>
+       <attribute name="action">app.clipboard-add-to-queue</attribute>
+      </item>
+      <submenu>
+       <attribute name="label" translatable="yes">Add to Playlist</attribute>
+       <attribute name="rb-playlist-menu-link"></attribute>
+      </submenu>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Pr_operties</attribute>
+       <attribute name="action">app.clipboard-properties</attribute>
+      </item>
+    </section>
+    <section>
+      <section>
+       <attribute name="rb-menu-link">delete-menu</attribute>
+      </section>       
+      <item>
+       <attribute name="label" translatable="yes">_Move to Trash</attribute>
+       <attribute name="action">app.clipboard-move-to-trash</attribute>
+      </item>
+    </section>
+    <section>
+      <attribute name="rb-plugin-menu-link">edit</attribute>
+    </section>
+  </menu>
+</interface>
diff --git a/data/ui/import-errors-popup.ui b/data/ui/import-errors-popup.ui
new file mode 100644
index 0000000..40875b3
--- /dev/null
+++ b/data/ui/import-errors-popup.ui
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="import-errors-popup">
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">_Remove</attribute>
+       <attribute name="action">app.clipboard-delete</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">_Move to Trash</attribute>
+       <attribute name="action">app.clipboard-move-to-trash</attribute>
+      </item>
+    </section>
+  </menu>
+</interface>
diff --git a/data/ui/library-toolbar.ui b/data/ui/library-toolbar.ui
new file mode 100644
index 0000000..5f4a4b1
--- /dev/null
+++ b/data/ui/library-toolbar.ui
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="library-toolbar">
+    <section>
+      <submenu>
+       <attribute name="label" translatable="yes">Edit</attribute>
+       <attribute name="rb-menu-link">edit-menu</attribute>
+      </submenu>
+      <item>
+       <attribute name="label" translatable="yes">Browse</attribute>
+       <attribute name="rb-property-bind">show-browser</attribute>
+       <attribute name="accel">&lt;Primary&gt;b</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">View All</attribute>
+       <attribute name="action">app.source-view-all</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Import</attribute>
+       <attribute name="action">app.library-import</attribute>
+      </item>
+    </section>
+    <section>
+      <attribute name="rb-plugin-menu-link">library-toolbar</attribute>
+    </section>
+  </menu>
+</interface>
+
diff --git a/data/ui/main-toolbar.ui b/data/ui/main-toolbar.ui
new file mode 100644
index 0000000..ac2afbe
--- /dev/null
+++ b/data/ui/main-toolbar.ui
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.0 -->
+  <object class="GtkToolbar" id="main-toolbar">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="toolbar_style">icons</property>
+    <property name="icon_size">6</property>
+    <child>
+      <object class="GtkToolButton" id="previous-button">
+        <property name="use_action_appearance">False</property>
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="tooltip_text" translatable="yes">Start playing the previous song</property>
+        <property name="use_action_appearance">False</property>
+        <property name="action_name">app.play-previous</property>
+        <property name="label" translatable="yes">Previous</property>
+        <property name="icon_name">media-skip-backward</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="homogeneous">True</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkToggleToolButton" id="play-button">
+        <property name="use_action_appearance">False</property>
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="tooltip_text" translatable="yes">Pause playback</property>
+        <property name="use_action_appearance">False</property>
+        <property name="action_name">app.play</property>
+        <property name="label" translatable="yes">Play</property>
+        <property name="icon_name">media-playback-start</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="homogeneous">True</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkToolButton" id="next-button">
+        <property name="use_action_appearance">False</property>
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="tooltip_text" translatable="yes">Start playing the next song</property>
+        <property name="use_action_appearance">False</property>
+        <property name="action_name">app.play-next</property>
+        <property name="label" translatable="yes">Next</property>
+        <property name="icon_name">media-skip-forward</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="homogeneous">True</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkSeparatorToolItem" id="separator1">
+        <property name="use_action_appearance">False</property>
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="use_action_appearance">False</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="homogeneous">True</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkToggleToolButton" id="repeat-button">
+        <property name="use_action_appearance">False</property>
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="tooltip_text" translatable="yes">Play first song again after all songs are 
played</property>
+        <property name="use_action_appearance">False</property>
+        <property name="action_name">app.play-repeat</property>
+        <property name="label" translatable="yes">Repeat</property>
+        <property name="icon_name">media-playlist-repeat</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="homogeneous">True</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkToggleToolButton" id="shuffle-button">
+        <property name="use_action_appearance">False</property>
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="tooltip_text" translatable="yes">Play songs in a random order</property>
+        <property name="use_action_appearance">False</property>
+        <property name="action_name">app.play-shuffle</property>
+        <property name="label" translatable="yes">Shuffle</property>
+        <property name="icon_name">media-playlist-shuffle</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="homogeneous">True</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkSeparatorToolItem" id="separator2">
+        <property name="use_action_appearance">False</property>
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="use_action_appearance">False</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="homogeneous">True</property>
+      </packing>
+    </child>
+  </object>
+</interface>
diff --git a/data/ui/missing-files-popup.ui b/data/ui/missing-files-popup.ui
new file mode 100644
index 0000000..d1607b2
--- /dev/null
+++ b/data/ui/missing-files-popup.ui
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="missing-files-popup">
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">_Remove</attribute>
+       <attribute name="action">app.clipboard-delete</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">_Properties</attribute>
+       <attribute name="action">app.clipboard-properties</attribute>
+      </item>
+    </section>
+  </menu>
+</interface>
diff --git a/data/ui/playlist-menu.ui b/data/ui/playlist-menu.ui
new file mode 100644
index 0000000..b6f4eeb
--- /dev/null
+++ b/data/ui/playlist-menu.ui
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="playlist-menu">
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">_Edit...</attribute>
+       <attribute name="action">app.playlist-edit</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">_Rename</attribute>
+       <attribute name="action">app.playlist-rename</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">_Queue All Tracks</attribute>
+       <attribute name="action">app.playlist-queue</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">_Shuffle Playlist</attribute>
+       <attribute name="action">app.playlist-shuffle</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">_Save to File...</attribute>
+       <attribute name="action">app.playlist-save</attribute>
+      </item>
+    </section>
+    <section>
+      <attribute name="rb-plugin-menu-link">playlist-menu</attribute>
+    </section>
+  </menu>
+</interface>
diff --git a/data/ui/playlist-popup.ui b/data/ui/playlist-popup.ui
new file mode 100644
index 0000000..ecbcc73
--- /dev/null
+++ b/data/ui/playlist-popup.ui
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="playlist-popup">
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Add to Queue</attribute>
+       <attribute name="action">app.clipboard-add-to-queue</attribute>
+      </item>
+      <submenu>
+       <attribute name="label" translatable="yes">Add to Playlist</attribute>
+       <attribute name="rb-playlist-menu-link"></attribute>
+      </submenu>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Copy</attribute>
+       <attribute name="action">app.clipboard-copy</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Cut</attribute>
+       <attribute name="action">app.clipboard-cut</attribute>
+      </item>
+    </section>
+    <section>
+      <section>
+       <attribute name="rb-menu-link">delete-menu</attribute>
+      </section>       
+      <item>
+       <attribute name="label" translatable="yes">_Move to Trash</attribute>
+       <attribute name="action">app.clipboard-trash</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Browse this Genre</attribute>
+       <attribute name="action">app.browser-select-genre</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Browse this Artist</attribute>
+       <attribute name="action">app.browser-select-artist</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Browse this Album</attribute>
+       <attribute name="action">app.browser-select-album</attribute>
+      </item>
+    </section>
+    <section>
+      <attribute name="rb-plugin-menu-link">playlist-popup</attribute>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Pr_operties</attribute>
+       <attribute name="action">app.clipboard-properties</attribute>
+      </item>
+    </section>
+  </menu>
+</interface>
diff --git a/data/ui/playlist-toolbar.ui b/data/ui/playlist-toolbar.ui
new file mode 100644
index 0000000..ca42efd
--- /dev/null
+++ b/data/ui/playlist-toolbar.ui
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="playlist-toolbar">
+    <section>
+      <submenu>
+       <attribute name="label" translatable="yes">Edit</attribute>
+       <attribute name="rb-menu-link">edit-menu</attribute>
+      </submenu>
+      <item>
+       <attribute name="label" translatable="yes">Browse</attribute>
+       <attribute name="rb-property-bind">show-browser</attribute>
+       <attribute name="accel">&lt;Primary&gt;b</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">View All</attribute>
+       <attribute name="action">app.source-view-all</attribute>
+      </item>
+      <submenu>
+       <attribute name="label" translatable="yes">Playlist</attribute>
+       <attribute name="rb-menu-link">playlist-menu</attribute>
+      </submenu>
+    </section>
+    <section>
+      <attribute name="rb-plugin-menu-link">playlist-toolbar</attribute>
+    </section>
+  </menu>
+</interface>
diff --git a/data/ui/podcast-add-dialog.ui b/data/ui/podcast-add-dialog.ui
index 6462b1e..c1574de 100644
--- a/data/ui/podcast-add-dialog.ui
+++ b/data/ui/podcast-add-dialog.ui
@@ -1,9 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <!-- interface-requires gtk+ 3.0 -->
-  <object class="GtkAction" id="subscribe">
-    <property name="label" translatable="yes">Subscribe</property>
-  </object>
   <object class="GtkGrid" id="podcast-add-dialog">
     <property name="visible">True</property>
     <property name="can_focus">False</property>
diff --git a/data/ui/podcast-popups.ui b/data/ui/podcast-popups.ui
new file mode 100644
index 0000000..9c60061
--- /dev/null
+++ b/data/ui/podcast-popups.ui
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="podcast-feed-popup">
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">New Podcast Feed...</attribute>
+       <attribute name="action">app.podcast-add</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Update All Feeds</attribute>
+       <attribute name="action">app.podcast-feed-update-all</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Update Podcast Feed</attribute>
+       <attribute name="action">app.podcast-feed-update</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Delete Podcast Feed</attribute>
+       <attribute name="action">app.clipboard-delete</attribute>
+      </item>
+    </section>
+    <section>
+      <attribute name="rb-plugin-menu-link">podcast-feed-popup</attribute>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Properties</attribute>
+       <attribute name="action">app.clipboard-properties</attribute>
+      </item>
+    </section>
+  </menu>
+  <menu id="podcast-episode-popup">
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Download Episode</attribute>
+       <attribute name="action">app.podcast-download</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Cancel Download</attribute>
+       <attribute name="action">app.podcast-download-cancel</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Delete</attribute>
+       <attribute name="action">app.clipboard-delete</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Add to Queue</attribute>
+       <attribute name="action">app.clipboard-add-to-queue</attribute>
+      </item>
+    </section>
+    <section>
+      <attribute name="rb-plugin-menu-link">podcast-episode-popup</attribute>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Properties</attribute>
+       <attribute name="action">app.clipboard-properties</attribute>
+      </item>
+    </section>
+  </menu>
+</interface>
diff --git a/data/ui/podcast-toolbar.ui b/data/ui/podcast-toolbar.ui
new file mode 100644
index 0000000..76cd43d
--- /dev/null
+++ b/data/ui/podcast-toolbar.ui
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="podcast-toolbar">
+    <section>
+      <submenu>
+       <attribute name="label" translatable="yes">Edit</attribute>
+       <attribute name="rb-menu-link">edit-menu</attribute>
+      </submenu>
+      <item>
+       <attribute name="label" translatable="yes">Browse</attribute>
+       <attribute name="rb-property-bind">show-browser</attribute>
+       <attribute name="accel">&lt;Primary&gt;b</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">View All</attribute>
+       <attribute name="action">app.source-view-all</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Add</attribute>
+       <attribute name="action">app.podcast-add</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Update</attribute>
+       <attribute name="action">app.podcast-feed-update</attribute>
+      </item>
+    </section>
+    <section>
+      <attribute name="rb-plugin-menu-link">podcast-toolbar</attribute>
+    </section>
+  </menu>
+</interface>
diff --git a/data/ui/queue-popups.ui b/data/ui/queue-popups.ui
new file mode 100644
index 0000000..6090f19
--- /dev/null
+++ b/data/ui/queue-popups.ui
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="queue-source-popup">
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Remove from Play Queue</attribute>
+       <attribute name="action">app.clipboard-delete</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Shuffle Play Queue</attribute>
+       <attribute name="action">app.queue-shuffle</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">_Save to File...</attribute>
+       <attribute name="action">app.save-playlist</attribute>
+      </item>
+    </section>
+    <section>
+      <attribute name="rb-plugin-menu-link">queue-popup</attribute>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Pr_operties</attribute>
+       <attribute name="action">app.clipboard-properties</attribute>
+      </item>
+    </section>
+  </menu>
+  <menu id="queue-sidepane-popup">
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Remove from Play Queue</attribute>
+       <attribute name="action">app.clipboard-delete</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Clear Play Queue</attribute>
+       <attribute name="action">app.queue-clear</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Shuffle Play Queue</attribute>
+       <attribute name="action">app.queue-shuffle</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">_Save to File...</attribute>
+       <attribute name="action">app.save-playlist</attribute>
+      </item>
+    </section>
+    <section>
+      <attribute name="rb-plugin-menu-link">queue-popup</attribute>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Pr_operties</attribute>
+       <attribute name="action">app.queue-properties</attribute>
+      </item>
+    </section>
+  </menu>
+</interface>
diff --git a/data/ui/queue-toolbar.ui b/data/ui/queue-toolbar.ui
new file mode 100644
index 0000000..9aadefe
--- /dev/null
+++ b/data/ui/queue-toolbar.ui
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="queue-toolbar">
+    <section>
+      <submenu>
+       <attribute name="label" translatable="yes">Edit</attribute>
+       <attribute name="rb-menu-link">edit-menu</attribute>
+      </submenu>
+      <item>
+       <attribute name="label" translatable="yes">Shuffle</attribute>
+       <attribute name="action">app.queue-shuffle</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Clear</attribute>
+       <attribute name="action">app.queue-clear</attribute>
+      </item>
+      <submenu>
+       <attribute name="label" translatable="yes">Playlist</attribute>
+       <attribute name="rb-menu-link">playlist-menu</attribute>
+      </submenu>
+    </section>
+    <section>
+      <attribute name="rb-plugin-menu-link">queue-toolbar</attribute>
+    </section>
+  </menu>
+</interface>
diff --git a/doc/reference/Makefile.am b/doc/reference/Makefile.am
index 665d3c2..d9f3aa4 100644
--- a/doc/reference/Makefile.am
+++ b/doc/reference/Makefile.am
@@ -34,8 +34,6 @@ CFILE_GLOB=$(top_srcdir)/lib/*.c
 IGNORE_HFILES= \
        config.h \
        eggdesktopfile.h \
-       eggsmclient-private.h \
-       eggsmclient.h \
        md5.h \
        rb-cut-and-paste-code.h \
        rb-marshal.h \
diff --git a/doc/reference/rhythmbox-docs.sgml b/doc/reference/rhythmbox-docs.sgml
index 9da7437..e776771 100644
--- a/doc/reference/rhythmbox-docs.sgml
+++ b/doc/reference/rhythmbox-docs.sgml
@@ -45,6 +45,7 @@
 
        <chapter>       
                <title>Shell</title>
+               <xi:include href="xml/rb-application.xml"/>
                <xi:include href="xml/rb-history.xml"/>
                <xi:include href="xml/rb-play-order.xml"/>
                <xi:include href="xml/rb-play-order-random.xml"/>
diff --git a/doc/reference/rhythmbox.types b/doc/reference/rhythmbox.types
index 6e96204..7b76aeb 100644
--- a/doc/reference/rhythmbox.types
+++ b/doc/reference/rhythmbox.types
@@ -2,6 +2,7 @@
 #include <glib-object.h>
 #include <gtk/gtk.h>
 
+#include "rb-application.h"
 #include "rb-async-copy.h"
 #include "rb-auto-playlist-source.h"
 #include "rb-cell-renderer-pixbuf.h"
@@ -71,6 +72,7 @@
 #include "rhythmdb-entry.h"
 #include "rhythmdb-entry-type.h"
 
+rb_application_get_type
 rb_auto_playlist_source_get_type
 rb_browser_source_get_type
 rb_cell_renderer_pixbuf_get_type
diff --git a/help/C/index.docbook b/help/C/index.docbook
index 88e5245..a8fd65a 100644
--- a/help/C/index.docbook
+++ b/help/C/index.docbook
@@ -1567,17 +1567,6 @@
               <entry>
                 <keycombo>
                   <keycap>Ctrl</keycap>
-                  <keycap>E</keycap>
-                </keycombo>
-              </entry>
-              <entry>
-                <para>Extract CD (launch Sound-Juicer)</para>
-              </entry>
-            </row>
-            <row>
-              <entry>
-                <keycombo>
-                  <keycap>Ctrl</keycap>
                   <keycap>J</keycap>
                 </keycombo>
               </entry>
@@ -1607,28 +1596,6 @@
                 <para>Create a New playlist</para>
               </entry>
             </row>
-            <row>
-              <entry>
-                <keycombo>
-                  <keycap>Ctrl</keycap>
-                  <keycap>I</keycap>
-                </keycombo>
-              </entry>
-              <entry>
-                <para>Add a new Internet Radio Station</para>
-              </entry>
-            </row>
-            <row>
-              <entry>
-                <keycombo>
-                  <keycap>Ctrl</keycap>
-                  <keycap>P</keycap>
-                </keycombo>
-              </entry>
-              <entry>
-                <para>Add a New Podcast Feed</para>
-              </entry>
-           </row>
            <row>
               <entry>
                 <keycombo>
diff --git a/lib/Makefile.am b/lib/Makefile.am
index fd20835..4ed8e5f 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -17,10 +17,6 @@ librb_la_SOURCES =                                   \
        rb-debug.c                                      \
        eggdesktopfile.c                                \
        eggdesktopfile.h                                \
-       eggsmclient.c                                   \
-       eggsmclient.h                                   \
-       eggsmclient-private.h                           \
-       eggsmclient-xsmp.c                              \
        rb-file-helpers.c                               \
        rb-builder-helpers.c                            \
        rb-stock-icons.c                                \
diff --git a/lib/rb-builder-helpers.c b/lib/rb-builder-helpers.c
index bc8ed33..f975089 100644
--- a/lib/rb-builder-helpers.c
+++ b/lib/rb-builder-helpers.c
@@ -80,6 +80,33 @@ rb_builder_load (const char *file, gpointer user_data)
        return builder;
 }
 
+/**
+ * rb_builder_load_plugin_file:
+ * @plugin: #RBPlugin instance
+ * @file: name of file to load
+ * @user_data: user data to pass to autoconnected signal handlers
+ *
+ * Like #rb_builder_load, except it finds files associated with
+ * plugins as well as those in the core data directories.
+ *
+ * Return value: (transfer full): #GtkBuilder object built from the file
+ */
+GtkBuilder *
+rb_builder_load_plugin_file (GObject *plugin, const char *file, gpointer user_data)
+{
+       char *path;
+       GtkBuilder *builder;
+
+       path = rb_find_plugin_data_file (plugin, file);
+       if (path == NULL) {
+               return NULL;
+       }
+
+       builder = rb_builder_load (path, user_data);
+       g_free (path);
+       return builder;
+}
+
 
 /**
  * rb_builder_boldify_label:
@@ -137,4 +164,3 @@ rb_combo_box_hyphen_separator_func (GtkTreeModel *model,
        return (strcmp (s, "-") == 0);
 }
 
-
diff --git a/lib/rb-builder-helpers.h b/lib/rb-builder-helpers.h
index 86efc21..5e7b064 100644
--- a/lib/rb-builder-helpers.h
+++ b/lib/rb-builder-helpers.h
@@ -33,12 +33,14 @@
 G_BEGIN_DECLS
 
 GtkBuilder *rb_builder_load (const char *file, gpointer user_data);
+GtkBuilder *rb_builder_load_plugin_file (GObject *plugin, const char *file, gpointer user_data);
 
 void rb_builder_boldify_label (GtkBuilder *builder, const char *name);
 
 gboolean rb_combo_box_hyphen_separator_func (GtkTreeModel *model,
                                             GtkTreeIter *iter,
                                             gpointer data);
+
 G_END_DECLS
 
 #endif /* __RB_BUILDER_HELPERS_H */
diff --git a/lib/rb-util.c b/lib/rb-util.c
index 5445cff..ab86a89 100644
--- a/lib/rb-util.c
+++ b/lib/rb-util.c
@@ -377,28 +377,6 @@ rb_image_new_from_stock (const gchar *stock_id, GtkIconSize size)
 }
 
 /**
- * rb_gtk_action_popup_menu: (skip):
- * @uimanager: a #GtkUIManager
- * @path: UI path for the popup to display
- *
- * Simple shortcut for getting a popup menu from a #GtkUIManager and
- * displaying it.
- */
-void
-rb_gtk_action_popup_menu (GtkUIManager *uimanager, const char *path)
-{
-       GtkWidget *menu;
-
-       menu = gtk_ui_manager_get_widget (uimanager, path);
-       if (menu == NULL) {
-               g_warning ("Couldn't get menu widget for %s", path);
-       } else {
-               gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 3, 
-                               gtk_get_current_event_time ());
-       }
-}
-
-/**
  * rb_is_main_thread:
  *
  * Checks if currently executing on the main thread.
@@ -1351,3 +1329,48 @@ rb_settings_delayed_sync (GSettings *settings, RBDelayedSyncFunc sync_func, gpoi
                g_object_set_data_full (G_OBJECT (settings), DELAYED_SYNC_DATA_ITEM, data, destroy);
        }
 }
+
+/**
+ * rb_menu_update_link:
+ * @menu: menu to update
+ * @link_attr: attribute indicating the menu link to update
+ * @target: new menu link target
+ *
+ * Updates a submenu link to point to the specified target menu.
+ */
+void
+rb_menu_update_link (GMenu *menu, const char *link_attr, GMenuModel *target)
+{
+       GMenuModel *mm = G_MENU_MODEL (menu);
+       int i;
+
+       for (i = 0; i < g_menu_model_get_n_items (mm); i++) {
+               const char *link;
+               const char *label;
+               GMenuModel *section;
+
+               /* only recurse into sections, not submenus */
+               section = g_menu_model_get_item_link (mm, i, G_MENU_LINK_SECTION);
+               if (section != NULL && G_IS_MENU (section)) {
+                       rb_menu_update_link (G_MENU (section), link_attr, target);
+               }
+
+               if (g_menu_model_get_item_attribute (mm, i, link_attr, "s", &link)) {
+                       GMenuItem *item;
+
+                       g_menu_model_get_item_attribute (mm, i, "label", "s", &label);
+                       g_menu_remove (menu, i);
+
+                       item = g_menu_item_new (label, NULL);
+                       g_menu_item_set_attribute (item, link_attr, "s", "hi");
+                       if (target) {
+                               g_menu_item_set_link (item, G_MENU_LINK_SUBMENU, target);
+                       } else {
+                               /* set a nonexistant action name so it gets disabled */
+                               g_menu_item_set_detailed_action (item, "nonexistant-action");
+                       }
+
+                       g_menu_insert_item (menu, i, item);
+               }
+       }
+}
diff --git a/lib/rb-util.h b/lib/rb-util.h
index 2225e64..0c767f3 100644
--- a/lib/rb-util.h
+++ b/lib/rb-util.h
@@ -56,8 +56,6 @@ char *rb_make_time_string (guint seconds);
 char *rb_make_duration_string (guint duration);
 char *rb_make_elapsed_time_string (guint elapsed, guint duration, gboolean show_remaining);
 
-void rb_gtk_action_popup_menu (GtkUIManager *uimanager, const char *path);
-
 GtkWidget *rb_image_new_from_stock (const gchar *stock_id, GtkIconSize size);
 
 void rb_threads_init (void);
@@ -115,6 +113,8 @@ typedef void (*RBDelayedSyncFunc)(GSettings *settings, gpointer data);
 
 void rb_settings_delayed_sync (GSettings *settings, RBDelayedSyncFunc sync_func, gpointer data, 
GDestroyNotify destroy);
 
+void rb_menu_update_link (GMenu *menu, const char *link_attr, GMenuModel *target);
+
 G_END_DECLS
 
 #endif /* __RB_UTIL_H */
diff --git a/plugins/audiocd/Makefile.am b/plugins/audiocd/Makefile.am
index b132b4d..d6d35ce 100644
--- a/plugins/audiocd/Makefile.am
+++ b/plugins/audiocd/Makefile.am
@@ -46,10 +46,8 @@ libaudiocd_la_LIBADD += $(NULL)
 
 gtkbuilderdir = $(plugindatadir)
 gtkbuilder_DATA =                                      \
-       album-info.ui
-
-uixmldir = $(plugindatadir)
-uixml_DATA = audiocd-ui.xml
+       album-info.ui                                   \
+       audiocd-toolbar.ui
 
 noinst_PROGRAMS = test-cd
 
@@ -67,7 +65,7 @@ plugin_in_files = audiocd.plugin.in
 
 plugin_DATA = $(plugin_in_files:.plugin.in=.plugin)
 
-EXTRA_DIST = $(gtkbuilder_DATA) $(uixml_DATA) $(plugin_in_files)
+EXTRA_DIST = $(gtkbuilder_DATA) $(plugin_in_files)
 
 CLEANFILES = $(plugin_DATA)
 DISTCLEANFILES = $(plugin_DATA)
diff --git a/plugins/audiocd/audiocd-toolbar.ui b/plugins/audiocd/audiocd-toolbar.ui
new file mode 100644
index 0000000..5f8d8e3
--- /dev/null
+++ b/plugins/audiocd/audiocd-toolbar.ui
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="audiocd-toolbar">
+    <section>
+      <submenu>
+       <attribute name="label" translatable="yes">Edit</attribute>
+       <attribute name="rb-menu-link">edit-menu</attribute>
+      </submenu>
+      <item>
+       <attribute name="label" translatable="yes">Extract</attribute>
+       <attribute name="action">app.audiocd-copy-tracks</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Eject</attribute>
+       <attribute name="action">app.removable-media-eject</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Reload</attribute>
+       <attribute name="action">app.audiocd-reload</attribute>
+      </item>
+    </section>
+    <section>
+      <attribute name="rb-plugin-menu-link">audiocd-toolbar</attribute>
+    </section>
+  </menu>
+</interface>
diff --git a/plugins/audiocd/rb-audiocd-plugin.c b/plugins/audiocd/rb-audiocd-plugin.c
index b22eafb..77c6ad4 100644
--- a/plugins/audiocd/rb-audiocd-plugin.c
+++ b/plugins/audiocd/rb-audiocd-plugin.c
@@ -65,7 +65,6 @@ typedef struct
        PeasExtensionBase parent;
 
        RBShell    *shell;
-       guint       ui_merge_id;
 
        GHashTable *sources;
        char       *playing_uri;
@@ -251,24 +250,6 @@ create_source_cb (RBRemovableMediaManager *rmm,
                g_signal_connect_object (G_OBJECT (source),
                                         "deleted", G_CALLBACK (rb_audiocd_plugin_source_deleted),
                                         plugin, 0);
-
-               if (plugin->ui_merge_id == 0) {
-                       char *filename;
-                       GtkUIManager *uimanager;
-
-                       g_object_get (shell, "ui-manager", &uimanager, NULL);
-
-                       filename = rb_find_plugin_data_file (G_OBJECT (plugin), "audiocd-ui.xml");
-                       if (filename != NULL) {
-                               plugin->ui_merge_id = gtk_ui_manager_add_ui_from_file (uimanager, filename, 
NULL);
-                               gtk_ui_manager_ensure_update (uimanager);
-                       } else {
-                               g_warning ("Unable to find file: audiocd-ui.xml");
-                       }
-
-                       g_free (filename);
-                       g_object_unref (uimanager);
-               }
        }
 
        g_object_unref (shell);
@@ -368,25 +349,18 @@ impl_deactivate   (PeasActivatable *bplugin)
 {
        RBAudioCdPlugin         *plugin = RB_AUDIOCD_PLUGIN (bplugin);
        RBRemovableMediaManager *rmm = NULL;
-       GtkUIManager            *uimanager = NULL;
        RBShell                 *shell;
 
        g_object_get (plugin, "object", &shell, NULL);
        g_object_get (shell,
                      "removable-media-manager", &rmm,
-                     "ui-manager", &uimanager,
                      NULL);
        g_signal_handlers_disconnect_by_func (rmm, create_source_cb, plugin);
 
        g_hash_table_foreach (plugin->sources, (GHFunc)_delete_cb, plugin);
        g_hash_table_destroy (plugin->sources);
        plugin->sources = NULL;
-       if (plugin->ui_merge_id) {
-               gtk_ui_manager_remove_ui (uimanager, plugin->ui_merge_id);
-               plugin->ui_merge_id = 0;
-       }
 
-       g_object_unref (uimanager);
        g_object_unref (rmm);
        g_object_unref (shell);
 }
diff --git a/plugins/audiocd/rb-audiocd-source.c b/plugins/audiocd/rb-audiocd-source.c
index 2adba5c..d8def59 100644
--- a/plugins/audiocd/rb-audiocd-source.c
+++ b/plugins/audiocd/rb-audiocd-source.c
@@ -48,6 +48,7 @@
 #include "rb-shell-player.h"
 #include "rb-audiocd-info.h"
 #include "rb-musicbrainz-lookup.h"
+#include "rb-application.h"
 
 enum
 {
@@ -62,7 +63,6 @@ static void rb_audiocd_device_source_init (RBDeviceSourceInterface *interface);
 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 (RBDisplayPage *page);
 static void impl_delete_thyself (RBDisplayPage *page);
 
 static guint impl_want_uri (RBSource *source, const char *uri);
@@ -78,8 +78,9 @@ static gboolean update_disc_number_cb (GtkWidget *widget, GdkEventFocus *event,
 
 static void rb_audiocd_source_load_disc_info (RBAudioCdSource *source);
 static gboolean rb_audiocd_source_load_metadata (RBAudioCdSource *source);
-static void reload_metadata_cmd (GtkAction *action, RBAudioCdSource *source);
-static void copy_tracks_cmd (GtkAction *action, RBAudioCdSource *source);
+
+static void reload_metadata_action_cb (GSimpleAction *, GVariant *, gpointer);
+static void copy_tracks_action_cb (GSimpleAction *, GVariant *, gpointer);
 
 static void extract_cell_data_func (GtkTreeViewColumn *column,
                                    GtkCellRenderer *renderer,
@@ -118,8 +119,6 @@ struct _RBAudioCdSourcePrivate
        GtkWidget *year_entry;
        GtkWidget *genre_entry;
        GtkWidget *disc_number_entry;
-
-       GtkActionGroup *action_group;
 };
 
 G_DEFINE_DYNAMIC_TYPE_EXTENDED (
@@ -139,15 +138,6 @@ GType rb_audiocd_entry_type_get_type (void);
 
 G_DEFINE_DYNAMIC_TYPE (RBAudioCdEntryType, rb_audiocd_entry_type, RHYTHMDB_TYPE_ENTRY_TYPE);
 
-static GtkActionEntry rb_audiocd_source_actions[] = {
-       { "AudioCdCopyTracks", GTK_STOCK_CDROM, N_("_Extract to Library"), NULL,
-         N_("Copy tracks to the library"),
-         G_CALLBACK (copy_tracks_cmd) },
-       { "AudioCdSourceReloadMetadata", GTK_STOCK_REFRESH, N_("Reload"), NULL,
-       N_("Reload Album Information"),
-       G_CALLBACK (reload_metadata_cmd) },
-};
-
 static void
 rb_audiocd_entry_type_class_init (RBAudioCdEntryTypeClass *klass)
 {
@@ -198,7 +188,6 @@ 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;
 
        source_class->impl_can_paste = (RBSourceFeatureFunc) rb_false_function;
@@ -257,12 +246,7 @@ rb_audiocd_source_finalize (GObject *object)
 static void
 rb_audiocd_source_dispose (GObject *object)
 {
-       RBAudioCdSource *source = RB_AUDIOCD_SOURCE (object);
-
-       if (source->priv->action_group != NULL) {
-               g_object_unref (source->priv->action_group);
-               source->priv->action_group = NULL;
-       }
+       /*RBAudioCdSource *source = RB_AUDIOCD_SOURCE (object);*/
 
        G_OBJECT_CLASS (rb_audiocd_source_parent_class)->dispose (object);
 }
@@ -300,11 +284,10 @@ rb_audiocd_source_constructed (GObject *object)
        RBAudioCdSource *source;
        GtkCellRenderer *renderer;
        GtkTreeViewColumn *extract;
-       GtkUIManager *ui_manager;
+       GtkAccelGroup *accel_group;
        GtkBuilder *builder;
        GtkWidget *grid;
        GtkWidget *widget;
-       GtkAction *action;
        GObject *plugin;
        RBShell *shell;
        RBShellPlayer *shell_player;
@@ -313,8 +296,11 @@ rb_audiocd_source_constructed (GObject *object)
        RhythmDBQuery *query;
        RhythmDBEntryType *entry_type;
        RBSourceToolbar *toolbar;
-       char *ui_file;
        int toggle_width;
+       GActionEntry actions[] = {
+               { "audiocd-copy-tracks", copy_tracks_action_cb },
+               { "audiocd-reload-metadata", reload_metadata_action_cb }
+       };
 
        RB_CHAIN_GOBJECT_METHOD (rb_audiocd_source_parent_class, constructed, object);
        source = RB_AUDIOCD_SOURCE (object);
@@ -326,28 +312,14 @@ rb_audiocd_source_constructed (GObject *object)
        g_object_get (shell,
                      "db", &db,
                      "shell-player", &shell_player,
-                     "ui-manager", &ui_manager,
+                     "accel-group", &accel_group,
                      NULL);
 
-       source->priv->action_group =
-               _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
-                                                       "AudioCdActions",
-                                                       NULL, 0, NULL);
-       _rb_action_group_add_display_page_actions (source->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 (source->priv->action_group,
-                                             "AudioCdCopyTracks");
-       /* Translators: this is the toolbar button label
-          for Copy to Library action. */
-       g_object_set (action, "short-label", _("Extract"), NULL);
+       _rb_add_display_page_actions (G_ACTION_MAP (g_application_get_default ()), G_OBJECT (shell), actions, 
G_N_ELEMENTS (actions));
 
        /* source toolbar */
-       toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (source), ui_manager);
-       g_object_unref (ui_manager);
+       toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (source), accel_group);
+       g_object_unref (accel_group);
 
        g_object_get (source, "entry-type", &entry_type, NULL);
        query = rhythmdb_query_parse (db,
@@ -415,13 +387,9 @@ rb_audiocd_source_constructed (GObject *object)
 
        /* set up the album info widgets */
        g_object_get (source, "plugin", &plugin, NULL);
-       ui_file = rb_find_plugin_data_file (G_OBJECT (plugin), "album-info.ui");
-       g_assert (ui_file != NULL);
+       builder = rb_builder_load_plugin_file (G_OBJECT (plugin), "album-info.ui", NULL);
        g_object_unref (plugin);
 
-       builder = rb_builder_load (ui_file, NULL);
-       g_free (ui_file);
-
        source->priv->infogrid = GTK_WIDGET (gtk_builder_get_object (builder, "album_info"));
        g_assert (source->priv->infogrid != NULL);
 
@@ -466,6 +434,8 @@ rb_audiocd_source_new (GObject *plugin,
 {
        GObject *source;
        GSettings *settings;
+       GMenu *toolbar;
+       GtkBuilder *builder;
        RhythmDBEntryType *entry_type;
        RhythmDB *db;
        char *name;
@@ -487,6 +457,10 @@ rb_audiocd_source_new (GObject *plugin,
        g_object_unref (db);
        g_free (name);
 
+       builder = rb_builder_load_plugin_file (G_OBJECT (plugin), "audiocd-toolbar.ui", NULL);
+       toolbar = G_MENU (gtk_builder_get_object (builder, "audiocd-toolbar"));
+       rb_application_link_shared_menus (RB_APPLICATION (g_application_get_default ()), toolbar);
+
        settings = g_settings_new ("org.gnome.rhythmbox.plugins.audiocd");
        source = g_object_new (RB_TYPE_AUDIOCD_SOURCE,
                               "entry-type", entry_type,
@@ -496,9 +470,10 @@ rb_audiocd_source_new (GObject *plugin,
                               "load-status", RB_SOURCE_LOAD_STATUS_LOADING,
                               "show-browser", FALSE,
                               "settings", g_settings_get_child (settings, "source"),
-                              "toolbar-path", "/AudioCdSourceToolBar",
+                              "toolbar-menu", toolbar,
                               NULL);
        g_object_unref (settings);
+       g_object_unref (builder);
 
        rb_shell_register_entry_type_for_source (shell, RB_SOURCE (source), entry_type);
 
@@ -1025,10 +1000,9 @@ rb_audiocd_source_load_metadata (RBAudioCdSource *source)
 }
 
 static void
-reload_metadata_cmd (GtkAction *action, RBAudioCdSource *source)
+reload_metadata_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
-       g_return_if_fail (RB_IS_AUDIOCD_SOURCE (source));
-
+       RBAudioCdSource *source = RB_AUDIOCD_SOURCE (data);
        rb_audiocd_source_load_metadata (source);
 }
 
@@ -1208,13 +1182,6 @@ rb_audiocd_is_mount_audiocd (GMount *mount)
        return result;
 }
 
-static gboolean
-impl_show_popup (RBDisplayPage *page)
-{
-       _rb_display_page_show_popup (page, "/AudioCdSourcePopup");
-       return TRUE;
-}
-
 static guint
 impl_want_uri (RBSource *source, const char *uri)
 {
@@ -1469,8 +1436,9 @@ copy_entry (RhythmDBQueryModel *model,
 }
 
 static void
-copy_tracks_cmd (GtkAction *action, RBAudioCdSource *source)
+copy_tracks_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
+       RBAudioCdSource *source = RB_AUDIOCD_SOURCE (data);
        RBShell *shell;
        RBSource *library;
        RhythmDBQueryModel *model;
diff --git a/plugins/audioscrobbler/rb-audioscrobbler-profile-page.c 
b/plugins/audioscrobbler/rb-audioscrobbler-profile-page.c
index e7634ce..c3a112f 100644
--- a/plugins/audioscrobbler/rb-audioscrobbler-profile-page.c
+++ b/plugins/audioscrobbler/rb-audioscrobbler-profile-page.c
@@ -51,7 +51,6 @@
 #include "rb-audioscrobbler-radio-source.h"
 #include "rb-audioscrobbler-radio-track-entry-type.h"
 
-#define AUDIOSCROBBLER_PROFILE_PAGE_POPUP_PATH "/AudioscrobblerProfilePagePopup"
 
 struct _RBAudioscrobblerProfilePagePrivate {
        RBAudioscrobblerService *service;
@@ -111,13 +110,10 @@ struct _RBAudioscrobblerProfilePagePrivate {
        GHashTable *button_to_popup_menu_map;
        GHashTable *popup_menu_to_data_map;
 
-       guint ui_merge_id;
-       GtkActionGroup *profile_action_group;
-       GtkActionGroup *service_action_group;
-       char *love_action_name;
-       char *ban_action_name;
-       char *download_action_name;
-       char *toolbar_path;
+       GMenu *toolbar_menu;
+       GSimpleAction *love_action;
+       GSimpleAction *ban_action;
+       GSimpleAction *download_action;
 };
 
 
@@ -169,12 +165,11 @@ static void playing_song_changed_cb (RBShellPlayer *player,
                                      RBAudioscrobblerProfilePage *page);
 static void update_service_actions_sensitivity (RBAudioscrobblerProfilePage *page, RhythmDBEntry *entry);
 
-/* GtkAction callbacks */
-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 love_track_action_cb (GSimpleAction *, GVariant *, gpointer);
+static void ban_track_action_cb (GSimpleAction *, GVariant *, gpointer);
+static void download_track_action_cb (GSimpleAction *, GVariant *, gpointer);
+static void refresh_profile_action_cb (GSimpleAction *, GVariant *, gpointer);
 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, RBAudioscrobblerProfilePage *page);
@@ -232,23 +227,14 @@ static void list_item_listen_top_fans_activated_cb (GtkMenuItem *menuitem,
 /* RBDisplayPage implementations */
 static void impl_selected (RBDisplayPage *page);
 static void impl_deselected (RBDisplayPage *page);
-static gboolean impl_show_popup (RBDisplayPage *page);
 static void impl_delete_thyself (RBDisplayPage *page);
 
 enum {
        PROP_0,
        PROP_SERVICE,
-       PROP_TOOLBAR_PATH
+       PROP_TOOLBAR_MENU
 };
 
-static GtkActionEntry profile_actions [] =
-{
-       { "AudioscrobblerProfileRefresh", NULL, N_("Refresh Profile"), NULL,
-         N_("Refresh your Profile"),
-         G_CALLBACK (refresh_profile_action_cb) }
-};
-
-
 G_DEFINE_DYNAMIC_TYPE (RBAudioscrobblerProfilePage, rb_audioscrobbler_profile_page, RB_TYPE_DISPLAY_PAGE)
 
 RBDisplayPage *
@@ -303,7 +289,6 @@ rb_audioscrobbler_profile_page_class_init (RBAudioscrobblerProfilePageClass *kla
        page_class = RB_DISPLAY_PAGE_CLASS (klass);
        page_class->selected = impl_selected;
        page_class->deselected = impl_deselected;
-       page_class->show_popup = impl_show_popup;
        page_class->delete_thyself = impl_delete_thyself;
 
        g_object_class_install_property (object_class,
@@ -314,12 +299,12 @@ rb_audioscrobbler_profile_page_class_init (RBAudioscrobblerProfilePageClass *kla
                                                              RB_TYPE_AUDIOSCROBBLER_SERVICE,
                                                               G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
        g_object_class_install_property (object_class,
-                                        PROP_TOOLBAR_PATH,
-                                        g_param_spec_string ("toolbar-path",
-                                                             "toolbar path",
-                                                             "toolbar UI path",
-                                                             NULL,
-                                                             G_PARAM_READABLE));
+                                        PROP_TOOLBAR_MENU,
+                                        g_param_spec_object ("toolbar-menu",
+                                                             "toolbar menu",
+                                                             "toolbar menu",
+                                                             G_TYPE_MENU,
+                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
        g_type_class_add_private (klass, sizeof (RBAudioscrobblerProfilePagePrivate));
 }
@@ -474,13 +459,10 @@ rb_audioscrobbler_profile_page_dispose (GObject* object)
 static void
 rb_audioscrobbler_profile_page_finalize (GObject *object)
 {
+       /*
        RBAudioscrobblerProfilePage *page;
        page = RB_AUDIOSCROBBLER_PROFILE_PAGE (object);
-
-       g_free (page->priv->love_action_name);
-       g_free (page->priv->ban_action_name);
-       g_free (page->priv->download_action_name);
-       g_free (page->priv->toolbar_path);
+       */
 
        G_OBJECT_CLASS (rb_audioscrobbler_profile_page_parent_class)->finalize (object);
 }
@@ -493,8 +475,8 @@ rb_audioscrobbler_profile_page_get_property (GObject *object,
 {
        RBAudioscrobblerProfilePage *page = RB_AUDIOSCROBBLER_PROFILE_PAGE (object);
        switch (prop_id) {
-       case PROP_TOOLBAR_PATH:
-               g_value_set_string (value, page->priv->toolbar_path);
+       case PROP_TOOLBAR_MENU:
+               g_value_set_object (value, page->priv->toolbar_menu);
                break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -513,6 +495,8 @@ rb_audioscrobbler_profile_page_set_property (GObject *object,
        case PROP_SERVICE:
                page->priv->service = g_value_dup_object (value);
                break;
+       case PROP_TOOLBAR_MENU:
+               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
@@ -639,56 +623,51 @@ init_profile_ui (RBAudioscrobblerProfilePage *page)
 static void
 init_actions (RBAudioscrobblerProfilePage *page)
 {
-       char *ui_file;
        RBShell *shell;
        RBShellPlayer *player;
        GObject *plugin;
-       GtkUIManager *ui_manager;
+       GtkAccelGroup *accel_group;
        RhythmDBEntry *entry;
-       char *group_name;
-       char *toolbar_name;
-
-       g_object_get (page, "shell", &shell, "plugin", &plugin, "ui-manager", &ui_manager, NULL);
-       ui_file = rb_find_plugin_data_file (plugin, "audioscrobbler-profile-ui.xml");
-       page->priv->ui_merge_id = gtk_ui_manager_add_ui_from_file (ui_manager, ui_file, NULL);
-
-       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 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 (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 [] =
-       {
-               { page->priv->love_action_name, "emblem-favorite", N_("Love"), NULL,
-                 N_("Mark this song as loved"),
-                 G_CALLBACK (love_track_action_cb) },
-               { 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) },
-               { page->priv->download_action_name, GTK_STOCK_SAVE, N_("Download"), NULL,
-                 N_("Download the currently playing track"),
-                 G_CALLBACK (download_track_action_cb) }
+       GActionMap *map;
+       char *action_name;
+       int i;
+       GActionEntry actions[] = {
+               { "audioscrobbler-profile-refresh", refresh_profile_action_cb }
+       };
+       GActionEntry service_actions[] = {
+               { "audioscrobbler-%s-love-track", love_track_action_cb },
+               { "audioscrobbler-%s-ban-track", ban_track_action_cb },
+               { "audioscrobbler-%s-download-track", download_track_action_cb },
        };
 
-       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);
+       g_object_get (page, "shell", &shell, "plugin", &plugin, "accel-group", &accel_group, NULL);
+
+       map = G_ACTION_MAP (g_application_get_default ());
+       _rb_add_display_page_actions (map,
+                                     G_OBJECT (shell),
+                                     actions,
+                                     G_N_ELEMENTS (actions));
+
+       /* fill in action names; we need separate action instances for each service */
+       for (i = 0; i < G_N_ELEMENTS (service_actions); i++) {
+               service_actions[i].name = g_strdup_printf (service_actions[i].name,
+                                                          rb_audioscrobbler_service_get_name 
(page->priv->service));
+       }
+
+       _rb_add_display_page_actions (map,
+                                     G_OBJECT (shell),
+                                     service_actions,
+                                     G_N_ELEMENTS (service_actions));
+
+       page->priv->love_action = G_SIMPLE_ACTION (g_action_map_lookup_action (map, service_actions[0].name));
+       page->priv->ban_action = G_SIMPLE_ACTION (g_action_map_lookup_action (map, service_actions[1].name));
+       page->priv->download_action = G_SIMPLE_ACTION (g_action_map_lookup_action (map, 
service_actions[2].name));
+       /* ugh leaks
+       for (i = 0; i < G_N_ELEMENTS (service_actions); i++) {
+               g_free (service_actions[i].name);
+       }
+       */
+
        g_object_get (shell, "shell-player", &player, NULL);
        entry = rb_shell_player_get_playing_entry (player);
        update_service_actions_sensitivity (page, entry);
@@ -698,22 +677,25 @@ init_actions (RBAudioscrobblerProfilePage *page)
        g_object_unref (player);
 
        /* set up toolbar */
-       toolbar_name = g_strdup_printf ("%sSourceToolBar", rb_audioscrobbler_service_get_name 
(page->priv->service));
-       page->priv->toolbar_path = g_strdup_printf ("/%sSourceToolBar", rb_audioscrobbler_service_get_name 
(page->priv->service));
-       gtk_ui_manager_add_ui (ui_manager, page->priv->ui_merge_id, "/", toolbar_name, NULL, 
GTK_UI_MANAGER_TOOLBAR, TRUE);
-       gtk_ui_manager_add_ui (ui_manager, page->priv->ui_merge_id, page->priv->toolbar_path, "Love", 
page->priv->love_action_name, GTK_UI_MANAGER_TOOLITEM, FALSE);
-       gtk_ui_manager_add_ui (ui_manager, page->priv->ui_merge_id, page->priv->toolbar_path, "Ban", 
page->priv->ban_action_name, GTK_UI_MANAGER_TOOLITEM, FALSE);
-       gtk_ui_manager_add_ui (ui_manager, page->priv->ui_merge_id, page->priv->toolbar_path, "Download", 
page->priv->download_action_name, GTK_UI_MANAGER_TOOLITEM, FALSE);
-
-       page->priv->toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (page), ui_manager);
+       page->priv->toolbar_menu = g_menu_new ();
+       action_name = g_strdup_printf ("app.audioscrobbler-%s-love-track", rb_audioscrobbler_service_get_name 
(page->priv->service));
+       g_menu_append (page->priv->toolbar_menu, _("Love"), action_name);
+       g_free (action_name);
+
+       action_name = g_strdup_printf ("app.audioscrobbler-%s-ban-track", rb_audioscrobbler_service_get_name 
(page->priv->service));
+       g_menu_append (page->priv->toolbar_menu, _("Ban"), action_name);
+       g_free (action_name);
+
+       action_name = g_strdup_printf ("app.audioscrobbler-%s-download-track", 
rb_audioscrobbler_service_get_name (page->priv->service));
+       g_menu_append (page->priv->toolbar_menu, _("Download"), action_name);
+       g_free (action_name);
+
+       page->priv->toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (page), accel_group);
        gtk_box_pack_start (GTK_BOX (page->priv->main_vbox), GTK_WIDGET (page->priv->toolbar), FALSE, FALSE, 
0);
 
-       g_free (ui_file);
-       g_free (toolbar_name);
        g_object_unref (shell);
        g_object_unref (plugin);
-       g_object_unref (ui_manager);
-       g_free (group_name);
+       g_object_unref (accel_group);
 }
 
 static void
@@ -954,45 +936,38 @@ playing_song_changed_cb (RBShellPlayer *player,
 static void
 update_service_actions_sensitivity (RBAudioscrobblerProfilePage *page, RhythmDBEntry *entry)
 {
-       GtkAction *love;
-       GtkAction *ban;
-       GtkAction *download;
-
        /* enable love/ban if an entry is playing */
-       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 (entry == NULL) {
-               gtk_action_set_sensitive (love, FALSE);
-               gtk_action_set_sensitive (ban, FALSE);
+               g_simple_action_set_enabled (page->priv->love_action, FALSE);
+               g_simple_action_set_enabled (page->priv->ban_action, FALSE);
        } else {
-               gtk_action_set_sensitive (love, TRUE);
-               gtk_action_set_sensitive (ban, TRUE);
+               g_simple_action_set_enabled (page->priv->love_action, TRUE);
+               g_simple_action_set_enabled (page->priv->ban_action, TRUE);
        }
 
        /* enable download if the playing entry is a radio track from this service which provides a download 
url */
-       download = gtk_action_group_get_action (page->priv->service_action_group, 
page->priv->download_action_name);
        if (entry != NULL &&
            rhythmdb_entry_get_entry_type (entry) == RHYTHMDB_ENTRY_TYPE_AUDIOSCROBBLER_RADIO_TRACK) {
                RBAudioscrobblerRadioTrackData *data;
                data = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RBAudioscrobblerRadioTrackData);
 
                if (data->service == page->priv->service && data->download_url != NULL) {
-                       gtk_action_set_sensitive (download, TRUE);
+                       g_simple_action_set_enabled (page->priv->download_action, TRUE);
                } else {
-                       gtk_action_set_sensitive (download, FALSE);
+                       g_simple_action_set_enabled (page->priv->download_action, FALSE);
                }
        } else {
-               gtk_action_set_sensitive (download, FALSE);
+               g_simple_action_set_enabled (page->priv->download_action, FALSE);
        }
 }
 
 static void
-love_track_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page)
+love_track_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
 {
+       RBAudioscrobblerProfilePage *page = RB_AUDIOSCROBBLER_PROFILE_PAGE (data);
        RBShell *shell;
        RBShellPlayer *shell_player;
        RhythmDBEntry *playing;
-       GtkAction *ban_action;
 
        g_object_get (page, "shell", &shell, NULL);
        g_object_get (shell, "shell-player", &shell_player, NULL);
@@ -1005,18 +980,16 @@ love_track_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page)
                rhythmdb_entry_unref (playing);
        }
 
-       /* disable love/ban */
-       gtk_action_set_sensitive (action, FALSE);
-       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_simple_action_set_enabled (page->priv->ban_action, FALSE);
 
        g_object_unref (shell_player);
        g_object_unref (shell);
 }
 
 static void
-ban_track_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page)
+ban_track_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
 {
+       RBAudioscrobblerProfilePage *page = RB_AUDIOSCROBBLER_PROFILE_PAGE (data);
        RBShell *shell;
        RBShellPlayer *shell_player;
        RhythmDBEntry *playing;
@@ -1040,14 +1013,14 @@ ban_track_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page)
 }
 
 static void
-download_track_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page)
+download_track_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
 {
+       RBAudioscrobblerProfilePage *page = RB_AUDIOSCROBBLER_PROFILE_PAGE (data);
        RBShell *shell;
        RBShellPlayer *shell_player;
        RhythmDBEntry *playing;
 
-       /* disable the action */
-       gtk_action_set_sensitive (action, FALSE);
+       g_simple_action_set_enabled (action, FALSE);
 
        g_object_get (page, "shell", &shell, NULL);
        g_object_get (shell, "shell-player", &shell_player, NULL);
@@ -1144,8 +1117,9 @@ download_track_batch_complete_cb (RBTrackTransferBatch *batch,
 }
 
 static void
-refresh_profile_action_cb (GtkAction *action, RBAudioscrobblerProfilePage *page)
+refresh_profile_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
 {
+       RBAudioscrobblerProfilePage *page = RB_AUDIOSCROBBLER_PROFILE_PAGE (data);
        rb_audioscrobbler_user_force_update (page->priv->user);
 }
 
@@ -1865,19 +1839,11 @@ impl_deselected (RBDisplayPage *bpage)
        page->priv->update_timeout_id = 0;
 }
 
-static gboolean
-impl_show_popup (RBDisplayPage *page)
-{
-       _rb_display_page_show_popup (page, AUDIOSCROBBLER_PROFILE_PAGE_POPUP_PATH);
-       return TRUE;
-}
-
 static void
 impl_delete_thyself (RBDisplayPage *bpage)
 {
        RBAudioscrobblerProfilePage *page;
        GList *i;
-       GtkUIManager *ui_manager;
 
        rb_debug ("deleting profile page");
 
@@ -1886,12 +1852,6 @@ impl_delete_thyself (RBDisplayPage *bpage)
        for (i = page->priv->radio_sources; i != NULL; i = i->next) {
                rb_display_page_delete_thyself (i->data);
        }
-
-       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);
 }
 
 void
diff --git a/plugins/audioscrobbler/rb-audioscrobbler-radio-source.c 
b/plugins/audioscrobbler/rb-audioscrobbler-radio-source.c
index 7287607..6f5f3cf 100644
--- a/plugins/audioscrobbler/rb-audioscrobbler-radio-source.c
+++ b/plugins/audioscrobbler/rb-audioscrobbler-radio-source.c
@@ -185,9 +185,6 @@ struct _RBAudioscrobblerRadioSourcePrivate
 
        RBExtDB *art_store;
 
-       guint ui_merge_id;
-       GtkActionGroup *action_group;
-
        /* used when streaming radio using old api */
        char *old_api_password;
        char *old_api_session_id;
@@ -247,18 +244,12 @@ static void password_info_bar_response_cb (GtkInfoBar *info_bar,
                                            int response_id,
                                            RBAudioscrobblerRadioSource *source);
 
-/* action callbacks */
-static void rename_station_action_cb (GtkAction *action,
-                                      RBAudioscrobblerRadioSource *source);
-static void delete_station_action_cb (GtkAction *action,
-                                      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 gboolean impl_show_popup (RBDisplayPage *page);
 static void impl_delete_thyself (RBDisplayPage *page);
+static gboolean impl_can_remove (RBDisplayPage *page);
+static void impl_remove (RBDisplayPage *page);
 
 /* RBSource implementations */
 static RBEntryView *impl_get_entry_view (RBSource *asource);
@@ -274,18 +265,6 @@ enum {
        PROP_PLAY_ORDER
 };
 
-#define AUDIOSCROBBLER_RADIO_SOURCE_POPUP_PATH "/AudioscrobblerRadioSourcePopup"
-
-static GtkActionEntry rb_audioscrobbler_radio_source_actions [] =
-{
-       { "AudioscrobblerRadioRenameStation", NULL, N_("_Rename Station"), NULL,
-         N_("Rename station"),
-         G_CALLBACK (rename_station_action_cb) },
-       { "AudioscrobblerRadioDeleteStation", GTK_STOCK_DELETE, N_("_Delete Station"), NULL,
-         N_("Delete station"),
-         G_CALLBACK (delete_station_action_cb) }
-};
-
 G_DEFINE_DYNAMIC_TYPE (RBAudioscrobblerRadioSource, rb_audioscrobbler_radio_source, RB_TYPE_STREAMING_SOURCE)
 
 RBSource *
@@ -300,7 +279,7 @@ rb_audioscrobbler_radio_source_new (RBAudioscrobblerProfilePage *parent,
        RBShell *shell;
        GObject *plugin;
        RhythmDB *db;
-       char *toolbar_path;
+       GMenu *toolbar_menu;
 
        g_object_get (parent, "shell", &shell, "plugin", &plugin, NULL);
        g_object_get (shell, "db", &db, NULL);
@@ -309,7 +288,7 @@ rb_audioscrobbler_radio_source_new (RBAudioscrobblerProfilePage *parent,
                rb_audioscrobbler_radio_track_register_entry_type (db);
        }
 
-       g_object_get (parent, "toolbar-path", &toolbar_path, NULL);
+       g_object_get (parent, "toolbar-menu", &toolbar_menu, NULL);
 
        source = g_object_new (RB_TYPE_AUDIOSCROBBLER_RADIO_SOURCE,
                               "shell", shell,
@@ -321,13 +300,13 @@ rb_audioscrobbler_radio_source_new (RBAudioscrobblerProfilePage *parent,
                                "username", username,
                               "session-key", session_key,
                               "station-url", station_url,
-                              "toolbar-path", toolbar_path,
+                              "toolbar-menu", toolbar_menu,
                               NULL);
 
        g_object_unref (shell);
        g_object_unref (plugin);
        g_object_unref (db);
-       g_free (toolbar_path);
+       g_object_unref (toolbar_menu);
 
        return source;
 }
@@ -349,8 +328,9 @@ rb_audioscrobbler_radio_source_class_init (RBAudioscrobblerRadioSourceClass *kla
        page_class = RB_DISPLAY_PAGE_CLASS (klass);
        page_class->selected = impl_selected;
        page_class->get_status = impl_get_status;
-       page_class->show_popup = impl_show_popup;
        page_class->delete_thyself = impl_delete_thyself;
+       page_class->can_remove = impl_can_remove;
+       page_class->remove = impl_remove;
 
        source_class = RB_SOURCE_CLASS (klass);
        source_class->impl_can_rename = (RBSourceFeatureFunc) rb_true_function;
@@ -435,10 +415,8 @@ rb_audioscrobbler_radio_source_constructed (GObject *object)
        GtkWidget *error_info_bar_content_area;
        GtkWidget *password_info_bar_label;
        GtkWidget *password_info_bar_content_area;
-       GObject *plugin;
-       GtkUIManager *ui_manager;
+       GtkAccelGroup *accel_group;
        RBSourceToolbar *toolbar;
-       char *ui_file;
 
        RB_CHAIN_GOBJECT_METHOD (rb_audioscrobbler_radio_source_parent_class, constructed, object);
 
@@ -447,7 +425,7 @@ rb_audioscrobbler_radio_source_constructed (GObject *object)
        g_object_get (shell,
                      "db", &db,
                      "shell-player", &shell_player,
-                     "ui-manager", &ui_manager,
+                     "accel-group", &accel_group,
                      NULL);
 
        source->priv->art_store = rb_ext_db_new ("album-art");
@@ -457,7 +435,7 @@ rb_audioscrobbler_radio_source_constructed (GObject *object)
        gtk_container_add (GTK_CONTAINER (source), main_vbox);
 
        /* toolbar */
-       toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (source), ui_manager);
+       toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (source), accel_group);
        gtk_box_pack_start (GTK_BOX (main_vbox), GTK_WIDGET (toolbar), FALSE, FALSE, 0);
        gtk_widget_show_all (GTK_WIDGET (toolbar));
 
@@ -512,29 +490,12 @@ rb_audioscrobbler_radio_source_constructed (GObject *object)
                                 G_CALLBACK (playing_song_changed_cb),
                                 source, 0);
 
-       /* merge ui */
-       g_object_get (source, "plugin", &plugin, NULL);
-       ui_file = rb_find_plugin_data_file (plugin, "audioscrobbler-radio-ui.xml");
-       source->priv->ui_merge_id = gtk_ui_manager_add_ui_from_file (ui_manager, ui_file, NULL);
-
-       /* 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_display_page (shell, RB_DISPLAY_PAGE (source), RB_DISPLAY_PAGE 
(source->priv->parent));
 
        g_object_unref (shell);
        g_object_unref (shell_player);
        g_object_unref (db);
-       g_object_unref (plugin);
-       g_object_unref (ui_manager);
-       g_free (ui_file);
+       g_object_unref (accel_group);
 }
 
 static void
@@ -1340,25 +1301,17 @@ password_info_bar_response_cb (GtkInfoBar *info_bar,
        old_api_shake_hands (source);
 }
 
-static void
-rename_station_action_cb (GtkAction *action, RBAudioscrobblerRadioSource *source)
+static gboolean
+impl_can_remove (RBDisplayPage *page)
 {
-       RBShell *shell;
-       RBDisplayPageTree *page_tree;
-
-       g_object_get (source, "shell", &shell, NULL);
-       g_object_get (shell, "display-page-tree", &page_tree, NULL);
-
-       rb_display_page_tree_edit_source_name (page_tree, RB_SOURCE (source));
-
-       g_object_unref (shell);
-       g_object_unref (page_tree);
+       return TRUE;
 }
 
 static void
-delete_station_action_cb (GtkAction *action, RBAudioscrobblerRadioSource *source)
+impl_remove (RBDisplayPage *page)
 {
-       rb_audioscrobbler_profile_page_remove_radio_station (source->priv->parent, RB_SOURCE (source));
+       RBAudioscrobblerRadioSource *source = RB_AUDIOSCROBBLER_RADIO_SOURCE (page);
+       rb_audioscrobbler_profile_page_remove_radio_station (source->priv->parent, RB_SOURCE (page));
 }
 
 static void
@@ -1405,19 +1358,11 @@ impl_handle_eos (RBSource *asource)
        return RB_SOURCE_EOF_NEXT;
 }
 
-static gboolean
-impl_show_popup (RBDisplayPage *page)
-{
-       _rb_display_page_show_popup (page, AUDIOSCROBBLER_RADIO_SOURCE_POPUP_PATH);
-       return TRUE;
-}
-
 static void
 impl_delete_thyself (RBDisplayPage *page)
 {
        RBAudioscrobblerRadioSource *source;
        RBShell *shell;
-       GtkUIManager *ui_manager;
        RhythmDB *db;
        GtkTreeIter iter;
        gboolean loop;
@@ -1426,12 +1371,9 @@ impl_delete_thyself (RBDisplayPage *page)
 
        source = RB_AUDIOSCROBBLER_RADIO_SOURCE (page);
 
-       g_object_get (source, "shell", &shell, "ui-manager", &ui_manager, NULL);
+       g_object_get (source, "shell", &shell, NULL);
        g_object_get (shell, "db", &db, NULL);
 
-       /* unmerge ui */
-       gtk_ui_manager_remove_ui (ui_manager, source->priv->ui_merge_id);
-
        /* Ensure playing entry isn't deleted twice */
        source->priv->playing_entry = NULL;
 
@@ -1450,7 +1392,6 @@ impl_delete_thyself (RBDisplayPage *page)
        rhythmdb_commit (db);
 
        g_object_unref (shell);
-       g_object_unref (ui_manager);
        g_object_unref (db);
 }
 
diff --git a/plugins/brasero-disc-recorder/rb-disc-recorder-plugin.c 
b/plugins/brasero-disc-recorder/rb-disc-recorder-plugin.c
index 160501c..6dbe8d8 100644
--- a/plugins/brasero-disc-recorder/rb-disc-recorder-plugin.c
+++ b/plugins/brasero-disc-recorder/rb-disc-recorder-plugin.c
@@ -57,6 +57,7 @@
 #include "rb-playlist-source.h"
 #include "rb-dialog.h"
 #include "rb-file-helpers.h"
+#include "rb-application.h"
 
 #define RB_TYPE_DISC_RECORDER_PLUGIN           (rb_disc_recorder_plugin_get_type ())
 #define RB_DISC_RECORDER_PLUGIN(o)             (G_TYPE_CHECK_INSTANCE_CAST ((o), 
RB_TYPE_DISC_RECORDER_PLUGIN, RBDiscRecorderPlugin))
@@ -69,11 +70,12 @@ typedef struct
 {
        PeasExtensionBase parent;
 
-       GtkActionGroup *action_group;
-       guint           ui_merge_id;
-
        RBDisplayPage  *selected_page;
        guint           enabled : 1;
+
+       GAction        *burn_action;
+       GAction        *copy_action;
+
 } RBDiscRecorderPlugin;
 
 typedef struct
@@ -86,19 +88,9 @@ G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module);
 RB_DEFINE_PLUGIN(RB_TYPE_DISC_RECORDER_PLUGIN, RBDiscRecorderPlugin, rb_disc_recorder_plugin,)
 
 static void rb_disc_recorder_plugin_init (RBDiscRecorderPlugin *plugin);
-static void cmd_burn_source (GtkAction          *action,
-                            RBDiscRecorderPlugin *pi);
-static void cmd_duplicate_cd (GtkAction          *action,
-                             RBDiscRecorderPlugin *pi);
-
-static GtkActionEntry rb_disc_recorder_plugin_actions [] = {
-       { "MusicPlaylistBurnToDiscPlaylist", "media-optical-audio-new", N_("_Create Audio CD..."), NULL,
-         N_("Create an audio CD from playlist"),
-         G_CALLBACK (cmd_burn_source) },
-       { "MusicAudioCDDuplicate", "media-optical-copy", N_("Duplicate Audio CD..."), NULL,
-         N_("Create a copy of this audio CD"),
-         G_CALLBACK (cmd_duplicate_cd) },
-};
+
+static void burn_playlist_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data);
+static void duplicate_cd_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data);
 
 #define RB_RECORDER_ERROR rb_disc_recorder_error_quark ()
 
@@ -468,18 +460,16 @@ source_burn (RBDiscRecorderPlugin *pi,
 }
 
 static void
-cmd_burn_source (GtkAction            *action,
-                RBDiscRecorderPlugin *pi)
+burn_playlist_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
 {
-       if (pi->selected_page != NULL && RB_IS_SOURCE (pi->selected_page)) {
-               source_burn (pi, RB_SOURCE (pi->selected_page));
-       }
+       RBDiscRecorderPlugin *pi = RB_DISC_RECORDER_PLUGIN (data);
+       source_burn (pi, RB_SOURCE (pi->selected_page));
 }
 
 static void
-cmd_duplicate_cd (GtkAction            *action,
-                 RBDiscRecorderPlugin  *pi)
+duplicate_cd_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
 {
+       RBDiscRecorderPlugin *pi = RB_DISC_RECORDER_PLUGIN (data);
        gchar *device;
        GVolume *volume;
 
@@ -503,13 +493,11 @@ playlist_entries_changed (GtkTreeModel         *model,
                          RhythmDBEntry        *entry,
                          RBDiscRecorderPlugin *pi)
 {
-       int        num_tracks;
-       GtkAction *action;
+       int num_tracks;
 
        num_tracks = gtk_tree_model_iter_n_children (model, NULL);
 
-       action = gtk_action_group_get_action (pi->action_group, "MusicPlaylistBurnToDiscPlaylist");
-       gtk_action_set_sensitive (action, (num_tracks > 0));
+       g_simple_action_set_enabled (G_SIMPLE_ACTION (pi->burn_action), (num_tracks > 0));
 }
 
 static void
@@ -566,7 +554,6 @@ static void
 update_source (RBDiscRecorderPlugin *pi,
               RBShell            *shell)
 {
-       GtkAction *burn_action, *copy_action;
        gboolean   playlist_active, is_audiocd_active;
        RBDisplayPage *selected_page;
        const char *page_type;
@@ -592,11 +579,6 @@ update_source (RBDiscRecorderPlugin *pi,
                is_audiocd_active = FALSE;
        }
 
-       burn_action = gtk_action_group_get_action (pi->action_group,
-                                                  "MusicPlaylistBurnToDiscPlaylist");
-       copy_action = gtk_action_group_get_action (pi->action_group,
-                                                  "MusicAudioCDDuplicate");
-
        if (pi->enabled && playlist_active && rb_disc_recorder_has_burner (pi)) {
                RhythmDBQueryModel *model;
 
@@ -613,15 +595,14 @@ update_source (RBDiscRecorderPlugin *pi,
 
                playlist_entries_changed (GTK_TREE_MODEL (model), NULL, pi);
                g_object_unref (model);
-               gtk_action_set_visible (burn_action, TRUE);
        } else {
-               gtk_action_set_visible (burn_action, FALSE);
+               g_simple_action_set_enabled (G_SIMPLE_ACTION (pi->burn_action), FALSE);
        }
 
        if (pi->enabled && is_audiocd_active && is_copy_available (pi)) {
-               gtk_action_set_visible (copy_action, TRUE);
+               g_simple_action_set_enabled (G_SIMPLE_ACTION (pi->copy_action), TRUE);
        } else {
-               gtk_action_set_visible (copy_action, FALSE);
+               g_simple_action_set_enabled (G_SIMPLE_ACTION (pi->copy_action), FALSE);
        }
 
        if (pi->selected_page != NULL) {
@@ -639,29 +620,18 @@ shell_selected_page_notify_cb (RBShell            *shell,
        update_source (pi, shell);
 }
 
-static struct ui_paths {
-       const char *path;
-       gboolean for_burn;
-       gboolean for_copy;
-} ui_paths[] = {
-       { "/QueueSourceToolBar/PluginPlaceholder", TRUE, FALSE },
-       { "/QueueSourcePopup/PluginPlaceholder", TRUE, FALSE },
-       { "/PlaylistSourcePopup/PluginPlaceholder", TRUE, FALSE },
-       { "/AutoPlaylistSourcePopup/PluginPlaceholder", TRUE, FALSE },
-       { "/AutoPlaylistSourceToolBar/PluginPlaceholder", TRUE, FALSE },
-       { "/StaticPlaylistSourceToolBar/PluginPlaceholder", TRUE, FALSE },
-       { "/AudioCdSourcePopup/PluginPlaceholder", FALSE, TRUE },
-       { "/AudioCdSourceToolBar/PluginPlaceholder", FALSE, TRUE },
-};
-
 static void
 impl_activate (PeasActivatable *plugin)
 {
        RBDiscRecorderPlugin *pi = RB_DISC_RECORDER_PLUGIN (plugin);
-       GtkUIManager         *uimanager = NULL;
-       GtkAction            *action;
+       GMenuItem            *item;
        RBShell              *shell;
-       int                   i;
+       GApplication         *app;
+
+       GActionEntry actions[] = {
+               { "burn-playlist", burn_playlist_action_cb },
+               { "burn-duplicate-cd", duplicate_cd_action_cb }
+       };
 
        g_object_get (pi, "object", &shell, NULL);
 
@@ -671,54 +641,27 @@ impl_activate (PeasActivatable *plugin)
 
        brasero_media_library_start ();
 
-       g_object_get (shell,
-                     "ui-manager", &uimanager,
-                     NULL);
-
        g_signal_connect_object (G_OBJECT (shell),
                                 "notify::selected-page",
                                 G_CALLBACK (shell_selected_page_notify_cb),
                                 pi, 0);
 
-       /* add UI */
-       pi->action_group = gtk_action_group_new ("DiscRecorderActions");
-       gtk_action_group_set_translation_domain (pi->action_group,
-                                                GETTEXT_PACKAGE);
-       gtk_action_group_add_actions (pi->action_group,
-                                     rb_disc_recorder_plugin_actions, G_N_ELEMENTS 
(rb_disc_recorder_plugin_actions),
-                                     pi);
-       gtk_ui_manager_insert_action_group (uimanager, pi->action_group, 0);
-       pi->ui_merge_id = gtk_ui_manager_new_merge_id (uimanager);
-       for (i = 0; i < G_N_ELEMENTS (ui_paths); i++) {
-               if (ui_paths[i].for_burn)
-                       gtk_ui_manager_add_ui (uimanager,
-                                              pi->ui_merge_id,
-                                              ui_paths[i].path,
-                                              "MusicPlaylistBurnToDiscPlaylistMenu",
-                                              "MusicPlaylistBurnToDiscPlaylist",
-                                              GTK_UI_MANAGER_AUTO,
-                                              FALSE);
-               if (ui_paths[i].for_copy)
-                       gtk_ui_manager_add_ui (uimanager,
-                                              pi->ui_merge_id,
-                                              ui_paths[i].path,
-                                              "MusicAudioCDDuplicateMenu",
-                                              "MusicAudioCDDuplicate",
-                                              GTK_UI_MANAGER_AUTO,
-                                              FALSE);
-       }
-       g_object_unref (uimanager);
+       app = g_application_get_default ();
+       g_action_map_add_action_entries (G_ACTION_MAP (app), actions, G_N_ELEMENTS (actions), pi);
+       pi->burn_action = g_action_map_lookup_action (G_ACTION_MAP (app), "burn-playlist");
+       pi->copy_action = g_action_map_lookup_action (G_ACTION_MAP (app), "burn-duplicate-cd");
 
-        action = gtk_action_group_get_action (pi->action_group, "MusicPlaylistBurnToDiscPlaylist");
-       /* Translators: this is the toolbar button label for */
-       /* Create Audio CD action                            */
-       g_object_set (action, "short-label", _("Burn"), NULL);
+       item = g_menu_item_new (_("Create Audio CD..."), "app.burn-playlist");
+       rb_application_add_plugin_menu_item (RB_APPLICATION (g_application_get_default ()),
+                                            "playlist-menu",
+                                            "burn-playlist",
+                                            item);
 
-        action = gtk_action_group_get_action (pi->action_group,
-                                             "MusicAudioCDDuplicate");
-       /* Translators: this is the toolbar button label for */
-       /* Duplicate Audio CD action                         */
-       g_object_set (action, "short-label", _("Copy CD"), NULL);
+       item = g_menu_item_new (_("Duplicate Audio CD..."), "app.burn-duplicate-cd");
+       rb_application_add_plugin_menu_item (RB_APPLICATION (g_application_get_default ()),
+                                            "audiocd-toolbar",
+                                            "burn-duplicate-cd",
+                                            item);
 
        update_source (pi, shell);
 
@@ -729,7 +672,6 @@ static void
 impl_deactivate        (PeasActivatable *plugin)
 {
        RBDiscRecorderPlugin *pi = RB_DISC_RECORDER_PLUGIN (plugin);
-       GtkUIManager         *uimanager = NULL;
        RBShell              *shell;
 
        g_object_get (pi, "object", &shell, NULL);
@@ -747,23 +689,15 @@ impl_deactivate   (PeasActivatable *plugin)
 
        g_signal_handlers_disconnect_by_func (shell, shell_selected_page_notify_cb, pi);
 
-       g_object_get (shell,
-                     "ui-manager", &uimanager,
-                     NULL);
-
-       gtk_ui_manager_remove_ui (uimanager, pi->ui_merge_id);
-       gtk_ui_manager_remove_action_group (uimanager, pi->action_group);
-
-       g_object_unref (uimanager);
-
        /* NOTE: don't deactivate libbrasero-media as it could be in use somewhere else */
+       rb_application_remove_plugin_menu_item (RB_APPLICATION (g_application_get_default ()),
+                                               "playlist-menu",
+                                               "burn-playlist");
+       rb_application_remove_plugin_menu_item (RB_APPLICATION (g_application_get_default ()),
+                                               "audiocd-toolbar",
+                                               "burn-duplicate-cd");
 
        g_object_unref (shell);
-
-       if (pi->action_group != NULL) {
-               g_object_unref (pi->action_group);
-               pi->action_group = NULL;
-       }
 }
 
 G_MODULE_EXPORT void
diff --git a/plugins/context/ContextView.py b/plugins/context/ContextView.py
index fa92bc5..f7515ce 100644
--- a/plugins/context/ContextView.py
+++ b/plugins/context/ContextView.py
@@ -32,23 +32,13 @@ import LyricsTab as lt
 import LinksTab as lit
 
 import rb
-from gi.repository import GObject, Gtk, Gdk, Pango, Gio
+from gi.repository import GObject, Gtk, Gdk, Pango, Gio, GLib
 from gi.repository import RB
 from gi.repository import WebKit
 
 import gettext
 gettext.install('rhythmbox', RB.locale_dir())
 
-context_ui = """
-<ui>
-    <menubar name="MenuBar">
-        <menu name="ViewMenu" action="View">
-            <menuitem name="Context" action="ToggleContextView" />
-        </menu>
-    </menubar>
-</ui>
-"""
-
 class ContextView (GObject.GObject):
 
     def __init__ (self, shell, plugin):
@@ -91,16 +81,16 @@ class ContextView (GObject.GObject):
         self.current = 'artist'
         self.tab[self.current].activate ()
 
-        # Add button to toggle visibility of pane
-        self.action = ('ToggleContextView','gtk-info', _("Conte_xt Pane"),
-                        None, _("Change the visibility of the context pane"),
-                        self.toggle_visibility, True)
-        self.action_group = Gtk.ActionGroup(name='ContextPluginActions')
-        self.action_group.add_toggle_actions([self.action])
-        uim = self.shell.props.ui_manager
-        uim.insert_action_group (self.action_group, 0)
-        self.ui_id = uim.add_ui_from_string(context_ui)
-        uim.ensure_update()
+       app = shell.props.application
+       action = Gio.SimpleAction.new_stateful("view-context-pane", None, GLib.Variant.new_boolean(True))
+       action.connect("activate", self.toggle_visibility, None)
+
+       window = shell.props.window
+       window.add_action(action)
+
+       item = Gio.MenuItem.new(label=_("Context Pane"), detailed_action="win.view-context-pane")
+       app.add_plugin_menu_item("view", "view-context-pane", item)
+
 
     def deactivate (self, shell):
         self.shell = None
@@ -122,9 +112,9 @@ class ContextView (GObject.GObject):
        self.websettings = None
        self.buttons = None
        self.top_five_list = None
-        uim = shell.props.ui_manager
-        uim.remove_ui (self.ui_id)
-        uim.remove_action_group (self.action_group)
+
+       app = shell.props.application
+       app.remove_plugin_menu_item("view", "view-context-pane")
 
     def connect_signals(self):
         self.player_cb_ids = ( self.sp.connect ('playing-changed', self.playing_changed_cb),
@@ -145,13 +135,13 @@ class ContextView (GObject.GObject):
         for key, id in self.tab_cb_ids:
             self.tab[key].disconnect (id)
 
-    def toggle_visibility (self, action):
-        if not self.visible:
-            self.shell.add_widget (self.vbox, RB.ShellUILocation.RIGHT_SIDEBAR, True, True)
-            self.visible = True
-        else:
+    def toggle_visibility (self, action, parameter, data):
+       if self.visible:
             self.shell.remove_widget (self.vbox, RB.ShellUILocation.RIGHT_SIDEBAR)
             self.visible = False
+        else:
+            self.shell.add_widget (self.vbox, RB.ShellUILocation.RIGHT_SIDEBAR, True, True)
+            self.visible = True
 
     def change_tab (self, tab, newtab):
         print "swapping tab from %s to %s" % (self.current, newtab)
diff --git a/plugins/daap/Makefile.am b/plugins/daap/Makefile.am
index e82870a..b950bef 100644
--- a/plugins/daap/Makefile.am
+++ b/plugins/daap/Makefile.am
@@ -69,10 +69,7 @@ INCLUDES += $(GNOME_KEYRING_CFLAGS)
 endif
 
 gtkbuilderdir = $(plugindatadir)
-gtkbuilder_DATA = daap-prefs.ui
-
-uixmldir = $(plugindatadir)
-uixml_DATA = daap-ui.xml
+gtkbuilder_DATA = daap-prefs.ui daap-toolbar.ui
 
 plugin_in_files = daap.plugin.in
 
diff --git a/plugins/daap/daap-toolbar.ui b/plugins/daap/daap-toolbar.ui
new file mode 100644
index 0000000..ba8cece
--- /dev/null
+++ b/plugins/daap/daap-toolbar.ui
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="daap-toolbar">
+    <section>
+      <submenu>
+       <attribute name="label" translatable="yes">Edit</attribute>
+       <attribute name="rb-menu-link">edit-menu</attribute>
+      </submenu>
+      <item>
+       <attribute name="label" translatable="yes">Browse</attribute>
+       <attribute name="rb-property-bind">show-browser</attribute>
+       <attribute name="accel">&lt;Primary&gt;b</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">View All</attribute>
+       <attribute name="action">app.source-view-all</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Disconnect</attribute>
+       <attribute name="action">app.daap-disconnect</attribute>
+      </item>
+    </section>
+  </menu>
+</interface>
diff --git a/plugins/daap/rb-daap-plugin.c b/plugins/daap/rb-daap-plugin.c
index 9f9b889..b2f115f 100644
--- a/plugins/daap/rb-daap-plugin.c
+++ b/plugins/daap/rb-daap-plugin.c
@@ -51,6 +51,7 @@
 #include "rb-builder-helpers.h"
 #include "rb-uri-dialog.h"
 #include "rb-display-page-group.h"
+#include "rb-application.h"
 
 #include "rb-daap-container-record.h"
 #include "rb-daap-record-factory.h"
@@ -92,8 +93,7 @@ struct _RBDaapPlugin
        gboolean sharing;
        gboolean shutdown;
 
-       GtkActionGroup *daap_action_group;
-       guint daap_ui_merge_id;
+       GSimpleAction *new_share_action;
 
        DMAPMdnsBrowser *mdns_browser;
 
@@ -121,8 +121,7 @@ G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module);
 
 static void rb_daap_plugin_init (RBDaapPlugin *plugin);
 
-static void rb_daap_plugin_cmd_disconnect (GtkAction *action, RBSource *source);
-static void rb_daap_plugin_cmd_connect (GtkAction *action, RBDaapPlugin *plugin);
+static void new_share_action_cb (GSimpleAction *, GVariant *, gpointer);
 
 static void create_pixbufs (RBDaapPlugin *plugin);
 static void start_browsing (RBDaapPlugin *plugin);
@@ -149,20 +148,6 @@ RB_DEFINE_PLUGIN(RB_TYPE_DAAP_PLUGIN,
                 (G_IMPLEMENT_INTERFACE_DYNAMIC (PEAS_GTK_TYPE_CONFIGURABLE,
                                                peas_gtk_configurable_iface_init)))
 
-static GtkActionEntry rb_daap_plugin_actions [] =
-{
-       { "MusicNewDAAPShare", GTK_STOCK_CONNECT, N_("Connect to _DAAP share..."), NULL,
-         N_("Connect to a new DAAP share"),
-         G_CALLBACK (rb_daap_plugin_cmd_connect) },
-};
-
-static GtkActionEntry rb_daap_source_actions[] =
-{
-       { "DaapSourceDisconnect", GTK_STOCK_DISCONNECT, N_("_Disconnect"), NULL,
-         N_("Disconnect from DAAP share"),
-         G_CALLBACK (rb_daap_plugin_cmd_disconnect) },
-};
-
 static void
 rb_daap_plugin_init (RBDaapPlugin *plugin)
 {
@@ -185,9 +170,9 @@ impl_activate (PeasActivatable *bplugin)
 {
        RBDaapPlugin *plugin = RB_DAAP_PLUGIN (bplugin);
        gboolean no_registration;
-       GtkUIManager *uimanager = NULL;
-       char *uifile;
        RBShell *shell;
+       GApplication *app;
+       GMenuModel *menu;
 
        plugin->shutdown = FALSE;
 
@@ -208,29 +193,15 @@ impl_activate (PeasActivatable *bplugin)
 
        create_pixbufs (plugin);
 
-       g_object_get (shell, "ui-manager", &uimanager, NULL);
-
-       /* add actions */
-       plugin->daap_action_group = gtk_action_group_new ("DaapActions");
-       gtk_action_group_set_translation_domain (plugin->daap_action_group,
-                                                GETTEXT_PACKAGE);
-       gtk_action_group_add_actions (plugin->daap_action_group,
-                                     rb_daap_plugin_actions, G_N_ELEMENTS (rb_daap_plugin_actions),
-                                     plugin);
-       _rb_action_group_add_display_page_actions (plugin->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->daap_action_group, 0);
-
-       /* add UI */
-       uifile = rb_find_plugin_data_file (G_OBJECT (plugin), "daap-ui.xml");
-       if (uifile != NULL) {
-               plugin->daap_ui_merge_id = gtk_ui_manager_add_ui_from_file (uimanager, uifile, NULL);
-               g_free (uifile);
-       }
+       app = g_application_get_default ();
+       plugin->new_share_action = g_simple_action_new ("daap-new-share", NULL);
+       g_signal_connect (plugin->new_share_action, "activate", G_CALLBACK (new_share_action_cb), plugin);
+       g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (plugin->new_share_action));
 
-       g_object_unref (uimanager);
+       rb_application_add_plugin_menu_item (RB_APPLICATION (app),
+                                            "display-page-add",
+                                            "daap-new-share",
+                                            g_menu_item_new (_("Connect to DAAP share..."), 
"app.daap-new-share"));
 
        /*
         * Don't use daap when the no-registration flag is set.
@@ -256,7 +227,6 @@ static void
 impl_deactivate        (PeasActivatable *bplugin)
 {
        RBDaapPlugin *plugin = RB_DAAP_PLUGIN (bplugin);
-       GtkUIManager *uimanager = NULL;
        RBShell *shell;
 
        rb_debug ("Shutting down DAAP plugin");
@@ -266,6 +236,9 @@ impl_deactivate     (PeasActivatable *bplugin)
        unregister_daap_dbus_iface (plugin);
        plugin->shutdown = TRUE;
 
+       rb_application_remove_plugin_menu_item (RB_APPLICATION (g_application_get_default ()),
+                                               "display-page-add",
+                                               "daap-new-share");
        if (plugin->sharing)
                rb_daap_sharing_shutdown (shell);
 
@@ -280,13 +253,6 @@ impl_deactivate    (PeasActivatable *bplugin)
 
        g_object_unref (plugin->dacp_share);
 
-       g_object_get (shell, "ui-manager", &uimanager, NULL);
-
-       gtk_ui_manager_remove_ui (uimanager, plugin->daap_ui_merge_id);
-       gtk_ui_manager_remove_action_group (uimanager, plugin->daap_action_group);
-
-       g_object_unref (uimanager);
-
        if (plugin->daap_share_pixbuf != NULL) {
                g_object_unref (plugin->daap_share_pixbuf);
                plugin->daap_share_pixbuf = NULL;
@@ -622,19 +588,6 @@ libdmapsharing_debug (const char *domain,
        }
 }
 
-/* daap share connect/disconnect commands */
-
-static void
-rb_daap_plugin_cmd_disconnect (GtkAction *action, RBSource *source)
-{
-       if (!RB_IS_DAAP_SOURCE (source)) {
-               g_warning ("got non-Daap source for Daap action");
-               return;
-       }
-
-       rb_daap_source_disconnect (RB_DAAP_SOURCE (source));
-}
-
 static void
 new_daap_share_location_added_cb (RBURIDialog *dialog,
                                  const char *location,
@@ -673,9 +626,9 @@ new_daap_share_response_cb (GtkDialog *dialog, int response, gpointer meh)
 }
 
 static void
-rb_daap_plugin_cmd_connect (GtkAction *action,
-                           RBDaapPlugin *plugin)
+new_share_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
+       RBDaapPlugin *plugin = RB_DAAP_PLUGIN (data);
        GtkWidget *dialog;
 
        dialog = rb_uri_dialog_new (_("New DAAP share"), _("Host:port of DAAP share:"));
@@ -686,7 +639,6 @@ rb_daap_plugin_cmd_connect (GtkAction *action,
        g_signal_connect (dialog, "response", G_CALLBACK (new_daap_share_response_cb), NULL);
 }
 
-
 /* daap:// URI -> RBDAAPSource mapping */
 
 static gboolean
diff --git a/plugins/daap/rb-daap-source.c b/plugins/daap/rb-daap-source.c
index 82cb91e..8140eba 100644
--- a/plugins/daap/rb-daap-source.c
+++ b/plugins/daap/rb-daap-source.c
@@ -72,8 +72,8 @@ static void rb_daap_source_get_property  (GObject *object,
                                          GParamSpec *pspec);
 
 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 void disconnect_action_cb (GSimpleAction *, GVariant *, gpointer);
 
 static void rb_daap_entry_type_class_init (RBDAAPEntryTypeClass *klass);
 static void rb_daap_entry_type_init (RBDAAPEntryType *etype);
@@ -81,8 +81,6 @@ GType rb_daap_entry_type_get_type (void);
 
 struct RBDAAPSourcePrivate
 {
-       GtkActionGroup *action_group;
-
        char *service_name;
        char *host;
        guint port;
@@ -178,7 +176,6 @@ rb_daap_source_class_init (RBDAAPSourceClass *klass)
 
        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;
@@ -294,6 +291,24 @@ rb_daap_source_get_property (GObject *object,
        }
 }
 
+static void
+impl_constructed (GObject *object)
+{
+       RBShell *shell;
+       GActionEntry actions[] = {
+               { "daap-disconnect", disconnect_action_cb },
+       };
+
+       RB_CHAIN_GOBJECT_METHOD (rb_daap_source_parent_class, constructed, object);
+
+       g_object_get (object, "shell", &shell, NULL);
+       _rb_add_display_page_actions (G_ACTION_MAP (g_application_get_default ()),
+                                     G_OBJECT (shell),
+                                     actions,
+                                     G_N_ELEMENTS (actions));
+       g_object_unref (shell);
+}
+
 
 RBSource *
 rb_daap_source_new (RBShell *shell,
@@ -310,6 +325,8 @@ rb_daap_source_new (RBShell *shell,
        RhythmDB *db;
        char *entry_type_name;
        GSettings *settings;
+       GtkBuilder *builder;
+       GMenu *toolbar;
 
        g_object_get (shell, "db", &db, NULL);
        entry_type_name = g_strdup_printf ("daap:%s:%s:%s", service_name, name, host);
@@ -326,6 +343,10 @@ rb_daap_source_new (RBShell *shell,
 
        icon = rb_daap_plugin_get_icon (RB_DAAP_PLUGIN (plugin), password_protected, FALSE);
 
+       builder = rb_builder_load_plugin_file (plugin, "daap-toolbar.ui", NULL);
+       toolbar = G_MENU (gtk_builder_get_object (builder, "daap-toolbar"));
+       rb_application_link_shared_menus (RB_APPLICATION (g_application_get_default ()), toolbar);
+
        settings = g_settings_new ("org.gnome.rhythmbox.plugins.daap");
        source = RB_SOURCE (g_object_new (RB_TYPE_DAAP_SOURCE,
                                          "service-name", service_name,
@@ -340,16 +361,16 @@ rb_daap_source_new (RBShell *shell,
                                          "plugin", G_OBJECT (plugin),
                                          "load-status", RB_SOURCE_LOAD_STATUS_NOT_LOADED,
                                          "settings", g_settings_get_child (settings, "source"),
-                                         "toolbar-path", "/DAAPSourceToolBar",
+                                         "toolbar-menu", toolbar,
                                          NULL));
        g_object_unref (settings);
+       g_object_unref (builder);
 
        if (icon != NULL) {
                g_object_unref (icon);
        }
 
-       rb_shell_register_entry_type_for_source (shell, source,
-                                                entry_type);
+       rb_shell_register_entry_type_for_source (shell, source, entry_type);
 
        return source;
 }
@@ -792,11 +813,11 @@ rb_daap_source_disconnect (RBDAAPSource *daap_source)
        rb_debug ("DAAP connection finished");
 }
 
-static gboolean
-rb_daap_source_show_popup (RBDisplayPage *page)
+static void
+disconnect_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
-       _rb_display_page_show_popup (page, "/DAAPSourcePopup");
-       return TRUE;
+       RBDaapSource *source = RB_DAAP_SOURCE (data);
+       rb_daap_source_disconnect (source);
 }
 
 SoupMessageHeaders *
diff --git a/plugins/fmradio/Makefile.am b/plugins/fmradio/Makefile.am
index 06a886c..c028953 100644
--- a/plugins/fmradio/Makefile.am
+++ b/plugins/fmradio/Makefile.am
@@ -38,12 +38,12 @@ INCLUDES = \
 plugin_in_files = fmradio.plugin.in
 %.plugin: %.plugin.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) 
$(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
 
-uixmldir = $(plugindatadir)
-uixml_DATA = fmradio-ui.xml
+gtkbuilderdir = $(plugindatadir)
+gtkbuilder_DATA = fmradio-toolbar.ui fmradio-popup.ui
 
 plugin_DATA = $(plugin_in_files:.plugin.in=.plugin)
 
-EXTRA_DIST = $(uixml_DATA) $(plugin_in_files)
+EXTRA_DIST = $(gtkbuilder_DATA) $(plugin_in_files)
 
 CLEANFILES = $(plugin_DATA)
 DISTCLEANFILES = $(plugin_DATA)
diff --git a/plugins/fmradio/fmradio-popup.ui b/plugins/fmradio/fmradio-popup.ui
new file mode 100644
index 0000000..bc30cf2
--- /dev/null
+++ b/plugins/fmradio/fmradio-popup.ui
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="fmradio-popup">
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Delete</attribute>
+       <attribute name="action">app.clipboard-delete</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Properties</attribute>
+       <attribute name="action">app.clipboard-properties</attribute>
+      </item>
+    </section>
+  </menu>
+</interface>
diff --git a/plugins/fmradio/fmradio-toolbar.ui b/plugins/fmradio/fmradio-toolbar.ui
new file mode 100644
index 0000000..b138c60
--- /dev/null
+++ b/plugins/fmradio/fmradio-toolbar.ui
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="fmradio-toolbar">
+    <section>
+      <submenu>
+       <attribute name="label" translatable="yes">Edit</attribute>
+       <attribute name="rb-menu-link">edit-menu</attribute>
+      </submenu>
+      <item>
+       <attribute name="label" translatable="yes">New</attribute>
+       <attribute name="action">app.fmradio-new-station</attribute>
+      </item>
+    </section>
+  </menu>
+</interface>
diff --git a/plugins/fmradio/rb-fm-radio-plugin.c b/plugins/fmradio/rb-fm-radio-plugin.c
index 5407296..46a9e58 100644
--- a/plugins/fmradio/rb-fm-radio-plugin.c
+++ b/plugins/fmradio/rb-fm-radio-plugin.c
@@ -52,7 +52,6 @@ typedef struct _RBFMRadioPluginClass RBFMRadioPluginClass;
 struct _RBFMRadioPlugin {
        PeasExtensionBase parent;
        RBSource *source;
-       guint ui_merge_id;
 };
 
 struct _RBFMRadioPluginClass {
@@ -75,9 +74,7 @@ impl_activate (PeasActivatable *plugin)
 {
        RBFMRadioPlugin *pi = RB_FM_RADIO_PLUGIN (plugin);
        RBRadioTuner *tuner;
-       GtkUIManager *uimanager;
        RBShell *shell;
-       char *filename;
 
        tuner = rb_radio_tuner_new (NULL, NULL);
        if (tuner == NULL)
@@ -87,23 +84,11 @@ impl_activate (PeasActivatable *plugin)
        rb_radio_tuner_update (tuner);
 
        g_object_get (plugin, "object", &shell, NULL);
-       pi->source = rb_fm_radio_source_new (shell, tuner);
+       pi->source = rb_fm_radio_source_new (G_OBJECT (plugin), shell, tuner);
        rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (pi->source), RB_DISPLAY_PAGE_GROUP_LIBRARY);    
  /* devices? */
 
        g_object_unref (tuner);
 
-       filename = rb_find_plugin_data_file (G_OBJECT (plugin), "fmradio-ui.xml");
-       if (filename != NULL) {
-               g_object_get (shell, "ui-manager", &uimanager, NULL);
-               pi->ui_merge_id = gtk_ui_manager_add_ui_from_file (uimanager,
-                                                                  filename,
-                                                                  NULL);
-               g_object_unref (uimanager);
-               g_free(filename);
-       } else {
-               g_warning ("Unable to find file: fmradio-ui.xml");
-       }
-
        g_object_unref (shell);
 }
 
@@ -111,23 +96,11 @@ static void
 impl_deactivate (PeasActivatable *plugin)
 {
        RBFMRadioPlugin *pi = RB_FM_RADIO_PLUGIN (plugin);
-       GtkUIManager *uimanager;
-       RBShell *shell;
 
        if (pi->source) {
                rb_display_page_delete_thyself (RB_DISPLAY_PAGE (pi->source));
                pi->source = NULL;
        }
-
-       if (pi->ui_merge_id) {
-               g_object_get (plugin, "object", &shell, NULL);
-               g_object_get (shell, "ui-manager", &uimanager, NULL);
-               g_object_unref (shell);
-
-               gtk_ui_manager_remove_ui (uimanager, pi->ui_merge_id);
-               g_object_unref (uimanager);
-               pi->ui_merge_id = 0;
-       }
 }
 
 G_MODULE_EXPORT void
diff --git a/plugins/fmradio/rb-fm-radio-source.c b/plugins/fmradio/rb-fm-radio-source.c
index 6b22982..14bdba0 100644
--- a/plugins/fmradio/rb-fm-radio-source.c
+++ b/plugins/fmradio/rb-fm-radio-source.c
@@ -43,6 +43,8 @@
 
 #include "rb-fm-radio-source.h"
 #include "rb-radio-tuner.h"
+#include "rb-builder-helpers.h"
+#include "rb-application.h"
 
 typedef struct _RhythmDBEntryType RBFMRadioEntryType;
 typedef struct _RhythmDBEntryTypeClass RBFMRadioEntryTypeClass;
@@ -55,19 +57,17 @@ static void     rb_fm_radio_source_do_query    (RBFMRadioSource *self);
 static void     rb_fm_radio_source_songs_view_sort_order_changed (GObject *obj,
                                                                  GParamSpec *pspec,
                                                                  RBFMRadioSource *self);
-static void      rb_fm_radio_source_songs_view_show_popup (
+static void     rb_fm_radio_source_songs_view_show_popup (
                                                RBEntryView *view,
                                                gboolean over_entry,
                                                RBFMRadioSource *self);
 
-static void      rb_fm_radio_source_cmd_new_station (GtkAction *action,
-                                                    RBFMRadioSource *self);
+static void    new_station_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data);
 
 static void playing_entry_changed (RBShellPlayer *player, RhythmDBEntry *entry,
                                   RBFMRadioSource *self);
 
 static void         impl_delete         (RBSource *source);
-static gboolean     impl_show_popup     (RBDisplayPage *page);
 static RBEntryView *impl_get_entry_view (RBSource *source);
 
 static void rb_fm_radio_entry_type_class_init (RBFMRadioEntryTypeClass *klass);
@@ -84,13 +84,7 @@ struct _RBFMRadioSourcePrivate {
 
        RBRadioTuner *tuner;
 
-       GtkActionGroup *action_group;
-};
-
-static GtkActionEntry rb_fm_radio_source_actions[] = {
-       { "MusicNewFMRadioStation", GTK_STOCK_NEW, N_("New FM R_adio Station"),
-         NULL, N_("Create a new FM Radio station"),
-         G_CALLBACK (rb_fm_radio_source_cmd_new_station) },
+       GMenuModel *popup;
 };
 
 G_DEFINE_DYNAMIC_TYPE (RBFMRadioSource, rb_fm_radio_source, RB_TYPE_SOURCE);
@@ -126,14 +120,11 @@ 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;
 
-       page_class->show_popup = impl_show_popup;
-
        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;
@@ -161,8 +152,11 @@ rb_fm_radio_source_constructed (GObject *object)
        RBFMRadioSource *self;
        RBShell *shell;
        RBSourceToolbar *toolbar;
-       GtkUIManager *ui_manager;
+       GtkAccelGroup *accel_group;
        GtkWidget *grid;
+       GActionEntry actions[] = {
+               { "fmradio-new-station", new_station_action_cb }
+       };
 
        RB_CHAIN_GOBJECT_METHOD (rb_fm_radio_source_parent_class, constructed, object);
        self = RB_FM_RADIO_SOURCE (object);
@@ -174,19 +168,17 @@ rb_fm_radio_source_constructed (GObject *object)
        g_object_get (shell,
                      "db", &self->priv->db,
                      "shell-player", &self->priv->player,
-                     "ui-manager", &ui_manager,
+                     "accel-group", &accel_group,
                      NULL);
        g_object_unref (shell);
 
-       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),
-               self);
+       _rb_add_display_page_actions (G_ACTION_MAP (g_application_get_default ()),
+                                     G_OBJECT (shell),
+                                     actions,
+                                     G_N_ELEMENTS (actions));
 
-       toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (self), ui_manager);
-       g_object_unref (toolbar);
+       toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (self), accel_group);
+       g_object_unref (accel_group);
 
        self->priv->stations = rb_entry_view_new (self->priv->db,
                                                  G_OBJECT (self->priv->player),
@@ -223,11 +215,13 @@ rb_fm_radio_source_constructed (GObject *object)
 }
 
 RBSource *
-rb_fm_radio_source_new (RBShell *shell, RBRadioTuner *tuner)
+rb_fm_radio_source_new (GObject *plugin, RBShell *shell, RBRadioTuner *tuner)
 {
        RBFMRadioSource *self;
        RhythmDBEntryType *entry_type;
        RhythmDB *db;
+       GtkBuilder *builder;
+       GMenu *toolbar;
 
        g_object_get (shell, "db", &db, NULL);
 
@@ -241,16 +235,21 @@ rb_fm_radio_source_new (RBShell *shell, RBRadioTuner *tuner)
                rhythmdb_register_entry_type (db, entry_type);
        }
 
+       builder = rb_builder_load_plugin_file (plugin, "fmradio-toolbar.ui", NULL);
+       toolbar = G_MENU (gtk_builder_get_object (builder, "fmradio-toolbar"));
+       rb_application_link_shared_menus (RB_APPLICATION (g_application_get_default ()), toolbar);
+
        self = g_object_new (RB_TYPE_FM_RADIO_SOURCE,
                             "name", _("FM Radio"),
                             "shell", shell,
                             "entry-type", entry_type,
-                            "toolbar-path", "/FMRadioSourceToolBar",
+                            "toolbar-menu", toolbar,
                             NULL);
        self->priv->tuner = g_object_ref (tuner);
        rb_shell_register_entry_type_for_source (shell, RB_SOURCE (self),
                                                 entry_type);
        g_object_unref (db);
+       g_object_unref (builder);
        return RB_SOURCE (self);
 }
 
@@ -274,11 +273,6 @@ rb_fm_radio_source_dispose (GObject *object)
                self->priv->tuner = NULL;
        }
 
-       if (self->priv->action_group) {
-               g_object_unref (self->priv->action_group);
-               self->priv->action_group = NULL;
-       }
-
        G_OBJECT_CLASS (rb_fm_radio_source_parent_class)->dispose (object);
 }
 
@@ -312,15 +306,34 @@ rb_fm_radio_source_songs_view_sort_order_changed (GObject *object, GParamSpec *p
 static void
 rb_fm_radio_source_songs_view_show_popup (RBEntryView *view,
                                          gboolean over_entry,
-                                         RBFMRadioSource *self)
+                                         RBFMRadioSource *source)
 {
-       if (self == NULL)
+       GtkWidget *menu;
+
+       if (over_entry == FALSE)
                return;
 
-       if (over_entry)
-               _rb_display_page_show_popup (RB_DISPLAY_PAGE (self), "/FMRadioViewPopup");
-       else
-               _rb_display_page_show_popup (RB_DISPLAY_PAGE (self), "/FMRadioSourcePopup");
+       if (source->priv->popup == NULL) {
+               GtkBuilder *builder;
+               GObject *plugin;
+               g_object_get (source, "plugin", &plugin, NULL);
+               builder = rb_builder_load_plugin_file (plugin, "fmradio-popup.ui", NULL);
+               g_object_unref (plugin);
+
+               source->priv->popup = G_MENU_MODEL (gtk_builder_get_object (builder, "fmradio-popup"));
+               g_object_ref (source->priv->popup);
+               g_object_unref (builder);
+       }
+
+       menu = gtk_menu_new_from_model (source->priv->popup);
+       gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (source), NULL);
+       gtk_menu_popup (GTK_MENU (menu),
+                       NULL,
+                       NULL,
+                       NULL,
+                       NULL,
+                       3,
+                       gtk_get_current_event_time ());
 }
 
 void
@@ -382,15 +395,16 @@ new_station_response_cb (GtkDialog *dialog, int response, gpointer meh)
 }
 
 static void
-rb_fm_radio_source_cmd_new_station (GtkAction *action, RBFMRadioSource *self)
+new_station_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
+       RBFMRadioSource *source = RB_FM_RADIO_SOURCE (data);
        GtkWidget *dialog;
 
        dialog = rb_uri_dialog_new (_("New FM Radio Station"),
                                    _("Frequency of radio station"));
        g_signal_connect_object (dialog, "location-added",
                                 G_CALLBACK (new_station_location_added),
-                                self, 0);
+                                source, 0);
        g_signal_connect (dialog, "response", G_CALLBACK (new_station_response_cb), NULL);
        gtk_widget_show_all (dialog);
 }
@@ -453,13 +467,6 @@ impl_delete (RBSource *source)
        g_list_free (selection);
 }
 
-static gboolean
-impl_show_popup (RBDisplayPage *page)
-{
-       _rb_display_page_show_popup (page, "/FMRadioSourcePopup");
-       return TRUE;
-}
-
 static RBEntryView *
 impl_get_entry_view (RBSource *source)
 {
diff --git a/plugins/fmradio/rb-fm-radio-source.h b/plugins/fmradio/rb-fm-radio-source.h
index 0304607..343b74e 100644
--- a/plugins/fmradio/rb-fm-radio-source.h
+++ b/plugins/fmradio/rb-fm-radio-source.h
@@ -57,7 +57,8 @@ struct _RBFMRadioSourceClass {
 
 GType     rb_fm_radio_source_get_type         (void);
 
-RBSource *rb_fm_radio_source_new               (RBShell *shell,
+RBSource *rb_fm_radio_source_new               (GObject *plugin,
+                                               RBShell *shell,
                                                RBRadioTuner *tuner);
 
 void     rb_fm_radio_source_add_station       (RBFMRadioSource *source,
diff --git a/plugins/generic-player/Makefile.am b/plugins/generic-player/Makefile.am
index f6db0dc..02dbad3 100644
--- a/plugins/generic-player/Makefile.am
+++ b/plugins/generic-player/Makefile.am
@@ -40,11 +40,8 @@ INCLUDES =                                           \
        $(RHYTHMBOX_CFLAGS)                             \
        -D_BSD_SOURCE
 
-uixmldir = $(plugindatadir)
-uixml_DATA = generic-player-ui.xml
-
 gtkbuilderdir = $(plugindatadir)
-gtkbuilder_DATA = generic-player-info.ui
+gtkbuilder_DATA = generic-player-info.ui generic-player-toolbar.ui
 
 plugin_in_files = generic-player.plugin.in
 
@@ -52,7 +49,7 @@ plugin_in_files = generic-player.plugin.in
 
 plugin_DATA = $(plugin_in_files:.plugin.in=.plugin)
 
-EXTRA_DIST = $(uixml_DATA) $(gtkbuilder_DATA) $(plugin_in_files)
+EXTRA_DIST = $(gtkbuilder_DATA) $(plugin_in_files)
 
 CLEANFILES = $(plugin_DATA)
 DISTCLEANFILES = $(plugin_DATA)
diff --git a/plugins/generic-player/generic-player-toolbar.ui 
b/plugins/generic-player/generic-player-toolbar.ui
new file mode 100644
index 0000000..c5fb49f
--- /dev/null
+++ b/plugins/generic-player/generic-player-toolbar.ui
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="generic-player-toolbar">
+    <section>
+      <submenu>
+       <attribute name="label" translatable="yes">Edit</attribute>
+       <attribute name="rb-menu-link">edit-menu</attribute>
+      </submenu>
+      <item>
+       <attribute name="label" translatable="yes">Browse</attribute>
+       <attribute name="rb-property-bind">show-browser</attribute>
+       <attribute name="accel">&lt;Primary&gt;b</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">View All</attribute>
+       <attribute name="action">app.source-view-all</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Properties</attribute>
+       <attribute name="action">app.media-player-properties</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Eject</attribute>
+       <attribute name="action">app.removable-media-eject</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Sync</attribute>
+        <attribute name="action">app.media-player-sync</attribute>
+      </item>
+    </section>
+  </menu>
+</interface>
diff --git a/plugins/generic-player/rb-generic-player-playlist-source.c 
b/plugins/generic-player/rb-generic-player-playlist-source.c
index 9ad6cdb..4af8b86 100644
--- a/plugins/generic-player/rb-generic-player-playlist-source.c
+++ b/plugins/generic-player/rb-generic-player-playlist-source.c
@@ -385,7 +385,8 @@ rb_generic_player_playlist_source_new (RBShell *shell,
                                       RBGenericPlayerSource *player_source,
                                       const char *playlist_file,
                                       const char *device_root,
-                                      RhythmDBEntryType *entry_type)
+                                      RhythmDBEntryType *entry_type,
+                                      GMenuModel *playlist_menu)
 {
        RBSource *source;
        source = RB_SOURCE (g_object_new (RB_TYPE_GENERIC_PLAYER_PLAYLIST_SOURCE,
@@ -395,6 +396,7 @@ rb_generic_player_playlist_source_new (RBShell *shell,
                                          "player-source", player_source,
                                          "playlist-path", playlist_file,
                                          "device-root", device_root,
+                                         "playlist-menu", playlist_menu,
                                          NULL));
 
        if (load_playlist (RB_GENERIC_PLAYER_PLAYLIST_SOURCE (source)) == FALSE) {
@@ -408,10 +410,17 @@ rb_generic_player_playlist_source_new (RBShell *shell,
        return source;
 }
 
-void
-rb_generic_player_playlist_delete_from_player (RBGenericPlayerPlaylistSource *source)
+static gboolean
+impl_can_remove (RBDisplayPage *page)
 {
-       RBGenericPlayerPlaylistSourcePrivate *priv = GET_PRIVATE (source);
+       /* maybe check if read only? */
+       return TRUE;
+}
+
+static void
+impl_remove (RBDisplayPage *page)
+{
+       RBGenericPlayerPlaylistSourcePrivate *priv = GET_PRIVATE (page);
 
        if (priv->playlist_path != NULL) {
                GError *error = NULL;
@@ -427,6 +436,8 @@ rb_generic_player_playlist_delete_from_player (RBGenericPlayerPlaylistSource *so
        } else {
                rb_debug ("playlist was never saved: nothing to delete");
        }
+
+       rb_display_page_delete_thyself (page);
 }
 
 static void
@@ -504,27 +515,21 @@ impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSp
        }
 }
 
-static gboolean
-impl_show_popup (RBDisplayPage *page)
-{
-       _rb_display_page_show_popup (page, "/GenericPlayerPlaylistSourcePopup");
-       return TRUE;
-}
-
 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);
+       RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 
        object_class->dispose = impl_dispose;
        object_class->finalize = impl_finalize;
        object_class->get_property = impl_get_property;
        object_class->set_property = impl_set_property;
 
-       page_class->show_popup = impl_show_popup;
+       page_class->can_remove = impl_can_remove;
+       page_class->remove = impl_remove;
 
        source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_false_function;
 
diff --git a/plugins/generic-player/rb-generic-player-playlist-source.h 
b/plugins/generic-player/rb-generic-player-playlist-source.h
index 57b3c54..9b11348 100644
--- a/plugins/generic-player/rb-generic-player-playlist-source.h
+++ b/plugins/generic-player/rb-generic-player-playlist-source.h
@@ -56,8 +56,8 @@ RBSource *    rb_generic_player_playlist_source_new (RBShell *shell,
                                                       RBGenericPlayerSource *source,
                                                       const char *playlist_file,
                                                       const char *device_root,
-                                                      RhythmDBEntryType *entry_type);
-void           rb_generic_player_playlist_delete_from_player (RBGenericPlayerPlaylistSource *source);
+                                                      RhythmDBEntryType *entry_type,
+                                                      GMenuModel *playlist_menu);
 
 void           _rb_generic_player_playlist_source_register_type (GTypeModule *module);
 
diff --git a/plugins/generic-player/rb-generic-player-plugin.c 
b/plugins/generic-player/rb-generic-player-plugin.c
index d0abb46..c0b87d9 100644
--- a/plugins/generic-player/rb-generic-player-plugin.c
+++ b/plugins/generic-player/rb-generic-player-plugin.c
@@ -64,10 +64,7 @@ typedef struct
 {
        PeasExtensionBase parent;
 
-       guint ui_merge_id;
-
        GList *player_sources;
-       GtkActionGroup *actions;
 } RBGenericPlayerPlugin;
 
 typedef struct
@@ -80,24 +77,8 @@ G_MODULE_EXPORT void peas_register_types (PeasObjectModule  *module);
 
 static void rb_generic_player_plugin_init (RBGenericPlayerPlugin *plugin);
 
-static void rb_generic_player_plugin_new_playlist (GtkAction *action, RBSource *source);
-static void rb_generic_player_plugin_delete_playlist (GtkAction *action, RBSource *source);
-static void rb_generic_player_plugin_properties (GtkAction *action, RBSource *source);
-
 RB_DEFINE_PLUGIN(RB_TYPE_GENERIC_PLAYER_PLUGIN, RBGenericPlayerPlugin, rb_generic_player_plugin,)
 
-static GtkActionEntry rb_generic_player_plugin_actions[] = {
-       { "GenericPlayerSourceNewPlaylist", RB_STOCK_PLAYLIST_NEW, N_("New Playlist"), NULL,
-         N_("Create a new playlist on this device"),
-         G_CALLBACK (rb_generic_player_plugin_new_playlist) },
-       { "GenericPlayerPlaylistDelete", GTK_STOCK_DELETE, N_("Delete Playlist"), NULL,
-         N_("Delete this playlist"),
-         G_CALLBACK (rb_generic_player_plugin_delete_playlist) },
-       { "GenericPlayerSourceProperties", GTK_STOCK_PROPERTIES, N_("_Properties"), NULL,
-         N_("Display device properties"),
-         G_CALLBACK (rb_generic_player_plugin_properties) }
-};
-
 static void
 rb_generic_player_plugin_init (RBGenericPlayerPlugin *plugin)
 {
@@ -110,43 +91,6 @@ rb_generic_player_plugin_source_deleted (RBGenericPlayerSource *source, RBGeneri
        plugin->player_sources = g_list_remove (plugin->player_sources, source);
 }
 
-static void
-rb_generic_player_plugin_new_playlist (GtkAction *action, RBSource *source)
-{
-       RBShell *shell;
-       RBSource *playlist;
-       RBDisplayPageTree *page_tree;
-       RhythmDBEntryType *entry_type;
-
-       g_return_if_fail (RB_IS_GENERIC_PLAYER_SOURCE (source));
-       g_object_get (source,
-                     "shell", &shell,
-                     "entry-type", &entry_type,
-                     NULL);
-
-       playlist = rb_generic_player_playlist_source_new (shell, RB_GENERIC_PLAYER_SOURCE (source), NULL, 
NULL, entry_type);
-       g_object_unref (entry_type);
-
-       rb_generic_player_source_add_playlist (RB_GENERIC_PLAYER_SOURCE (source),
-                                              shell,
-                                              playlist);
-
-       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);
-}
-
-static void
-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_display_page_delete_thyself (RB_DISPLAY_PAGE (source));
-}
-
 static RBSource *
 create_source_cb (RBRemovableMediaManager *rmm, GMount *mount, MPIDDevice *device_info, 
RBGenericPlayerPlugin *plugin)
 {
@@ -162,33 +106,7 @@ create_source_cb (RBRemovableMediaManager *rmm, GMount *mount, MPIDDevice *devic
        if (source == NULL && rb_generic_player_is_mount_player (mount, device_info))
                source = rb_generic_player_source_new (G_OBJECT (plugin), shell, mount, device_info);
 
-       if (plugin->actions == NULL) {
-               plugin->actions = gtk_action_group_new ("GenericPlayerActions");
-               gtk_action_group_set_translation_domain (plugin->actions, GETTEXT_PACKAGE);
-
-               _rb_action_group_add_display_page_actions (plugin->actions,
-                                                          G_OBJECT (shell),
-                                                          rb_generic_player_plugin_actions,
-                                                          G_N_ELEMENTS (rb_generic_player_plugin_actions));
-       }
-
        if (source) {
-               if (plugin->ui_merge_id == 0) {
-                       GtkUIManager *uimanager = NULL;
-                       char *file = NULL;
-
-                       g_object_get (shell, "ui-manager", &uimanager, NULL);
-
-                       gtk_ui_manager_insert_action_group (uimanager, plugin->actions, 0);
-
-                       file = rb_find_plugin_data_file (G_OBJECT (plugin), "generic-player-ui.xml");
-                       plugin->ui_merge_id = gtk_ui_manager_add_ui_from_file (uimanager,
-                                                                              file,
-                                                                              NULL);
-                       g_free (file);
-                       g_object_unref (uimanager);
-               }
-
                plugin->player_sources = g_list_prepend (plugin->player_sources, source);
                g_signal_connect_object (G_OBJECT (source),
                                         "deleted", G_CALLBACK (rb_generic_player_plugin_source_deleted),
@@ -233,13 +151,11 @@ impl_deactivate   (PeasActivatable *bplugin)
 {
        RBGenericPlayerPlugin *plugin = RB_GENERIC_PLAYER_PLUGIN (bplugin);
        RBRemovableMediaManager *rmm;
-       GtkUIManager *uimanager;
        RBShell *shell;
 
        g_object_get (plugin, "object", &shell, NULL);
        g_object_get (shell,
                      "removable-media-manager", &rmm,
-                     "ui-manager", &uimanager,
                      NULL);
 
        g_signal_handlers_disconnect_by_func (G_OBJECT (rmm), create_source_cb, plugin);
@@ -248,23 +164,10 @@ impl_deactivate   (PeasActivatable *bplugin)
        g_list_free (plugin->player_sources);
        plugin->player_sources = NULL;
 
-       if (plugin->ui_merge_id) {
-               gtk_ui_manager_remove_ui (uimanager, plugin->ui_merge_id);
-               plugin->ui_merge_id = 0;
-       }
-
-       g_object_unref (uimanager);
        g_object_unref (rmm);
        g_object_unref (shell);
 }
 
-static void
-rb_generic_player_plugin_properties (GtkAction *action, RBSource *source)
-{
-       g_return_if_fail (RB_IS_GENERIC_PLAYER_SOURCE (source));
-       rb_media_player_source_show_properties (RB_MEDIA_PLAYER_SOURCE (source));
-}
-
 G_MODULE_EXPORT void
 peas_register_types (PeasObjectModule *module)
 {
diff --git a/plugins/generic-player/rb-generic-player-source.c 
b/plugins/generic-player/rb-generic-player-source.c
index 4c92bcb..f3f5326 100644
--- a/plugins/generic-player/rb-generic-player-source.c
+++ b/plugins/generic-player/rb-generic-player-source.c
@@ -55,6 +55,8 @@
 #include "rb-gst-media-types.h"
 #include "rb-sync-settings.h"
 #include "rb-missing-plugins.h"
+#include "rb-application.h"
+#include "rb-display-page-menu.h"
 
 static void rb_generic_player_device_source_init (RBDeviceSourceInterface *interface);
 static void rb_generic_player_source_transfer_target_init (RBTransferTargetInterface *interface);
@@ -72,7 +74,6 @@ static void impl_get_property (GObject *object,
 
 static void load_songs (RBGenericPlayerSource *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 void impl_selected (RBDisplayPage *page);
@@ -106,6 +107,8 @@ static char * default_uri_to_playlist_uri (RBGenericPlayerSource *source,
                                           const char *uri,
                                           TotemPlParserType playlist_type);
 
+static void new_playlist_action_cb (GSimpleAction *, GVariant *, gpointer);
+
 enum
 {
        PROP_0,
@@ -137,6 +140,9 @@ typedef struct
        MPIDDevice *device_info;
        GMount *mount;
 
+       GSimpleAction *new_playlist_action;
+       char *new_playlist_action_name;
+
 } RBGenericPlayerSourcePrivate;
 
 G_DEFINE_DYNAMIC_TYPE_EXTENDED (
@@ -162,7 +168,6 @@ rb_generic_player_source_class_init (RBGenericPlayerSourceClass *klass)
        object_class->constructed = impl_constructed;
        object_class->dispose = impl_dispose;
 
-       page_class->show_popup = impl_show_popup;
        page_class->delete_thyself = impl_delete_thyself;
        page_class->get_status = impl_get_status;
        page_class->selected = impl_selected;
@@ -256,6 +261,9 @@ impl_constructed (GObject *object)
        GFile *root;
        GFileInfo *info;
        GError *error = NULL;
+       char *label;
+       char *fullname;
+       char *name;
 
        RB_CHAIN_GOBJECT_METHOD (rb_generic_player_source_parent_class, constructed, object);
        source = RB_GENERIC_PLAYER_SOURCE (object);
@@ -267,6 +275,7 @@ impl_constructed (GObject *object)
        g_object_get (source,
                      "shell", &shell,
                      "entry-type", &entry_type,
+                     "name", &name,
                      NULL);
 
        g_object_get (shell, "db", &priv->db, NULL);
@@ -276,7 +285,19 @@ impl_constructed (GObject *object)
                                                           entry_type,
                                                           priv->ignore_type);
 
-       g_object_unref (shell);
+
+       priv->new_playlist_action_name = g_strdup_printf ("generic-player-%p-playlist-new", source);
+       fullname = g_strdup_printf ("app.%s", priv->new_playlist_action_name);
+
+       label = g_strdup_printf (_("New Playlist on %s"), name);
+
+       rb_application_add_plugin_menu_item (RB_APPLICATION (g_application_get_default ()),
+                                            "display-page-add-playlist",
+                                            priv->new_playlist_action_name,
+                                            g_menu_item_new (label, fullname));
+       g_free (fullname);
+       g_free (label);
+       g_free (name);
 
        root = g_mount_get_root (priv->mount);
        mount_name = g_mount_get_name (priv->mount);
@@ -295,8 +316,27 @@ impl_constructed (GObject *object)
        g_object_unref (root);
 
        g_object_get (priv->device_info, "playlist-formats", &playlist_formats, NULL);
-       if (playlist_formats != NULL && g_strv_length (playlist_formats) > 0) {
-               g_object_set (entry_type, "has-playlists", TRUE, NULL);
+       if ((priv->read_only == FALSE) && playlist_formats != NULL && g_strv_length (playlist_formats) > 0) {
+               RBDisplayPageModel *model;
+               GMenu *playlist_menu;
+               GMenuModel *playlists;
+
+               priv->new_playlist_action = g_simple_action_new (priv->new_playlist_action_name, NULL);
+               g_signal_connect (priv->new_playlist_action, "activate", G_CALLBACK (new_playlist_action_cb), 
source);
+               g_action_map_add_action (G_ACTION_MAP (g_application_get_default ()), G_ACTION 
(priv->new_playlist_action));
+
+               g_object_get (shell, "display-page-model", &model, NULL);
+               playlists = rb_display_page_menu_new (model,
+                                                     RB_DISPLAY_PAGE (source),
+                                                     RB_TYPE_GENERIC_PLAYER_PLAYLIST_SOURCE,
+                                                     "app.playlist-add-to");
+               g_object_unref (model);
+
+               playlist_menu = g_menu_new ();
+               g_menu_append (playlist_menu, _("Add to New Playlist"), priv->new_playlist_action_name);
+               g_menu_append_section (playlist_menu, NULL, playlists);
+
+               g_object_set (source, "playlist-menu", playlist_menu, NULL);
        }
        g_strfreev (playlist_formats);
        g_object_unref (entry_type);
@@ -321,6 +361,7 @@ impl_constructed (GObject *object)
        }
        g_strfreev (output_formats);
 
+       g_object_unref (shell);
 }
 
 static void
@@ -413,6 +454,10 @@ impl_dispose (GObject *object)
                priv->mount = NULL;
        }
 
+       rb_application_remove_plugin_menu_item (RB_APPLICATION (g_application_get_default ()),
+                                               "display-page-add-playlist",
+                                               priv->new_playlist_action_name);
+
        G_OBJECT_CLASS (rb_generic_player_source_parent_class)->dispose (object);
 }
 
@@ -424,6 +469,8 @@ rb_generic_player_source_new (GObject *plugin, RBShell *shell, GMount *mount, MP
        RhythmDBEntryType *error_type;
        RhythmDBEntryType *ignore_type;
        RhythmDB *db;
+       GtkBuilder *builder;
+       GMenu *toolbar;
        GVolume *volume;
        GSettings *settings;
        char *name;
@@ -468,6 +515,10 @@ rb_generic_player_source_new (GObject *plugin, RBShell *shell, GMount *mount, MP
        g_object_unref (volume);
        g_free (path);
 
+       builder = rb_builder_load_plugin_file (plugin, "generic-player-toolbar.ui", NULL);
+       toolbar = G_MENU (gtk_builder_get_object (builder, "generic-player-toolbar"));
+       rb_application_link_shared_menus (RB_APPLICATION (g_application_get_default ()), toolbar);
+
        settings = g_settings_new ("org.gnome.rhythmbox.plugins.generic-player");
        source = RB_GENERIC_PLAYER_SOURCE (g_object_new (RB_TYPE_GENERIC_PLAYER_SOURCE,
                                                         "plugin", plugin,
@@ -479,9 +530,10 @@ rb_generic_player_source_new (GObject *plugin, RBShell *shell, GMount *mount, MP
                                                         "device-info", device_info,
                                                         "load-status", RB_SOURCE_LOAD_STATUS_LOADING,
                                                         "settings", g_settings_get_child (settings, 
"source"),
-                                                        "toolbar-path", "/GenericPlayerSourceToolBar",
+                                                        "toolbar-menu", toolbar,
                                                         NULL));
        g_object_unref (settings);
+       g_object_unref (builder);
 
        rb_shell_register_entry_type_for_source (shell, RB_SOURCE (source), entry_type);
 
@@ -658,13 +710,6 @@ rb_generic_player_is_mount_player (GMount *mount, MPIDDevice *device_info)
        return result;
 }
 
-static gboolean
-impl_show_popup (RBDisplayPage *page)
-{
-       _rb_display_page_show_popup (page, "/GenericPlayerSourcePopup");
-       return TRUE;
-}
-
 static void
 impl_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress)
 {
@@ -779,11 +824,13 @@ load_playlist_file (RBGenericPlayerSource *source,
        RhythmDBEntryType *entry_type;
        RBGenericPlayerPlaylistSource *playlist;
        RBShell *shell;
+       GMenuModel *playlist_menu;
        char *mount_path;
 
        g_object_get (source,
                      "shell", &shell,
                      "entry-type", &entry_type,
+                     "playlist-menu", &playlist_menu,
                      NULL);
 
        mount_path = rb_generic_player_source_get_mount_path (source);
@@ -793,12 +840,14 @@ load_playlist_file (RBGenericPlayerSource *source,
                                                               source,
                                                               playlist_path,
                                                               mount_path,
-                                                              entry_type));
+                                                              entry_type,
+                                                              playlist_menu));
 
        if (playlist != NULL) {
                rb_generic_player_source_add_playlist (source, shell, RB_SOURCE (playlist));
        }
 
+       g_object_unref (playlist_menu);
        g_object_unref (entry_type);
        g_object_unref (shell);
        g_free (mount_path);
@@ -1427,13 +1476,15 @@ impl_add_playlist (RBMediaPlayerSource *source, char *name, GList *entries)
        RhythmDBEntryType *entry_type;
        RBShell *shell;
        GList *i;
+       GMenuModel *playlist_menu;
 
        g_object_get (source,
                      "shell", &shell,
                      "entry-type", &entry_type,
+                     "playlist-menu", &playlist_menu,
                      NULL);
 
-       playlist = rb_generic_player_playlist_source_new (shell, RB_GENERIC_PLAYER_SOURCE (source), NULL, 
NULL, entry_type);
+       playlist = rb_generic_player_playlist_source_new (shell, RB_GENERIC_PLAYER_SOURCE (source), NULL, 
NULL, entry_type, playlist_menu);
        g_object_unref (entry_type);
 
        rb_generic_player_source_add_playlist (RB_GENERIC_PLAYER_SOURCE (source),
@@ -1447,6 +1498,7 @@ impl_add_playlist (RBMediaPlayerSource *source, char *name, GList *entries)
                                                     -1);
        }
 
+       g_object_unref (playlist_menu);
        g_object_unref (shell);
 }
 
@@ -1459,9 +1511,8 @@ impl_remove_playlists (RBMediaPlayerSource *source)
 
        playlists = g_list_copy (priv->playlists);
        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_display_page_delete_thyself (RB_DISPLAY_PAGE (p));
+               RBDisplayPage *p = RB_DISPLAY_PAGE (t->data);
+               rb_display_page_remove (p);
        }
 
        g_list_free (playlists);
@@ -1472,3 +1523,32 @@ _rb_generic_player_source_register_type (GTypeModule *module)
 {
        rb_generic_player_source_register_type (module);
 }
+
+static void
+new_playlist_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
+{
+       RBGenericPlayerSource *source = RB_GENERIC_PLAYER_SOURCE (data);
+       RBShell *shell;
+       RBSource *playlist;
+       RBDisplayPageTree *page_tree;
+       RhythmDBEntryType *entry_type;
+       GMenuModel *playlist_menu;
+
+       g_object_get (source,
+                     "shell", &shell,
+                     "entry-type", &entry_type,
+                     "playlist-menu", &playlist_menu,
+                     NULL);
+
+       playlist = rb_generic_player_playlist_source_new (shell, source, NULL, NULL, entry_type, 
playlist_menu);
+       g_object_unref (entry_type);
+
+       rb_generic_player_source_add_playlist (source, shell, playlist);
+
+       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 (playlist_menu);
+       g_object_unref (shell);
+}
diff --git a/plugins/ipod/Makefile.am b/plugins/ipod/Makefile.am
index ff10ab9..cc0eef3 100644
--- a/plugins/ipod/Makefile.am
+++ b/plugins/ipod/Makefile.am
@@ -44,15 +44,13 @@ plugin_in_files = ipod.plugin.in
 
 %.plugin: %.plugin.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) 
$(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
 
-uixmldir = $(plugindatadir)
-uixml_DATA = ipod-ui.xml
-
 plugin_DATA = $(plugin_in_files:.plugin.in=.plugin)
 
 gtkbuilderdir = $(plugindatadir)
 gtkbuilder_DATA =                                      \
        ipod-info.ui                                    \
-       ipod-init.ui
+       ipod-init.ui                                    \
+       ipod-toolbar.ui
 
 EXTRA_DIST = $(uixml_DATA) $(plugin_in_files) $(gtkbuilder_DATA)
 
diff --git a/plugins/ipod/ipod-toolbar.ui b/plugins/ipod/ipod-toolbar.ui
new file mode 100644
index 0000000..8a3b7ca
--- /dev/null
+++ b/plugins/ipod/ipod-toolbar.ui
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="ipod-toolbar">
+    <section>
+      <submenu>
+       <attribute name="label" translatable="yes">Edit</attribute>
+       <attribute name="rb-menu-link">edit-menu</attribute>
+      </submenu>
+      <item>
+       <attribute name="label" translatable="yes">Browse</attribute>
+       <attribute name="rb-property-bind">show-browser</attribute>
+       <attribute name="accel">&lt;Primary&gt;b</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">View All</attribute>
+       <attribute name="action">app.source-view-all</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Properties</attribute>
+       <attribute name="action">app.media-player-properties</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Eject</attribute>
+       <attribute name="action">app.removable-media-eject</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Sync</attribute>
+        <attribute name="action">app.media-player-sync</attribute>
+      </item>
+    </section>
+  </menu>
+</interface>
diff --git a/plugins/ipod/rb-ipod-plugin.c b/plugins/ipod/rb-ipod-plugin.c
index e0907bc..7143ea9 100644
--- a/plugins/ipod/rb-ipod-plugin.c
+++ b/plugins/ipod/rb-ipod-plugin.c
@@ -65,8 +65,6 @@ typedef struct
        PeasExtensionBase parent;
 
        RBShell *shell;
-       GtkActionGroup *action_group;
-       guint ui_merge_id;
 
        GList *ipod_sources;
 } RBIpodPlugin;
@@ -85,34 +83,10 @@ static RBSource * create_source_cb (RBRemovableMediaManager *rmm,
                                    GMount *mount,
                                    MPIDDevice *device_info,
                                    RBIpodPlugin *plugin);
-static void  rb_ipod_plugin_cmd_rename (GtkAction *action, RBSource *source);
-static void  rb_ipod_plugin_cmd_playlist_new (GtkAction *action, RBSource *source);
-static void  rb_ipod_plugin_cmd_playlist_rename (GtkAction *action, RBSource *source);
-static void  rb_ipod_plugin_cmd_playlist_delete (GtkAction *action, RBSource *source);
-static void  rb_ipod_plugin_cmd_properties (GtkAction *action, RBSource *source);
 
 RB_DEFINE_PLUGIN(RB_TYPE_IPOD_PLUGIN, RBIpodPlugin, rb_ipod_plugin,)
 
 
-static GtkActionEntry rb_ipod_plugin_actions [] =
-{
-       { "iPodSourceRename", NULL, N_("_Rename"), NULL,
-         N_("Rename iPod"),
-         G_CALLBACK (rb_ipod_plugin_cmd_rename) },
-       { "iPodProperties", GTK_STOCK_PROPERTIES, N_("_Properties"), NULL,
-         N_("Display iPod properties"),
-         G_CALLBACK (rb_ipod_plugin_cmd_properties) },
-       { "iPodSourcePlaylistNew", RB_STOCK_PLAYLIST_NEW, N_("_New Playlist"), NULL,
-         N_("Add new playlist to iPod"),
-         G_CALLBACK (rb_ipod_plugin_cmd_playlist_new) },
-       { "iPodPlaylistSourceRename", NULL, N_("_Rename"), NULL,
-         N_("Rename playlist"),
-         G_CALLBACK (rb_ipod_plugin_cmd_playlist_rename) },
-       { "iPodPlaylistSourceDelete", GTK_STOCK_REMOVE, N_("_Delete"), NULL,
-         N_("Delete playlist"),
-         G_CALLBACK (rb_ipod_plugin_cmd_playlist_delete) },
-};
-
 static void
 rb_ipod_plugin_init (RBIpodPlugin *plugin)
 {
@@ -124,35 +98,15 @@ impl_activate (PeasActivatable *bplugin)
 {
        RBIpodPlugin *plugin = RB_IPOD_PLUGIN (bplugin);
        RBRemovableMediaManager *rmm = NULL;
-       GtkUIManager *uimanager = NULL;
        RBShell *shell;
        gboolean scanned;
-       char *file;
 
        g_object_get (plugin, "object", &shell, NULL);
 
        g_object_get (G_OBJECT (shell),
                      "removable-media-manager", &rmm,
-                     "ui-manager", &uimanager,
                      NULL);
 
-       rb_media_player_source_init_actions (shell);
-
-       /* add ipod UI */
-       plugin->action_group = gtk_action_group_new ("iPodActions");
-       gtk_action_group_set_translation_domain (plugin->action_group,
-                                                GETTEXT_PACKAGE);
-       _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_find_plugin_data_file (G_OBJECT (bplugin), "ipod-ui.xml");
-       plugin->ui_merge_id = gtk_ui_manager_add_ui_from_file (uimanager,
-                                                              file,
-                                                              NULL);
-       g_free (file);
-
        /* watch for new removable media, and cause a rescan */
        g_signal_connect (G_OBJECT (rmm),
                          "create-source-mount", G_CALLBACK (create_source_cb),
@@ -164,7 +118,6 @@ impl_activate (PeasActivatable *bplugin)
                rb_removable_media_manager_scan (rmm);
 
        g_object_unref (rmm);
-       g_object_unref (uimanager);
        g_object_unref (shell);
 }
 
@@ -173,26 +126,20 @@ impl_deactivate   (PeasActivatable *bplugin)
 {
        RBIpodPlugin *plugin = RB_IPOD_PLUGIN (bplugin);
        RBRemovableMediaManager *rmm = NULL;
-       GtkUIManager *uimanager = NULL;
        RBShell *shell;
 
        g_object_get (plugin, "object", &shell, NULL);
 
        g_object_get (shell,
                      "removable-media-manager", &rmm,
-                     "ui-manager", &uimanager,
                      NULL);
 
-       gtk_ui_manager_remove_ui (uimanager, plugin->ui_merge_id);
-       gtk_ui_manager_remove_action_group (uimanager, plugin->action_group);
-
        g_signal_handlers_disconnect_by_func (G_OBJECT (rmm), create_source_cb, plugin);
 
        g_list_foreach (plugin->ipod_sources, (GFunc)rb_display_page_delete_thyself, NULL);
        g_list_free (plugin->ipod_sources);
        plugin->ipod_sources = NULL;
 
-       g_object_unref (uimanager);
        g_object_unref (rmm);
        g_object_unref (shell);
 }
@@ -227,72 +174,6 @@ create_source_cb (RBRemovableMediaManager *rmm, GMount *mount, MPIDDevice *devic
        return src;
 }
 
-static void
-rb_ipod_plugin_cmd_properties (GtkAction *action, RBSource *source)
-{
-       g_return_if_fail (RB_IS_IPOD_SOURCE (source));
-       rb_media_player_source_show_properties (RB_MEDIA_PLAYER_SOURCE (source));
-}
- 
-static void
-rb_ipod_plugin_cmd_rename (GtkAction *action, RBSource *source)
-{
-       RBDisplayPageTree *page_tree = NULL;
-       RBShell *shell;
-
-       g_return_if_fail (RB_IS_IPOD_SOURCE (source));
-
-       /* FIXME: this is pretty ugly, the sourcelist should automatically add
-        * a "rename" menu item for sources that have can_rename == TRUE.
-        * This is a bit trickier to handle though, since playlists want
-        * to make rename sensitive/unsensitive instead of showing/hiding it
-        */
-
-       g_object_get (source, "shell", &shell, NULL);
-       g_object_get (shell, "display-page-tree", &page_tree, NULL);
-
-       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 (page_tree);
-       g_object_unref (shell);
-}
-
-static void
-rb_ipod_plugin_cmd_playlist_rename (GtkAction *action, RBSource *source)
-{
-       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, "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);
-}
-
-static void
-rb_ipod_plugin_cmd_playlist_delete (GtkAction *action, RBSource *source)
-{
-       RBiPodSource *ipod_source;
-
-       g_return_if_fail (RB_IS_IPOD_STATIC_PLAYLIST_SOURCE (source));
-
-       g_object_get (source, "ipod-source", &ipod_source, NULL);
-       rb_ipod_source_remove_playlist (ipod_source, source);
-       g_object_unref (ipod_source);
-}
-
-static void
-rb_ipod_plugin_cmd_playlist_new (GtkAction *action, RBSource *source)
-{
-       g_return_if_fail (RB_IS_IPOD_SOURCE (source));
-       rb_ipod_source_new_playlist (RB_IPOD_SOURCE (source));
-}
-
 G_MODULE_EXPORT void
 peas_register_types (PeasObjectModule *module)
 {
diff --git a/plugins/ipod/rb-ipod-source.c b/plugins/ipod/rb-ipod-source.c
index 4d32ed8..13de14f 100644
--- a/plugins/ipod/rb-ipod-source.c
+++ b/plugins/ipod/rb-ipod-source.c
@@ -58,18 +58,20 @@
 #include "rb-transfer-target.h"
 #include "rb-ext-db.h"
 #include "rb-dialog.h"
+#include "rb-application.h"
+#include "rb-display-page-menu.h"
 
 static void rb_ipod_device_source_init (RBDeviceSourceInterface *interface);
 static void rb_ipod_source_transfer_target_init (RBTransferTargetInterface *interface);
 
 static void rb_ipod_source_constructed (GObject *object);
 static void rb_ipod_source_dispose (GObject *object);
+static void rb_ipod_source_finalize (GObject *object);
 
 static void impl_delete (RBSource *asource);
 static RBTrackTransferBatch *impl_paste (RBSource *source, GList *entries);
 static void rb_ipod_load_songs (RBiPodSource *source);
 
-static gboolean impl_show_popup (RBDisplayPage *page);
 static void impl_delete_thyself (RBDisplayPage *page);
 static void impl_selected (RBDisplayPage *page);
 
@@ -107,6 +109,7 @@ static void rb_ipod_source_get_property (GObject *object,
                                         GParamSpec *pspec);
 static gboolean ensure_loaded (RBiPodSource *source);
 
+static RBIpodStaticPlaylistSource *add_rb_playlist (RBiPodSource *source, Itdb_Playlist *playlist);
 
 static RhythmDB *get_db_for_source (RBiPodSource *source);
 
@@ -138,6 +141,9 @@ typedef struct
        GtkWidget *init_dialog;
        GtkWidget *model_combo;
        GtkWidget *name_entry;
+
+       GSimpleAction *new_playlist_action;
+       char *new_playlist_action_name;
 } RBiPodSourcePrivate;
 
 typedef struct {
@@ -173,16 +179,15 @@ rb_ipod_source_class_init (RBiPodSourceClass *klass)
 
        object_class->constructed = rb_ipod_source_constructed;
        object_class->dispose = rb_ipod_source_dispose;
+       object_class->finalize = rb_ipod_source_finalize;
 
        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->selected = impl_selected;
 
        source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_false_function;
-       source_class->impl_can_rename = (RBSourceFeatureFunc) rb_true_function;
        source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
        source_class->impl_delete = impl_delete;
 
@@ -299,6 +304,54 @@ rb_ipod_source_set_ipod_name (RBiPodSource *source, const char *name)
 }
 
 static void
+new_playlist_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
+{
+       RBiPodSource *source = RB_IPOD_SOURCE (data);
+       RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
+       Itdb_Playlist *ipod_playlist;
+
+       if (priv->ipod_db == NULL) {
+               rb_debug ("can't create new ipod playlist with no ipod db");
+               return;
+       }
+
+       ipod_playlist = itdb_playlist_new (_("New playlist"), FALSE);
+       rb_ipod_db_add_playlist (priv->ipod_db, ipod_playlist);
+       add_rb_playlist (source, ipod_playlist);
+}
+
+static void
+create_new_playlist_item (RBiPodSource *source)
+{
+       RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
+       char *label;
+       char *fullname;
+       char *name;
+
+       fullname = g_strdup_printf ("app.%s", priv->new_playlist_action_name);
+
+       g_object_get (source, "name", &name, NULL);
+       label = g_strdup_printf (_("New Playlist on %s"), name);
+
+       rb_application_add_plugin_menu_item (RB_APPLICATION (g_application_get_default ()),
+                                            "display-page-add-playlist",
+                                            priv->new_playlist_action_name,
+                                            g_menu_item_new (label, fullname));
+       g_free (fullname);
+       g_free (label);
+       g_free (name);
+}
+
+static void
+remove_new_playlist_item (RBiPodSource *source)
+{
+       RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
+       rb_application_remove_plugin_menu_item (RB_APPLICATION (g_application_get_default ()),
+                                               "display-page-add-playlist",
+                                               priv->new_playlist_action_name);
+}
+
+static void
 rb_ipod_source_name_changed_cb (RBiPodSource *source, GParamSpec *spec,
                                gpointer data)
 {
@@ -307,6 +360,9 @@ rb_ipod_source_name_changed_cb (RBiPodSource *source, GParamSpec *spec,
        g_object_get (source, "name", &name, NULL);
        rb_ipod_source_set_ipod_name (source, name);
        g_free (name);
+
+       remove_new_playlist_item (source);
+       create_new_playlist_item (source);
 }
 
 static void
@@ -320,7 +376,9 @@ finish_construction (RBiPodSource *source)
        RBEntryView *songs;
        RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
        GstEncodingTarget *target;
-
+       GMenuModel *playlist_menu;
+       RBDisplayPageModel *model;
+       RBShell *shell;
 
        songs = rb_source_get_entry_view (RB_SOURCE (source));
        rb_entry_view_append_column (songs, RB_ENTRY_VIEW_COL_RATING, FALSE);
@@ -337,6 +395,25 @@ finish_construction (RBiPodSource *source)
        gst_encoding_target_add_profile (target, rb_gst_get_encoding_profile ("audio/x-aac"));
        g_object_set (source, "encoding-target", target, NULL);
 
+       priv->new_playlist_action_name = g_strdup_printf ("ipod-%p-playlist-new", source);
+       priv->new_playlist_action = g_simple_action_new (priv->new_playlist_action_name, NULL);
+       if (priv->ipod_db == NULL) {
+               g_simple_action_set_enabled (priv->new_playlist_action, FALSE);
+       }
+       g_signal_connect (priv->new_playlist_action, "activate", G_CALLBACK (new_playlist_action_cb), source);
+       g_action_map_add_action (G_ACTION_MAP (g_application_get_default ()), G_ACTION 
(priv->new_playlist_action));
+
+       g_object_get (source, "shell", &shell, NULL);
+       g_object_get (shell, "display-page-model", &model, NULL);
+       playlist_menu = rb_display_page_menu_new (model,
+                                                 RB_DISPLAY_PAGE (source),
+                                                 RB_TYPE_IPOD_STATIC_PLAYLIST_SOURCE,
+                                                 "app.playlist-add-to");
+       g_object_set (source, "playlist-menu", playlist_menu, NULL);
+       g_object_unref (model);
+       g_object_unref (shell);
+
+       create_new_playlist_item (source);
 }
 
 static void
@@ -463,15 +540,27 @@ rb_ipod_source_constructed (GObject *object)
 }
 
 static void
+rb_ipod_source_finalize (GObject *object)
+{
+       RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (object);
+
+       g_free (priv->new_playlist_action_name);
+
+       G_OBJECT_CLASS (rb_ipod_source_parent_class)->finalize (object);
+}
+
+static void
 rb_ipod_source_dispose (GObject *object)
 {
        RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (object);
 
-       if (priv->ipod_db) {
-               g_object_unref (G_OBJECT (priv->ipod_db));
-               priv->ipod_db = NULL;
+       if (priv->new_playlist_action) {
+               remove_new_playlist_item (RB_IPOD_SOURCE (object));
+               g_clear_object (&priv->new_playlist_action);
        }
 
+       g_clear_object (&priv->ipod_db);
+
        if (priv->entry_map) {
                g_hash_table_destroy (priv->entry_map);
                priv->entry_map = NULL;
@@ -489,15 +578,8 @@ rb_ipod_source_dispose (GObject *object)
                priv->offline_plays = NULL;
        }
 
-       if (priv->mount) {
-               g_object_unref (priv->mount);
-               priv->mount = NULL;
-       }
-
-       if (priv->art_store) {
-               g_object_unref (priv->art_store);
-               priv->art_store = NULL;
-       }
+       g_clear_object (&priv->mount);
+       g_clear_object (&priv->art_store);
 
        if (priv->init_dialog) {
                gtk_widget_destroy (priv->init_dialog);
@@ -516,6 +598,8 @@ rb_ipod_source_new (GObject *plugin,
        RBiPodSource *source;
        RhythmDBEntryType *entry_type;
        RhythmDB *db;
+       GtkBuilder *builder;
+       GMenu *toolbar;
        GVolume *volume;
        GSettings *settings;
        char *name;
@@ -540,6 +624,10 @@ rb_ipod_source_new (GObject *plugin,
        g_free (name);
        g_free (path);
 
+       builder = rb_builder_load_plugin_file (plugin, "ipod-toolbar.ui", NULL);
+       toolbar = G_MENU (gtk_builder_get_object (builder, "ipod-toolbar"));
+       rb_application_link_shared_menus (RB_APPLICATION (g_application_get_default ()), toolbar);
+
        settings = g_settings_new ("org.gnome.rhythmbox.plugins.ipod");
        source = RB_IPOD_SOURCE (g_object_new (RB_TYPE_IPOD_SOURCE,
                                               "plugin", plugin,
@@ -549,9 +637,10 @@ rb_ipod_source_new (GObject *plugin,
                                               "device-info", device_info,
                                               "load-status", RB_SOURCE_LOAD_STATUS_LOADING,
                                               "settings", g_settings_get_child (settings, "source"),
-                                              "toolbar-path", "/iPodSourceToolBar",
+                                              "toolbar-menu", toolbar,
                                               NULL));
        g_object_unref (settings);
+       g_object_unref (builder);
 
        rb_shell_register_entry_type_for_source (shell, RB_SOURCE (source), entry_type);
         g_object_unref (entry_type);
@@ -616,17 +705,20 @@ add_rb_playlist (RBiPodSource *source, Itdb_Playlist *playlist)
        GList *it;
        RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
        RhythmDBEntryType *entry_type;
+       GMenuModel *playlist_menu;
 
        g_object_get (source,
                          "shell", &shell,
                          "entry-type", &entry_type,
+                         "playlist-menu", &playlist_menu,
                          NULL);
 
        playlist_source = rb_ipod_static_playlist_source_new (shell,
                                                               source,
                                                               priv->ipod_db,
                                                               playlist,
-                                                              entry_type);
+                                                              entry_type,
+                                                             playlist_menu);
        g_object_unref (entry_type);
 
        for (it = playlist->members; it != NULL; it = it->next) {
@@ -1194,6 +1286,8 @@ rb_ipod_load_songs (RBiPodSource *source)
                        g_object_set (RB_SOURCE (source),
                                      "name", name,
                                      NULL);
+                       remove_new_playlist_item (source);
+                       create_new_playlist_item (source);
                }
                 g_signal_connect (G_OBJECT (source), "notify::name",
                                  (GCallback)rb_ipod_source_name_changed_cb,
@@ -1202,13 +1296,6 @@ rb_ipod_load_songs (RBiPodSource *source)
        }
 }
 
-static gboolean
-impl_show_popup (RBDisplayPage *page)
-{
-       _rb_display_page_show_popup (page, "/iPodSourcePopup");
-       return TRUE;
-}
-
 typedef struct {
        RBMediaPlayerSource *source;
        RBMediaPlayerSourceDeleteCallback callback;
@@ -1871,23 +1958,6 @@ impl_delete_thyself (RBDisplayPage *page)
        RB_DISPLAY_PAGE_CLASS (rb_ipod_source_parent_class)->delete_thyself (page);
 }
 
-Itdb_Playlist *
-rb_ipod_source_new_playlist (RBiPodSource *source)
-{
-       RBiPodSourcePrivate *priv = IPOD_SOURCE_GET_PRIVATE (source);
-       Itdb_Playlist *ipod_playlist;
-
-       if (priv->ipod_db == NULL) {
-               rb_debug ("can't create new ipod playlist with no ipod db");
-               return NULL;
-       }
-
-       ipod_playlist = itdb_playlist_new (_("New playlist"), FALSE);
-       rb_ipod_db_add_playlist (priv->ipod_db, ipod_playlist);
-       add_rb_playlist (source, ipod_playlist);
-       return ipod_playlist;
-}
-
 void
 rb_ipod_source_remove_playlist (RBiPodSource *ipod_source,
                                RBSource *source)
diff --git a/plugins/ipod/rb-ipod-source.h b/plugins/ipod/rb-ipod-source.h
index 9af5fa3..953b86d 100644
--- a/plugins/ipod/rb-ipod-source.h
+++ b/plugins/ipod/rb-ipod-source.h
@@ -60,7 +60,6 @@ RBMediaPlayerSource   *rb_ipod_source_new             (GObject *plugin,
 GType                  rb_ipod_source_get_type         (void);
 void                    _rb_ipod_source_register_type   (GTypeModule *module);
 
-Itdb_Playlist *                rb_ipod_source_new_playlist     (RBiPodSource *source);
 void                   rb_ipod_source_remove_playlist  (RBiPodSource *ipod_source,
                                                         RBSource *source);
 
diff --git a/plugins/ipod/rb-ipod-static-playlist-source.c b/plugins/ipod/rb-ipod-static-playlist-source.c
index d389baa..653fb85 100644
--- a/plugins/ipod/rb-ipod-static-playlist-source.c
+++ b/plugins/ipod/rb-ipod-static-playlist-source.c
@@ -44,7 +44,6 @@ static void rb_ipod_static_playlist_source_get_property (GObject *object,
                                          GValue *value,
                                          GParamSpec *pspec);
 
-static gboolean impl_show_popup (RBDisplayPage *page);
 
 typedef struct
 {
@@ -224,20 +223,33 @@ source_name_changed_cb (RBIpodStaticPlaylistSource *source,
        g_free (name);
 }
 
+static gboolean
+impl_can_remove (RBDisplayPage *page)
+{
+       return TRUE;
+}
+
+static void
+impl_remove (RBDisplayPage *page)
+{
+       RBIpodStaticPlaylistSourcePrivate *priv = IPOD_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (page);
+       rb_ipod_source_remove_playlist (priv->ipod_source, RB_SOURCE (page));
+}
 
 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);
+       RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 
        object_class->constructed = rb_ipod_static_playlist_source_constructed;
        object_class->dispose = rb_ipod_static_playlist_source_dispose;
        object_class->get_property = rb_ipod_static_playlist_source_get_property;
        object_class->set_property = rb_ipod_static_playlist_source_set_property;
 
-       page_class->show_popup = impl_show_popup;
+       page_class->can_remove = impl_can_remove;
+       page_class->remove = impl_remove;
 
        source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_false_function;
        source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
@@ -329,7 +341,8 @@ rb_ipod_static_playlist_source_new (RBShell *shell,
                                    RBiPodSource *ipod_source,
                                    RbIpodDb *ipod_db,
                                    Itdb_Playlist *playlist,
-                                   RhythmDBEntryType *entry_type)
+                                   RhythmDBEntryType *entry_type,
+                                   GMenuModel *playlist_menu)
 {
        RBIpodStaticPlaylistSource *source;
 
@@ -343,6 +356,7 @@ rb_ipod_static_playlist_source_new (RBShell *shell,
                                                               "ipod-source", ipod_source,
                                                               "ipod-db", ipod_db,
                                                               "itdb-playlist", playlist,
+                                                              "playlist-menu", playlist_menu,
                                                               NULL));
 
        return source;
@@ -397,13 +411,6 @@ rb_ipod_static_playlist_source_get_property (GObject *object,
        }
 }
 
-static gboolean
-impl_show_popup (RBDisplayPage *page)
-{
-       _rb_display_page_show_popup (page, "/iPodPlaylistSourcePopup");
-       return TRUE;
-}
-
 void
 _rb_ipod_static_playlist_source_register_type (GTypeModule *module)
 {
diff --git a/plugins/ipod/rb-ipod-static-playlist-source.h b/plugins/ipod/rb-ipod-static-playlist-source.h
index 383b447..2e3caa3 100644
--- a/plugins/ipod/rb-ipod-static-playlist-source.h
+++ b/plugins/ipod/rb-ipod-static-playlist-source.h
@@ -52,7 +52,8 @@ RBIpodStaticPlaylistSource *  rb_ipod_static_playlist_source_new (RBShell *shell,
                                                                    RBiPodSource *source,
                                                                    RbIpodDb *ipod_db,
                                                                    Itdb_Playlist *playlist,
-                                                                   RhythmDBEntryType *entry_type);
+                                                                   RhythmDBEntryType *entry_type,
+                                                                   GMenuModel *playlist_menu);
 
 G_END_DECLS
 
diff --git a/plugins/iradio/Makefile.am b/plugins/iradio/Makefile.am
index 5185c9c..8c67130 100644
--- a/plugins/iradio/Makefile.am
+++ b/plugins/iradio/Makefile.am
@@ -40,12 +40,11 @@ INCLUDES =                                          \
        -D_BSD_SOURCE
 
 gtkbuilderdir = $(plugindatadir)
-gtkbuilder_DATA = \
+gtkbuilder_DATA =                                      \
+       iradio-popup.ui                                 \
+       iradio-toolbar.ui                               \
        station-properties.ui
 
-uixmldir = $(plugindatadir)
-uixml_DATA = iradio-ui.xml
-
 xspfdir = $(plugindatadir)
 xspf_DATA = iradio-initial.xspf
 
diff --git a/plugins/iradio/iradio-popup.ui b/plugins/iradio/iradio-popup.ui
new file mode 100644
index 0000000..e4e8f3a
--- /dev/null
+++ b/plugins/iradio/iradio-popup.ui
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="iradio-popup">
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Remove</attribute>
+       <attribute name="action">app.clipboard-delete</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Properties</attribute>
+       <attribute name="action">app.clipboard-properties</attribute>
+      </item>
+    </section>
+  </menu>
+</interface>
diff --git a/plugins/iradio/iradio-toolbar.ui b/plugins/iradio/iradio-toolbar.ui
new file mode 100644
index 0000000..325144f
--- /dev/null
+++ b/plugins/iradio/iradio-toolbar.ui
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="iradio-toolbar">
+    <section>
+      <submenu>
+       <attribute name="label" translatable="yes">Edit</attribute>
+       <attribute name="rb-menu-link">edit-menu</attribute>
+      </submenu>
+      <item>
+       <attribute name="label" translatable="yes">Browse</attribute>
+       <attribute name="rb-property-bind">show-browser</attribute>
+       <attribute name="accel">&lt;Primary&gt;b</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">View All</attribute>
+       <attribute name="action">app.source-view-all</attribute>
+      </item>
+      <item>
+       <attribute name="context">Radio</attribute>
+       <attribute name="label" translatable="yes">Add</attribute>
+       <attribute name="action">app.iradio-new-station</attribute>
+      </item>
+    </section>
+    <section>
+      <attribute name="rb-plugin-menu-link">iradio-toolbar</attribute>
+    </section>
+  </menu>
+</interface>
diff --git a/plugins/iradio/rb-iradio-plugin.c b/plugins/iradio/rb-iradio-plugin.c
index a085642..d691d78 100644
--- a/plugins/iradio/rb-iradio-plugin.c
+++ b/plugins/iradio/rb-iradio-plugin.c
@@ -61,7 +61,6 @@ typedef struct
        PeasExtensionBase parent;
 
        RBSource *source;
-       guint ui_merge_id;
 } RBIRadioPlugin;
 
 typedef struct
@@ -84,26 +83,12 @@ static void
 impl_activate (PeasActivatable *plugin)
 {
        RBIRadioPlugin *pi = RB_IRADIO_PLUGIN (plugin);
-       GtkUIManager *uimanager;
-       char *filename;
        RBShell *shell;
 
        g_object_get (pi, "object", &shell, NULL);
        pi->source = rb_iradio_source_new (shell, G_OBJECT (plugin));
        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_find_plugin_data_file (G_OBJECT (plugin), "iradio-ui.xml");
-       if (filename != NULL) {
-               pi->ui_merge_id = gtk_ui_manager_add_ui_from_file (uimanager,
-                                                             filename,
-                                                             NULL);
-       } else {
-               g_warning ("Unable to find file: iradio-ui.xml");
-       }
-
-       g_free (filename);
-       g_object_unref (uimanager);
        g_object_unref (shell);
 }
 
@@ -111,20 +96,9 @@ static void
 impl_deactivate        (PeasActivatable *plugin)
 {
        RBIRadioPlugin *pi = RB_IRADIO_PLUGIN (plugin);
-       GtkUIManager *uimanager;
-       RBShell *shell;
-
-       g_object_get (pi, "object", &shell, NULL);
-
-       g_object_get (shell, "ui-manager", &uimanager, NULL);
-       gtk_ui_manager_remove_ui (uimanager, pi->ui_merge_id);
-       g_object_unref (uimanager);
 
        rb_display_page_delete_thyself (RB_DISPLAY_PAGE (pi->source));
-       g_object_unref (pi->source);
        pi->source = NULL;
-
-       g_object_unref (shell);
 }
 
 G_MODULE_EXPORT void
diff --git a/plugins/iradio/rb-iradio-source.c b/plugins/iradio/rb-iradio-source.c
index 7326c03..e4d95ed 100644
--- a/plugins/iradio/rb-iradio-source.c
+++ b/plugins/iradio/rb-iradio-source.c
@@ -54,6 +54,8 @@
 #include "rb-cut-and-paste-code.h"
 #include "rb-source-search-basic.h"
 #include "rb-source-toolbar.h"
+#include "rb-builder-helpers.h"
+#include "rb-application.h"
 
 /* icon names */
 #define IRADIO_SOURCE_ICON  "library-internet-radio"
@@ -89,7 +91,6 @@ static void rb_iradio_entry_type_init (RBIRadioEntryType *etype);
 GType rb_iradio_entry_type_get_type (void);
 
 /* page methods */
-static gboolean impl_show_popup (RBDisplayPage *page);
 static void impl_get_status (RBDisplayPage *page, char **text, char **progress_text, float *progress);
 
 /* source methods */
@@ -117,8 +118,7 @@ static void stations_view_drag_data_received_cb (GtkWidget *widget,
                                                 GtkSelectionData *data,
                                                 guint info, guint time,
                                                 RBIRadioSource *source);
-static void rb_iradio_source_cmd_new_station (GtkAction *action,
-                                             RBIRadioSource *source);
+static void new_station_action_cb (GSimpleAction *, GVariant *, gpointer);
 
 static void playing_source_changed_cb (RBShellPlayer *player,
                                       RBSource *source,
@@ -134,8 +134,6 @@ struct RBIRadioSourcePrivate
 {
        RhythmDB *db;
 
-       GtkActionGroup *action_group;
-
        RBSourceToolbar *toolbar;
        RBPropertyView *genres;
        RBEntryView *stations;
@@ -150,17 +148,12 @@ struct RBIRadioSourcePrivate
        gint info_available_id;
 
        gboolean dispose_has_run;
+
+       GMenuModel *popup;
 };
 
 #define RB_IRADIO_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_IRADIO_SOURCE, 
RBIRadioSourcePrivate))
 
-static GtkActionEntry rb_iradio_source_actions [] =
-{
-       { "MusicNewInternetRadioStation", IRADIO_NEW_STATION_ICON, N_("New Internet _Radio Station..."), 
"<control>I",
-         N_("Create a new Internet Radio station"),
-         G_CALLBACK (rb_iradio_source_cmd_new_station) }
-};
-
 static const GtkTargetEntry stations_view_drag_types[] = {
        {  "text/uri-list", 0, 0 },
        {  "_NETSCAPE_URL", 0, 1 },
@@ -201,7 +194,6 @@ 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;
 
        source_class->impl_can_copy = (RBSourceFeatureFunc) rb_false_function;
@@ -257,11 +249,6 @@ rb_iradio_source_dispose (GObject *object)
                source->priv->db = NULL;
        }
 
-       if (source->priv->action_group != NULL) {
-               g_object_unref (source->priv->action_group);
-               source->priv->action_group = NULL;
-       }
-
        if (source->priv->default_search != NULL) {
                g_object_unref (source->priv->default_search);
                source->priv->default_search = NULL;
@@ -280,13 +267,16 @@ rb_iradio_source_constructed (GObject *object)
 {
        RBIRadioSource *source;
        RBShell *shell;
-       GtkAction *action;
+       GtkWidget *window;
        GSettings *settings;
-       GtkUIManager *ui_manager;
+       GtkAccelGroup *accel_group;
        GtkWidget *grid;
        GtkWidget *paned;
        gint size;
        GdkPixbuf *pixbuf;
+       GActionEntry actions[] = {
+               { "iradio-new-station", new_station_action_cb },
+       };
 
        RB_CHAIN_GOBJECT_METHOD (rb_iradio_source_parent_class, constructed, object);
        source = RB_IRADIO_SOURCE (object);
@@ -297,7 +287,7 @@ rb_iradio_source_constructed (GObject *object)
        g_object_get (shell,
                      "db", &source->priv->db,
                      "shell-player", &source->priv->player,
-                     "ui-manager", &ui_manager,
+                     "accel-group", &accel_group,
                      NULL);
        g_object_unref (shell);
 
@@ -331,18 +321,7 @@ rb_iradio_source_constructed (GObject *object)
                g_object_unref (plugin);
        }
 
-       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");
-        /* Translators: this is the toolbar button label for 
-           New Internet Radio Station action. */
-        g_object_set (action, "short-label", C_("Radio", "Add"), NULL);
-
+       _rb_add_display_page_actions (G_ACTION_MAP (g_application_get_default ()), G_OBJECT (shell), actions, 
G_N_ELEMENTS (actions));
 
        /* set up stations view */
        source->priv->stations = rb_entry_view_new (source->priv->db, G_OBJECT (source->priv->player),
@@ -397,8 +376,8 @@ rb_iradio_source_constructed (GObject *object)
        gtk_paned_pack2 (GTK_PANED (paned), GTK_WIDGET (source->priv->stations), TRUE, FALSE);
 
        /* set up toolbar */
-       source->priv->toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (source), ui_manager);
-       rb_source_toolbar_add_search_entry (source->priv->toolbar, NULL, _("Search your internet radio 
stations"));
+       source->priv->toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (source), accel_group);
+       rb_source_toolbar_add_search_entry (source->priv->toolbar, _("Search your internet radio stations"));
 
        grid = gtk_grid_new ();
        gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
@@ -423,6 +402,8 @@ rb_iradio_source_constructed (GObject *object)
        source->priv->default_search = rb_iradio_source_search_new ();
 
        rb_iradio_source_do_query (source);
+
+       g_object_unref (accel_group);
 }
 
 static void
@@ -468,6 +449,8 @@ rb_iradio_source_new (RBShell *shell, GObject *plugin)
        RhythmDBEntryType *entry_type;
        RhythmDB *db;
        GSettings *settings;
+       GtkBuilder *builder;
+       GMenu *toolbar;
 
        g_object_get (shell, "db", &db, NULL);
 
@@ -483,6 +466,10 @@ rb_iradio_source_new (RBShell *shell, GObject *plugin)
        }
        g_object_unref (db);
 
+       builder = rb_builder_load_plugin_file (plugin, "iradio-toolbar.ui", NULL);
+       toolbar = G_MENU (gtk_builder_get_object (builder, "iradio-toolbar"));
+       rb_application_link_shared_menus (RB_APPLICATION (g_application_get_default ()), toolbar);
+
        settings = g_settings_new ("org.gnome.rhythmbox.plugins.iradio");
        source = RB_SOURCE (g_object_new (RB_TYPE_IRADIO_SOURCE,
                                          "name", _("Radio"),
@@ -490,9 +477,10 @@ rb_iradio_source_new (RBShell *shell, GObject *plugin)
                                          "entry-type", entry_type,
                                          "plugin", plugin,
                                          "settings", g_settings_get_child (settings, "source"),
-                                         "toolbar-path", "/IRadioSourceToolBar",
+                                         "toolbar-menu", toolbar,
                                          NULL));
        g_object_unref (settings);
+       g_object_unref (builder);
        rb_shell_register_entry_type_for_source (shell, source, entry_type);
        return source;
 }
@@ -721,14 +709,32 @@ rb_iradio_source_songs_show_popup_cb (RBEntryView *view,
                                      gboolean over_entry,
                                      RBIRadioSource *source)
 {
-       if (source == NULL) {
+       GtkWidget *menu;
+
+       if (over_entry == FALSE)
                return;
+
+       if (source->priv->popup == NULL) {
+               GtkBuilder *builder;
+               GObject *plugin;
+               g_object_get (source, "plugin", &plugin, NULL);
+               builder = rb_builder_load_plugin_file (plugin, "iradio-popup.ui", NULL);
+               g_object_unref (plugin);
+
+               source->priv->popup = G_MENU_MODEL (gtk_builder_get_object (builder, "iradio-popup"));
+               g_object_ref (source->priv->popup);
+               g_object_unref (builder);
        }
 
-       if (over_entry)
-               _rb_display_page_show_popup (RB_DISPLAY_PAGE (source), "/IRadioViewPopup");
-       else
-               _rb_display_page_show_popup (RB_DISPLAY_PAGE (source), "/IRadioSourcePopup");
+       menu = gtk_menu_new_from_model (source->priv->popup);
+       gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (source), NULL);
+       gtk_menu_popup (GTK_MENU (menu),
+                       NULL,
+                       NULL,
+                       NULL,
+                       NULL,
+                       3,
+                       gtk_get_current_event_time ());
 }
 
 static void
@@ -949,13 +955,6 @@ stations_view_drag_data_received_cb (GtkWidget *widget,
        return;
 }
 
-static gboolean
-impl_show_popup (RBDisplayPage *page)
-{
-       _rb_display_page_show_popup (page, "/IRadioSourcePopup");
-       return TRUE;
-}
-
 static void
 new_station_location_added (RBURIDialog    *dialog,
                            const char     *uri,
@@ -971,9 +970,9 @@ new_station_response_cb (GtkDialog *dialog, int response, gpointer meh)
 }
 
 static void
-rb_iradio_source_cmd_new_station (GtkAction *action,
-                                 RBIRadioSource *source)
+new_station_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
+       RBIRadioSource *source = RB_IRADIO_SOURCE (data);
        GtkWidget *dialog;
 
        rb_debug ("Got new station command");
diff --git a/plugins/lyrics/lyrics.py b/plugins/lyrics/lyrics.py
index 7fd4a51..50e86e7 100644
--- a/plugins/lyrics/lyrics.py
+++ b/plugins/lyrics/lyrics.py
@@ -39,15 +39,6 @@ from LyricsConfigureDialog import LyricsConfigureDialog
 import gettext
 gettext.install('rhythmbox', RB.locale_dir())
 
-ui_str = """
-<ui>
-  <menubar name="MenuBar">
-       <menu name="ViewMenu" action="View">
-         <menuitem name="ViewSongLyrics" action="ViewSongLyrics"/>
-       </menu>
-  </menubar>
-</ui>
-"""
 
 LYRIC_TITLE_STRIP=["\(live[^\)]*\)", "\(acoustic[^\)]*\)", "\([^\)]*mix\)", "\([^\)]*version\)", 
"\([^\)]*edit\)", "\(feat[^\)]*\)"]
 LYRIC_TITLE_REPLACE=[("/", "-"), (" & ", " and ")]
@@ -344,18 +335,16 @@ class LyricsDisplayPlugin(GObject.Object, Peas.Activatable):
 
        def do_activate (self):
                shell = self.object
-               self.action = Gtk.Action (name='ViewSongLyrics', label=_("Song L_yrics"),
-                                         tooltip=_("Display lyrics for the playing song"),
-                                         stock_id='rb-song-lyrics')
-               self.activate_id = self.action.connect ('activate', self.show_song_lyrics, shell)
-               
-               self.action_group = Gtk.ActionGroup (name='SongLyricsPluginActions')
-               self.action_group.add_action_with_accel (self.action, "<control>L")
-               
-               uim = shell.props.ui_manager
-               uim.insert_action_group (self.action_group, 0)
-               self.ui_id = uim.add_ui_from_string (ui_str)
-               uim.ensure_update ()
+
+               self.action = Gio.SimpleAction.new("view-lyrics", None)
+               self.action.connect("activate", self.show_song_lyrics, shell)
+               # set accelerator?
+               window = shell.props.window
+               window.add_action(self.action)
+
+               app = shell.props.application
+               item = Gio.MenuItem.new(label=_("Song Lyrics"), detailed_action="win.view-lyrics")
+               app.add_plugin_menu_item("view", "view-lyrics", item)
 
                sp = shell.props.shell_player
                self.pec_id = sp.connect('playing-song-changed', self.playing_entry_changed)
@@ -369,11 +358,9 @@ class LyricsDisplayPlugin(GObject.Object, Peas.Activatable):
        def do_deactivate (self):
                shell = self.object
                        
-               uim = shell.props.ui_manager
-               uim.remove_ui (self.ui_id)
-               uim.remove_action_group (self.action_group)
-
-               self.action_group = None
+               app = shell.props.application
+               app.remove_plugin_menu_item("view", "view-lyrics")
+               app.remove_action("view-lyrics")
                self.action = None
 
                sp = shell.props.shell_player
@@ -388,7 +375,7 @@ class LyricsDisplayPlugin(GObject.Object, Peas.Activatable):
                        self.window = None
 
 
-       def show_song_lyrics (self, action, shell):
+       def show_song_lyrics (self, action, parameter, shell):
 
                if self.window is not None:
                        self.window.destroy ()
@@ -404,11 +391,11 @@ class LyricsDisplayPlugin(GObject.Object, Peas.Activatable):
 
        def playing_entry_changed (self, sp, entry):
                if entry is not None:
-                       self.action.set_sensitive (True)
+                       self.action.set_enabled (True)
                        if self.window is not None:
                                self.window.update_song_lyrics(entry)
                else:
-                       self.action.set_sensitive (False)
+                       self.action.set_enabled (False)
 
        def window_deleted (self, window):
                print "lyrics window destroyed"
diff --git a/plugins/magnatune/MagnatuneSource.py b/plugins/magnatune/MagnatuneSource.py
index b52322e..435264f 100644
--- a/plugins/magnatune/MagnatuneSource.py
+++ b/plugins/magnatune/MagnatuneSource.py
@@ -35,7 +35,7 @@ import zipfile
 
 import rb
 from gi.repository import RB
-from gi.repository import GObject, Gtk, Gdk, Gio
+from gi.repository import GObject, Gtk, Gdk, Gio, GLib
 
 from TrackListHandler import TrackListHandler
 from DownloadAlbumHandler import DownloadAlbumHandler, MagnatuneDownloadError
@@ -65,6 +65,7 @@ class MagnatuneSource(RB.BrowserSource):
                RB.BrowserSource.__init__(self)
                self.hate = self
 
+               self.__popup = None
                self.__settings = Gio.Settings("org.gnome.rhythmbox.plugins.magnatune")
                # source state
                self.__activated = False
@@ -95,8 +96,16 @@ class MagnatuneSource(RB.BrowserSource):
        # RBSource methods
        #
 
-       def do_impl_show_entry_popup(self):
-               self.show_source_popup("/MagnatuneSourceViewPopup")
+       def do_show_entry_popup(self):
+               if self.__popup is None:
+                       builder = Gtk.Builder()
+                       builder.add_from_file(rb.find_plugin_file(self.props.plugin, "magnatune-popup.ui"))
+                       self.__popup = builder.get_object("magnatune-popup")
+
+               menu = Gtk.Menu.new_from_model(self.__popup)
+               menu.attach_to_widget(self, None)
+               menu.popup(None, None, None, None, 3, Gtk.get_current_event_time())
+
 
        def do_get_status(self, status, progress_text, progress):
                if self.__updating:
@@ -135,7 +144,7 @@ class MagnatuneSource(RB.BrowserSource):
                        self.__show_loading_screen(True)
 
                        # start our catalogue updates
-                       self.__update_id = GObject.timeout_add_seconds(6 * 60 * 60, self.__update_catalogue)
+                       self.__update_id = GLib.timeout_add_seconds(6 * 60 * 60, self.__update_catalogue)
                        self.__update_catalogue()
 
        def do_impl_can_delete(self):
@@ -383,7 +392,7 @@ class MagnatuneSource(RB.BrowserSource):
                        return False
 
                if self.__notify_id == 0:
-                       self.__notify_id = GObject.idle_add(change_idle_cb)
+                       self.__notify_id = GLib.idle_add(change_idle_cb)
 
        #
        # internal purchasing code
@@ -459,9 +468,9 @@ class MagnatuneSource(RB.BrowserSource):
                                remove_download_files()
 
                        if len(self.__downloads) == 0: # All downloads are complete
-                               shell = self.props.shell
-                               manager = shell.props.ui_manager
-                               
manager.get_action("/MagnatuneSourceViewPopup/MagnatuneCancelDownload").set_sensitive(False)
+                               app = Gio.Application.get_default()
+                               action = app.lookup_action("magnatune-download-cancel")
+                               action.set_enabled(False)
                                if success:
                                        shell.notify_custom(4000, _("Finished Downloading"), _("All Magnatune 
downloads have been completed."), None, False)
 
@@ -505,9 +514,9 @@ class MagnatuneSource(RB.BrowserSource):
                                             
Gio.FileCreateFlags.PRIVATE|Gio.FileCreateFlags.REPLACE_DESTINATION,
                                             None)
 
-               shell = self.props.shell
-               manager = shell.props.ui_manager
-               manager.get_action("/MagnatuneSourceViewPopup/MagnatuneCancelDownload").set_sensitive(True)
+               app = Gio.Application.get_default()
+               action = app.lookup_action("magnatune-download-cancel")
+               action.set_enabled(True)
 
                try:
                        # For some reason, Gio.FileCopyFlags.OVERWRITE doesn't work for copy_async
@@ -526,9 +535,9 @@ class MagnatuneSource(RB.BrowserSource):
                for download in self.__copies.values():
                        download.cancel()
 
-               shell = self.props.shell
-               manager = shell.props.ui_manager
-               manager.get_action("/MagnatuneSourceViewPopup/MagnatuneCancelDownload").set_sensitive(False)
+               app = Gio.Application.get_default()
+               action = app.lookup_action("magnatune-download-cancel")
+               action.set_enabled(False)
 
        def playing_entry_changed(self, entry):
                if not self.__db or not entry:
diff --git a/plugins/magnatune/Makefile.am b/plugins/magnatune/Makefile.am
index 098e9f2..50b348b 100644
--- a/plugins/magnatune/Makefile.am
+++ b/plugins/magnatune/Makefile.am
@@ -17,7 +17,9 @@ plugin_in_files = magnatune.plugin.in
 gtkbuilderdir = $(plugindatadir)
 gtkbuilder_DATA =      \
                magnatune-loading.ui                    \
+               magnatune-popup.ui                      \
                magnatune-prefs.ui                      \
+               magnatune-toolbar.ui                    \
                magnatune_logo_color_small.png          \
                magnatune_logo_color_tiny.png
 
diff --git a/plugins/magnatune/magnatune-popup.ui b/plugins/magnatune/magnatune-popup.ui
new file mode 100644
index 0000000..482b317
--- /dev/null
+++ b/plugins/magnatune/magnatune-popup.ui
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="magnatune-popup">
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Add to Queue</attribute>
+       <attribute name="action">app.clipboard-add-to-queue</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Download Album</attribute>
+       <attribute name="action">app.magnatune-album-download</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Artist Info</attribute>
+       <attribute name="action">app.magnatune-artist-info</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Cancel Download</attribute>
+       <attribute name="action">app.magnatune-download-cancel</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Browse this Genre</attribute>
+       <attribute name="action">app.browser-select-genre</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Browse this Artist</attribute>
+       <attribute name="action">app.browser-select-artist</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Browse this Album</attribute>
+       <attribute name="action">app.browser-select-album</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+       <attribute name="label" translatable="yes">Pr_operties</attribute>
+       <attribute name="action">app.clipboard-properties</attribute>
+      </item>
+    </section>
+  </menu>
+</interface>
diff --git a/plugins/magnatune/magnatune-toolbar.ui b/plugins/magnatune/magnatune-toolbar.ui
new file mode 100644
index 0000000..98f3937
--- /dev/null
+++ b/plugins/magnatune/magnatune-toolbar.ui
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="magnatune-toolbar">
+    <section>
+      <submenu>
+       <attribute name="label" translatable="yes">Edit</attribute>
+       <attribute name="rb-menu-link">edit-menu</attribute>
+      </submenu>
+      <item>
+       <attribute name="label" translatable="yes">Browse</attribute>
+       <attribute name="rb-property-bind">show-browser</attribute>
+       <attribute name="accel">&lt;Primary&gt;b</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Download</attribute>
+       <attribute name="action">app.magnatune-album-download</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Artist</attribute>
+       <attribute name="action">app.magnatune-artist-info</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Cancel</attribute>
+       <attribute name="action">app.magnatune-download-cancel</attribute>
+      </item>
+    </section>
+  </menu>
+</interface>
diff --git a/plugins/magnatune/magnatune.py b/plugins/magnatune/magnatune.py
index e88f0e2..ef4330e 100644
--- a/plugins/magnatune/magnatune.py
+++ b/plugins/magnatune/magnatune.py
@@ -43,31 +43,6 @@ import MagnatuneAccount
 import gettext
 gettext.install('rhythmbox', RB.locale_dir())
 
-popup_ui = """
-<ui>
-  <popup name="MagnatuneSourceViewPopup">
-    <menuitem name="AddToQueueLibraryPopup" action="AddToQueue"/>
-    <menuitem name="MagnatuneDownloadAlbum" action="MagnatuneDownloadAlbum"/>
-    <menuitem name="MagnatuneArtistInfo" action="MagnatuneArtistInfo"/>
-    <menuitem name="MagnatuneCancelDownload" action="MagnatuneCancelDownload"/>
-    <separator/>
-    <menuitem name="BrowseGenreLibraryPopup" action="BrowserSrcChooseGenre"/>
-    <menuitem name="BrowseArtistLibraryPopup" action="BrowserSrcChooseArtist"/>
-    <menuitem name="BrowseAlbumLibraryPopup" action="BrowserSrcChooseAlbum"/>
-    <separator/>
-    <menuitem name="PropertiesLibraryPopup" action="MusicProperties"/>
-  </popup>
-  <toolbar name="MagnatuneToolBar">
-    <toolitem name="Browse" action="ViewBrowser"/>
-    <toolitem name="MagnatuneDownloadAlbumToolbar" action="MagnatuneDownloadAlbum"/>
-    <toolitem name="MagnatuneArtistInfoToolbar" action="MagnatuneArtistInfo"/>
-    <toolitem name="MagnatuneCancelDownloadToolbar" action="MagnatuneCancelDownload"/>
-  </toolbar>
-</ui>
-"""
-
-
-
 class MagnatuneEntryType(RB.RhythmDBEntryType):
        def __init__(self):
                RB.RhythmDBEntryType.__init__(self, name='magnatune')
@@ -103,6 +78,18 @@ class Magnatune(GObject.GObject, Peas.Activatable):
        def __init__(self):
                GObject.GObject.__init__(self)
 
+       def download_album_action_cb(self, action, parameter):
+               shell = self.object
+               shell.props.selected_page.download_album()
+
+       def artist_info_action_cb(self, action, parameter):
+               shell = self.object
+               shell.props.selected_page.display_artist_info()
+       
+       def cancel_download_action_cb(self, action, parameter):
+               shell = self.object
+               shell.props.selected_page.cancel_downloads()
+
        def do_activate(self):
                shell = self.object
                self.db = shell.props.db
@@ -118,6 +105,25 @@ class Magnatune(GObject.GObject, Peas.Activatable):
                what, width, height = Gtk.icon_size_lookup(Gtk.IconSize.LARGE_TOOLBAR)
                icon = rb.try_load_icon(theme, "magnatune", width, 0)
 
+               app = Gio.Application.get_default()
+               action = Gio.SimpleAction(name="magnatune-album-download")
+               action.connect("activate", self.download_album_action_cb)
+               app.add_action(action)
+
+               action = Gio.SimpleAction(name="magnatune-artist-info")
+               action.connect("activate", self.artist_info_action_cb)
+               app.add_action(action)
+
+               action = Gio.SimpleAction(name="magnatune-download-cancel")
+               action.connect("activate", self.cancel_download_action_cb)
+               action.set_enabled(False)
+               app.add_action(action)
+
+               builder = Gtk.Builder()
+               builder.add_from_file(rb.find_plugin_file(self, "magnatune-toolbar.ui"))
+               toolbar = builder.get_object("magnatune-toolbar")
+               app.link_shared_menus(toolbar)
+
                group = RB.DisplayPageGroup.get_by_id ("stores")
                settings = Gio.Settings("org.gnome.rhythmbox.plugins.magnatune")
                self.source = GObject.new(MagnatuneSource,
@@ -127,45 +133,15 @@ class Magnatune(GObject.GObject, Peas.Activatable):
                                          plugin=self,
                                          settings=settings.get_child("source"),
                                          name=_("Magnatune"),
-                                         toolbar_path="/MagnatuneToolBar")
+                                         toolbar_menu=toolbar)
 
                shell.register_entry_type_for_source(self.source, self.entry_type)
                shell.append_display_page(self.source, group)
 
-               manager = shell.props.ui_manager
-               # Add the popup menu actions
-               self.action_group = Gtk.ActionGroup(name='MagnatunePluginActions')
-
-               action = Gtk.Action(name='MagnatuneDownloadAlbum', label=_("Download Album"),
-                               tooltip=_("Download this album from Magnatune"),
-                               stock_id='gtk-save')
-               action.connect('activate', lambda a: shell.props.selected_page.download_album())
-               self.action_group.add_action(action)
-               action = Gtk.Action(name='MagnatuneArtistInfo', label=_("Artist Information"),
-                               tooltip=_("Get information about this artist"),
-                               stock_id='gtk-info')
-               action.connect('activate', lambda a: shell.props.selected_page.display_artist_info())
-               self.action_group.add_action(action)
-               action = Gtk.Action(name='MagnatuneCancelDownload', label=_("Cancel Downloads"),
-                               tooltip=_("Stop album downloads"),
-                               stock_id='gtk-stop')
-               action.connect('activate', lambda a: shell.props.selected_page.cancel_downloads())
-               action.set_sensitive(False)
-               self.action_group.add_action(action)
-
-               manager.insert_action_group(self.action_group, 0)
-               self.ui_id = manager.add_ui_from_string(popup_ui)
-
                self.pec_id = shell.props.shell_player.connect('playing-song-changed', 
self.playing_entry_changed)
-               manager.ensure_update()
-
 
        def do_deactivate(self):
                shell = self.object
-               manager = shell.props.ui_manager
-               manager.remove_ui(self.ui_id)
-               manager.remove_action_group(self.action_group)
-               self.action_group = None
 
                shell.props.shell_player.disconnect(self.pec_id)
 
diff --git a/plugins/mtpdevice/Makefile.am b/plugins/mtpdevice/Makefile.am
index ac193bc..3afc995 100644
--- a/plugins/mtpdevice/Makefile.am
+++ b/plugins/mtpdevice/Makefile.am
@@ -51,16 +51,14 @@ plugin_in_files = mtpdevice.plugin.in
 
 %.plugin: %.plugin.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) 
$(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
 
-uixmldir = $(plugindatadir)
-uixml_DATA = mtp-ui.xml
-
 plugin_DATA = $(plugin_in_files:.plugin.in=.plugin)
 
 gtkbuilderdir = $(plugindatadir)
 gtkbuilder_DATA =                                      \
-       mtp-info.ui
+       mtp-info.ui                                     \
+       mtp-toolbar.ui
 
-EXTRA_DIST = $(uixml_DATA) $(plugin_in_files) $(gtkbuilder_DATA)
+EXTRA_DIST = $(plugin_in_files) $(gtkbuilder_DATA)
 
 CLEANFILES = $(plugin_DATA)
 DISTCLEANFILES = $(plugin_DATA)
diff --git a/plugins/mtpdevice/mtp-toolbar.ui b/plugins/mtpdevice/mtp-toolbar.ui
new file mode 100644
index 0000000..b9a4d95
--- /dev/null
+++ b/plugins/mtpdevice/mtp-toolbar.ui
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="mtp-toolbar">
+    <section>
+      <submenu>
+       <attribute name="label" translatable="yes">Edit</attribute>
+       <attribute name="rb-menu-link">edit-menu</attribute>
+      </submenu>
+      <item>
+       <attribute name="label" translatable="yes">Browse</attribute>
+       <attribute name="rb-property-bind">show-browser</attribute>
+       <attribute name="accel">&lt;Primary&gt;b</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">View All</attribute>
+       <attribute name="action">app.source-view-all</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Properties</attribute>
+       <attribute name="action">app.media-player-properties</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Eject</attribute>
+       <attribute name="action">app.removable-media-eject</attribute>
+      </item>
+      <item>
+       <attribute name="label" translatable="yes">Sync</attribute>
+        <attribute name="action">app.media-player-sync</attribute>
+      </item>
+    </section>
+  </menu>
+</interface>
diff --git a/plugins/mtpdevice/rb-mtp-plugin.c b/plugins/mtpdevice/rb-mtp-plugin.c
index 70c8678..56b0668 100644
--- a/plugins/mtpdevice/rb-mtp-plugin.c
+++ b/plugins/mtpdevice/rb-mtp-plugin.c
@@ -75,9 +75,6 @@ typedef struct
 {
        PeasExtensionBase parent;
 
-       GtkActionGroup *action_group;
-       guint ui_merge_id;
-
        guint create_device_source_id;
 
        GList *mtp_sources;
@@ -107,24 +104,11 @@ static void rb_mtp_plugin_device_removed (LibHalContext *context, const char *ud
 static gboolean rb_mtp_plugin_setup_dbus_hal_connection (RBMtpPlugin *plugin);
 #endif
 
-static void rb_mtp_plugin_rename (GtkAction *action, RBSource *source);
-static void rb_mtp_plugin_properties (GtkAction *action, RBSource *source);
-
 GType rb_mtp_src_get_type (void);
 GType rb_mtp_sink_get_type (void);
 
 RB_DEFINE_PLUGIN(RB_TYPE_MTP_PLUGIN, RBMtpPlugin, rb_mtp_plugin,)
 
-static GtkActionEntry rb_mtp_plugin_actions [] =
-{
-       { "MTPSourceRename", NULL, N_("_Rename"), NULL,
-         N_("Rename MTP-device"),
-         G_CALLBACK (rb_mtp_plugin_rename) },
-       { "MTPSourceProperties", GTK_STOCK_PROPERTIES, N_("_Properties"), NULL,
-         N_("Display device properties"),
-         G_CALLBACK (rb_mtp_plugin_properties) }
-};
-
 static void
 rb_mtp_plugin_init (RBMtpPlugin *plugin)
 {
@@ -136,9 +120,7 @@ static void
 impl_activate (PeasActivatable *bplugin)
 {
        RBMtpPlugin *plugin = RB_MTP_PLUGIN (bplugin);
-       GtkUIManager *uimanager = NULL;
        RBRemovableMediaManager *rmm;
-       char *file = NULL;
        RBShell *shell;
 #if defined(HAVE_GUDEV)
        gboolean rmm_scanned = FALSE;
@@ -148,25 +130,7 @@ impl_activate (PeasActivatable *bplugin)
 #endif
 
        g_object_get (plugin, "object", &shell, NULL);
-
-       g_object_get (shell,
-                    "ui-manager", &uimanager,
-                    "removable-media-manager", &rmm,
-                    NULL);
-
-       /* ui */
-       rb_media_player_source_init_actions (shell);
-       plugin->action_group = gtk_action_group_new ("MTPActions");
-       gtk_action_group_set_translation_domain (plugin->action_group,
-                                                GETTEXT_PACKAGE);
-       _rb_action_group_add_display_page_actions (plugin->action_group,
-                                                  G_OBJECT (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_find_plugin_data_file (G_OBJECT (bplugin), "mtp-ui.xml");
-       plugin->ui_merge_id = gtk_ui_manager_add_ui_from_file (uimanager, file, NULL);
-       g_object_unref (uimanager);
+       g_object_get (shell, "removable-media-manager", &rmm, NULL);
        g_object_unref (shell);
 
        /* device detection */
@@ -218,19 +182,14 @@ static void
 impl_deactivate (PeasActivatable *bplugin)
 {
        RBMtpPlugin *plugin = RB_MTP_PLUGIN (bplugin);
-       GtkUIManager *uimanager = NULL;
        RBRemovableMediaManager *rmm = NULL;
        RBShell *shell;
 
        g_object_get (plugin, "object", &shell, NULL);
        g_object_get (shell,
-                     "ui-manager", &uimanager,
                      "removable-media-manager", &rmm,
                      NULL);
 
-       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_display_page_delete_thyself, NULL);
        g_list_free (plugin->mtp_sources);
        plugin->mtp_sources = NULL;
@@ -255,36 +214,10 @@ impl_deactivate (PeasActivatable *bplugin)
        }
 #endif
 
-       g_object_unref (uimanager);
        g_object_unref (rmm);
        g_object_unref (shell);
 }
 
-static void
-rb_mtp_plugin_rename (GtkAction *action, RBSource *source)
-{
-       RBShell *shell;
-       RBDisplayPageTree *page_tree;
-
-       g_return_if_fail (RB_IS_MTP_SOURCE (source));
-
-       g_object_get (source, "shell", &shell, NULL);
-       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);
-}
-
-static void
-rb_mtp_plugin_properties (GtkAction *action, RBSource *source)
-{
-       g_return_if_fail (RB_IS_MTP_SOURCE (source));
-       rb_media_player_source_show_properties (RB_MEDIA_PLAYER_SOURCE (source));
-}
-
-
 #if defined(HAVE_GUDEV)
 static void
 source_deleted_cb (RBMtpSource *source, RBMtpPlugin *plugin)
@@ -292,35 +225,6 @@ source_deleted_cb (RBMtpSource *source, RBMtpPlugin *plugin)
        plugin->mtp_sources = g_list_remove (plugin->mtp_sources, source);
 }
 
-static void
-set_properties_action_sensitive (RBMtpPlugin *plugin, RBMtpSource *source)
-{
-       gboolean selected;
-       RBSourceLoadStatus load_status;
-       GtkAction *action;
-
-       g_object_get (source,
-                     "selected", &selected,
-                     "load-status", &load_status,
-                     NULL);
-       if (selected) {
-               action = gtk_action_group_get_action (plugin->action_group, "MTPSourceProperties");
-               gtk_action_set_sensitive (action, (load_status == RB_SOURCE_LOAD_STATUS_LOADED));
-       }
-}
-
-static void
-source_selected_cb (GObject *object, GParamSpec *pspec, RBMtpPlugin *plugin)
-{
-       set_properties_action_sensitive (plugin, RB_MTP_SOURCE (object));
-}
-
-static void
-source_load_status_cb (GObject *object, GParamSpec *pspec, RBMtpPlugin *plugin)
-{
-       set_properties_action_sensitive (plugin, RB_MTP_SOURCE (object));
-}
-
 static int
 get_property_as_int (GUdevDevice *device, const char *property, int base)
 {
@@ -391,12 +295,6 @@ create_source_device_cb (RBRemovableMediaManager *rmm, GObject *device_obj, RBMt
                        g_signal_connect_object (G_OBJECT (source),
                                                "deleted", G_CALLBACK (source_deleted_cb),
                                                plugin, 0);
-                       g_signal_connect_object (G_OBJECT (source),
-                                                "notify::selected", G_CALLBACK (source_selected_cb),
-                                                plugin, 0);
-                       g_signal_connect_object (G_OBJECT (source),
-                                                "notify::load-status", G_CALLBACK (source_load_status_cb),
-                                                plugin, 0);
                        g_object_unref (shell);
                        return source;
                }
diff --git a/plugins/mtpdevice/rb-mtp-source.c b/plugins/mtpdevice/rb-mtp-source.c
index 8134d09..ca0bd36 100644
--- a/plugins/mtpdevice/rb-mtp-source.c
+++ b/plugins/mtpdevice/rb-mtp-source.c
@@ -55,6 +55,7 @@
 #include "rb-sync-settings.h"
 #include "rb-gst-media-types.h"
 #include "rb-ext-db.h"
+#include "rb-application.h"
 
 #include "rb-mtp-source.h"
 #include "rb-mtp-thread.h"
@@ -76,7 +77,6 @@ static void rb_mtp_source_get_property (GObject *object,
 
 static void impl_delete (RBSource *asource);
 static RBTrackTransferBatch *impl_paste (RBSource *asource, GList *entries);
-static gboolean impl_show_popup (RBDisplayPage *page);
 static gboolean impl_uri_is_source (RBSource *asource, const char *uri);
 
 static gboolean impl_track_added (RBTransferTarget *target,
@@ -187,10 +187,8 @@ 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->selected = impl_selected;
 
-       source_class->impl_can_rename = (RBSourceFeatureFunc) rb_true_function;
        source_class->impl_can_delete = (RBSourceFeatureFunc) rb_true_function;
        source_class->impl_can_paste = (RBSourceFeatureFunc) rb_true_function;
        source_class->impl_can_move_to_trash = (RBSourceFeatureFunc) rb_false_function;
@@ -380,6 +378,7 @@ rb_mtp_source_constructed (GObject *object)
        GdkPixbuf *pixbuf;
        gint size;
 
+
        RB_CHAIN_GOBJECT_METHOD (rb_mtp_source_parent_class, constructed, object);
        source = RB_MTP_SOURCE (object);
 
@@ -577,6 +576,8 @@ rb_mtp_source_new (RBShell *shell,
        RhythmDBEntryType *entry_type;
        RhythmDB *db = NULL;
        GSettings *settings;
+       GtkBuilder *builder;
+       GMenu *toolbar;
        char *name = NULL;
 
        g_object_get (shell, "db", &db, NULL);
@@ -591,6 +592,10 @@ rb_mtp_source_new (RBShell *shell,
        g_free (name);
        g_object_unref (db);
 
+       builder = rb_builder_load_plugin_file (plugin, "mtp-toolbar.ui", NULL);
+       toolbar = G_MENU (gtk_builder_get_object (builder, "mtp-toolbar"));
+       rb_application_link_shared_menus (RB_APPLICATION (g_application_get_default ()), toolbar);
+
        settings = g_settings_new ("org.gnome.rhythmbox.plugins.mtpdevice");
        source = RB_MTP_SOURCE (g_object_new (RB_TYPE_MTP_SOURCE,
                                              "plugin", plugin,
@@ -605,10 +610,11 @@ rb_mtp_source_new (RBShell *shell,
 #endif
                                              "load-status", RB_SOURCE_LOAD_STATUS_LOADING,
                                              "settings", g_settings_get_child (settings, "source"),
-                                             "toolbar-path", "/MTPSourceToolBar",
+                                             "toolbar-menu", toolbar,
                                              "name", _("Media Player"),
                                              NULL));
        g_object_unref (settings);
+       g_object_unref (builder);
 
        rb_shell_register_entry_type_for_source (shell, RB_SOURCE (source), entry_type);
 
@@ -1086,13 +1092,6 @@ impl_paste (RBSource *source, GList *entries)
        return rb_transfer_target_transfer (RB_TRANSFER_TARGET (source), entries, defer);
 }
 
-static gboolean
-impl_show_popup (RBDisplayPage *page)
-{
-       _rb_display_page_show_popup (page, "/MTPSourcePopup");
-       return TRUE;
-}
-
 static RhythmDB *
 get_db_for_source (RBMtpSource *source)
 {
diff --git a/plugins/power-manager/rb-power-manager-plugin.c b/plugins/power-manager/rb-power-manager-plugin.c
index 36934fc..70b48d4 100644
--- a/plugins/power-manager/rb-power-manager-plugin.c
+++ b/plugins/power-manager/rb-power-manager-plugin.c
@@ -52,8 +52,7 @@ typedef struct
 {
        PeasExtensionBase parent;
 
-       GDBusProxy *proxy;
-       guint32 cookie;
+       guint cookie;
        gint handler_id;
        gint timeout_id;
 } RBGPMPlugin;
@@ -74,140 +73,40 @@ rb_gpm_plugin_init (RBGPMPlugin *plugin)
 }
 
 static gboolean
-ignore_error (GError *error)
-{
-       if (error == NULL)
-               return TRUE;
-
-       /* ignore 'no such service' type errors */
-       if (error->domain == G_DBUS_ERROR) {
-               if (error->code == G_DBUS_ERROR_NAME_HAS_NO_OWNER ||
-                   error->code == G_DBUS_ERROR_SERVICE_UNKNOWN)
-                       return TRUE;
-       }
-
-       return FALSE;
-}
-
-static gboolean
-create_dbus_proxy (RBGPMPlugin *plugin)
-{
-       GError *error = NULL;
-
-       if (plugin->proxy != NULL) {
-               return TRUE;
-       }
-
-       plugin->proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
-                                                      G_DBUS_PROXY_FLAGS_NONE,
-                                                      NULL,
-                                                      "org.gnome.SessionManager",
-                                                      "/org/gnome/SessionManager",
-                                                      "org.gnome.SessionManager",
-                                                      NULL,
-                                                      &error);
-       if (error != NULL && ignore_error (error) == FALSE) {
-               g_warning ("Failed to create dbus proxy for org.gnome.SessionManager: %s",
-                          error->message);
-               g_error_free (error);
-               return FALSE;
-       }
-
-       return TRUE;
-}
-
-static void
-inhibit_done (GObject *proxy, GAsyncResult *res, RBGPMPlugin *plugin)
-{
-       GError *error = NULL;
-       GVariant *result;
-
-       result = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, &error);
-       if (error != NULL) {
-               if (!ignore_error (error)) {
-                       g_warning ("Unable to inhibit session suspend: %s", error->message);
-               } else {
-                       rb_debug ("unable to inhibit: %s", error->message);
-               }
-               g_clear_error (&error);
-       } else {
-               g_variant_get (result, "(u)", &plugin->cookie);
-               rb_debug ("inhibited, got cookie %u", plugin->cookie);
-
-               g_variant_unref (result);
-       }
-       g_object_unref (plugin);
-}
-
-static gboolean
 inhibit (RBGPMPlugin *plugin)
 {
-       GtkWindow *window;
-       gulong xid = 0;
-       GError *error = NULL;
        RBShell *shell;
+       GtkApplication *app;
+       GtkWindow *window;
 
        plugin->timeout_id = 0;
        if (plugin->cookie != 0) {
-               rb_debug ("Was going to inhibit gnome-session, but we already have done");
-               return FALSE;
-       }
-
-       if (create_dbus_proxy (plugin) == FALSE) {
+               rb_debug ("Was going to inhibit session manager, but we already have done");
                return FALSE;
        }
 
        rb_debug ("inhibiting");
-       g_object_ref (plugin);
 
        g_object_get (plugin, "object", &shell, NULL);
-       g_object_get (shell, "window", &window, NULL);
+       g_object_get (shell,
+                     "application", &app,
+                     "window", &window,
+                     NULL);
        g_object_unref (shell);
 
-       xid = gdk_x11_window_get_xid (gtk_widget_get_window (GTK_WIDGET (window)));
-       g_dbus_proxy_call (plugin->proxy,
-                          "Inhibit",
-                          g_variant_new ("(susu)", "rhythmbox", xid, _("Playing"), 4),
-                          G_DBUS_CALL_FLAGS_NONE,
-                          -1,
-                          NULL,
-                          (GAsyncReadyCallback) inhibit_done,
-                          plugin);
-       if (error != NULL) {
-               g_warning ("Unable to inhibit session suspend: %s", error->message);
-               g_clear_error (&error);
-       }
+       gtk_application_inhibit (app, window, GTK_APPLICATION_INHIBIT_IDLE, _("Playing"));
 
        g_object_unref (window);
+       g_object_unref (app);
        return FALSE;
 }
 
-static void
-uninhibit_done (GObject *proxy, GAsyncResult *res, RBGPMPlugin *plugin)
-{
-       GError *error = NULL;
-       GVariant *result;
-
-       result = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, &error);
-       if (error != NULL) {
-               if (!ignore_error (error)) {
-                       g_warning ("Failed to uninhibit session suspend: %s", error->message);
-               } else {
-                       rb_debug ("failed to uninhibit: %s", error->message);
-               }
-               g_clear_error (&error);
-       } else {
-               rb_debug ("uninhibited");
-               plugin->cookie = 0;
-
-               g_variant_unref (result);
-       }
-       g_object_unref (plugin);
-}
-
 static gboolean
 uninhibit (RBGPMPlugin *plugin)
 {
+       GtkApplication *app;
+       RBShell *shell;
+
        plugin->timeout_id = 0;
 
        if (plugin->cookie == 0) {
@@ -215,19 +114,14 @@ uninhibit (RBGPMPlugin *plugin)
                return FALSE;
        }
 
-       if (create_dbus_proxy (plugin) == FALSE) {
-               return FALSE;
-       }
+       g_object_get (plugin, "object", &shell, NULL);
+       g_object_get (shell, "application", &app, NULL);
+       g_object_unref (shell);
 
        rb_debug ("uninhibiting; cookie = %u", plugin->cookie);
-       g_dbus_proxy_call (plugin->proxy,
-                          "Uninhibit",
-                          g_variant_new ("(u)", plugin->cookie),
-                          G_DBUS_CALL_FLAGS_NONE,
-                          -1,
-                          NULL,
-                          (GAsyncReadyCallback) uninhibit_done,
-                          g_object_ref (plugin));
+       gtk_application_uninhibit (app, plugin->cookie);
+       plugin->cookie = 0;
+       g_object_unref (app);
        return FALSE;
 }
 
@@ -303,11 +197,6 @@ impl_deactivate (PeasActivatable *bplugin)
 
        g_object_unref (shell);
        g_object_unref (shell_player);
-
-       if (plugin->proxy != NULL) {
-               g_object_unref (plugin->proxy);
-               plugin->proxy = NULL;
-       }
 }
 
 G_MODULE_EXPORT void
diff --git a/plugins/pythonconsole/pythonconsole.py b/plugins/pythonconsole/pythonconsole.py
index b78251c..6a889d2 100644
--- a/plugins/pythonconsole/pythonconsole.py
+++ b/plugins/pythonconsole/pythonconsole.py
@@ -37,7 +37,7 @@ import sys
 import re
 import traceback
 
-from gi.repository import Gtk, Gdk, GObject, Pango, Peas
+from gi.repository import GLib, Gtk, Gdk, Gio, GObject, Pango, Peas
 from gi.repository import RB
 
 import gettext
@@ -49,22 +49,6 @@ try:
 except:
        have_rpdb2 = False
 
-import gettext
-gettext.install('rhythmbox', RB.locale_dir())
-
-ui_str = """
-<ui>
-  <menubar name="MenuBar">
-    <menu name="ToolsMenu" action="Tools">
-      <placeholder name="ToolsOps_5">
-        <menuitem name="PythonConsole" action="PythonConsole"/>
-        <menuitem name="PythonDebugger" action="PythonDebugger"/>
-      </placeholder>
-    </menu>
-  </menubar>
-</ui>
-"""
-
 class PythonConsolePlugin(GObject.Object, Peas.Activatable):
        __gtype_name__ = 'PythonConsolePlugin'
 
@@ -73,50 +57,44 @@ class PythonConsolePlugin(GObject.Object, Peas.Activatable):
        def __init__(self):
                GObject.Object.__init__(self)
                self.window = None
-                       
+               
        def do_activate(self):
-               data = dict()
                shell = self.object
-               manager = shell.props.ui_manager
-               
-               data['action_group'] = Gtk.ActionGroup(name='PythonConsolePluginActions')
+               app = shell.props.application
 
-               action = Gtk.Action(name='PythonConsole', label=_("_Python Console"),
-                                   tooltip=_("Show Rhythmbox's python console"),
-                                   stock_id='gnome-mime-text-x-python')
+               action = Gio.SimpleAction.new("python-console", None)
                action.connect('activate', self.show_console, shell)
-               data['action_group'].add_action(action)
+               app.add_action(action)
+
+               app.add_plugin_menu_item("tools",
+                                        "python-console",
+                                        Gio.MenuItem.new(label=_("Python Console"),
+                                                         detailed_action="app.python-console"))
 
-               action = Gtk.Action(name='PythonDebugger', label=_("Python Debugger"),
-                                   tooltip=_("Enable remote python debugging with rpdb2"),
-                                   stock_id=None)
                if have_rpdb2:
+                       action = Gio.SimpleAction.new("python-debugger", None)
                        action.connect('activate', self.enable_debugging, shell)
-               else:
-                       action.set_visible(False)
-               data['action_group'].add_action(action)
-                               
-               manager.insert_action_group(data['action_group'], 0)
-               data['ui_id'] = manager.add_ui_from_string(ui_str)
-               manager.ensure_update()
+                       app.add_action(action)
+
+                       app.add_plugin_menu_item("tools",
+                                                "python-debugger",
+                                                Gio.MenuItem.new(label=_("Python Debugger"),
+                                                                 detailed_action="app.python-debugger"))
                
-               shell.PythonConsolePluginInfo = data
-       
        def do_deactivate(self):
                shell = self.object
-               data = shell.PythonConsolePluginInfo
-
-               manager = shell.props.ui_manager
-               manager.remove_ui(data['ui_id'])
-               manager.remove_action_group(data['action_group'])
-               manager.ensure_update()
+               app = shell.props.application
 
-               shell.PythonConsolePluginInfo = None
+               app.remove_plugin_menu_item("tools", "python-console")
+               app.remove_plugin_menu_item("tools", "python-debugger")
+               app.remove_action("python-console")
+               app.remove_action("python-debugger")
                
                if self.window is not None:
                        self.window.destroy()
                
-       def show_console(self, action, shell):
+
+       def show_console(self, action, parameter, shell):
                if not self.window:
                        ns = {'__builtins__' : __builtins__, 
                              'RB' : RB,
@@ -138,7 +116,7 @@ class PythonConsolePlugin(GObject.Object, Peas.Activatable):
                        self.window.show_all()
                self.window.grab_focus()
 
-       def enable_debugging(self, action, shell):
+       def enable_debugging(self, action, parameter, shell):
                pwd_path = os.path.join(rb.user_data_dir(), "rpdb2_password")
                msg = _("After you press OK, Rhythmbox will wait until you connect to it with winpdb or 
rpdb2. If you have not set a debugger password in the file %s, it will use the default password 
('rhythmbox').") % pwd_path
                dialog = Gtk.MessageDialog(None, 0, Gtk.MessageType.INFO, Gtk.ButtonsType.OK_CANCEL, msg)
diff --git a/plugins/sendto/sendto.py b/plugins/sendto/sendto.py
index dd33531..036482a 100644
--- a/plugins/sendto/sendto.py
+++ b/plugins/sendto/sendto.py
@@ -25,27 +25,12 @@
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 
 import rb
-from gi.repository import Gtk, GObject, GLib, Peas
+from gi.repository import Gio, GObject, GLib, Peas
 from gi.repository import RB
 
 import gettext
 gettext.install('rhythmbox', RB.locale_dir())
 
-ui_definition = """
-<ui>
-    <popup name="BrowserSourceViewPopup">
-        <menuitem name="SendToLibraryPopup" action="SendTo" />
-    </popup>
-
-    <popup name="PlaylistViewPopup">
-        <menuitem name="SendToPlaylistPopup" action="SendTo" />
-    </popup>
-
-    <popup name="QueuePlaylistViewPopup">
-        <menuitem name="SendToQueuePlaylistPopup" action="SendTo" />
-    </popup>
-</ui>"""
-
 class SendToPlugin (GObject.Object, Peas.Activatable):
     __gtype_name__ = 'SendToPlugin'
 
@@ -55,30 +40,32 @@ class SendToPlugin (GObject.Object, Peas.Activatable):
         GObject.Object.__init__(self)
 
     def do_activate(self):
-        self.__action = Gtk.Action(name='SendTo', label=_("Send to..."),
-                                tooltip=_("Send files by mail, instant message..."),
-                                stock_id='')
-       shell = self.object
-        self.__action.connect('activate', self.send_to, shell)
+       self.__action = Gio.SimpleAction(name='sendto')
+       self.__action.connect('activate', self.send_to)
 
-        self.__action_group = Gtk.ActionGroup(name='SendToActionGroup')
-        self.__action_group.add_action(self.__action)
+       app = Gio.Application.get_default()
+       app.add_action(self.__action)
 
-       uim = shell.props.ui_manager
-        uim.insert_action_group(self.__action_group, -1)
-        self.__ui_id = uim.add_ui_from_string(ui_definition)
+       item = Gio.MenuItem()
+       item.set_label(_("Send to..."))
+       item.set_detailed_action('app.sendto')
+       app.add_plugin_menu_item('edit', 'sendto', item)
+       app.add_plugin_menu_item('browser-popup', 'sendto', item)
+       app.add_plugin_menu_item('playlist-popup', 'sendto', item)
+       app.add_plugin_menu_item('queue-popup', 'sendto', item)
 
     def do_deactivate(self):
        shell = self.object
-       uim = shell.props.ui_manager
-        uim.remove_action_group(self.__action_group)
-        uim.remove_ui(self.__ui_id)
-        uim.ensure_update()
-
-        del self.__action_group
-        del self.__action
+       app = Gio.Application.get_default()
+       app.remove_action('sendto')
+       app.remove_plugin_menu_item('edit', 'sendto')
+       app.remove_plugin_menu_item('browser-popup', 'sendto')
+       app.remove_plugin_menu_item('playlist-popup', 'sendto')
+       app.remove_plugin_menu_item('queue-popup', 'sendto')
+       del self.__action
 
-    def send_to(self, action, shell):
+    def send_to(self, action, data):
+       shell = self.object
         page = shell.props.selected_page
         if not hasattr(page, "get_entry_view"):
             return
diff --git a/plugins/visualizer/rb-visualizer-menu.c b/plugins/visualizer/rb-visualizer-menu.c
index b8dce3a..be7018b 100644
--- a/plugins/visualizer/rb-visualizer-menu.c
+++ b/plugins/visualizer/rb-visualizer-menu.c
@@ -41,53 +41,6 @@ const VisualizerQuality rb_visualizer_quality[] = {
        { N_("High quality"),   "high", 800,    600,    30,     1 }
 };
 
-static void
-set_check_item_foreach (GtkWidget *widget, GtkCheckMenuItem *item)
-{
-       GtkCheckMenuItem *check = GTK_CHECK_MENU_ITEM (widget);
-       gtk_check_menu_item_set_active (check, check == item);
-}
-
-static void
-quality_item_toggled_cb (GtkMenuItem *item, gpointer data)
-{
-       int index = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "quality"));
-       GSettings *settings = g_object_get_data (G_OBJECT (item), "settings");
-
-       if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)) == FALSE) {
-               return;
-       }
-
-       rb_debug ("vis quality %d (%s) activated", index, rb_visualizer_quality[index].setting);
-       g_settings_set_string (settings, "quality", rb_visualizer_quality[index].setting);
-
-       g_signal_handlers_block_by_func (item, quality_item_toggled_cb, data);
-       gtk_container_foreach (GTK_CONTAINER (data),
-                              (GtkCallback) set_check_item_foreach,
-                              GTK_CHECK_MENU_ITEM (item));
-       g_signal_handlers_unblock_by_func (item, quality_item_toggled_cb, data);
-}
-
-static void
-vis_plugin_item_activate_cb (GtkMenuItem *item, gpointer data)
-{
-       const char *name = g_object_get_data (G_OBJECT (item), "element-name");
-       GSettings *settings = g_object_get_data (G_OBJECT (item), "settings");
-
-       if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)) == FALSE) {
-               return;
-       }
-
-       rb_debug ("vis element %s activated", name);
-       g_settings_set_string (settings, "vis-plugin", name);
-
-       g_signal_handlers_block_by_func (item, vis_plugin_item_activate_cb, data);
-       gtk_container_foreach (GTK_CONTAINER (data),
-                              (GtkCallback) set_check_item_foreach,
-                              GTK_CHECK_MENU_ITEM (item));
-       g_signal_handlers_unblock_by_func (item, vis_plugin_item_activate_cb, data);
-}
-
 static gboolean
 vis_plugin_filter (GstPluginFeature *feature, gpointer data)
 {
@@ -100,50 +53,45 @@ vis_plugin_filter (GstPluginFeature *feature, gpointer data)
        return (g_strrstr (gst_element_factory_get_klass (f), "Visualization") != NULL);
 }
 
-GtkWidget *
-rb_visualizer_create_popup_menu (GtkToggleAction *fullscreen_action)
+GMenu *
+rb_visualizer_create_popup_menu (const char *fullscreen_action)
 {
        GSettings *settings;
-       GtkWidget *menu;
-       GtkWidget *submenu;
-       GtkWidget *item;
+       GMenu *menu;
+       GMenu *section;
+       GMenu *submenu;
+       GMenuItem *item;
+       GAction *quality;
+       GAction *effect;
        GList *features;
        GList *t;
-       char *active_element;
-       int quality;
        int i;
 
-       menu = gtk_menu_new ();
+       menu = g_menu_new ();
 
        settings = g_settings_new ("org.gnome.rhythmbox.plugins.visualizer");
+       quality = g_settings_create_action (settings, "vis-quality");
+       effect = g_settings_create_action (settings, "vis-plugin");
 
        /* fullscreen item */
-       item = gtk_action_create_menu_item (GTK_ACTION (fullscreen_action));
-       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+       section = g_menu_new ();
+       g_menu_append (section, _("Fullscreen"), fullscreen_action);
+       g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
 
        /* quality submenu */
-       quality = g_settings_get_enum (settings, "quality");
-       submenu = gtk_menu_new ();
+       submenu = g_menu_new ();
        for (i = 0; i < G_N_ELEMENTS (rb_visualizer_quality); i++) {
-               item = gtk_check_menu_item_new_with_label (_(rb_visualizer_quality[i].name));
-
-               gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), (i == quality));
-
-               g_object_set_data (G_OBJECT (item), "quality", GINT_TO_POINTER (i));
-               g_object_set_data (G_OBJECT (item), "settings", settings);
-               g_signal_connect (item, "toggled", G_CALLBACK (quality_item_toggled_cb), submenu);
-               gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
+               item = g_menu_item_new (_(rb_visualizer_quality[i].name), NULL);
+               g_menu_item_set_action_and_target (item, "app.vis-quality", "i", i);
+               g_menu_append_item (submenu, item);
        }
 
-       item = gtk_menu_item_new_with_mnemonic (_("_Quality"));
-       gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
-       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+       g_menu_append_submenu (menu, _("Quality"), G_MENU_MODEL (submenu));
 
        /* effect submenu */
-       submenu = gtk_menu_new ();
+       submenu = g_menu_new ();
 
        rb_debug ("building vis plugin list");
-       active_element = g_settings_get_string (settings, "vis-plugin");
        features = gst_registry_feature_filter (gst_registry_get (),
                                                vis_plugin_filter,
                                                FALSE, NULL);
@@ -157,24 +105,13 @@ rb_visualizer_create_popup_menu (GtkToggleAction *fullscreen_action)
                element_name = gst_plugin_feature_get_name (f);
                rb_debug ("adding visualizer element %s (%s)", element_name, name);
 
-               item = gtk_check_menu_item_new_with_label (name);
-               gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
-                                               g_strcmp0 (element_name, active_element) == 0);
-               g_object_set_data (G_OBJECT (item), "element-name", g_strdup (element_name));
-               g_object_set_data (G_OBJECT (item), "settings", settings);
-               gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
-               g_signal_connect (item,
-                                 "activate",
-                                 G_CALLBACK (vis_plugin_item_activate_cb),
-                                 submenu);
+               item = g_menu_item_new (name, NULL);
+               g_menu_item_set_action_and_target (item, "app.vis-plugin", "s", element_name);
+               g_menu_append_item (submenu, item);
        }
        gst_plugin_feature_list_free (features);
 
-       item = gtk_menu_item_new_with_mnemonic (_("_Visual Effect"));
-       gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
-       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
-
-       gtk_widget_show_all (menu);
+       g_menu_append_submenu (menu, _("Visual Effect"), G_MENU_MODEL (submenu));
        return menu;
 }
 
diff --git a/plugins/visualizer/rb-visualizer-menu.h b/plugins/visualizer/rb-visualizer-menu.h
index d49cfae..5a17296 100644
--- a/plugins/visualizer/rb-visualizer-menu.h
+++ b/plugins/visualizer/rb-visualizer-menu.h
@@ -47,7 +47,7 @@ extern const VisualizerQuality rb_visualizer_quality[];
 
 int    rb_visualizer_menu_clip_quality         (int value);
 
-GtkWidget *rb_visualizer_create_popup_menu     (GtkToggleAction *fullscreen_action);
+GMenu *rb_visualizer_create_popup_menu         (const char *fullscreen_action);
 
 G_END_DECLS
 
diff --git a/plugins/visualizer/rb-visualizer-page.c b/plugins/visualizer/rb-visualizer-page.c
index 6741b88..dffd5bc 100644
--- a/plugins/visualizer/rb-visualizer-page.c
+++ b/plugins/visualizer/rb-visualizer-page.c
@@ -60,7 +60,7 @@ enum {
 static guint signals[LAST_SIGNAL] = {0,};
 
 RBVisualizerPage *
-rb_visualizer_page_new (GObject *plugin, RBShell *shell, GtkToggleAction *fullscreen, GtkWidget *popup)
+rb_visualizer_page_new (GObject *plugin, RBShell *shell, GSimpleAction *fullscreen, GMenuModel *popup)
 {
        GObject *page;
        GdkPixbuf *pixbuf;
@@ -91,7 +91,7 @@ static void
 set_action_state (RBVisualizerPage *page, gboolean active)
 {
        page->setting_state = TRUE;
-       g_object_set (page->fullscreen_action, "active", active, NULL);
+       g_simple_action_set_state (page->fullscreen_action, g_variant_new_boolean (active));
        page->setting_state = FALSE;
 }
 
@@ -178,7 +178,7 @@ toggle_fullscreen (RBVisualizerPage *page)
 }
 
 static void
-toggle_fullscreen_cb (GtkAction *action, RBVisualizerPage *page)
+toggle_fullscreen_cb (GSimpleAction *action, GVariant *parameters, RBVisualizerPage *page)
 {
        if (page->setting_state == FALSE) {
                toggle_fullscreen (page);
@@ -193,7 +193,10 @@ stage_button_press_cb (ClutterActor *stage, ClutterEvent *event, RBVisualizerPag
                toggle_fullscreen (page);
                clutter_threads_enter ();
        } else if (event->button.button == 3) {
-               rb_display_page_show_popup (RB_DISPLAY_PAGE (page));
+               GtkWidget *menu;
+
+               menu = gtk_menu_new_from_model (page->popup);
+               gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 3, event->any.time);
        }
 
        return FALSE;
@@ -233,14 +236,6 @@ create_embed (RBVisualizerPage *page)
        return embed;
 }
 
-static gboolean
-impl_show_popup (RBDisplayPage *page)
-{
-       RBVisualizerPage *vpage = RB_VISUALIZER_PAGE (page);
-       gtk_menu_popup (GTK_MENU (vpage->popup), NULL, NULL, NULL, NULL, 3, gtk_get_current_event_time ());
-       return TRUE;
-}
-
 static void
 impl_selected (RBDisplayPage *bpage)
 {
@@ -388,10 +383,7 @@ impl_constructed (GObject *object)
        gst_element_add_pad (page->sink, gst_ghost_pad_new ("sink", pad));
        gst_object_unref (pad);
 
-       g_signal_connect_object (page->fullscreen_action,
-                                "toggled",
-                                G_CALLBACK (toggle_fullscreen_cb),
-                                page, 0);
+       g_signal_connect (page->fullscreen_action, "activate", G_CALLBACK (toggle_fullscreen_cb), page);
 }
 
 static void
@@ -412,7 +404,6 @@ rb_visualizer_page_class_init (RBVisualizerPageClass *klass)
 
        page_class->selected = impl_selected;
        page_class->deselected = impl_deselected;
-       page_class->show_popup = impl_show_popup;
 
        g_object_class_install_property (object_class,
                                         PROP_SINK,
@@ -426,14 +417,14 @@ rb_visualizer_page_class_init (RBVisualizerPageClass *klass)
                                         g_param_spec_object ("popup",
                                                              "popup",
                                                              "popup menu",
-                                                             GTK_TYPE_WIDGET,
+                                                             G_TYPE_MENU_MODEL,
                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
        g_object_class_install_property (object_class,
                                         PROP_FULLSCREEN_ACTION,
                                         g_param_spec_object ("fullscreen-action",
                                                              "fullscreen action",
-                                                             "GtkToggleAction for fullscreen",
-                                                             GTK_TYPE_TOGGLE_ACTION,
+                                                             "fullscreen action",
+                                                             G_TYPE_SIMPLE_ACTION,
                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
        signals[START] = g_signal_new ("start",
diff --git a/plugins/visualizer/rb-visualizer-page.h b/plugins/visualizer/rb-visualizer-page.h
index f021d6a..044253b 100644
--- a/plugins/visualizer/rb-visualizer-page.h
+++ b/plugins/visualizer/rb-visualizer-page.h
@@ -53,8 +53,8 @@ struct _RBVisualizerPage
        GtkWidget *fullscreen;
        GtkWidget *fullscreen_embed;
 
-       GtkWidget *popup;
-       GtkToggleAction *fullscreen_action;
+       GMenuModel *popup_menu;
+       GSimpleAction *fullscreen_action;
        gboolean setting_state;
 };
 
@@ -75,8 +75,8 @@ void           _rb_visualizer_page_register_type (GTypeModule *module);
 
 RBVisualizerPage        *rb_visualizer_page_new              (GObject *plugin,
                                                              RBShell *shell,
-                                                             GtkToggleAction *fullscreen,
-                                                             GtkWidget *popup);
+                                                             GSimpleAction *fullscreen,
+                                                             GMenuModel *popup_menu);
 
 G_END_DECLS
 
diff --git a/plugins/visualizer/rb-visualizer-plugin.c b/plugins/visualizer/rb-visualizer-plugin.c
index c07c34c..6ae98b8 100644
--- a/plugins/visualizer/rb-visualizer-plugin.c
+++ b/plugins/visualizer/rb-visualizer-plugin.c
@@ -111,7 +111,7 @@ fixate_vis_caps (RBVisualizerPlugin *plugin)
        if (gst_caps_is_fixed (caps) == FALSE) {
                guint i;
                char *dbg;
-               const VisualizerQuality *q = &rb_visualizer_quality[g_settings_get_enum (plugin->settings, 
"quality")];
+               const VisualizerQuality *q = &rb_visualizer_quality[g_settings_get_enum (plugin->settings, 
"vis-quality")];
 
                rb_debug ("fixating caps towards %dx%d, %d/%d", q->width, q->height, q->fps_n, q->fps_d);
                caps = gst_caps_make_writable (caps);
@@ -355,8 +355,7 @@ impl_activate (PeasActivatable *activatable)
        RBVisualizerPlugin *pi = RB_VISUALIZER_PLUGIN (activatable);
        RBDisplayPageGroup *page_group;
        RhythmDBEntry *entry;
-       GtkToggleAction *fullscreen;
-       GtkWidget *menu;
+       GSimpleAction *fullscreen;
        RBShell *shell;
 
        g_object_get (pi, "object", &shell, NULL);
@@ -365,10 +364,7 @@ impl_activate (PeasActivatable *activatable)
        g_signal_connect_object (pi->settings, "changed", G_CALLBACK (settings_changed_cb), pi, 0);
 
        /* create UI actions and menus and stuff */
-       fullscreen = gtk_toggle_action_new ("VisualizerFullscreen",
-                                           _("Fullscreen"),
-                                           _("Toggle fullscreen visual effects"),
-                                           GTK_STOCK_FULLSCREEN);
+       fullscreen = g_simple_action_new_stateful ("visualizer-toggle", "b", "false");
        menu = rb_visualizer_create_popup_menu (fullscreen);
        g_object_ref_sink (menu);
 
diff --git a/po/POTFILES.in b/po/POTFILES.in
index f47a274..66b410d 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -6,24 +6,38 @@ backends/rb-player.c
 data/playlists.xml.in
 data/rhythmbox.desktop.in.in
 data/rhythmbox-device.desktop.in.in
+[type: gettext/glade]data/ui/app-menu.ui
+[type: gettext/glade]data/ui/browser-popup.ui
 [type: gettext/glade]data/ui/create-playlist.ui
+[type: gettext/glade]data/ui/display-page-add-menu.ui
+[type: gettext/glade]data/ui/edit-menu.ui
 [type: gettext/glade]data/ui/general-prefs.ui
 [type: gettext/glade]data/ui/import-dialog.ui
+[type: gettext/glade]data/ui/import-errors-popup.ui
 [type: gettext/glade]data/ui/library-prefs.ui
+[type: gettext/glade]data/ui/library-toolbar.ui
+[type: gettext/glade]data/ui/main-toolbar.ui
 [type: gettext/glade]data/ui/media-player-properties.ui
+[type: gettext/glade]data/ui/missing-files-popup.ui
 [type: gettext/glade]data/ui/playback-prefs.ui
+[type: gettext/glade]data/ui/playlist-menu.ui
+[type: gettext/glade]data/ui/playlist-popup.ui
 [type: gettext/glade]data/ui/playlist-save.ui
+[type: gettext/glade]data/ui/playlist-toolbar.ui
 [type: gettext/glade]data/ui/podcast-add-dialog.ui
 [type: gettext/glade]data/ui/podcast-feed-properties.ui
+[type: gettext/glade]data/ui/podcast-popups.ui
 [type: gettext/glade]data/ui/podcast-prefs.ui
 [type: gettext/glade]data/ui/podcast-properties.ui
+[type: gettext/glade]data/ui/podcast-toolbar.ui
+[type: gettext/glade]data/ui/queue-popups.ui
+[type: gettext/glade]data/ui/queue-toolbar.ui
 [type: gettext/glade]data/ui/song-info-multiple.ui
 [type: gettext/glade]data/ui/song-info.ui
 [type: gettext/glade]data/ui/sync-dialog.ui
 [type: gettext/glade]data/ui/sync-state.ui
 [type: gettext/glade]data/ui/uri-new.ui
 lib/eggdesktopfile.c
-lib/eggsmclient.c
 lib/rb-cut-and-paste-code.c
 lib/rb-file-helpers.c
 lib/rb-util.c
@@ -34,6 +48,7 @@ plugins/artsearch/artsearch.py
 plugins/artsearch/lastfm.py
 [type: gettext/glade]plugins/audiocd/album-info.ui
 [type: gettext/ini]plugins/audiocd/audiocd.plugin.in
+[type: gettext/glade]plugins/audiocd/audiocd-toolbar.ui
 plugins/audiocd/rb-audiocd-info.c
 plugins/audiocd/rb-audiocd-plugin.c
 plugins/audiocd/rb-audiocd-source.c
@@ -61,6 +76,7 @@ plugins/context/tmpl/loading.html
 plugins/context/tmpl/lyrics-tmpl.html
 [type: gettext/ini]plugins/daap/daap.plugin.in
 [type: gettext/glade]plugins/daap/daap-prefs.ui
+[type: gettext/glade]plugins/daap/daap-toolbar.ui
 plugins/daap/rb-daap-plugin.c
 plugins/daap/rb-daap-sharing.c
 plugins/daap/rb-daap-source.c
@@ -69,8 +85,11 @@ plugins/daap/rb-rhythmdb-dmap-db-adapter.c
 [type: gettext/ini]plugins/dbus-media-server/dbus-media-server.plugin.in
 plugins/dbus-media-server/rb-dbus-media-server-plugin.c
 [type: gettext/ini]plugins/fmradio/fmradio.plugin.in
+[type: gettext/glade]plugins/fmradio/fmradio-popup.ui
+[type: gettext/glade]plugins/fmradio/fmradio-toolbar.ui
 plugins/fmradio/rb-fm-radio-source.c
 [type: gettext/glade]plugins/generic-player/generic-player-info.ui
+[type: gettext/glade]plugins/generic-player/generic-player-toolbar.ui
 [type: gettext/ini]plugins/generic-player/generic-player.plugin.in
 plugins/generic-player/rb-generic-player-plugin.c
 plugins/generic-player/rb-generic-player-source.c
@@ -83,11 +102,14 @@ plugins/grilo/rb-grilo-source.c
 plugins/im-status/im-status.py
 [type: gettext/glade]plugins/ipod/ipod-info.ui
 [type: gettext/glade]plugins/ipod/ipod-init.ui
+[type: gettext/glade]plugins/ipod/ipod-toolbar.ui
 [type: gettext/ini]plugins/ipod/ipod.plugin.in
 plugins/ipod/rb-ipod-helpers.c
 plugins/ipod/rb-ipod-plugin.c
 plugins/ipod/rb-ipod-source.c
 [type: gettext/ini]plugins/iradio/iradio.plugin.in
+[type: gettext/glade]plugins/iradio/iradio-popup.ui
+[type: gettext/glade]plugins/iradio/iradio-toolbar.ui
 plugins/iradio/rb-iradio-source.c
 plugins/iradio/rb-station-properties-dialog.c
 [type: gettext/glade]plugins/iradio/station-properties.ui
@@ -99,6 +121,8 @@ plugins/lyrics/LyricsConfigureDialog.py
 plugins/lyrics/lyrics.py
 plugins/lyrics/LyricsSites.py
 [type: gettext/glade]plugins/magnatune/magnatune-loading.ui
+[type: gettext/glade]plugins/magnatune/magnatune-popup.ui
+[type: gettext/glade]plugins/magnatune/magnatune-toolbar.ui
 [type: gettext/ini]plugins/magnatune/magnatune.plugin.in
 [type: gettext/glade]plugins/magnatune/magnatune-prefs.ui
 plugins/magnatune/magnatune.py
@@ -107,6 +131,7 @@ plugins/magnatune/MagnatuneSource.py
 [type: gettext/ini]plugins/mpris/mpris.plugin.in
 [type: gettext/ini]plugins/mtpdevice/mtpdevice.plugin.in
 [type: gettext/glade]plugins/mtpdevice/mtp-info.ui
+[type: gettext/glade]plugins/mtpdevice/mtp-toolbar.ui
 plugins/mtpdevice/rb-mtp-gst-sink.c
 plugins/mtpdevice/rb-mtp-gst-src.c
 plugins/mtpdevice/rb-mtp-plugin.c
@@ -148,6 +173,7 @@ sample-plugins/sample/rb-sample-plugin.c
 [type: gettext/ini]sample-plugins/sample/sample.plugin.in
 [type: gettext/ini]sample-plugins/sample-vala/sample-vala.plugin.in
 shell/main.c
+shell/rb-application.c
 shell/rb-playlist-manager.c
 shell/rb-play-order.c
 shell/rb-removable-media-manager.c
diff --git a/podcast/rb-podcast-main-source.c b/podcast/rb-podcast-main-source.c
index bac625f..ec573a4 100644
--- a/podcast/rb-podcast-main-source.c
+++ b/podcast/rb-podcast-main-source.c
@@ -39,6 +39,7 @@
 #include "rb-file-helpers.h"
 #include "rb-util.h"
 #include "rb-stock-icons.h"
+#include "rb-application.h"
 
 struct _RBPodcastMainSourcePrivate
 {
@@ -55,6 +56,8 @@ rb_podcast_main_source_new (RBShell *shell, RBPodcastManager *podcast_manager)
        RhythmDBQuery *base_query;
        RhythmDB *db;
        GSettings *settings;
+       GtkBuilder *builder;
+       GMenu *toolbar;
 
        g_object_get (shell, "db", &db, NULL);
        base_query = rhythmdb_query_parse (db,
@@ -66,6 +69,10 @@ rb_podcast_main_source_new (RBShell *shell, RBPodcastManager *podcast_manager)
 
        settings = g_settings_new (PODCAST_SETTINGS_SCHEMA);
 
+       builder = rb_builder_load ("podcast-toolbar.ui", NULL);
+       toolbar = G_MENU (gtk_builder_get_object (builder, "podcast-toolbar"));
+       rb_application_link_shared_menus (RB_APPLICATION (g_application_get_default ()), toolbar);
+
        source = RB_SOURCE (g_object_new (RB_TYPE_PODCAST_MAIN_SOURCE,
                                          "name", _("Podcasts"),
                                          "shell", shell,
@@ -73,10 +80,11 @@ rb_podcast_main_source_new (RBShell *shell, RBPodcastManager *podcast_manager)
                                          "podcast-manager", podcast_manager,
                                          "base-query", base_query,
                                          "settings", g_settings_get_child (settings, "source"),
-                                         "toolbar-path", "/PodcastSourceToolBar",
+                                         "toolbar-menu", toolbar,
                                          "show-all-feeds", TRUE,
                                          NULL));
        g_object_unref (settings);
+       g_object_unref (builder);
 
        rhythmdb_query_free (base_query);
 
diff --git a/podcast/rb-podcast-source.c b/podcast/rb-podcast-source.c
index 4dc89b2..f5e7341 100644
--- a/podcast/rb-podcast-source.c
+++ b/podcast/rb-podcast-source.c
@@ -66,21 +66,17 @@
 #include "rb-cell-renderer-pixbuf.h"
 #include "rb-podcast-add-dialog.h"
 #include "rb-source-toolbar.h"
+#include "rb-builder-helpers.h"
+#include "rb-application.h"
+
+static void podcast_add_action_cb (GSimpleAction *, GVariant *, gpointer);
+static void podcast_download_action_cb (GSimpleAction *, GVariant *, gpointer);
+static void podcast_download_cancel_action_cb (GSimpleAction *, GVariant *, gpointer);
+static void podcast_feed_properties_action_cb (GSimpleAction *, GVariant *, gpointer);
+static void podcast_feed_update_action_cb (GSimpleAction *, GVariant *, gpointer);
+static void podcast_feed_update_all_action_cb (GSimpleAction *, GVariant *, gpointer);
+static void podcast_feed_delete_action_cb (GSimpleAction *, GVariant *, gpointer);
 
-static void podcast_cmd_new_podcast            (GtkAction *action,
-                                                RBPodcastSource *source);
-static void podcast_cmd_download_post          (GtkAction *action,
-                                                RBPodcastSource *source);
-static void podcast_cmd_cancel_download                (GtkAction *action,
-                                                RBPodcastSource *source);
-static void podcast_cmd_delete_feed            (GtkAction *action,
-                                                RBPodcastSource *source);
-static void podcast_cmd_update_feed            (GtkAction *action,
-                                                RBPodcastSource *source);
-static void podcast_cmd_update_all             (GtkAction *action,
-                                                RBPodcastSource *source);
-static void podcast_cmd_properties_feed                (GtkAction *action,
-                                                RBPodcastSource *source);
 
 struct _RBPodcastSourcePrivate
 {
@@ -91,13 +87,11 @@ struct _RBPodcastSourcePrivate
        GtkWidget *grid;
        GtkWidget *paned;
        GtkWidget *add_dialog;
-       GtkAction *add_action;
        RBSourceToolbar *toolbar;
 
        RhythmDBPropertyModel *feed_model;
        RBPropertyView *feeds;
        RBEntryView *posts;
-       GtkActionGroup *action_group;
 
        GList *selected_feeds;
        RhythmDBQuery *base_query;
@@ -109,40 +103,13 @@ struct _RBPodcastSourcePrivate
 
        GdkPixbuf *error_pixbuf;
        GdkPixbuf *refresh_pixbuf;
-};
 
-
-static GtkActionEntry rb_podcast_source_actions [] =
-{
-       { "MusicNewPodcast", RB_STOCK_PODCAST_NEW, N_("_New Podcast Feed..."), NULL,
-         N_("Subscribe to a new podcast feed"),
-         G_CALLBACK (podcast_cmd_new_podcast) },
-       { "PodcastSrcDownloadPost", NULL, N_("Download _Episode"), NULL,
-         N_("Download Podcast Episode"),
-         G_CALLBACK (podcast_cmd_download_post) },
-       { "PodcastSrcCancelDownload", GTK_STOCK_CANCEL, N_("_Cancel Download"), NULL,
-         N_("Cancel Episode Download"),
-         G_CALLBACK (podcast_cmd_cancel_download) },
-       { "PodcastFeedProperties", GTK_STOCK_PROPERTIES, N_("_Properties"), NULL,
-         N_("Episode Properties"),
-         G_CALLBACK (podcast_cmd_properties_feed) },
-       { "PodcastFeedUpdate", GTK_STOCK_REFRESH, N_("_Update Podcast Feed"), NULL,
-         N_("Update Feed"),
-         G_CALLBACK (podcast_cmd_update_feed) },
-       { "PodcastFeedDelete", GTK_STOCK_DELETE, N_("_Delete Podcast Feed"), NULL,
-         N_("Delete Feed"),
-         G_CALLBACK (podcast_cmd_delete_feed) },
-       { "PodcastUpdateAllFeeds", GTK_STOCK_REFRESH, N_("_Update All Feeds"), NULL,
-         N_("Update all feeds"),
-         G_CALLBACK (podcast_cmd_update_all) },
+       GMenuModel *feed_popup;
+       GMenuModel *episode_popup;
+       GMenuModel *search_popup;
+       GAction *search_action;
 };
 
-static GtkRadioActionEntry rb_podcast_source_radio_actions [] =
-{
-       { "PodcastSearchAll", NULL, N_("Search all fields"), NULL, NULL, RHYTHMDB_PROP_SEARCH_MATCH },
-       { "PodcastSearchFeeds", NULL, N_("Search podcast feeds"), NULL, NULL, RHYTHMDB_PROP_ALBUM_FOLDED },
-       { "PodcastSearchEpisodes", NULL, N_("Search podcast episodes"), NULL, NULL, 
RHYTHMDB_PROP_TITLE_FOLDED }
-};
 
 static const GtkTargetEntry posts_view_drag_types[] = {
        {  "text/uri-list", 0, 0 },
@@ -175,74 +142,68 @@ podcast_posts_show_popup_cb (RBEntryView *view,
                             gboolean over_entry,
                             RBPodcastSource *source)
 {
-       if (G_OBJECT (source) == NULL) {
-               return;
-       } else if (!over_entry) {
-               _rb_display_page_show_popup (RB_DISPLAY_PAGE (source), "/PodcastSourcePopup");
-       } else {
-               GtkAction* action;
-               GList *lst;
-               gboolean downloadable = FALSE;
-               gboolean cancellable = FALSE;
-
-               lst = rb_entry_view_get_selected_entries (view);
+       GAction* action;
+       GList *lst;
+       gboolean downloadable = FALSE;
+       gboolean cancellable = FALSE;
+       GtkWidget *menu;
+       GtkWidget *window;
 
-               while (lst) {
-                       RhythmDBEntry *entry = (RhythmDBEntry*) lst->data;
-                       gulong status = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_STATUS);
+       lst = rb_entry_view_get_selected_entries (view);
 
-                       if (rb_podcast_manager_entry_in_download_queue (source->priv->podcast_mgr, entry)) {
-                               cancellable = TRUE;
-                       } else if (status != RHYTHMDB_PODCAST_STATUS_COMPLETE) {
-                               downloadable = TRUE;
-                       }
+       while (lst) {
+               RhythmDBEntry *entry = (RhythmDBEntry*) lst->data;
+               gulong status = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_STATUS);
 
-                       lst = lst->next;
+               if (rb_podcast_manager_entry_in_download_queue (source->priv->podcast_mgr, entry)) {
+                       cancellable = TRUE;
+               } else if (status != RHYTHMDB_PODCAST_STATUS_COMPLETE) {
+                       downloadable = TRUE;
                }
 
-               g_list_foreach (lst, (GFunc)rhythmdb_entry_unref, NULL);
-               g_list_free (lst);
+               lst = lst->next;
+       }
+
+       g_list_foreach (lst, (GFunc)rhythmdb_entry_unref, NULL);
+       g_list_free (lst);
 
-               action = gtk_action_group_get_action (source->priv->action_group, "PodcastSrcDownloadPost");
-               gtk_action_set_sensitive (action, downloadable);
+       window = gtk_widget_get_toplevel (GTK_WIDGET (source));
+       action = g_action_map_lookup_action (G_ACTION_MAP (window), "podcast-download");
+       g_simple_action_set_enabled (G_SIMPLE_ACTION (action), downloadable);
 
-               action = gtk_action_group_get_action (source->priv->action_group, "PodcastSrcCancelDownload");
-               gtk_action_set_sensitive (action, cancellable);
+       action = g_action_map_lookup_action (G_ACTION_MAP (window), "podcast-cancel-download");
+       g_simple_action_set_enabled (G_SIMPLE_ACTION (action), cancellable);
 
-               _rb_display_page_show_popup (RB_DISPLAY_PAGE (source), "/PodcastViewPopup");
-       }
+       menu = gtk_menu_new_from_model (source->priv->episode_popup);
+       gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (source), NULL);
+       gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 3, gtk_get_current_event_time ());
 }
 
 static void
 podcast_feeds_show_popup_cb (RBPropertyView *view,
                             RBPodcastSource *source)
 {
-       if (G_OBJECT (source) == NULL) {
-               return;
-       } else {
-               GtkAction *act_update;
-               GtkAction *act_properties;
-               GtkAction *act_delete;
-               GList *lst;
+       GAction *act_update;
+       GAction *act_properties;
+       GAction *act_delete;
+       GtkWidget *window;
+       GtkWidget *menu;
+       GList *lst;
 
-               lst = source->priv->selected_feeds;
+       lst = source->priv->selected_feeds;
 
-               act_update = gtk_action_group_get_action (source->priv->action_group, "PodcastFeedUpdate");
-               act_properties = gtk_action_group_get_action (source->priv->action_group, 
"PodcastFeedProperties");
-               act_delete = gtk_action_group_get_action (source->priv->action_group, "PodcastFeedDelete");
+       window = gtk_widget_get_toplevel (GTK_WIDGET (source));
+       act_update = g_action_map_lookup_action (G_ACTION_MAP (window), "podcast-feed-update");
+       act_properties = g_action_map_lookup_action (G_ACTION_MAP (window), "podcast-feed-properties");
+       act_delete = g_action_map_lookup_action (G_ACTION_MAP (window), "podcast-feed-delete");
 
-               if (lst) {
-                       gtk_action_set_visible (act_update, TRUE);
-                       gtk_action_set_visible (act_properties, TRUE);
-                       gtk_action_set_visible (act_delete, TRUE);
-               } else {
-                       gtk_action_set_visible (act_update, FALSE);
-                       gtk_action_set_visible (act_properties, FALSE);
-                       gtk_action_set_visible (act_delete, FALSE);
-               }
+       g_simple_action_set_enabled (G_SIMPLE_ACTION (act_update), lst != NULL);
+       g_simple_action_set_enabled (G_SIMPLE_ACTION (act_properties), lst != NULL);
+       g_simple_action_set_enabled (G_SIMPLE_ACTION (act_delete), lst != NULL);
 
-               _rb_display_page_show_popup (RB_DISPLAY_PAGE (source), "/PodcastFeedViewPopup");
-       }
+       menu = gtk_menu_new_from_model (source->priv->feed_popup);
+       gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (source), NULL);
+       gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 3, gtk_get_current_event_time ());
 }
 
 static GPtrArray *
@@ -412,8 +373,9 @@ yank_clipboard_url (GtkClipboard *clipboard, const char *text, RBPodcastSource *
 }
 
 static void
-podcast_cmd_new_podcast (GtkAction *action, RBPodcastSource *source)
+podcast_add_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
+       RBPodcastSource *source = RB_PODCAST_SOURCE (data);
        RhythmDBQueryModel *query_model;
 
        rb_podcast_add_dialog_reset (RB_PODCAST_ADD_DIALOG (source->priv->add_dialog), NULL, FALSE);
@@ -442,14 +404,18 @@ podcast_cmd_new_podcast (GtkAction *action, RBPodcastSource *source)
 void
 rb_podcast_source_add_feed (RBPodcastSource *source, const char *text)
 {
-       gtk_action_activate (source->priv->add_action);
+       GtkWidget *window;
+
+       window = gtk_widget_get_toplevel (GTK_WIDGET (source));
+       g_action_group_activate_action (G_ACTION_GROUP (window), "podcast-add", NULL);
 
        rb_podcast_add_dialog_reset (RB_PODCAST_ADD_DIALOG (source->priv->add_dialog), text, TRUE);
 }
 
 static void
-podcast_cmd_download_post (GtkAction *action, RBPodcastSource *source)
+podcast_download_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
+       RBPodcastSource *source = RB_PODCAST_SOURCE (data);
        GList *lst;
        GValue val = {0, };
        RBEntryView *posts;
@@ -481,8 +447,9 @@ podcast_cmd_download_post (GtkAction *action, RBPodcastSource *source)
 }
 
 static void
-podcast_cmd_cancel_download (GtkAction *action, RBPodcastSource *source)
+podcast_download_cancel_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
+       RBPodcastSource *source = RB_PODCAST_SOURCE (data);
        GList *lst;
        GValue val = {0, };
        RBEntryView *posts;
@@ -538,8 +505,9 @@ podcast_remove_response_cb (GtkDialog *dialog, int response, RBPodcastSource *so
 }
 
 static void
-podcast_cmd_delete_feed (GtkAction *action, RBPodcastSource *source)
+podcast_feed_delete_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
+       RBPodcastSource *source = RB_PODCAST_SOURCE (data);
        GtkWidget *dialog;
        GtkWidget *button;
        GtkWindow *window;
@@ -584,8 +552,9 @@ podcast_cmd_delete_feed (GtkAction *action, RBPodcastSource *source)
 }
 
 static void
-podcast_cmd_properties_feed (GtkAction *action, RBPodcastSource *source)
+podcast_feed_properties_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
+       RBPodcastSource *source = RB_PODCAST_SOURCE (data);
        RhythmDBEntry *entry;
        GtkWidget *dialog;
        const char *location;
@@ -606,8 +575,9 @@ podcast_cmd_properties_feed (GtkAction *action, RBPodcastSource *source)
 }
 
 static void
-podcast_cmd_update_feed (GtkAction *action, RBPodcastSource *source)
+podcast_feed_update_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
+       RBPodcastSource *source = RB_PODCAST_SOURCE (data);
        GList *feeds, *l;
 
        rb_debug ("Update action");
@@ -630,8 +600,9 @@ podcast_cmd_update_feed (GtkAction *action, RBPodcastSource *source)
 }
 
 static void
-podcast_cmd_update_all (GtkAction *action, RBPodcastSource *source)
+podcast_feed_update_all_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
+       RBPodcastSource *source = RB_PODCAST_SOURCE (data);
        rb_podcast_manager_update_feeds (source->priv->podcast_mgr);
 }
 
@@ -1038,9 +1009,15 @@ rb_podcast_source_new (RBShell *shell,
 {
        RBSource *source;
        GSettings *settings;
+       GtkBuilder *builder;
+       GMenu *toolbar;
 
        settings = g_settings_new (PODCAST_SETTINGS_SCHEMA);
 
+       builder = rb_builder_load ("podcast-toolbar.ui", NULL);
+       toolbar = G_MENU (gtk_builder_get_object (builder, "podcast-toolbar"));
+       rb_application_link_shared_menus (RB_APPLICATION (g_application_get_default ()), toolbar);
+
        source = RB_SOURCE (g_object_new (RB_TYPE_PODCAST_SOURCE,
                                          "name", name,
                                          "shell", shell,
@@ -1048,9 +1025,10 @@ rb_podcast_source_new (RBShell *shell,
                                          "podcast-manager", podcast_manager,
                                          "base-query", base_query,
                                          "settings", g_settings_get_child (settings, "source"),
-                                         "toolbar-path", "/PodcastSourceToolBar",
+                                         "toolbar-menu", toolbar,
                                          NULL));
        g_object_unref (settings);
+       g_object_unref (builder);
 
        if (icon_name != NULL) {
                GdkPixbuf *pixbuf;
@@ -1277,13 +1255,6 @@ impl_reset_filters (RBSource *asource)
        rb_property_view_set_selection (source->priv->feeds, NULL);
 }
 
-static gboolean
-impl_show_popup (RBDisplayPage *page)
-{
-       _rb_display_page_show_popup (page, "/PodcastSourcePopup");
-       return TRUE;
-}
-
 static void
 impl_song_properties (RBSource *asource)
 {
@@ -1319,9 +1290,9 @@ impl_get_status (RBDisplayPage *page, char **text, char **progress_text, float *
 
 
 static char *
-impl_get_delete_action (RBSource *source)
+impl_get_delete_label (RBSource *source)
 {
-       return g_strdup ("EditDelete");
+       return g_strdup (_("Delete"));
 }
 
 static void
@@ -1380,10 +1351,20 @@ impl_constructed (GObject *object)
        GtkCellRenderer *renderer;
        RBShell *shell;
        RBShellPlayer *shell_player;
-       GtkAction *action;
        GSettings *settings;
        int position;
-       GtkUIManager *ui_manager;
+       GtkAccelGroup *accel_group;
+       GtkBuilder *builder;
+       GMenu *section;
+       GActionEntry actions[] = {
+               { "podcast-add", podcast_add_action_cb },
+               { "podcast-download", podcast_download_action_cb },
+               { "podcast-cancel-download", podcast_download_cancel_action_cb },
+               { "podcast-feed-properties", podcast_feed_properties_action_cb },
+               { "podcast-feed-update", podcast_feed_update_action_cb },
+               { "podcast-feed-update-all", podcast_feed_update_all_action_cb },
+               { "podcast-feed-delete", podcast_feed_delete_action_cb }
+       };
 
        RB_CHAIN_GOBJECT_METHOD (rb_podcast_source_parent_class, constructed, object);
        source = RB_PODCAST_SOURCE (object);
@@ -1392,45 +1373,17 @@ impl_constructed (GObject *object)
        g_object_get (shell,
                      "db", &source->priv->db,
                      "shell-player", &shell_player,
-                     "ui-manager", &ui_manager,
+                     "accel-group", &accel_group,
                      NULL);
 
-       source->priv->action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
-                                                                            "PodcastActions",
-                                                                            NULL, 0,
-                                                                            source);
-
-       _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));
-
-       source->priv->add_action = gtk_action_group_get_action (source->priv->action_group,
-                                                               "MusicNewPodcast");
-       /* Translators: this is the toolbar button label
-          for New Podcast Feed action. */
-       g_object_set (source->priv->add_action, "short-label", C_("Podcast", "Add"), NULL);
-
-       action = gtk_action_group_get_action (source->priv->action_group,
-                                             "PodcastFeedUpdate");
-       /* Translators: this is the toolbar button label
-          for Update Feed action. */
-       g_object_set (action, "short-label", _("Update"), NULL);
-
-       if (gtk_action_group_get_action (source->priv->action_group,
-                                        rb_podcast_source_radio_actions[0].name) == NULL) {
-               gtk_action_group_add_radio_actions (source->priv->action_group,
-                                                   rb_podcast_source_radio_actions,
-                                                   G_N_ELEMENTS (rb_podcast_source_radio_actions),
-                                                   0,
-                                                   NULL,
-                                                   NULL);
-               rb_source_search_basic_create_for_actions (source->priv->action_group,
-                                                          rb_podcast_source_radio_actions,
-                                                          G_N_ELEMENTS (rb_podcast_source_radio_actions));
-       }
+       _rb_add_display_page_actions (G_ACTION_MAP (g_application_get_default ()), G_OBJECT (shell), actions, 
G_N_ELEMENTS (actions));
+
+       builder = rb_builder_load ("podcast-popups.ui", NULL);
+       source->priv->feed_popup = G_MENU_MODEL (gtk_builder_get_object (builder, "podcast-feed-popup"));
+       source->priv->episode_popup = G_MENU_MODEL (gtk_builder_get_object (builder, 
"podcast-episode-popup"));
+       g_object_unref (builder);
 
-       source->priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH);
+       source->priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH, NULL);
 
        source->priv->paned = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
 
@@ -1624,8 +1577,23 @@ impl_constructed (GObject *object)
                           GDK_ACTION_COPY | GDK_ACTION_MOVE);
 
        /* set up toolbar */
-       source->priv->toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (source), ui_manager);
-       rb_source_toolbar_add_search_entry (source->priv->toolbar, "/PodcastSourceSearchMenu", NULL);
+       source->priv->toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (source), accel_group);
+
+       source->priv->search_action = rb_source_create_search_action (RB_SOURCE (source));
+       g_action_map_add_action (G_ACTION_MAP (g_application_get_default ()), source->priv->search_action);
+
+       rb_source_search_basic_register (RHYTHMDB_PROP_SEARCH_MATCH, "search-match", _("Search all fields"));
+       rb_source_search_basic_register (RHYTHMDB_PROP_ALBUM_FOLDED, "podcast-feed", _("Search podcast 
feeds"));
+       rb_source_search_basic_register (RHYTHMDB_PROP_TITLE_FOLDED, "podcast-episode", _("Search podcast 
episodes"));
+
+       section = g_menu_new ();
+       rb_source_search_add_to_menu (section, "app", source->priv->search_action, "search-match");
+       rb_source_search_add_to_menu (section, "app", source->priv->search_action, "podcast-feed");
+       rb_source_search_add_to_menu (section, "app", source->priv->search_action, "podcast-episode");
+       source->priv->search_popup = G_MENU_MODEL (g_menu_new ());
+       g_menu_append_section (G_MENU (source->priv->search_popup), NULL, G_MENU_MODEL (section));
+
+       rb_source_toolbar_add_search_entry_menu (source->priv->toolbar, source->priv->search_popup, 
source->priv->search_action);
 
        /* pack the feed and post views into the source */
        gtk_paned_pack1 (GTK_PANED (source->priv->paned),
@@ -1666,7 +1634,7 @@ impl_constructed (GObject *object)
                                 GTK_WIDGET (source->priv->feeds));
 
        g_object_unref (settings);
-       g_object_unref (ui_manager);
+       g_object_unref (accel_group);
        g_object_unref (shell);
 
        rb_podcast_source_do_query (source, TRUE);
@@ -1679,35 +1647,13 @@ impl_dispose (GObject *object)
 
        source = RB_PODCAST_SOURCE (object);
 
-       if (source->priv->db != NULL) {
-               g_object_unref (source->priv->db);
-               source->priv->db = NULL;
-       }
-
-       if (source->priv->search_query != NULL) {
-               rhythmdb_query_free (source->priv->search_query);
-               source->priv->search_query = NULL;
-       }
-
-       if (source->priv->action_group != NULL) {
-               g_object_unref (source->priv->action_group);
-               source->priv->action_group = NULL;
-       }
-
-       if (source->priv->podcast_mgr != NULL) {
-               g_object_unref (source->priv->podcast_mgr);
-               source->priv->podcast_mgr = NULL;
-       }
-
-       if (source->priv->error_pixbuf != NULL) {
-               g_object_unref (source->priv->error_pixbuf);
-               source->priv->error_pixbuf = NULL;
-       }
-
-       if (source->priv->refresh_pixbuf != NULL) {
-               g_object_unref (source->priv->refresh_pixbuf);
-               source->priv->refresh_pixbuf = NULL;
-       }
+       g_clear_object (&source->priv->db);
+       g_clear_object (&source->priv->search_query);
+       g_clear_object (&source->priv->podcast_mgr);
+       g_clear_object (&source->priv->error_pixbuf);
+       g_clear_object (&source->priv->refresh_pixbuf);
+       g_clear_object (&source->priv->search_action);
+       g_clear_object (&source->priv->search_popup);
 
        G_OBJECT_CLASS (rb_podcast_source_parent_class)->dispose (object);
 }
@@ -1770,7 +1716,6 @@ rb_podcast_source_class_init (RBPodcastSourceClass *klass)
 
        page_class->get_status = impl_get_status;
        page_class->receive_drag = impl_receive_drag;
-       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;
@@ -1782,7 +1727,7 @@ rb_podcast_source_class_init (RBPodcastSourceClass *klass)
        source_class->impl_handle_eos = impl_handle_eos;
        source_class->impl_search = impl_search;
        source_class->impl_song_properties = impl_song_properties;
-       source_class->impl_get_delete_action = impl_get_delete_action;
+       source_class->impl_get_delete_label = impl_get_delete_label;
        source_class->impl_reset_filters = impl_reset_filters;
 
        g_object_class_install_property (object_class,
diff --git a/remote/dbus/rb-client.c b/remote/dbus/rb-client.c
index e4c1d69..abff0e4 100644
--- a/remote/dbus/rb-client.c
+++ b/remote/dbus/rb-client.c
@@ -551,7 +551,7 @@ rate_song (GDBusProxy *mpris, gdouble song_rating)
 static void
 state_changed_cb (GActionGroup *action, const char *action_name, GVariant *state, GMainLoop *loop)
 {
-       if (g_strcmp0 (action_name, "LoadURI") == 0) {
+       if (g_strcmp0 (action_name, "load-uri") == 0) {
                gboolean loaded, scanned;
 
                g_variant_get (state, "(bb)", &loaded, &scanned);
@@ -572,7 +572,7 @@ state_changed_signal_cb (GDBusProxy *proxy, const char *sender_name, const char
        }
 
        g_variant_get (parameters, "(sv)", &action, &state);
-       if (g_strcmp0 (action, "LoadURI") == 0) {
+       if (g_strcmp0 (action, "load-uri") == 0) {
                GApplication *app;
                app = g_object_get_data (G_OBJECT (proxy), "actual-app");
                state_changed_cb (G_ACTION_GROUP (app), action, state, loop);
@@ -663,7 +663,7 @@ main (int argc, char **argv)
        }
 
        /* wait until it's ready to accept control */
-       state = g_action_group_get_action_state (G_ACTION_GROUP (app), "LoadURI");
+       state = g_action_group_get_action_state (G_ACTION_GROUP (app), "load-uri");
        if (state == NULL) {
                rb_debug ("couldn't get app startup state");
                exit (0);
@@ -858,7 +858,7 @@ main (int argc, char **argv)
                                annoy (&error);
                        } else {
                                rb_debug ("importing %s", fileuri);
-                               g_action_group_activate_action (G_ACTION_GROUP (app), "LoadURI", 
g_variant_new ("(sb)", fileuri, FALSE));
+                               g_action_group_activate_action (G_ACTION_GROUP (app), "load-uri", 
g_variant_new ("(sb)", fileuri, FALSE));
                        }
                        g_free (fileuri);
                        g_object_unref (file);
@@ -868,13 +868,13 @@ main (int argc, char **argv)
        /* select/activate/play source */
        if (select_source) {
                rb_debug ("selecting source %s", select_source);
-               g_action_group_activate_action (G_ACTION_GROUP (app), "ActivateSource", g_variant_new 
("(su)", select_source, 0));
+               g_action_group_activate_action (G_ACTION_GROUP (app), "activate-source", g_variant_new 
("(su)", select_source, 0));
        } else if (activate_source) {
                rb_debug ("activating source %s", activate_source);
-               g_action_group_activate_action (G_ACTION_GROUP (app), "ActivateSource", g_variant_new 
("(su)", activate_source, 1));
+               g_action_group_activate_action (G_ACTION_GROUP (app), "activate-source", g_variant_new 
("(su)", activate_source, 1));
        } else if (play_source) {
                rb_debug ("playing source %s", play_source);
-               g_action_group_activate_action (G_ACTION_GROUP (app), "ActivateSource", g_variant_new 
("(su)", play_source, 2));
+               g_action_group_activate_action (G_ACTION_GROUP (app), "activate-source", g_variant_new 
("(su)", play_source, 2));
        }
 
        /* play uri */
@@ -888,7 +888,7 @@ main (int argc, char **argv)
                        g_warning ("couldn't convert \"%s\" to a URI", play_uri);
                } else {
                        rb_debug ("loading and playing %s", fileuri);
-                       g_action_group_activate_action (G_ACTION_GROUP (app), "LoadURI", g_variant_new 
("(sb)", fileuri, TRUE));
+                       g_action_group_activate_action (G_ACTION_GROUP (app), "load-uri", g_variant_new 
("(sb)", fileuri, TRUE));
                        annoy (&error);
                }
                g_free (fileuri);
diff --git a/rhythmdb/rhythmdb-entry-type.c b/rhythmdb/rhythmdb-entry-type.c
index d71a6d1..1abe00c 100644
--- a/rhythmdb/rhythmdb-entry-type.c
+++ b/rhythmdb/rhythmdb-entry-type.c
@@ -38,8 +38,7 @@ enum
        PROP_NAME,
        PROP_SAVE_TO_DISK,
        PROP_TYPE_DATA_SIZE,
-       PROP_CATEGORY,
-       PROP_HAS_PLAYLISTS              /* temporary */
+       PROP_CATEGORY
 };
 
 static void rhythmdb_entry_type_class_init (RhythmDBEntryTypeClass *klass);
@@ -53,7 +52,6 @@ struct _RhythmDBEntryTypePrivate
        gboolean save_to_disk;
        guint entry_type_data_size;
        RhythmDBEntryCategory category;
-       gboolean has_playlists;
 };
 
 G_DEFINE_TYPE (RhythmDBEntryType, rhythmdb_entry_type, G_TYPE_OBJECT)
@@ -235,9 +233,6 @@ impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSp
        case PROP_CATEGORY:
                etype->priv->category = g_value_get_enum (value);
                break;
-       case PROP_HAS_PLAYLISTS:
-               etype->priv->has_playlists = g_value_get_boolean (value);
-               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
@@ -265,9 +260,6 @@ impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *ps
        case PROP_CATEGORY:
                g_value_set_enum (value, etype->priv->category);
                break;
-       case PROP_HAS_PLAYLISTS:
-               g_value_set_boolean (value, etype->priv->has_playlists);
-               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
@@ -358,18 +350,6 @@ rhythmdb_entry_type_class_init (RhythmDBEntryTypeClass *klass)
                                                            RHYTHMDB_TYPE_ENTRY_CATEGORY,
                                                            RHYTHMDB_ENTRY_NORMAL,
                                                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-       /**
-        * RhythmDBEntryType:has-playlists:
-        *
-        * If %TRUE, entries of this type can be added to playlists.
-        */
-       g_object_class_install_property (object_class,
-                                        PROP_HAS_PLAYLISTS,
-                                        g_param_spec_boolean ("has-playlists",
-                                                              "has playlists",
-                                                              "whether this type of entry has playlists",
-                                                              FALSE,
-                                                              G_PARAM_READWRITE));
 
        g_type_class_add_private (klass, sizeof (RhythmDBEntryTypePrivate));
 }
diff --git a/rhythmdb/rhythmdb-song-entry-types.c b/rhythmdb/rhythmdb-song-entry-types.c
index f2203ef..ef73402 100644
--- a/rhythmdb/rhythmdb-song-entry-types.c
+++ b/rhythmdb/rhythmdb-song-entry-types.c
@@ -303,7 +303,6 @@ rhythmdb_register_song_entry_types (RhythmDB *db)
                                        "db", db,
                                        "name", "song",
                                        "save-to-disk", TRUE,
-                                       "has-playlists", TRUE,
                                        NULL);
 
        ignore_entry_type = g_object_new (rhythmdb_ignore_entry_type_get_type (),
diff --git a/shell/Makefile.am b/shell/Makefile.am
index a5dd0aa..be308bc 100644
--- a/shell/Makefile.am
+++ b/shell/Makefile.am
@@ -64,6 +64,8 @@ shellinclude_HEADERS =                                        \
 
 librhythmbox_core_la_SOURCES =                         \
        $(shellinclude_HEADERS)                         \
+       rb-application.c                                \
+       rb-application.h                                \
        rb-history.c                                    \
        rb-play-order.c                                 \
        rb-play-order-linear.c                          \
diff --git a/shell/main.c b/shell/main.c
index 696f3e9..cdc09d8 100644
--- a/shell/main.c
+++ b/shell/main.c
@@ -44,17 +44,15 @@
 #include "rb-shell.h"
 #include "rb-util.h"
 #include "eggdesktopfile.h"
-#include "eggsmclient.h"
 #include "rb-debug.h"
+#include "rb-application.h"
 
 int
 main (int argc, char **argv)
 {
-       RBShell *shell;
-       gboolean autostarted;
+       GApplication *app;
        char *desktop_file_path;
-       int new_argc;
-       char **new_argv;
+       int rc;
 
 #ifdef GDK_WINDOWING_X11
        if (XInitThreads () == 0) {
@@ -66,13 +64,13 @@ main (int argc, char **argv)
        /* disable multidevice so clutter-gtk events work.
         * this needs to be done before gtk_open, so the visualizer
         * plugin can't do it.
+        *
+        * XXX not necessary any more?
         */
        gdk_disable_multidevice ();
        g_type_init ();
        g_random_set_seed (time (0));
 
-       autostarted = (g_getenv ("DESKTOP_AUTOSTART_ID") != NULL);
-
 #ifdef USE_UNINSTALLED_DIRS
        desktop_file_path = g_build_filename (SHARE_UNINSTALLED_BUILDDIR, "rhythmbox.desktop", NULL);
 
@@ -99,21 +97,10 @@ main (int argc, char **argv)
 
        /* TODO: kill this function */
        rb_threads_init ();
-       if (glib_check_version (2, 31, 1) != NULL) {
-               gdk_threads_enter ();
-       }
-
-       new_argc = argc;
-       new_argv = argv;
-       shell = rb_shell_new (autostarted, &argc, &argv);
 
-       g_application_run (G_APPLICATION (shell), new_argc, new_argv);
-
-       g_object_unref (shell);
-
-       if (glib_check_version (2, 31, 1) != NULL) {
-               gdk_threads_leave ();
-       }
+       app = rb_application_new ();
+       rc = rb_application_run (RB_APPLICATION (app), argc, argv);
+       g_object_unref (app);
 
-       exit (0);
+       return rc;
 }
diff --git a/shell/rb-application.c b/shell/rb-application.c
new file mode 100644
index 0000000..3f8eff6
--- /dev/null
+++ b/shell/rb-application.c
@@ -0,0 +1,824 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2012 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 <glib/gi18n.h>
+
+#include <libpeas/peas.h>
+#include <libpeas-gtk/peas-gtk.h>
+
+#include <shell/rb-application.h>
+#include <shell/rb-shell.h>
+#include <lib/rb-debug.h>
+#include <lib/rb-file-helpers.h>
+#include <lib/rb-builder-helpers.h>
+#include <lib/rb-stock-icons.h>
+#include <widgets/rb-dialog.h>
+
+/**
+ * SECTION:rb-application
+ * @short_description: the rhythmbox subclass of GtkApplication
+ *
+ * RBApplication contains some interactions with the desktop
+ * environment, such as the app menu and processing of files specified
+ * on the command line.
+ */
+
+static void rb_application_class_init (RBApplicationClass *klass);
+static void rb_application_init (RBApplication *app);
+
+struct _RBApplicationPrivate
+{
+       RBShell *shell;
+
+       GtkWidget *plugins;
+
+       GHashTable *shared_menus;
+       GHashTable *plugin_menus;
+
+       gboolean autostarted;
+       gboolean no_update;
+       gboolean no_registration;
+       gboolean dry_run;
+       gboolean disable_plugins;
+       char *rhythmdb_file;
+       char *playlists_file;
+};
+
+G_DEFINE_TYPE (RBApplication, rb_application, GTK_TYPE_APPLICATION);
+
+enum {
+       PROP_0,
+       PROP_SHELL
+};
+
+static void
+load_uri_action_cb (GSimpleAction *action, GVariant *parameters, gpointer user_data)
+{
+       RBApplication *rb = RB_APPLICATION (user_data);
+       const char *uri;
+       gboolean play;
+
+       g_variant_get (parameters, "(&sb)", &uri, &play);
+
+       rb_shell_load_uri (RB_SHELL (rb->priv->shell), uri, play, NULL);
+}
+
+static void
+activate_source_action_cb (GSimpleAction *action, GVariant *parameters, gpointer user_data)
+{
+       RBApplication *rb = RB_APPLICATION (user_data);
+       const char *source;
+       guint play;
+
+       g_variant_get (parameters, "(&su)", &source, &play);
+       rb_shell_activate_source_by_uri (RB_SHELL (rb->priv->shell), source, play, NULL);
+}
+
+static void
+quit_action_cb (GSimpleAction *action, GVariant *parameters, gpointer user_data)
+{
+       RBApplication *rb = RB_APPLICATION (user_data);
+       rb_shell_quit (RB_SHELL (rb->priv->shell), NULL);
+}
+
+static gboolean
+plugins_window_delete_cb (GtkWidget *window,
+                         GdkEventAny *event,
+                         gpointer data)
+{
+       gtk_widget_hide (window);
+       return TRUE;
+}
+
+static void
+plugins_response_cb (GtkDialog *dialog,
+                    int response_id,
+                    gpointer data)
+{
+       if (response_id == GTK_RESPONSE_CLOSE)
+               gtk_widget_hide (GTK_WIDGET (dialog));
+}
+
+static void
+plugins_action_cb (GSimpleAction *action, GVariant *parameters, gpointer user_data)
+{
+       RBApplication *app = RB_APPLICATION (user_data);
+
+       if (app->priv->plugins == NULL) {
+               GtkWidget *content_area;
+               GtkWidget *manager;
+               GtkWindow *window;
+
+               g_object_get (app->priv->shell, "window", &window, NULL);
+
+               app->priv->plugins = gtk_dialog_new_with_buttons (_("Configure Plugins"),
+                                                                 window,
+                                                                 GTK_DIALOG_DESTROY_WITH_PARENT,
+                                                                 GTK_STOCK_CLOSE,
+                                                                 GTK_RESPONSE_CLOSE,
+                                                                 NULL);
+               content_area = gtk_dialog_get_content_area (GTK_DIALOG (app->priv->plugins));
+               gtk_container_set_border_width (GTK_CONTAINER (app->priv->plugins), 5);
+               gtk_box_set_spacing (GTK_BOX (content_area), 2);
+
+               g_signal_connect_object (G_OBJECT (app->priv->plugins),
+                                        "delete_event",
+                                        G_CALLBACK (plugins_window_delete_cb),
+                                        NULL, 0);
+               g_signal_connect_object (G_OBJECT (app->priv->plugins),
+                                        "response",
+                                        G_CALLBACK (plugins_response_cb),
+                                        NULL, 0);
+
+               manager = peas_gtk_plugin_manager_new (NULL);
+               gtk_widget_show_all (GTK_WIDGET (manager));
+               gtk_box_pack_start (GTK_BOX (content_area), manager, TRUE, TRUE, 0);
+               gtk_window_set_default_size (GTK_WINDOW (app->priv->plugins), 600, 400);
+
+               g_object_unref (window);
+       }
+
+       gtk_window_present (GTK_WINDOW (app->priv->plugins));
+}
+
+static void
+preferences_action_cb (GSimpleAction *action, GVariant *parameters, gpointer user_data)
+{
+       RBApplication *app = RB_APPLICATION (user_data);
+       RBShellPreferences *prefs;
+
+       g_object_get (app->priv->shell, "prefs", &prefs, NULL);
+
+       gtk_window_present (GTK_WINDOW (prefs));
+       g_object_unref (prefs);
+}
+
+static void
+about_action_cb (GSimpleAction *action, GVariant *parameters, gpointer user_data)
+{
+       RBApplication *app = RB_APPLICATION (user_data);
+       GtkWindow *window;
+       const char **tem;
+       GString *comment;
+
+       const char *authors[] = {
+               "",
+#include "MAINTAINERS.tab"
+               "",
+               NULL,
+#include "MAINTAINERS.old.tab"
+               "",
+               NULL,
+#include "AUTHORS.tab"
+               NULL
+       };
+
+       const char *documenters[] = {
+#include "DOCUMENTERS.tab"
+               NULL
+       };
+
+       const char *translator_credits = _("translator-credits");
+
+       const char *license[] = {
+               N_("Rhythmbox is free software; you can redistribute it and/or modify\n"
+                  "it under the terms of the GNU General Public License as published by\n"
+                  "the Free Software Foundation; either version 2 of the License, or\n"
+                  "(at your option) any later version.\n"),
+               N_("Rhythmbox is distributed in the hope that it will be useful,\n"
+                  "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+                  "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
+                  "GNU General Public License for more details.\n"),
+               N_("You should have received a copy of the GNU General Public License\n"
+                  "along with Rhythmbox; if not, write to the Free Software Foundation, Inc.,\n"
+                  "51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA\n")
+       };
+
+       char *license_trans;
+
+       authors[0] = _("Maintainers:");
+       for (tem = authors; *tem != NULL; tem++)
+               ;
+       *tem = _("Former Maintainers:");
+       for (; *tem != NULL; tem++)
+               ;
+       *tem = _("Contributors:");
+
+       comment = g_string_new (_("Music management and playback software for GNOME."));
+
+       license_trans = g_strconcat (_(license[0]), "\n", _(license[1]), "\n",
+                                    _(license[2]), "\n", NULL);
+
+       g_object_get (app->priv->shell, "window", &window, NULL);
+       gtk_show_about_dialog (GTK_WINDOW (window),
+                              "version", VERSION,
+                              "copyright", "Copyright \xc2\xa9 2005 - 2012 The Rhythmbox authors\nCopyright 
\xc2\xa9 2003 - 2005 Colin Walters\nCopyright \xc2\xa9 2002, 2003 Jorn Baayen",
+                              "license", license_trans,
+                              "website-label", _("Rhythmbox Website"),
+                              "website", "http://www.gnome.org/projects/rhythmbox";,
+                              "comments", comment->str,
+                              "authors", (const char **) authors,
+                              "documenters", (const char **) documenters,
+                              "translator-credits", strcmp (translator_credits, "translator-credits") != 0 ? 
translator_credits : NULL,
+                              "logo-icon-name", "rhythmbox",
+                              NULL);
+       g_string_free (comment, TRUE);
+       g_free (license_trans);
+       g_object_unref (window);
+}
+
+static void
+help_action_cb (GSimpleAction *action, GVariant *parameters, gpointer user_data)
+{
+       RBApplication *app = RB_APPLICATION (user_data);
+       GError *error = NULL;
+       GtkWindow *window;
+
+       g_object_get (app->priv->shell, "window", &window, NULL);
+
+       gtk_show_uri (gtk_widget_get_screen (GTK_WIDGET (window)),
+                     "ghelp:rhythmbox",
+                     gtk_get_current_event_time (),
+                     &error);
+
+       if (error != NULL) {
+               rb_error_dialog (NULL, _("Couldn't display help"),
+                                "%s", error->message);
+               g_error_free (error);
+       }
+
+       g_object_unref (window);
+}
+
+static void
+impl_activate (GApplication *app)
+{
+       RBApplication *rb = RB_APPLICATION (app);
+       rb_shell_present (rb->priv->shell, gtk_get_current_event_time (), NULL);
+}
+
+static void
+impl_open (GApplication *app, GFile **files, int n_files, const char *hint)
+{
+       RBApplication *rb = RB_APPLICATION (app);
+       int i;
+
+       for (i = 0; i < n_files; i++) {
+               char *uri;
+
+               uri = g_file_get_uri (files[i]);
+
+               /*
+                * rb_uri_exists won't work if the location isn't mounted.
+                * however, things that are interesting to mount are generally
+                * non-local, so we'll process them anyway.
+                */
+               if (rb_uri_is_local (uri) == FALSE || rb_uri_exists (uri)) {
+                       rb_shell_load_uri (rb->priv->shell, uri, TRUE, NULL);
+               }
+               g_free (uri);
+       }
+}
+
+static void
+load_state_changed_cb (GActionGroup *action_group, const char *action_name, GVariant *state, GPtrArray 
*files)
+{
+       gboolean loaded;
+       gboolean scanned;
+
+       if (g_strcmp0 (action_name, "load-uri") != 0) {
+               return;
+       }
+
+       g_variant_get (state, "(bb)", &loaded, &scanned);
+       if (loaded) {
+               rb_debug ("opening files now");
+               g_signal_handlers_disconnect_by_func (action_group, load_state_changed_cb, files);
+
+               g_application_open (G_APPLICATION (action_group), (GFile **)files->pdata, files->len, "");
+               g_ptr_array_free (files, TRUE);
+       }
+}
+
+static void
+impl_startup (GApplication *app)
+{
+       RBApplication *rb = RB_APPLICATION (app);
+       gboolean shell_shows_app_menu;
+       GtkBuilder *builder;
+       GMenuModel *menu;
+
+       GActionEntry app_actions[] = {
+
+               /* rhythmbox-client actions */
+               { "load-uri", load_uri_action_cb, "(sb)", "(false, false)" },
+               { "activate-source", activate_source_action_cb, "(su)" },
+
+               /* app menu actions */
+               { "plugins", plugins_action_cb },
+               { "preferences", preferences_action_cb },
+               { "help", help_action_cb },
+               { "about", about_action_cb },
+               { "quit", quit_action_cb },
+       };
+
+       (* G_APPLICATION_CLASS (rb_application_parent_class)->startup) (app);
+       
+       rb_stock_icons_init ();
+
+       g_action_map_add_action_entries (G_ACTION_MAP (app),
+                                        app_actions,
+                                        G_N_ELEMENTS (app_actions),
+                                        app);
+
+       g_object_get (gtk_settings_get_default (),
+                     "gtk-shell-shows-app-menu", &shell_shows_app_menu,
+                     NULL);
+
+       builder = rb_builder_load ("app-menu.ui", NULL);
+       menu = G_MENU_MODEL (gtk_builder_get_object (builder, "app-menu"));
+       rb_application_link_shared_menus (rb, G_MENU (menu));
+       rb_application_add_shared_menu (rb, "app-menu", menu);
+
+       /* only set the app menu if the shell shows it; otherwise, we'll
+        * stick a menu button in the toolbar.
+        */
+       if (shell_shows_app_menu) {
+               gtk_application_set_app_menu (GTK_APPLICATION (app), menu);
+       }
+       
+       g_object_unref (builder);
+
+       rb->priv->shell = RB_SHELL (g_object_new (RB_TYPE_SHELL,
+                                   "application", rb,
+                                   "autostarted", rb->priv->autostarted,
+                                   "no-registration", rb->priv->no_registration,
+                                   "no-update", rb->priv->no_update,
+                                   "dry-run", rb->priv->dry_run,
+                                   "rhythmdb-file", rb->priv->rhythmdb_file,
+                                   "playlists-file", rb->priv->playlists_file,
+                                   "disable-plugins", rb->priv->disable_plugins,
+                                   NULL));
+}
+
+
+static gboolean
+impl_local_command_line (GApplication *app, gchar ***args, int *exit_status)
+{
+       RBApplication *rb = RB_APPLICATION (app);
+       GError *error = NULL;
+       gboolean scanned;
+       gboolean loaded;
+       GPtrArray *files;
+       int n_files;
+       int i;
+
+       n_files = g_strv_length (*args) - 1;
+
+       if (rb->priv->no_registration) {
+               if (n_files > 0) {
+                       g_warning ("Unable to open files on the commandline with --no-registration");
+               }
+               impl_startup (app);
+               return TRUE;
+       }
+
+       if (!g_application_register (app, NULL, &error)) {
+               g_critical ("%s", error->message);
+               g_error_free (error);
+               *exit_status = 1;
+               return TRUE;
+       }
+
+       if (n_files <= 0) {
+               g_application_activate (app);
+               *exit_status = 0;
+               return TRUE;
+       }
+
+       files = g_ptr_array_new_with_free_func (g_object_unref);
+       for (i = 0; i < n_files; i++) {
+               g_ptr_array_add (files, g_file_new_for_commandline_arg ((*args)[i + 1]));
+       }
+
+       g_variant_get (g_action_group_get_action_state (G_ACTION_GROUP (app), "load-uri"), "(bb)", &loaded, 
&scanned);
+       if (loaded) {
+               rb_debug ("opening files immediately");
+               g_application_open (app, (GFile **)files->pdata, files->len, "");
+               g_ptr_array_free (files, TRUE);
+       } else {
+               rb_debug ("opening files once db is loaded");
+               g_signal_connect (app, "action-state-changed::load-uri", G_CALLBACK (load_state_changed_cb), 
files);
+       }
+
+       return TRUE;
+}
+
+static void
+impl_shutdown (GApplication *app)
+{
+       RBApplication *rb = RB_APPLICATION (app);
+
+       if (rb->priv->shell != NULL) {
+               g_object_unref (rb->priv->shell);
+               rb->priv->shell = NULL;
+       }
+
+       (* G_APPLICATION_CLASS (rb_application_parent_class)->shutdown) (app);
+}
+
+
+static void
+impl_finalize (GObject *object)
+{
+       RBApplication *app = RB_APPLICATION (object);
+       
+       g_hash_table_destroy (app->priv->shared_menus);
+       g_hash_table_destroy (app->priv->plugin_menus);
+       rb_file_helpers_shutdown ();
+       rb_stock_icons_shutdown ();
+       rb_refstring_system_shutdown ();
+
+       G_OBJECT_CLASS (rb_application_parent_class)->finalize (object);
+}
+
+static void
+impl_dispose (GObject *object)
+{
+       RBApplication *app = RB_APPLICATION (object);
+
+       g_clear_object (&app->priv->shell);
+
+       G_OBJECT_CLASS (rb_application_parent_class)->dispose (object);
+}
+
+static void
+impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+       /* RBApplication *app = RB_APPLICATION (object); */
+       switch (prop_id) {
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+       }
+}
+
+static void
+impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+       RBApplication *app = RB_APPLICATION (object);
+       switch (prop_id) {
+       case PROP_SHELL:
+               g_value_set_object (value, app->priv->shell);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+       }
+}
+
+static void
+rb_application_init (RBApplication *app)
+{
+       app->priv = G_TYPE_INSTANCE_GET_PRIVATE (app,
+                                                RB_TYPE_APPLICATION,
+                                                RBApplicationPrivate);
+       rb_user_data_dir ();
+       rb_refstring_system_init ();
+
+#ifdef USE_UNINSTALLED_DIRS
+       rb_file_helpers_init (TRUE);
+#else
+       rb_file_helpers_init (FALSE);
+#endif
+
+       app->priv->shared_menus = g_hash_table_new_full (g_str_hash,
+                                                        g_str_equal,
+                                                        (GDestroyNotify) g_free,
+                                                        (GDestroyNotify) g_object_unref);
+       app->priv->plugin_menus = g_hash_table_new_full (g_str_hash,
+                                                        g_str_equal,
+                                                        (GDestroyNotify) g_free,
+                                                        (GDestroyNotify) g_object_unref);
+
+       g_setenv ("PULSE_PROP_media.role", "music", TRUE);
+}
+
+static void
+rb_application_class_init (RBApplicationClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GApplicationClass *app_class = G_APPLICATION_CLASS (klass);
+
+       object_class->finalize = impl_finalize;
+       object_class->dispose = impl_dispose;
+       object_class->set_property = impl_set_property;
+       object_class->get_property = impl_get_property;
+
+       app_class->open = impl_open;
+       app_class->activate = impl_activate;
+       app_class->local_command_line = impl_local_command_line;
+       app_class->startup = impl_startup;
+       app_class->shutdown = impl_shutdown;
+
+       g_object_class_install_property (object_class,
+                                        PROP_SHELL,
+                                        g_param_spec_object ("shell",
+                                                             "shell",
+                                                             "RBShell instance",
+                                                             RB_TYPE_SHELL,
+                                                             G_PARAM_READABLE));
+
+
+       g_type_class_add_private (klass, sizeof (RBApplicationPrivate));
+}
+
+/**
+ * rb_application_new:
+ *
+ * Creates the application instance.
+ *
+ * Return value: application instance
+ */
+GApplication *
+rb_application_new (void)
+{
+       return G_APPLICATION (g_object_new (RB_TYPE_APPLICATION,
+                                           "application-id", "org.gnome.Rhythmbox3",
+                                           "flags", G_APPLICATION_HANDLES_OPEN,
+                                           NULL));
+}
+
+/**
+ * rb_application_run:
+ * @rb: the application instance
+ * @argc: arg count
+ * @argv: arg values
+ *
+ * Runs the application
+ *
+ * Return value: exit code
+ */
+int
+rb_application_run (RBApplication *rb, int argc, char **argv)
+{
+       GOptionContext *context;
+       gboolean debug = FALSE;
+       char *debug_match = NULL;
+       int nargc;
+       char **nargv;
+
+       GError *error = NULL;
+
+       g_application_set_default (G_APPLICATION (rb));
+       rb->priv->autostarted = (g_getenv ("DESKTOP_AUTOSTART_ID") != NULL);
+
+       const GOptionEntry options []  = {
+               { "debug",           'd', 0, G_OPTION_ARG_NONE,         &debug,           N_("Enable debug 
output"), NULL },
+               { "debug-match",     'D', 0, G_OPTION_ARG_STRING,       &debug_match,     N_("Enable debug 
output matching a specified string"), NULL },
+               { "no-update",         0, 0, G_OPTION_ARG_NONE,         &rb->priv->no_update, N_("Do not 
update the library with file changes"), NULL },
+               { "no-registration", 'n', 0, G_OPTION_ARG_NONE,         &rb->priv->no_registration, N_("Do 
not register the shell"), NULL },
+               { "dry-run",           0, 0, G_OPTION_ARG_NONE,         &rb->priv->dry_run,         N_("Don't 
save any data permanently (implies --no-registration)"), NULL },
+               { "disable-plugins",   0, 0, G_OPTION_ARG_NONE,         &rb->priv->disable_plugins, 
N_("Disable loading of plugins"), NULL },
+               { "rhythmdb-file",     0, 0, G_OPTION_ARG_STRING,       &rb->priv->rhythmdb_file,   N_("Path 
for database file to use"), NULL },
+               { "playlists-file",    0, 0, G_OPTION_ARG_STRING,       &rb->priv->playlists_file,   N_("Path 
for playlists file to use"), NULL },
+               { NULL }
+       };
+
+       context = g_option_context_new (NULL);
+       g_option_context_add_main_entries (context, options, GETTEXT_PACKAGE);
+       g_option_context_add_group (context, gst_init_get_option_group ());
+       g_option_context_add_group (context, gtk_get_option_group (TRUE));
+
+       nargc = argc;
+       nargv = argv;
+       if (g_option_context_parse (context, &nargc, &nargv, &error) == FALSE) {
+               g_print (_("%s\nRun '%s --help' to see a full list of available command line options.\n"),
+                        error->message, argv[0]);
+               g_error_free (error);
+               g_option_context_free (context);
+               return 1;
+       }
+       g_option_context_free (context);
+
+       if (!debug && debug_match)
+               rb_debug_init_match (debug_match);
+       else
+               rb_debug_init (debug);
+
+       return g_application_run (G_APPLICATION (rb), nargc, nargv);
+}
+
+/**
+ * rb_application_add_shared_menu:
+ * @app: the application instance
+ * @name: a name for the menu
+ * @menu: #GMenuModel instance
+ *
+ * Adds a menu model to the set of shared menus
+ * available for linking into other menus.
+ */
+void
+rb_application_add_shared_menu (RBApplication *app, const char *name, GMenuModel *menu)
+{
+       g_assert (menu != NULL);
+       g_hash_table_insert (app->priv->shared_menus, g_strdup (name), g_object_ref (menu));
+}
+
+/**
+ * rb_application_get_shared_menu:
+ * @app: the application instance
+ * @name: name of menu to return
+ *
+ * Returns a shared menu instance added with @rb_application_add_shared_menu
+ *
+ * Return value: (transfer none): menu model instance, or NULL if not found
+ */
+GMenuModel *
+rb_application_get_shared_menu (RBApplication *app, const char *name)
+{
+       return g_hash_table_lookup (app->priv->shared_menus, name);
+}
+
+/**
+ * rb_application_get_plugin_menu:
+ * @app: the application instance
+ * @name: name of plugin menu to return
+ *
+ * Returns a plugin menu instance.  Plugin menus are like shared menus except
+ * they are created empty on first access, and they consist solely of entries
+ * added through @rb_application_add_plugin_item.
+ *
+ * Return value: (transfer none): plugin menu instance.
+ */
+GMenuModel *
+rb_application_get_plugin_menu (RBApplication *app, const char *name)
+{
+       GMenuModel *menu;
+
+       menu = g_hash_table_lookup (app->priv->plugin_menus, name);
+       if (menu == NULL) {
+               menu = G_MENU_MODEL (g_menu_new ());
+               g_object_ref_sink (menu);
+               g_hash_table_insert (app->priv->plugin_menus, g_strdup (name), menu);
+       }
+
+       return menu;
+}
+
+/**
+ * rb_application_add_plugin_menu_item:
+ * @app: the application instance
+ * @menu: name of the menu to add to
+ * @id: id of the item to add (used to remove it, must be unique within the menu)
+ * @item: menu item to add
+ *
+ * Adds an item to a plugin menu.  The id can be used to remove the item.
+ */
+void
+rb_application_add_plugin_menu_item (RBApplication *app, const char *menu, const char *id, GMenuItem *item)
+{
+       GMenuModel *pmenu;
+
+       pmenu = rb_application_get_plugin_menu (app, menu);
+       g_assert (pmenu != NULL);
+
+       g_menu_item_set_attribute (item, "rb-plugin-item-id", "s", id);
+       g_menu_append_item (G_MENU (pmenu), item);
+}
+
+/**
+ * rb_application_remove_plugin_item:
+ * @app: the application instance
+ * @menu: plugin menu to remove the item from
+ * @id: id of the item to remove
+ *
+ * Removes an item from a plugin menu.
+ */
+void
+rb_application_remove_plugin_menu_item (RBApplication *app, const char *menu, const char *id)
+{
+       GMenuModel *pmenu;
+       int i;
+
+       pmenu = rb_application_get_plugin_menu (app, menu);
+       g_assert (pmenu != NULL);
+
+       for (i = 0; i < g_menu_model_get_n_items (pmenu); i++) {
+               char *item_id;
+
+               item_id = NULL;
+               g_menu_model_get_item_attribute (pmenu, i, "rb-plugin-item-id", "s", &item_id);
+               if (g_strcmp0 (item_id, id) == 0) {
+                       g_menu_remove (G_MENU (pmenu), i);
+                       g_free (item_id);
+                       return;
+               }
+               g_free (item_id);
+       }
+}
+
+
+
+/**
+ * rb_application_link_shared_menus:
+ * @app: the #RBApplication
+ * @menu: a #GMenu to process
+ *
+ * Processes shared menu links in the given menu.  Menu links take the
+ * form of items with "rb-menu-link" or "rb-plugin-menu-link" and "rb-menu-link-type" attributes.
+ * "rb-menu-link" specifies the name of a shared menu to link in,
+ * "rb-plugin-menu-link" specifies the name of a plugin menu to link in,
+ * "rb-menu-link-type" specifies the link type, either "section" or
+ * "submenu".  A link item must have "rb-menu-link-type" and one of
+ * "rb-menu-link" or "rb-plugin-menu-link".
+ */
+void
+rb_application_link_shared_menus (RBApplication *app, GMenu *menu)
+{
+       int i;
+
+       for (i = 0; i < g_menu_model_get_n_items (G_MENU_MODEL (menu)); i++) {
+               GMenuModel *symlink_menu;
+               GMenuLinkIter *iter;
+               GMenuModel *link;
+               const char *name;
+               const char *symlink;
+
+               symlink_menu = NULL;
+               symlink = NULL;
+               g_menu_model_get_item_attribute (G_MENU_MODEL (menu), i, "rb-menu-link", "s", &symlink);
+               if (symlink != NULL) {
+                       symlink_menu = rb_application_get_shared_menu (app, symlink);
+                       if (symlink_menu == NULL) {
+                               g_warning ("can't find target menu for link %s", symlink);
+                               continue;
+                       }
+               } else {
+                       g_menu_model_get_item_attribute (G_MENU_MODEL (menu), i, "rb-plugin-menu-link", "s", 
&symlink);
+                       if (symlink != NULL) {
+                               symlink_menu = rb_application_get_plugin_menu (app, symlink);
+                       }
+               }
+
+               iter = g_menu_model_iterate_item_links (G_MENU_MODEL (menu), i);
+
+               if (symlink_menu != NULL) {
+                       GMenuAttributeIter *attrs;
+                       const char *attr;
+                       GVariant *value;
+                       GMenuItem *item;
+
+                       if (g_menu_link_iter_get_next (iter, &name, &link)) {
+                               /* replace the existing item, since we can't modify it */
+                               item = g_menu_item_new (NULL, NULL);
+                               attrs = g_menu_model_iterate_item_attributes (G_MENU_MODEL (menu), i);
+                               while (g_menu_attribute_iter_get_next (attrs, &attr, &value)) {
+                                       g_menu_item_set_attribute_value (item, attr, value);
+                                       g_variant_unref (value);
+                               }
+
+                               g_menu_item_set_link (item, name, symlink_menu);
+
+                               g_menu_remove (menu, i);
+                               g_menu_insert_item (menu, i, item);
+
+                               g_object_unref (link);
+                       }
+               } else {
+                       /* recurse into submenus and sections */
+                       while (g_menu_link_iter_get_next (iter, &name, &link)) {
+                               if (G_IS_MENU (link)) {
+                                       rb_application_link_shared_menus (app, G_MENU (link));
+                               }
+                               g_object_unref (link);
+                       }
+               }
+               g_object_unref (iter);
+       }
+}
diff --git a/shell/rb-application.h b/shell/rb-application.h
new file mode 100644
index 0000000..50f3e28
--- /dev/null
+++ b/shell/rb-application.h
@@ -0,0 +1,73 @@
+/*
+ *  Copyright (C) 2012  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 <gtk/gtk.h>
+
+#ifndef RB_APPLICATION_H
+#define RB_APPLICATION_H
+
+#define RB_TYPE_APPLICATION         (rb_application_get_type ())
+#define RB_APPLICATION(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_APPLICATION, RBApplication))
+#define RB_APPLICATION_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_APPLICATION, RBApplicationClass))
+#define RB_IS_APPLICATION(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_APPLICATION))
+#define RB_IS_APPLICATION_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_APPLICATION))
+#define RB_APPLICATION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_APPLICATION, 
RBApplicationClass))
+
+typedef struct _RBApplication RBApplication;
+typedef struct _RBApplicationClass RBApplicationClass;
+typedef struct _RBApplicationPrivate RBApplicationPrivate;
+
+struct _RBApplication
+{
+       GtkApplication parent;
+
+       RBApplicationPrivate *priv;
+};
+
+struct _RBApplicationClass
+{
+       GtkApplicationClass parent_class;
+};
+
+GType          rb_application_get_type (void);
+
+GApplication * rb_application_new (void);
+
+int            rb_application_run (RBApplication *app, int argc, char **argv);
+
+void           rb_application_link_shared_menus (RBApplication *app, GMenu *menu);
+
+void           rb_application_add_shared_menu (RBApplication *app, const char *name, GMenuModel *menu);
+GMenuModel *   rb_application_get_shared_menu (RBApplication *app, const char *name);
+
+GMenuModel *   rb_application_get_plugin_menu (RBApplication *app, const char *menu);
+void           rb_application_add_plugin_menu_item (RBApplication *app, const char *menu, const char *id, 
GMenuItem *item);
+void           rb_application_remove_plugin_menu_item (RBApplication *app, const char *menu, const char *id);
+
+G_END_DECLS
+
+#endif /* RB_APPLICATION_H */
diff --git a/shell/rb-playlist-manager.c b/shell/rb-playlist-manager.c
index 20b7de4..2f4297c 100644
--- a/shell/rb-playlist-manager.c
+++ b/shell/rb-playlist-manager.c
@@ -60,6 +60,8 @@
 #include "rb-stock-icons.h"
 #include "rb-builder-helpers.h"
 #include "rb-util.h"
+#include "rb-application.h"
+#include "rb-display-page-menu.h"
 
 #define RB_PLAYLIST_MGR_VERSION (xmlChar *) "1.0"
 #define RB_PLAYLIST_MGR_PL (xmlChar *) "rhythmdb-playlists"
@@ -100,26 +102,18 @@ static const char *rb_playlist_manager_dbus_spec =
 
 static void rb_playlist_manager_class_init (RBPlaylistManagerClass *klass);
 static void rb_playlist_manager_init (RBPlaylistManager *mgr);
-static void rb_playlist_manager_cmd_load_playlist (GtkAction *action,
-                                                  RBPlaylistManager *mgr);
-static void rb_playlist_manager_cmd_save_playlist (GtkAction *action,
-                                                  RBPlaylistManager *mgr);
-static void rb_playlist_manager_cmd_save_queue (GtkAction *action,
-                                               RBPlaylistManager *mgr);
-static void rb_playlist_manager_cmd_new_playlist (GtkAction *action,
-                                                 RBPlaylistManager *mgr);
-static void rb_playlist_manager_cmd_new_automatic_playlist (GtkAction *action,
-                                                           RBPlaylistManager *mgr);
-static void rb_playlist_manager_cmd_shuffle_playlist (GtkAction *action,
-                                                     RBPlaylistManager *mgr);
-static void rb_playlist_manager_cmd_rename_playlist (GtkAction *action,
-                                                    RBPlaylistManager *mgr);
-static void rb_playlist_manager_cmd_delete_playlist (GtkAction *action,
-                                                    RBPlaylistManager *mgr);
-static void rb_playlist_manager_cmd_edit_automatic_playlist (GtkAction *action,
-                                                            RBPlaylistManager *mgr);
-static void rb_playlist_manager_cmd_queue_playlist (GtkAction *action,
-                                                   RBPlaylistManager *mgr);
+
+static void new_playlist_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data);
+static void new_auto_playlist_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data);
+static void load_playlist_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data);
+
+static void edit_auto_playlist_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data);
+static void rename_playlist_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data);
+static void queue_playlist_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data);
+static void shuffle_playlist_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data);
+static void save_playlist_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data);
+static void add_to_new_playlist_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data);
+static void add_to_playlist_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data);
 
 struct RBPlaylistManagerPrivate
 {
@@ -129,13 +123,8 @@ struct RBPlaylistManagerPrivate
 
        char *playlists_file;
 
-       RBDisplayPageModel *page_model;
-       RBDisplayPageTree *display_page_tree;
-
-       GtkActionGroup *actiongroup;
-       GtkUIManager *uimanager;
-
        RBStaticPlaylistSource *loading_playlist;
+       RBSource *new_playlist;
 
        gint dirty;
        gint saving;
@@ -147,9 +136,7 @@ enum
        PROP_0,
        PROP_PLAYLIST_NAME,
        PROP_SHELL,
-       PROP_SOURCE,
-       PROP_DISPLAY_PAGE_MODEL,
-       PROP_DISPLAY_PAGE_TREE,
+       PROP_SOURCE
 };
 
 enum
@@ -183,44 +170,6 @@ static RBPlaylistExportFilter playlist_formats[] = {
 };
 
 
-static GtkActionEntry rb_playlist_manager_actions [] =
-{
-       /* Submenu of Music */
-       { "Playlist", NULL, N_("_Playlist") },
-
-       { "MusicPlaylistNewPlaylist", RB_STOCK_PLAYLIST_NEW, N_("_New Playlist..."), "<control>N",
-         N_("Create a new playlist"),
-         G_CALLBACK (rb_playlist_manager_cmd_new_playlist) },
-       { "MusicPlaylistNewAutomaticPlaylist", RB_STOCK_AUTO_PLAYLIST_NEW, N_("New _Automatic Playlist..."), 
NULL,
-         N_("Create a new automatically updating playlist"),
-         G_CALLBACK (rb_playlist_manager_cmd_new_automatic_playlist) },
-       { "MusicPlaylistLoadPlaylist", NULL, N_("_Load from File..."), NULL,
-         N_("Choose a playlist to be loaded"),
-         G_CALLBACK (rb_playlist_manager_cmd_load_playlist) },
-       { "MusicPlaylistSavePlaylist", GTK_STOCK_SAVE_AS, N_("_Save to File..."), NULL,
-         N_("Save a playlist to a file"),
-         G_CALLBACK (rb_playlist_manager_cmd_save_playlist) },
-       { "MusicPlaylistRenamePlaylist", NULL, N_("_Rename"), NULL,
-         N_("Rename playlist"),
-         G_CALLBACK (rb_playlist_manager_cmd_rename_playlist) },
-       { "MusicPlaylistDeletePlaylist", GTK_STOCK_REMOVE, N_("_Delete"), NULL,
-         N_("Delete playlist"),
-         G_CALLBACK (rb_playlist_manager_cmd_delete_playlist) },
-       { "EditAutomaticPlaylist", GTK_STOCK_PROPERTIES, N_("_Edit..."), NULL,
-         N_("Change this automatic playlist"),
-         G_CALLBACK (rb_playlist_manager_cmd_edit_automatic_playlist) },
-       { "QueuePlaylist", NULL, N_("_Queue All Tracks"), NULL,
-         N_("Add all tracks in this playlist to the queue"),
-         G_CALLBACK (rb_playlist_manager_cmd_queue_playlist) },
-       { "ShufflePlaylist", NULL, N_("_Shuffle Playlist"), NULL,
-         N_("Shuffle the tracks in this playlist"),
-         G_CALLBACK (rb_playlist_manager_cmd_shuffle_playlist) },
-       { "MusicPlaylistSaveQueue", GTK_STOCK_SAVE_AS, N_("_Save to File..."), NULL,
-         N_("Save the play queue to a file"),
-         G_CALLBACK (rb_playlist_manager_cmd_save_queue) },
-};
-static guint rb_playlist_manager_n_actions = G_N_ELEMENTS (rb_playlist_manager_actions);
-
 G_DEFINE_TYPE (RBPlaylistManager, rb_playlist_manager, G_TYPE_OBJECT)
 
 
@@ -243,8 +192,6 @@ rb_playlist_manager_shutdown (RBPlaylistManager *mgr)
 /**
  * rb_playlist_manager_new:
  * @shell: the #RBShell
- * @page_model: the #RBDisplayPageModel
- * @page_tree: the #RBDisplayPageTree
  * @playlists_file: the full path to the playlist file to load
  *
  * Creates the #RBPlaylistManager instance
@@ -253,14 +200,10 @@ rb_playlist_manager_shutdown (RBPlaylistManager *mgr)
  */
 RBPlaylistManager *
 rb_playlist_manager_new (RBShell *shell,
-                        RBDisplayPageModel *page_model,
-                        RBDisplayPageTree *page_tree,
                         const char *playlists_file)
 {
        return g_object_new (RB_TYPE_PLAYLIST_MANAGER,
                             "shell", shell,
-                            "display-page-model", page_model,
-                            "display-page-tree", page_tree,
                             "playlists_file", playlists_file,
                             NULL);
 }
@@ -513,10 +456,14 @@ static gboolean
 rb_playlist_manager_is_dirty (RBPlaylistManager *mgr)
 {
        gboolean dirty = FALSE;
+       RBDisplayPageModel *page_model;
+
+       g_object_get (mgr->priv->shell, "display-page-model", &page_model, NULL);
 
-       gtk_tree_model_foreach (GTK_TREE_MODEL (mgr->priv->page_model),
+       gtk_tree_model_foreach (GTK_TREE_MODEL (page_model),
                                (GtkTreeModelForeachFunc) _is_dirty_playlist,
                                &dirty);
+       g_object_unref (page_model);
 
        /* explicitly check the play queue */
        if (dirty == FALSE) {
@@ -620,6 +567,7 @@ rb_playlist_manager_save_playlists (RBPlaylistManager *mgr, gboolean force)
 {
        xmlNodePtr root;
        struct RBPlaylistManagerSaveData *data;
+       RBDisplayPageModel *page_model;
        RBSource *queue_source;
 
        if (!force && !rb_playlist_manager_is_dirty (mgr)) {
@@ -640,13 +588,18 @@ rb_playlist_manager_save_playlists (RBPlaylistManager *mgr, gboolean force)
        root = xmlNewDocNode (data->doc, NULL, RB_PLAYLIST_MGR_PL, NULL);
        xmlDocSetRootElement (data->doc, root);
 
-       gtk_tree_model_foreach (GTK_TREE_MODEL (mgr->priv->page_model),
+       g_object_get (mgr->priv->shell,
+                     "display-page-model", &page_model,
+                     "queue-source", &queue_source,
+                     NULL);
+       gtk_tree_model_foreach (GTK_TREE_MODEL (page_model),
                                (GtkTreeModelForeachFunc)save_playlist_cb,
                                root);
 
        /* also save the play queue */
-       g_object_get (mgr->priv->shell, "queue-source", &queue_source, NULL);
        rb_playlist_source_save_to_xml (RB_PLAYLIST_SOURCE (queue_source), root);
+
+       g_object_unref (page_model);
        g_object_unref (queue_source);
 
        /* mark clean here.  if the save fails, we'll mark it dirty again */
@@ -660,6 +613,28 @@ rb_playlist_manager_save_playlists (RBPlaylistManager *mgr, gboolean force)
        return TRUE;
 }
 
+static void
+new_playlist_deleted_cb (RBDisplayPage *page, RBPlaylistManager *mgr)
+{
+       if (RB_SOURCE (page) == mgr->priv->new_playlist) {
+               g_clear_object (&mgr->priv->new_playlist);
+       }
+}
+
+static gboolean
+edit_new_playlist_name (RBPlaylistManager *mgr)
+{
+       RBDisplayPageTree *page_tree;
+       if (mgr->priv->new_playlist != NULL) {
+               g_object_get (mgr->priv->shell, "display-page-tree", &page_tree, NULL);
+               rb_display_page_tree_edit_source_name (page_tree, mgr->priv->new_playlist);
+               g_object_unref (page_tree);
+               g_signal_handlers_disconnect_by_func (mgr->priv->new_playlist, new_playlist_deleted_cb, mgr);
+               mgr->priv->new_playlist = NULL;
+       }
+       return FALSE;
+}
+
 /**
  * rb_playlist_manager_new_playlist:
  * @mgr: the #RBPlaylistManager
@@ -676,6 +651,7 @@ rb_playlist_manager_new_playlist (RBPlaylistManager *mgr,
                                  gboolean automatic)
 {
        RBSource *playlist;
+       
        if (automatic)
                playlist = rb_auto_playlist_source_new (mgr->priv->shell,
                                                        suggested_name,
@@ -688,12 +664,16 @@ rb_playlist_manager_new_playlist (RBPlaylistManager *mgr,
                                                          RHYTHMDB_ENTRY_TYPE_SONG);
 
        append_new_playlist_source (mgr, RB_PLAYLIST_SOURCE (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,
                       playlist);
 
+       mgr->priv->new_playlist = playlist;
+       g_signal_connect (playlist, "deleted", G_CALLBACK (new_playlist_deleted_cb), mgr);
+       g_idle_add ((GSourceFunc)edit_new_playlist_name, mgr);
+
        return playlist;
 }
 
@@ -833,10 +813,9 @@ rb_playlist_manager_new_playlist_from_selection_data (RBPlaylistManager *mgr,
 }
 
 static void
-rb_playlist_manager_cmd_new_playlist (GtkAction *action,
-                                     RBPlaylistManager *mgr)
+new_playlist_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
-       rb_playlist_manager_new_playlist (mgr, _("New Playlist"), FALSE);
+       rb_playlist_manager_new_playlist (RB_PLAYLIST_MANAGER (data), _("New Playlist"), FALSE);
 }
 
 static void
@@ -892,10 +871,12 @@ new_automatic_playlist_response_cb (GtkDialog *dialog, int response, RBPlaylistM
 }
 
 static void
-rb_playlist_manager_cmd_new_automatic_playlist (GtkAction *action,
-                                               RBPlaylistManager *mgr)
+new_auto_playlist_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
-       GtkWidget *creator = rb_query_creator_new (mgr->priv->db);
+       RBPlaylistManager *mgr = RB_PLAYLIST_MANAGER (data);
+       GtkWidget *creator;
+
+       creator = rb_query_creator_new (mgr->priv->db);
        gtk_widget_show_all (creator);
 
        g_signal_connect (creator,
@@ -941,9 +922,9 @@ edit_auto_playlist_deleted_cb (RBAutoPlaylistSource *playlist, EditAutoPlaylistD
 }
 
 static void
-rb_playlist_manager_cmd_edit_automatic_playlist (GtkAction *action,
-                                                RBPlaylistManager *mgr)
+edit_auto_playlist_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
+       RBPlaylistManager *mgr = RB_PLAYLIST_MANAGER (data);
        RBQueryCreator *creator;
        RBAutoPlaylistSource *playlist;
 
@@ -1013,9 +994,9 @@ _queue_track_cb (RhythmDBQueryModel *model,
 }
 
 static void
-rb_playlist_manager_cmd_queue_playlist (GtkAction *action,
-                                       RBPlaylistManager *mgr)
+queue_playlist_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
+       RBPlaylistManager *mgr = RB_PLAYLIST_MANAGER (data);
        RBSource *queue_source;
        RhythmDBQueryModel *model;
 
@@ -1031,9 +1012,9 @@ rb_playlist_manager_cmd_queue_playlist (GtkAction *action,
 }
 
 static void
-rb_playlist_manager_cmd_shuffle_playlist (GtkAction *action,
-                                       RBPlaylistManager *mgr)
+shuffle_playlist_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
+       RBPlaylistManager *mgr = RB_PLAYLIST_MANAGER (data);
        RhythmDBQueryModel *base_model;
 
        g_object_get (mgr->priv->selected_source, "base-query-model", &base_model, NULL);
@@ -1042,26 +1023,21 @@ rb_playlist_manager_cmd_shuffle_playlist (GtkAction *action,
 }
 
 static void
-rb_playlist_manager_cmd_rename_playlist (GtkAction *action,
-                                        RBPlaylistManager *mgr)
+rename_playlist_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
-       rb_debug ("Renaming playlist %p", mgr->priv->selected_source);
+       RBPlaylistManager *mgr = RB_PLAYLIST_MANAGER (data);
+       RBDisplayPageTree *page_tree;
 
-       rb_display_page_tree_edit_source_name (mgr->priv->display_page_tree,
-                                              mgr->priv->selected_source);
-       rb_playlist_manager_set_dirty (mgr, TRUE);
-}
+       rb_debug ("Renaming playlist %p", mgr->priv->selected_source);
 
-static void
-rb_playlist_manager_cmd_delete_playlist (GtkAction *action,
-                                        RBPlaylistManager *mgr)
-{
-       rb_debug ("Deleting playlist %p", mgr->priv->selected_source);
+       g_object_get (mgr->priv->shell, "display-page-tree", &page_tree, NULL);
+       rb_display_page_tree_edit_source_name (page_tree, mgr->priv->selected_source);
+       g_object_unref (page_tree);
 
-       rb_display_page_delete_thyself (RB_DISPLAY_PAGE (mgr->priv->selected_source));
        rb_playlist_manager_set_dirty (mgr, TRUE);
 }
 
+
 static void
 load_playlist_response_cb (GtkDialog *dialog,
                           int response_id,
@@ -1093,9 +1069,9 @@ load_playlist_response_cb (GtkDialog *dialog,
 }
 
 static void
-rb_playlist_manager_cmd_load_playlist (GtkAction *action,
-                                      RBPlaylistManager *mgr)
+load_playlist_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
+       RBPlaylistManager *mgr = RB_PLAYLIST_MANAGER (data);
        GtkWindow *window;
        GtkWidget *dialog;
        GtkFileFilter *filter;
@@ -1172,8 +1148,7 @@ save_playlist_response_cb (GtkDialog *dialog,
        if (export_type == RB_PLAYLIST_EXPORT_TYPE_UNKNOWN) {
                rb_error_dialog (NULL, _("Couldn't save playlist"), _("Unsupported file extension given."));
        } else {
-               rb_playlist_source_save_playlist (RB_PLAYLIST_SOURCE (source),
-                                                 file, export_type);
+               rb_playlist_source_save_playlist (RB_PLAYLIST_SOURCE (source), file, export_type);
                gtk_widget_destroy (GTK_WIDGET (dialog));
        }
 
@@ -1264,8 +1239,8 @@ setup_format_menu (GtkWidget* menu, GtkWidget *dialog)
                                 dialog, 0);
 }
 
-static void
-save_playlist (RBPlaylistManager *mgr, RBSource *source)
+void
+rb_playlist_manager_save_playlist_file (RBPlaylistManager *mgr, RBSource *source)
 {
        GtkBuilder *builder;
        GtkWidget *dialog;
@@ -1273,6 +1248,8 @@ save_playlist (RBPlaylistManager *mgr, RBSource *source)
        char *name;
        char *tmp;
 
+       g_return_if_fail (RB_IS_PLAYLIST_SOURCE (source));
+
        builder = rb_builder_load ("playlist-save.ui", mgr);
        dialog = GTK_WIDGET (gtk_builder_get_object (builder, "playlist_save_dialog"));
 
@@ -1296,20 +1273,49 @@ save_playlist (RBPlaylistManager *mgr, RBSource *source)
 }
 
 static void
-rb_playlist_manager_cmd_save_playlist (GtkAction *action,
-                                      RBPlaylistManager *mgr)
+save_playlist_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
+{
+       RBPlaylistManager *mgr = RB_PLAYLIST_MANAGER (data);
+       rb_playlist_manager_save_playlist_file (mgr, mgr->priv->selected_source);
+}
+
+static void
+add_to_new_playlist_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
-       save_playlist (mgr, mgr->priv->selected_source);
+       RBPlaylistManager *mgr = RB_PLAYLIST_MANAGER (data);
+       GList *entries;
+       RBSource *playlist_source;
+
+       rb_debug ("add to new playlist");
+
+       entries = rb_source_copy (mgr->priv->selected_source);
+       playlist_source = rb_playlist_manager_new_playlist (mgr, NULL, FALSE);
+       rb_source_paste (playlist_source, entries);
+
+       g_list_foreach (entries, (GFunc)rhythmdb_entry_unref, NULL);
+       g_list_free (entries);
 }
 
 static void
-rb_playlist_manager_cmd_save_queue (GtkAction *action,
-                                   RBPlaylistManager *mgr)
+add_to_playlist_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
-       RBSource *queue;
-       g_object_get (mgr->priv->shell, "queue-source", &queue, NULL);
-       save_playlist (mgr, queue);
-       g_object_unref (queue);
+       RBPlaylistManager *mgr = RB_PLAYLIST_MANAGER (data);
+       RBDisplayPageModel *model;
+       GList *entries;
+       RBDisplayPage *playlist_source;
+
+       g_object_get (mgr->priv->shell, "display-page-model", &model, NULL);
+       playlist_source = rb_display_page_menu_get_page (model, parameter);
+       if (playlist_source != NULL) {
+               entries = rb_source_copy (mgr->priv->selected_source);
+               rb_source_paste (RB_SOURCE (playlist_source), entries);
+
+               g_list_foreach (entries, (GFunc)rhythmdb_entry_unref, NULL);
+               g_list_free (entries);
+       }
+
+       g_object_unref (model);
+       g_object_unref (playlist_source);
 }
 
 static gboolean
@@ -1352,10 +1358,14 @@ GList *
 rb_playlist_manager_get_playlists (RBPlaylistManager *mgr)
 {
        GList *playlists = NULL;
+       RBDisplayPageModel *page_model;
 
-       gtk_tree_model_foreach (GTK_TREE_MODEL (mgr->priv->page_model),
+       g_object_get (mgr->priv->shell, "display-page-model", &page_model, NULL);
+       gtk_tree_model_foreach (GTK_TREE_MODEL (page_model),
                                (GtkTreeModelForeachFunc)list_playlists_cb,
                                &playlists);
+       g_object_unref (page_model);
+
        return g_list_reverse (playlists);
 }
 
@@ -1434,14 +1444,17 @@ static RBSource *
 _get_playlist_by_name (RBPlaylistManager *mgr,
                       const char *name)
 {
+       RBDisplayPageModel *page_model;
        FindPlaylistData d;
 
        d.name = name;
        d.source = NULL;
 
-       gtk_tree_model_foreach (GTK_TREE_MODEL (mgr->priv->page_model),
+       g_object_get (mgr->priv->shell, "display-page-model", &page_model, NULL);
+       gtk_tree_model_foreach (GTK_TREE_MODEL (page_model),
                                (GtkTreeModelForeachFunc)find_playlist_by_name_cb,
                                &d);
+       g_object_unref (page_model);
        return d.source;
 }
 
@@ -1720,47 +1733,20 @@ static const GDBusInterfaceVTable playlist_manager_vtable = {
 };
 
 static void
-rb_playlist_manager_set_uimanager (RBPlaylistManager *mgr,
-                                  GtkUIManager *uimanager)
-{
-       if (mgr->priv->uimanager != NULL) {
-               if (mgr->priv->actiongroup != NULL) {
-                       gtk_ui_manager_remove_action_group (mgr->priv->uimanager,
-                                                           mgr->priv->actiongroup);
-               }
-               g_object_unref (mgr->priv->uimanager);
-       }
-
-       mgr->priv->uimanager = uimanager;
-
-       if (mgr->priv->actiongroup == NULL) {
-               mgr->priv->actiongroup = gtk_action_group_new ("PlaylistManagerActions");
-               gtk_action_group_set_translation_domain (mgr->priv->actiongroup,
-                                                        GETTEXT_PACKAGE);
-               gtk_action_group_add_actions (mgr->priv->actiongroup,
-                                             rb_playlist_manager_actions,
-                                             rb_playlist_manager_n_actions,
-                                             mgr);
-       }
-
-       gtk_ui_manager_insert_action_group (mgr->priv->uimanager,
-                                           mgr->priv->actiongroup,
-                                           0);
-}
-
-static void
 rb_playlist_manager_set_source (RBPlaylistManager *mgr,
                                RBSource *source)
 {
+       GApplication *app;
        gboolean playlist_active;
        gboolean playlist_local = FALSE;
        gboolean party_mode;
        gboolean can_save;
-       gboolean can_delete;
        gboolean can_edit;
        gboolean can_rename;
        gboolean can_shuffle;
-       GtkAction *action;
+       GAction *gaction;
+
+       app = g_application_get_default ();
 
        party_mode = rb_shell_get_party_mode (mgr->priv->shell);
 
@@ -1775,38 +1761,27 @@ rb_playlist_manager_set_source (RBPlaylistManager *mgr,
        }
 
        can_save = playlist_local && !party_mode;
-       action = gtk_action_group_get_action (mgr->priv->actiongroup,
-                                             "MusicPlaylistSavePlaylist");
-       gtk_action_set_visible (action, can_save);
-
-       can_delete = (playlist_local && !party_mode &&
-                     !RB_IS_PLAY_QUEUE_SOURCE (mgr->priv->selected_source));
-       action = gtk_action_group_get_action (mgr->priv->actiongroup,
-                                             "MusicPlaylistDeletePlaylist");
-       gtk_action_set_visible (action, can_delete);
+       gaction = g_action_map_lookup_action (G_ACTION_MAP (app), "playlist-save");
+       g_object_set (gaction, "enabled", can_save, NULL);
 
        can_edit = (playlist_local && RB_IS_AUTO_PLAYLIST_SOURCE (mgr->priv->selected_source) &&
                    !party_mode);
-       action = gtk_action_group_get_action (mgr->priv->actiongroup,
-                                             "EditAutomaticPlaylist");
-       gtk_action_set_visible (action, can_edit);
+       gaction = g_action_map_lookup_action (G_ACTION_MAP (app), "playlist-edit");
+       g_object_set (gaction, "enabled", can_edit, NULL);
 
        can_rename = playlist_local && rb_source_can_rename (mgr->priv->selected_source);
-       action = gtk_action_group_get_action (mgr->priv->actiongroup,
-                                             "MusicPlaylistRenamePlaylist");
-       gtk_action_set_visible (action, can_rename);
+       gaction = g_action_map_lookup_action (G_ACTION_MAP (app), "playlist-rename");
+       g_object_set (gaction, "enabled", can_rename, NULL);
 
        can_shuffle = RB_IS_STATIC_PLAYLIST_SOURCE (mgr->priv->selected_source);
-       action = gtk_action_group_get_action (mgr->priv->actiongroup,
-                                             "ShufflePlaylist");
-       gtk_action_set_sensitive (action, can_shuffle);
+       gaction = g_action_map_lookup_action (G_ACTION_MAP (app), "playlist-shuffle");
+       g_object_set (gaction, "enabled", can_shuffle, NULL);
 }
 
 static void
 rb_playlist_manager_set_shell_internal (RBPlaylistManager *mgr,
                                        RBShell           *shell)
 {
-       GtkUIManager *uimanager = NULL;
        RhythmDB     *db = NULL;
 
        if (mgr->priv->db != NULL) {
@@ -1814,16 +1789,11 @@ rb_playlist_manager_set_shell_internal (RBPlaylistManager *mgr,
        }
 
        mgr->priv->shell = shell;
-
        if (mgr->priv->shell != NULL) {
-               g_object_get (mgr->priv->shell,
-                             "ui-manager", &uimanager,
-                             "db", &db,
-                             NULL);
+               g_object_get (mgr->priv->shell, "db", &db, NULL);
        }
 
        mgr->priv->db = db;
-       rb_playlist_manager_set_uimanager (mgr, uimanager);
 }
 
 static void
@@ -1845,12 +1815,6 @@ 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_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);
                break;
@@ -1875,12 +1839,6 @@ rb_playlist_manager_get_property (GObject *object,
        case PROP_SHELL:
                g_value_set_object (value, mgr->priv->shell);
                break;
-       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);
                break;
@@ -1891,10 +1849,34 @@ static void
 rb_playlist_manager_constructed (GObject *object)
 {
        GDBusConnection *bus;
+       GApplication *app;
        RBPlaylistManager *mgr = RB_PLAYLIST_MANAGER (object);
+       GtkBuilder *builder;
+       GMenuModel *menu;
+
+       GActionEntry actions[] = {
+               { "playlist-new", new_playlist_action_cb },
+               { "playlist-new-auto", new_auto_playlist_action_cb },
+               { "playlist-load", load_playlist_action_cb },
+               { "playlist-edit", edit_auto_playlist_action_cb },
+               { "playlist-rename", rename_playlist_action_cb },
+               { "playlist-queue", queue_playlist_action_cb },
+               { "playlist-shuffle", shuffle_playlist_action_cb },
+               { "playlist-save", save_playlist_action_cb },
+               { "playlist-add-to-new", add_to_new_playlist_action_cb },
+               { "playlist-add-to", add_to_playlist_action_cb, "s" }
+       };
 
        RB_CHAIN_GOBJECT_METHOD(rb_playlist_manager_parent_class, constructed, G_OBJECT (mgr));
 
+       app = g_application_get_default ();
+       g_action_map_add_action_entries (G_ACTION_MAP (app), actions, G_N_ELEMENTS (actions), mgr);
+
+       builder = rb_builder_load ("playlist-menu.ui", NULL);
+       menu = G_MENU_MODEL (gtk_builder_get_object (builder, "playlist-menu"));
+       rb_application_add_shared_menu (RB_APPLICATION (app), "playlist-menu", menu);
+       g_object_unref (builder);
+
        bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
        if (bus) {
                GDBusNodeInfo *node_info;
@@ -1946,30 +1928,8 @@ rb_playlist_manager_dispose (GObject *object)
 
        g_return_if_fail (mgr->priv != NULL);
 
-       if (mgr->priv->db != NULL) {
-               g_object_unref (mgr->priv->db);
-               mgr->priv->db = NULL;
-       }
-
-       if (mgr->priv->uimanager != NULL) {
-               g_object_unref (mgr->priv->uimanager);
-               mgr->priv->uimanager = 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) {
-               g_object_unref (mgr->priv->selected_source);
-               mgr->priv->selected_source = NULL;
-       }
+       g_clear_object (&mgr->priv->db);
+       g_clear_object (&mgr->priv->selected_source);
 
        G_OBJECT_CLASS (rb_playlist_manager_parent_class)->dispose (object);
 }
@@ -2030,20 +1990,6 @@ rb_playlist_manager_class_init (RBPlaylistManagerClass *klass)
                                                              RB_TYPE_SHELL,
                                                              G_PARAM_READWRITE));
 
-       g_object_class_install_property (object_class,
-                                        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:
         * @manager: the #RBPlaylistManager
diff --git a/shell/rb-playlist-manager.h b/shell/rb-playlist-manager.h
index f9e8fa0..1225e4e 100644
--- a/shell/rb-playlist-manager.h
+++ b/shell/rb-playlist-manager.h
@@ -88,8 +88,6 @@ typedef enum
 GType                  rb_playlist_manager_get_type    (void);
 
 RBPlaylistManager *    rb_playlist_manager_new         (RBShell *shell,
-                                                        RBDisplayPageModel *page_model,
-                                                        RBDisplayPageTree *page_tree,
                                                         const char *playlists_file);
 
 void                   rb_playlist_manager_shutdown    (RBPlaylistManager *mgr);
@@ -132,6 +130,8 @@ gboolean            rb_playlist_manager_export_playlist (RBPlaylistManager *mgr,
                                                             const gchar *uri,
                                                             gboolean m3u_format,
                                                             GError **error);
+void                   rb_playlist_manager_save_playlist_file (RBPlaylistManager *mgr,
+                                                               RBSource *source);
 
 G_END_DECLS
 
diff --git a/shell/rb-removable-media-manager.c b/shell/rb-removable-media-manager.c
index 95d93e3..06b5c2b 100644
--- a/shell/rb-removable-media-manager.c
+++ b/shell/rb-removable-media-manager.c
@@ -61,6 +61,7 @@
 
 static void rb_removable_media_manager_class_init (RBRemovableMediaManagerClass *klass);
 static void rb_removable_media_manager_init (RBRemovableMediaManager *mgr);
+static void rb_removable_media_manager_constructed (GObject *object);
 static void rb_removable_media_manager_dispose (GObject *object);
 static void rb_removable_media_manager_finalize (GObject *object);
 static void rb_removable_media_manager_set_property (GObject *object,
@@ -72,13 +73,9 @@ static void rb_removable_media_manager_get_property (GObject *object,
                                              GValue *value,
                                              GParamSpec *pspec);
 
-static void rb_removable_media_manager_cmd_check_devices (GtkAction *action,
-                                                         RBRemovableMediaManager *manager);
-static void rb_removable_media_manager_cmd_eject_medium (GtkAction *action,
-                                              RBRemovableMediaManager *mgr);
-static gboolean rb_removable_media_manager_source_can_eject (RBRemovableMediaManager *mgr);
-static void rb_removable_media_manager_set_uimanager (RBRemovableMediaManager *mgr,
-                                            GtkUIManager *uimanager);
+static void eject_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data);
+static void check_devices_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data);
+static void page_changed_cb (RBShell *shell, GParamSpec *pspec, RBRemovableMediaManager *mgr);
 
 static void rb_removable_media_manager_append_media_source (RBRemovableMediaManager *mgr, RBSource *source);
 
@@ -98,11 +95,7 @@ static void uevent_cb (GUdevClient *client, const char *action, GUdevDevice *dev
 typedef struct
 {
        RBShell *shell;
-
-       RBSource *selected_source;
-
-       GtkActionGroup *actiongroup;
-       GtkUIManager *uimanager;
+       guint page_changed_id;
 
        GList *sources;
        GHashTable *volume_mapping;
@@ -130,7 +123,6 @@ enum
 {
        PROP_0,
        PROP_SHELL,
-       PROP_SOURCE,
        PROP_SCANNED
 };
 
@@ -145,40 +137,18 @@ enum
 
 static guint rb_removable_media_manager_signals[LAST_SIGNAL] = { 0 };
 
-static GtkActionEntry rb_removable_media_manager_actions [] =
-{
-       { "RemovableSourceEject", GNOME_MEDIA_EJECT, N_("_Eject"), NULL,
-         N_("Eject this medium"),
-         G_CALLBACK (rb_removable_media_manager_cmd_eject_medium) },
-       { "MusicCheckDevices", NULL, N_("_Check for New Devices"), NULL,
-         N_("Check for new media storage devices that have not been automatically detected"),
-         G_CALLBACK (rb_removable_media_manager_cmd_check_devices) },
-};
-static guint rb_removable_media_manager_n_actions = G_N_ELEMENTS (rb_removable_media_manager_actions);
-
 static void
 rb_removable_media_manager_class_init (RBRemovableMediaManagerClass *klass)
 {
        GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
+       object_class->constructed = rb_removable_media_manager_constructed;
        object_class->dispose = rb_removable_media_manager_dispose;
        object_class->finalize = rb_removable_media_manager_finalize;
        object_class->set_property = rb_removable_media_manager_set_property;
        object_class->get_property = rb_removable_media_manager_get_property;
 
        /**
-        * RBRemovableMediaManager:source:
-        *
-        * The current selected source.
-        */
-       g_object_class_install_property (object_class,
-                                        PROP_SOURCE,
-                                        g_param_spec_object ("source",
-                                                             "RBSource",
-                                                             "RBSource object",
-                                                             RB_TYPE_SOURCE,
-                                                             G_PARAM_READWRITE));
-       /**
         * RBRemovableMediaManager:shell:
         *
         * The #RBShell instance.
@@ -189,7 +159,7 @@ rb_removable_media_manager_class_init (RBRemovableMediaManagerClass *klass)
                                                              "RBShell",
                                                              "RBShell object",
                                                              RB_TYPE_SHELL,
-                                                             G_PARAM_READWRITE));
+                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
        /**
         * RBRemovableMediaManager:scanned:
@@ -344,7 +314,7 @@ rb_removable_media_manager_init (RBRemovableMediaManager *mgr)
                                                              "mount-pre-unmount",
                                                              G_CALLBACK (mount_removed_cb),
                                                              mgr, 0);
-       priv->mount_removed_id = g_signal_connect_object (G_OBJECT (priv->volume_monitor),
+       priv->mount_removed_id = g_signal_connect_object (priv->volume_monitor,
                                                          "mount-removed",
                                                          G_CALLBACK (mount_removed_cb),
                                                          mgr, 0);
@@ -372,6 +342,26 @@ rb_removable_media_manager_init (RBRemovableMediaManager *mgr)
 }
 
 static void
+rb_removable_media_manager_constructed (GObject *object)
+{
+       RBRemovableMediaManager *mgr = RB_REMOVABLE_MEDIA_MANAGER (object);
+       RBRemovableMediaManagerPrivate *priv = GET_PRIVATE (mgr);
+
+       GApplication *app;
+       GActionEntry actions[] = {
+               { "check-devices", check_devices_action_cb },
+               { "removable-media-eject", eject_action_cb }
+       };
+
+       RB_CHAIN_GOBJECT_METHOD (rb_removable_media_manager_parent_class, constructed, object);
+
+       app = g_application_get_default ();
+       g_action_map_add_action_entries (G_ACTION_MAP (app), actions, G_N_ELEMENTS (actions), mgr);
+
+       priv->page_changed_id = g_signal_connect (priv->shell, "notify::selected-page", G_CALLBACK 
(page_changed_cb), mgr);
+}
+
+static void
 rb_removable_media_manager_dispose (GObject *object)
 {
        RBRemovableMediaManager *mgr = RB_REMOVABLE_MEDIA_MANAGER (object);
@@ -415,6 +405,11 @@ rb_removable_media_manager_dispose (GObject *object)
                priv->sources = NULL;
        }
 
+       if (priv->page_changed_id != 0) {
+               g_signal_handler_disconnect (priv->shell, priv->page_changed_id);
+               priv->page_changed_id = 0;
+       }
+
        G_OBJECT_CLASS (rb_removable_media_manager_parent_class)->dispose (object);
 }
 
@@ -440,30 +435,10 @@ rb_removable_media_manager_set_property (GObject *object,
 
        switch (prop_id)
        {
-       case PROP_SOURCE:
-       {
-               GtkAction *action;
-               gboolean can_eject;
-
-               priv->selected_source = g_value_get_object (value);
-               /* make 'eject' command sensitive if the source can be ejected. */
-               action = gtk_action_group_get_action (priv->actiongroup, "RemovableSourceEject");
-               can_eject = rb_removable_media_manager_source_can_eject (RB_REMOVABLE_MEDIA_MANAGER (object));
-               gtk_action_set_sensitive (action, can_eject);
-               break;
-       }
        case PROP_SHELL:
-       {
-               GtkUIManager *uimanager;
-
                priv->shell = g_value_get_object (value);
-               g_object_get (priv->shell,
-                             "ui-manager", &uimanager,
-                             NULL);
-               rb_removable_media_manager_set_uimanager (RB_REMOVABLE_MEDIA_MANAGER (object), uimanager);
-               g_object_unref (uimanager);
                break;
-       }
+       default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
        }
@@ -479,9 +454,6 @@ rb_removable_media_manager_get_property (GObject *object,
 
        switch (prop_id)
        {
-       case PROP_SOURCE:
-               g_value_set_object (value, priv->selected_source);
-               break;
        case PROP_SHELL:
                g_value_set_object (value, priv->shell);
                break;
@@ -785,65 +757,45 @@ rb_removable_media_manager_append_media_source (RBRemovableMediaManager *mgr, RB
 }
 
 static void
-rb_removable_media_manager_set_uimanager (RBRemovableMediaManager *mgr,
-                                         GtkUIManager *uimanager)
+page_changed_cb (RBShell *shell, GParamSpec *pspec, RBRemovableMediaManager *mgr)
 {
        RBRemovableMediaManagerPrivate *priv = GET_PRIVATE (mgr);
+       RBDisplayPage *page;
+       gboolean can_eject;
+       GApplication *app;
+       GAction *action;
 
-       if (priv->uimanager != NULL) {
-               if (priv->actiongroup != NULL) {
-                       gtk_ui_manager_remove_action_group (priv->uimanager,
-                                                           priv->actiongroup);
-               }
-               g_object_unref (G_OBJECT (priv->uimanager));
-               priv->uimanager = NULL;
-       }
-
-       priv->uimanager = uimanager;
+       g_object_get (priv->shell, "selected-page", &page, NULL);
 
-       if (priv->uimanager != NULL) {
-               g_object_ref (priv->uimanager);
-       }
-
-       if (priv->actiongroup == NULL) {
-               priv->actiongroup = gtk_action_group_new ("RemovableMediaActions");
-               gtk_action_group_set_translation_domain (priv->actiongroup,
-                                                        GETTEXT_PACKAGE);
-               gtk_action_group_add_actions (priv->actiongroup,
-                                             rb_removable_media_manager_actions,
-                                             rb_removable_media_manager_n_actions,
-                                             mgr);
+       if (RB_IS_DEVICE_SOURCE (page)) {
+               can_eject = rb_device_source_can_eject (RB_DEVICE_SOURCE (page));
+       } else {
+               can_eject = FALSE;
        }
 
-       gtk_ui_manager_insert_action_group (priv->uimanager,
-                                           priv->actiongroup,
-                                           0);
+       app = g_application_get_default ();
+       action = g_action_map_lookup_action (G_ACTION_MAP (app), "removable-media-eject");
+       g_object_set (action, "enabled", can_eject, NULL);
 }
 
-static gboolean
-rb_removable_media_manager_source_can_eject (RBRemovableMediaManager *mgr)
+static void
+eject_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
+       RBRemovableMediaManager *mgr = RB_REMOVABLE_MEDIA_MANAGER (data);
        RBRemovableMediaManagerPrivate *priv = GET_PRIVATE (mgr);
+       RBDisplayPage *page;
 
-       if (RB_IS_DEVICE_SOURCE (priv->selected_source) == FALSE) {
-               return FALSE;
-       }
-       return rb_device_source_can_eject (RB_DEVICE_SOURCE (priv->selected_source));
-}
+       g_object_get (priv->shell, "selected-page", &page, NULL);
 
-static void
-rb_removable_media_manager_cmd_eject_medium (GtkAction *action, RBRemovableMediaManager *mgr)
-{
-       RBRemovableMediaManagerPrivate *priv = GET_PRIVATE (mgr);
-       if (RB_IS_DEVICE_SOURCE (priv->selected_source)) {
-               rb_device_source_eject (RB_DEVICE_SOURCE (priv->selected_source));
+       if (RB_IS_DEVICE_SOURCE (page)) {
+               rb_device_source_eject (RB_DEVICE_SOURCE (page));
        }
 }
 
 static void
-rb_removable_media_manager_cmd_check_devices (GtkAction *action, RBRemovableMediaManager *manager)
+check_devices_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
-       rb_removable_media_manager_scan (manager);
+       rb_removable_media_manager_scan (RB_REMOVABLE_MEDIA_MANAGER (data));
 }
 
 /**
diff --git a/shell/rb-shell-clipboard.c b/shell/rb-shell-clipboard.c
index afe5e4e..8c6bc24 100644
--- a/shell/rb-shell-clipboard.c
+++ b/shell/rb-shell-clipboard.c
@@ -50,17 +50,21 @@
 #include <gtk/gtk.h>
 
 #include "rb-shell-clipboard.h"
-#include "rb-playlist-manager.h"
 #include "rb-play-queue-source.h"
 #include "rb-display-page-model.h"
 #include "rhythmdb.h"
 #include "rb-debug.h"
 #include "rb-stock-icons.h"
+#include "rb-util.h"
+#include "rb-application.h"
+#include "rb-builder-helpers.h"
+#include "rb-display-page-menu.h"
 
 static void rb_shell_clipboard_class_init (RBShellClipboardClass *klass);
 static void rb_shell_clipboard_init (RBShellClipboard *shell_clipboard);
 static void rb_shell_clipboard_dispose (GObject *object);
 static void rb_shell_clipboard_finalize (GObject *object);
+static void rb_shell_clipboard_constructed (GObject *object);
 static void rb_shell_clipboard_set_property (GObject *object,
                                             guint prop_id,
                                             const GValue *value,
@@ -70,27 +74,8 @@ static void rb_shell_clipboard_get_property (GObject *object,
                                             GValue *value,
                                             GParamSpec *pspec);
 static void rb_shell_clipboard_sync (RBShellClipboard *clipboard);
-static void rb_shell_clipboard_cmd_select_all (GtkAction *action,
-                                              RBShellClipboard *clipboard);
-static void rb_shell_clipboard_cmd_select_none (GtkAction *action,
-                                               RBShellClipboard *clipboard);
-static void rb_shell_clipboard_cmd_cut (GtkAction *action,
-                                       RBShellClipboard *clipboard);
-static void rb_shell_clipboard_cmd_copy (GtkAction *action,
-                                        RBShellClipboard *clipboard);
-static void rb_shell_clipboard_cmd_paste (GtkAction *action,
-                                         RBShellClipboard *clipboard);
-static void rb_shell_clipboard_cmd_delete (GtkAction *action,
-                                          RBShellClipboard *clipboard);
-static void rb_shell_clipboard_cmd_queue_delete (GtkAction *action,
-                                                RBShellClipboard *clipboard);
-static void rb_shell_clipboard_cmd_move_to_trash (GtkAction *action,
-                                                 RBShellClipboard *clipboard);
 static void rb_shell_clipboard_set (RBShellClipboard *clipboard,
                                    GList *nodes);
-static void rb_shell_clipboard_playlist_added_cb (RBPlaylistManager *mgr,
-                                                 RBPlaylistSource *source,
-                                                 RBShellClipboard *clipboard);
 static void rb_shell_clipboard_entry_deleted_cb (RhythmDB *db,
                                                 RhythmDBEntry *entry,
                                                 RBShellClipboard *clipboard);
@@ -99,30 +84,26 @@ static void rb_shell_clipboard_entryview_changed_cb (RBEntryView *view,
 static void rb_shell_clipboard_entries_changed_cb (RBEntryView *view,
                                                   gpointer stuff,
                                                   RBShellClipboard *clipboard);
-static void rb_shell_clipboard_cmd_add_to_playlist_new (GtkAction *action,
-                                                       RBShellClipboard *clipboard);
-static void rb_shell_clipboard_cmd_add_song_to_queue (GtkAction *action,
-                                                     RBShellClipboard *clipboard);
-static void rb_shell_clipboard_cmd_song_info (GtkAction *action,
-                                             RBShellClipboard *clipboard);
-static void rb_shell_clipboard_cmd_queue_song_info (GtkAction *action,
-                                                   RBShellClipboard *clipboard);
-static void rebuild_playlist_menu (RBShellClipboard *clipboard);
-static gboolean rebuild_playlist_menu_idle (RBShellClipboard *clipboard);
+
+static void playlist_menu_notify_cb (GObject *object, GParamSpec *pspec, RBShellClipboard *clipboard);
+
+static void cut_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data);
+static void copy_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data);
+static void paste_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data);
+static void select_all_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data);
+static void select_none_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data);
+static void add_to_queue_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data);
+static void properties_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data);
+static void delete_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data);
+static void move_to_trash_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data);
+
+
 
 struct RBShellClipboardPrivate
 {
        RhythmDB *db;
        RBSource *source;
        RBStaticPlaylistSource *queue_source;
-       RBPlaylistManager *playlist_manager;
-
-       GtkUIManager *ui_mgr;
-       GtkActionGroup *actiongroup;
-       guint playlist_menu_ui_id;
-
-       guint delete_action_ui_id;
-       GtkAction *delete_action;
 
        GHashTable *signal_hash;
 
@@ -131,6 +112,10 @@ struct RBShellClipboardPrivate
        guint idle_sync_id, idle_playlist_id;
 
        GList *entries;
+
+       GMenu *delete_menu;
+       GMenu *edit_menu;
+       GMenuModel *playlist_menu;
 };
 
 #define RB_SHELL_CLIPBOARD_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_SHELL_CLIPBOARD, 
RBShellClipboardPrivate))
@@ -139,71 +124,10 @@ enum
 {
        PROP_0,
        PROP_SOURCE,
-       PROP_ACTION_GROUP,
        PROP_DB,
        PROP_QUEUE_SOURCE,
-       PROP_PLAYLIST_MANAGER,
-       PROP_UI_MANAGER,
-};
-
-static GtkActionEntry rb_shell_clipboard_actions [] =
-{
-       { "EditSelectAll", NULL, N_("Select _All"), "<control>A",
-         N_("Select all songs"),
-         G_CALLBACK (rb_shell_clipboard_cmd_select_all) },
-       { "EditSelectNone", NULL, N_("D_eselect All"), "<shift><control>A",
-         N_("Deselect all songs"),
-         G_CALLBACK (rb_shell_clipboard_cmd_select_none) },
-       { "EditCut", GTK_STOCK_CUT, N_("Cu_t"), "<control>X",
-         N_("Cut selection"),
-         G_CALLBACK (rb_shell_clipboard_cmd_cut) },
-       { "EditCopy", GTK_STOCK_COPY, N_("_Copy"), "<control>C",
-         N_("Copy selection"),
-         G_CALLBACK (rb_shell_clipboard_cmd_copy) },
-       { "EditPaste", GTK_STOCK_PASTE, N_("_Paste"), "<control>V",
-         N_("Paste selection"),
-         G_CALLBACK (rb_shell_clipboard_cmd_paste) },
-       { "EditDelete", GTK_STOCK_DELETE, N_("_Delete"), NULL,
-         N_("Delete each selected item"),
-         G_CALLBACK (rb_shell_clipboard_cmd_delete) },
-       { "EditRemove", GTK_STOCK_REMOVE, N_("_Remove"), NULL,
-         N_("Remove each selected item from the library"),
-         G_CALLBACK (rb_shell_clipboard_cmd_delete) },
-       { "EditMovetoTrash", "user-trash", N_("_Move to Trash"), NULL,
-         N_("Move each selected item to the trash"),
-         G_CALLBACK (rb_shell_clipboard_cmd_move_to_trash) },
-
-       { "EditPlaylistAdd", NULL, N_("Add to P_laylist") },
-       { "EditPlaylistAddNew", RB_STOCK_PLAYLIST_NEW, N_("_New Playlist"), NULL,
-         N_("Add each selected song to a new playlist"),
-         G_CALLBACK (rb_shell_clipboard_cmd_add_to_playlist_new) },
-       { "AddToQueue", GTK_STOCK_ADD, N_("Add _to Play Queue"), NULL,
-         N_("Add each selected song to the play queue"),
-         G_CALLBACK (rb_shell_clipboard_cmd_add_song_to_queue) },
-       { "QueueDelete", GTK_STOCK_REMOVE, N_("Remove"), NULL,
-         N_("Remove each selected item from the play queue"),
-         G_CALLBACK (rb_shell_clipboard_cmd_queue_delete) },
-
-       { "MusicProperties", GTK_STOCK_PROPERTIES, N_("Pr_operties"), "<Alt>Return",
-         N_("Show information on each selected song"),
-         G_CALLBACK (rb_shell_clipboard_cmd_song_info) },
-       { "QueueMusicProperties", GTK_STOCK_PROPERTIES, N_("_Properties"), NULL,
-         N_("Show information on each selected song"),
-         G_CALLBACK (rb_shell_clipboard_cmd_queue_song_info) },
 };
-static guint rb_shell_clipboard_n_actions = G_N_ELEMENTS (rb_shell_clipboard_actions);
 
-static const char *delete_action_paths[] = {
-       "/MenuBar/EditMenu/DeleteActionPlaceholder",
-       "/BrowserSourceViewPopup/DeleteActionPlaceholder",
-};
-
-static const char *playlist_menu_paths[] = {
-       "/MenuBar/EditMenu/EditPlaylistAddMenu/EditPlaylistAddPlaceholder",
-       "/BrowserSourceViewPopup/BrowserSourcePopupPlaylistAdd/BrowserSourcePopupPlaylistAddPlaceholder",
-       "/PlaylistViewPopup/PlaylistPopupPlaylistAdd/PlaylistPopupPlaylistAddPlaceholder",
-};
-static guint num_playlist_menu_paths = G_N_ELEMENTS (playlist_menu_paths);
 
 G_DEFINE_TYPE (RBShellClipboard, rb_shell_clipboard, G_TYPE_OBJECT)
 
@@ -214,6 +138,7 @@ rb_shell_clipboard_class_init (RBShellClipboardClass *klass)
 
        object_class->dispose = rb_shell_clipboard_dispose;
        object_class->finalize = rb_shell_clipboard_finalize;
+       object_class->constructed = rb_shell_clipboard_constructed;
 
        object_class->set_property = rb_shell_clipboard_set_property;
        object_class->get_property = rb_shell_clipboard_get_property;
@@ -226,13 +151,6 @@ rb_shell_clipboard_class_init (RBShellClipboardClass *klass)
                                                              RB_TYPE_SOURCE,
                                                              G_PARAM_READWRITE));
        g_object_class_install_property (object_class,
-                                        PROP_ACTION_GROUP,
-                                        g_param_spec_object ("action-group",
-                                                             "GtkActionGroup",
-                                                             "GtkActionGroup object",
-                                                             GTK_TYPE_ACTION_GROUP,
-                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-       g_object_class_install_property (object_class,
                                         PROP_DB,
                                         g_param_spec_object ("db",
                                                              "RhythmDB",
@@ -246,20 +164,6 @@ rb_shell_clipboard_class_init (RBShellClipboardClass *klass)
                                                              "RBPlaylistSource object",
                                                              RB_TYPE_PLAYLIST_SOURCE,
                                                              G_PARAM_READWRITE));
-       g_object_class_install_property (object_class,
-                                        PROP_PLAYLIST_MANAGER,
-                                        g_param_spec_object ("playlist-manager",
-                                                             "RBPlaylistManager",
-                                                             "RBPlaylistManager object",
-                                                             RB_TYPE_PLAYLIST_MANAGER,
-                                                             G_PARAM_READWRITE));
-       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_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
        g_type_class_add_private (klass, sizeof (RBShellClipboardPrivate));
 }
@@ -282,16 +186,15 @@ unset_source_internal (RBShellClipboard *clipboard)
                RBEntryView *songs = rb_source_get_entry_view (clipboard->priv->source);
 
                if (songs) {
-                       g_signal_handlers_disconnect_by_func (G_OBJECT (songs),
+                       g_signal_handlers_disconnect_by_func (songs,
                                                              G_CALLBACK 
(rb_shell_clipboard_entryview_changed_cb),
                                                              clipboard);
-                       g_signal_handlers_disconnect_by_func (G_OBJECT (songs),
+                       g_signal_handlers_disconnect_by_func (songs,
                                                              G_CALLBACK 
(rb_shell_clipboard_entries_changed_cb),
                                                              clipboard);
                }
 
-               gtk_ui_manager_remove_ui (clipboard->priv->ui_mgr,
-                                         clipboard->priv->delete_action_ui_id);
+               g_signal_handlers_disconnect_by_func (clipboard->priv->source, G_CALLBACK 
(playlist_menu_notify_cb), clipboard);
        }
        clipboard->priv->source = NULL;
 }
@@ -309,6 +212,7 @@ rb_shell_clipboard_dispose (GObject *object)
        g_return_if_fail (shell_clipboard->priv != NULL);
 
        unset_source_internal (shell_clipboard);
+       g_clear_object (&shell_clipboard->priv->playlist_menu);
 
        if (shell_clipboard->priv->idle_sync_id != 0) {
                g_source_remove (shell_clipboard->priv->idle_sync_id);
@@ -345,6 +249,91 @@ rb_shell_clipboard_finalize (GObject *object)
 }
 
 static void
+add_delete_menu_item (RBShellClipboard *clipboard)
+{
+       char *label;
+
+       if (clipboard->priv->source) {
+               label = rb_source_get_delete_label (clipboard->priv->source);
+       } else {
+               label = g_strdup (_("Remove"));
+       }
+
+       if (g_menu_model_get_n_items (G_MENU_MODEL (clipboard->priv->delete_menu)) > 0) {
+               g_menu_remove (clipboard->priv->delete_menu, 0);
+       }
+       g_menu_append (clipboard->priv->delete_menu, label, "app.clipboard-delete");
+       g_free (label);
+}
+
+static void
+setup_add_to_playlist_menu (RBShellClipboard *clipboard)
+{
+       g_clear_object (&clipboard->priv->playlist_menu);
+       if (clipboard->priv->source) {
+               g_object_get (clipboard->priv->source, "playlist-menu", &clipboard->priv->playlist_menu, 
NULL);
+       }
+
+       if (clipboard->priv->playlist_menu) {
+               rb_menu_update_link (clipboard->priv->edit_menu,
+                                    "rb-playlist-menu-link",
+                                    G_MENU_MODEL (clipboard->priv->playlist_menu));
+       } else {
+               rb_menu_update_link (clipboard->priv->edit_menu, "rb-playlist-menu-link", NULL);
+       }
+}
+
+static void
+playlist_menu_notify_cb (GObject *object, GParamSpec *pspec, RBShellClipboard *clipboard)
+{
+       setup_add_to_playlist_menu (clipboard);
+}
+
+static void
+rb_shell_clipboard_constructed (GObject *object)
+{
+       RBApplication *app;
+       RBShellClipboard *clipboard;
+       GtkBuilder *builder;
+       GActionEntry actions[] = {
+               { "clipboard-cut", cut_action_cb },
+               { "clipboard-copy", copy_action_cb },
+               { "clipboard-paste", paste_action_cb },
+               { "clipboard-select-all", select_all_action_cb },
+               { "clipboard-select-none", select_none_action_cb },
+               { "clipboard-add-to-queue", add_to_queue_action_cb },
+               { "clipboard-properties", properties_action_cb },
+               { "clipboard-delete", delete_action_cb },
+               { "clipboard-trash", move_to_trash_action_cb },
+       };
+
+       RB_CHAIN_GOBJECT_METHOD (rb_shell_clipboard_parent_class, constructed, object);
+       clipboard = RB_SHELL_CLIPBOARD (object);
+
+       g_signal_connect_object (clipboard->priv->db,
+                                "entry_deleted",
+                                G_CALLBACK (rb_shell_clipboard_entry_deleted_cb),
+                                clipboard, 0);
+
+       g_action_map_add_action_entries (G_ACTION_MAP (g_application_get_default ()),
+                                        actions,
+                                        G_N_ELEMENTS (actions),
+                                        clipboard);
+
+       app = RB_APPLICATION (g_application_get_default ());
+
+       clipboard->priv->delete_menu = g_menu_new ();
+       add_delete_menu_item (clipboard);
+       rb_application_add_shared_menu (app, "delete-menu", G_MENU_MODEL (clipboard->priv->delete_menu));
+
+       builder = rb_builder_load ("edit-menu.ui", NULL);
+       clipboard->priv->edit_menu = G_MENU (gtk_builder_get_object (builder, "edit-menu"));
+       rb_application_link_shared_menus (app, clipboard->priv->edit_menu);
+       rb_application_add_shared_menu (app, "edit-menu", G_MENU_MODEL (clipboard->priv->edit_menu));
+       g_object_unref (builder);
+}
+
+static void
 rb_shell_clipboard_set_source_internal (RBShellClipboard *clipboard,
                                        RBSource *source)
 {
@@ -357,51 +346,35 @@ rb_shell_clipboard_set_source_internal (RBShellClipboard *clipboard,
 
        if (clipboard->priv->source != NULL) {
                RBEntryView *songs = rb_source_get_entry_view (clipboard->priv->source);
-               char *delete_action;
 
                if (songs) {
-                       g_signal_connect_object (G_OBJECT (songs),
+                       g_signal_connect_object (songs,
                                                 "selection-changed",
                                                 G_CALLBACK (rb_shell_clipboard_entryview_changed_cb),
                                                 clipboard, 0);
-                       g_signal_connect_object (G_OBJECT (songs),
+                       g_signal_connect_object (songs,
                                                 "entry-added",
                                                 G_CALLBACK (rb_shell_clipboard_entries_changed_cb),
                                                 clipboard, 0);
-                       g_signal_connect_object (G_OBJECT (songs),
+                       g_signal_connect_object (songs,
                                                 "entry-deleted",
                                                 G_CALLBACK (rb_shell_clipboard_entries_changed_cb),
                                                 clipboard, 0);
-                       g_signal_connect_object (G_OBJECT (songs),
+                       g_signal_connect_object (songs,
                                                 "entries-replaced",
                                                 G_CALLBACK (rb_shell_clipboard_entryview_changed_cb),
                                                 clipboard, 0);
                }
 
-               delete_action = rb_source_get_delete_action (source);
-               if (delete_action != NULL) {
-                       char *path;
-                       int i;
-                       for (i = 0; i < G_N_ELEMENTS (delete_action_paths); i++) {
-                               gtk_ui_manager_add_ui (clipboard->priv->ui_mgr,
-                                                      clipboard->priv->delete_action_ui_id,
-                                                      delete_action_paths[i],
-                                                      delete_action,
-                                                      delete_action,
-                                                      GTK_UI_MANAGER_AUTO,
-                                                      FALSE);
-                       }
-                       gtk_ui_manager_ensure_update (clipboard->priv->ui_mgr);
-
-                       /* locate action too */
-                       path = g_strdup_printf ("%s/%s", delete_action_paths[0], delete_action);
-                       clipboard->priv->delete_action = gtk_ui_manager_get_action (clipboard->priv->ui_mgr, 
path);
-                       g_free (path);
-               }
-               g_free (delete_action);
+               g_signal_connect (clipboard->priv->source,
+                                 "notify::playlist-menu",
+                                 G_CALLBACK (playlist_menu_notify_cb),
+                                 clipboard);
        }
 
-       rebuild_playlist_menu (clipboard);
+       add_delete_menu_item (clipboard);
+
+       setup_add_to_playlist_menu (clipboard);
 }
 
 static void
@@ -417,62 +390,11 @@ rb_shell_clipboard_set_property (GObject *object,
        case PROP_SOURCE:
                rb_shell_clipboard_set_source_internal (clipboard, g_value_get_object (value));
                break;
-       case PROP_ACTION_GROUP:
-               clipboard->priv->actiongroup = g_value_get_object (value);
-               gtk_action_group_add_actions (clipboard->priv->actiongroup,
-                                             rb_shell_clipboard_actions,
-                                             rb_shell_clipboard_n_actions,
-                                             clipboard);
-               break;
        case PROP_DB:
                clipboard->priv->db = g_value_get_object (value);
-               g_signal_connect_object (clipboard->priv->db,
-                                        "entry_deleted",
-                                        G_CALLBACK (rb_shell_clipboard_entry_deleted_cb),
-                                        clipboard, 0);
-               break;
-       case PROP_UI_MANAGER:
-               clipboard->priv->ui_mgr = g_value_get_object (value);
-               clipboard->priv->delete_action_ui_id =
-                       gtk_ui_manager_new_merge_id (clipboard->priv->ui_mgr);
-
-               break;
-       case PROP_PLAYLIST_MANAGER:
-               if (clipboard->priv->playlist_manager != NULL) {
-                       g_signal_handlers_disconnect_by_func (clipboard->priv->playlist_manager,
-                                                             G_CALLBACK 
(rb_shell_clipboard_playlist_added_cb),
-                                                             clipboard);
-               }
-
-               clipboard->priv->playlist_manager = g_value_get_object (value);
-               if (clipboard->priv->playlist_manager != NULL) {
-                       g_signal_connect_object (G_OBJECT (clipboard->priv->playlist_manager),
-                                                "playlist-added", G_CALLBACK 
(rb_shell_clipboard_playlist_added_cb),
-                                                clipboard, 0);
-
-                       rebuild_playlist_menu (clipboard);
-               }
-
                break;
        case PROP_QUEUE_SOURCE:
-               if (clipboard->priv->queue_source != NULL) {
-                       RBEntryView *sidebar;
-                       g_object_get (clipboard->priv->queue_source, "sidebar", &sidebar, NULL);
-                       g_signal_handlers_disconnect_by_func (sidebar,
-                                                             G_CALLBACK 
(rb_shell_clipboard_entryview_changed_cb),
-                                                             clipboard);
-                       g_object_unref (sidebar);
-               }
-
                clipboard->priv->queue_source = g_value_get_object (value);
-               if (clipboard->priv->queue_source != NULL) {
-                       RBEntryView *sidebar;
-                       g_object_get (clipboard->priv->queue_source, "sidebar", &sidebar, NULL);
-                       g_signal_connect_object (G_OBJECT (sidebar), "selection-changed",
-                                                G_CALLBACK (rb_shell_clipboard_entryview_changed_cb),
-                                                clipboard, 0);
-                       g_object_unref (sidebar);
-               }
                break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -493,18 +415,9 @@ rb_shell_clipboard_get_property (GObject *object,
        case PROP_SOURCE:
                g_value_set_object (value, clipboard->priv->source);
                break;
-       case PROP_ACTION_GROUP:
-               g_value_set_object (value, clipboard->priv->actiongroup);
-               break;
        case PROP_DB:
                g_value_set_object (value, clipboard->priv->db);
                break;
-       case PROP_UI_MANAGER:
-               g_value_set_object (value, clipboard->priv->ui_mgr);
-               break;
-       case PROP_PLAYLIST_MANAGER:
-               g_value_set_object (value, clipboard->priv->playlist_manager);
-               break;
        case PROP_QUEUE_SOURCE:
                g_value_set_object (value, clipboard->priv->queue_source);
                break;
@@ -534,8 +447,6 @@ rb_shell_clipboard_set_source (RBShellClipboard *clipboard,
 
 /**
  * rb_shell_clipboard_new:
- * @actiongroup: the #GtkActionGroup to use
- * @ui_mgr: the #GtkUIManager instance
  * @db: the #RhythmDB instance
  *
  * Creates the #RBShellClipboard instance
@@ -543,13 +454,9 @@ rb_shell_clipboard_set_source (RBShellClipboard *clipboard,
  * Return value: the #RBShellClipboard
  */
 RBShellClipboard *
-rb_shell_clipboard_new (GtkActionGroup *actiongroup,
-                       GtkUIManager *ui_mgr,
-                       RhythmDB *db)
+rb_shell_clipboard_new (RhythmDB *db)
 {
        return g_object_new (RB_TYPE_SHELL_CLIPBOARD,
-                            "action-group", actiongroup,
-                            "ui-manager", ui_mgr,
                             "db", db,
                             NULL);
 }
@@ -570,7 +477,6 @@ rb_shell_clipboard_sync (RBShellClipboard *clipboard)
 {
        RBEntryView *view;
        gboolean have_selection = FALSE;
-       gboolean have_sidebar_selection = FALSE;
        gboolean can_cut = FALSE;
        gboolean can_paste = FALSE;
        gboolean can_delete = FALSE;
@@ -579,8 +485,10 @@ rb_shell_clipboard_sync (RBShellClipboard *clipboard)
        gboolean can_move_to_trash = FALSE;
        gboolean can_select_all = FALSE;
        gboolean can_show_properties = FALSE;
-       GtkAction *action;
-       RhythmDBEntryType *entry_type;
+       GApplication *app;
+       GAction *gaction;
+
+       app = g_application_get_default ();
 
        if (clipboard->priv->source) {
                view = rb_source_get_entry_view (clipboard->priv->source);
@@ -590,13 +498,6 @@ rb_shell_clipboard_sync (RBShellClipboard *clipboard)
                }
        }
 
-       if (clipboard->priv->queue_source) {
-               RBEntryView *sidebar;
-               g_object_get (clipboard->priv->queue_source, "sidebar", &sidebar, NULL);
-               have_sidebar_selection = rb_entry_view_have_selection (sidebar);
-               g_object_unref (sidebar);
-       }
-
        rb_debug ("syncing clipboard");
 
        if (clipboard->priv->source != NULL && g_list_length (clipboard->priv->entries) > 0)
@@ -613,58 +514,38 @@ rb_shell_clipboard_sync (RBShellClipboard *clipboard)
                        can_add_to_queue = rb_source_can_add_to_queue (clipboard->priv->source);
        }
 
-       action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditCut");
-       g_object_set (action, "sensitive", can_cut, NULL);
-
-       if (clipboard->priv->delete_action != NULL) {
-               g_object_set (clipboard->priv->delete_action, "sensitive", can_delete, NULL);
-       }
-
-       action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditMovetoTrash");
-       g_object_set (action, "sensitive", can_move_to_trash, NULL);
-
-       action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditCopy");
-       g_object_set (action, "sensitive", can_copy, NULL);
+       gaction = g_action_map_lookup_action (G_ACTION_MAP (app), "clipboard-delete");
+       g_object_set (gaction, "enabled", can_delete, NULL);
 
-       action = gtk_action_group_get_action (clipboard->priv->actiongroup,"EditPaste");
-       g_object_set (action, "sensitive", can_paste, NULL);
+       gaction = g_action_map_lookup_action (G_ACTION_MAP (app), "clipboard-trash");
+       g_object_set (gaction, "enabled", can_move_to_trash, NULL);
 
-       action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditPlaylistAdd");
-       g_object_set (action, "sensitive", can_copy, NULL);
+       gaction = g_action_map_lookup_action (G_ACTION_MAP (app), "clipboard-cut");
+       g_object_set (gaction, "enabled", can_cut, NULL);
 
-       action = gtk_action_group_get_action (clipboard->priv->actiongroup, "AddToQueue");
-       g_object_set (action, "sensitive", can_add_to_queue, NULL);
+       gaction = g_action_map_lookup_action (G_ACTION_MAP (app), "clipboard-copy");
+       g_object_set (gaction, "enabled", can_copy, NULL);
 
-       action = gtk_action_group_get_action (clipboard->priv->actiongroup, "MusicProperties");
-       g_object_set (action, "sensitive", can_show_properties, NULL);
+       gaction = g_action_map_lookup_action (G_ACTION_MAP (app), "clipboard-paste");
+       g_object_set (gaction, "enabled", can_paste, NULL);
+       
+       gaction = g_action_map_lookup_action (G_ACTION_MAP (app), "clipboard-add-to-queue");
+       g_object_set (gaction, "enabled", can_add_to_queue, NULL);
+       
+       gaction = g_action_map_lookup_action (G_ACTION_MAP (app), "clipboard-properties");
+       g_object_set (gaction, "enabled", can_show_properties, NULL);
 
-       action = gtk_action_group_get_action (clipboard->priv->actiongroup, "QueueMusicProperties");
-       g_object_set (action, "sensitive", have_sidebar_selection, NULL);
+       gaction = g_action_map_lookup_action (G_ACTION_MAP (app), "clipboard-select-all");
+       g_object_set (gaction, "enabled", can_select_all, NULL);
+       
+       gaction = g_action_map_lookup_action (G_ACTION_MAP (app), "clipboard-select-none");
+       g_object_set (gaction, "enabled", have_selection, NULL);
 
-       action = gtk_action_group_get_action (clipboard->priv->actiongroup, "QueueDelete");
-       g_object_set (action, "sensitive", have_sidebar_selection, NULL);
+       gaction = g_action_map_lookup_action (G_ACTION_MAP (app), "playlist-add-to");
+       g_object_set (gaction, "enabled", have_selection, NULL);
 
-       action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditSelectAll");
-       g_object_set (action, "sensitive", can_select_all, NULL);
-
-       action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditSelectNone");
-       g_object_set (action, "sensitive", have_selection, NULL);
-
-       /* disable the whole add-to-playlist menu if the source's entry type doesn't have playlists */
-       action = gtk_action_group_get_action (clipboard->priv->actiongroup, "EditPlaylistAdd");
-       if (clipboard->priv->source != NULL) {
-               g_object_get (clipboard->priv->source, "entry-type", &entry_type, NULL);
-               if (entry_type != NULL) {
-                       gboolean has_playlists;
-                       g_object_get (entry_type, "has-playlists", &has_playlists, NULL);
-                       gtk_action_set_sensitive (action, has_playlists);
-                       g_object_unref (entry_type);
-               } else {
-                       gtk_action_set_sensitive (action, FALSE);
-               }
-       } else {
-               gtk_action_set_sensitive (action, FALSE);
-       }
+       gaction = g_action_map_lookup_action (G_ACTION_MAP (app), "playlist-add-to-new");
+       g_object_set (gaction, "enabled", have_selection, NULL);
 }
 
 static GtkWidget*
@@ -681,9 +562,9 @@ get_focussed_widget (RBShellClipboard *clipboard)
 }
 
 static void
-rb_shell_clipboard_cmd_select_all (GtkAction *action,
-                                  RBShellClipboard *clipboard)
+select_all_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
+       RBShellClipboard *clipboard = RB_SHELL_CLIPBOARD (data);
        RBEntryView *entryview;
        GtkWidget *widget;
 
@@ -701,9 +582,9 @@ rb_shell_clipboard_cmd_select_all (GtkAction *action,
 }
 
 static void
-rb_shell_clipboard_cmd_select_none (GtkAction *action,
-                                   RBShellClipboard *clipboard)
+select_none_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
+       RBShellClipboard *clipboard = RB_SHELL_CLIPBOARD (data);
        RBEntryView *entryview;
        GtkWidget *widget;
 
@@ -720,52 +601,37 @@ rb_shell_clipboard_cmd_select_none (GtkAction *action,
 }
 
 static void
-rb_shell_clipboard_cmd_cut (GtkAction *action,
-                           RBShellClipboard *clipboard)
+cut_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
 {
-       rb_debug ("cut");
-       rb_shell_clipboard_set (clipboard,
-                               rb_source_cut (clipboard->priv->source));
+       RBShellClipboard *clipboard = RB_SHELL_CLIPBOARD (data);
+       rb_shell_clipboard_set (clipboard, rb_source_cut (clipboard->priv->source));
 }
 
 static void
-rb_shell_clipboard_cmd_copy (GtkAction *action,
-                            RBShellClipboard *clipboard)
+copy_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
 {
-       rb_debug ("copy");
-       rb_shell_clipboard_set (clipboard,
-                               rb_source_copy (clipboard->priv->source));
+       RBShellClipboard *clipboard = RB_SHELL_CLIPBOARD (data);
+       rb_shell_clipboard_set (clipboard, rb_source_copy (clipboard->priv->source));
 }
 
 static void
-rb_shell_clipboard_cmd_paste (GtkAction *action,
-                             RBShellClipboard *clipboard)
+paste_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
 {
-       rb_debug ("paste");
+       RBShellClipboard *clipboard = RB_SHELL_CLIPBOARD (data);
        rb_source_paste (clipboard->priv->source, clipboard->priv->entries);
 }
 
 static void
-rb_shell_clipboard_cmd_delete (GtkAction *action,
-                              RBShellClipboard *clipboard)
+delete_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
 {
-       rb_debug ("delete");
+       RBShellClipboard *clipboard = RB_SHELL_CLIPBOARD (data);
        rb_source_delete (clipboard->priv->source);
 }
 
 static void
-rb_shell_clipboard_cmd_queue_delete (GtkAction *action,
-                                    RBShellClipboard *clipboard)
+move_to_trash_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
 {
-       rb_debug ("delete");
-       rb_play_queue_source_sidebar_delete (RB_PLAY_QUEUE_SOURCE (clipboard->priv->queue_source));
-}
-
-static void
-rb_shell_clipboard_cmd_move_to_trash (GtkAction *action,
-                                     RBShellClipboard *clipboard)
-{
-       rb_debug ("movetotrash");
+       RBShellClipboard *clipboard = RB_SHELL_CLIPBOARD (data);
        rb_source_move_to_trash (clipboard->priv->source);
 }
 
@@ -821,259 +687,16 @@ rb_shell_clipboard_entries_changed_cb (RBEntryView *view,
 }
 
 static void
-rb_shell_clipboard_cmd_add_to_playlist_new (GtkAction *action,
-                                           RBShellClipboard *clipboard)
+add_to_queue_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
 {
-       GList *entries;
-       RBSource *playlist_source;
-
-       rb_debug ("add to new playlist");
-
-       entries = rb_source_copy (clipboard->priv->source);
-       playlist_source = rb_playlist_manager_new_playlist (clipboard->priv->playlist_manager,
-                                                           NULL, FALSE);
-       rb_source_paste (playlist_source, entries);
-
-       g_list_foreach (entries, (GFunc)rhythmdb_entry_unref, NULL);
-       g_list_free (entries);
-}
-
-static void
-rb_shell_clipboard_cmd_add_song_to_queue (GtkAction *action,
-                                         RBShellClipboard *clipboard)
-{
-       rb_debug ("add to queue");
+       RBShellClipboard *clipboard = RB_SHELL_CLIPBOARD (data);
        rb_source_add_to_queue (clipboard->priv->source,
                                RB_SOURCE (clipboard->priv->queue_source));
 }
 
 static void
-rb_shell_clipboard_cmd_song_info (GtkAction *action,
-                                 RBShellClipboard *clipboard)
+properties_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
 {
-       rb_debug ("song info");
-
+       RBShellClipboard *clipboard = RB_SHELL_CLIPBOARD (data);
        rb_source_song_properties (clipboard->priv->source);
 }
-
-static void
-rb_shell_clipboard_cmd_queue_song_info (GtkAction *action,
-                                       RBShellClipboard *clipboard)
-{
-       rb_debug ("song info");
-       rb_play_queue_source_sidebar_song_info (RB_PLAY_QUEUE_SOURCE (clipboard->priv->queue_source));
-}
-
-static void
-rb_shell_clipboard_playlist_add_cb (GtkAction *action,
-                                   RBShellClipboard *clipboard)
-{
-       RBSource *playlist_source;
-       GList *entries;
-
-       rb_debug ("add to exisintg playlist");
-       playlist_source = g_object_get_data (G_OBJECT (action), "playlist-source");
-
-       entries = rb_source_copy (clipboard->priv->source);
-       rb_source_paste (playlist_source, entries);
-
-       g_list_foreach (entries, (GFunc)rhythmdb_entry_unref, NULL);
-       g_list_free (entries);
-}
-
-static char *
-generate_action_name (RBStaticPlaylistSource *source,
-                     RBShellClipboard *clipboard)
-{
-       return g_strdup_printf ("AddToPlaylistClipboardAction%p", source);
-}
-
-static void
-rb_shell_clipboard_playlist_deleted_cb (RBStaticPlaylistSource *source,
-                                       RBShellClipboard *clipboard)
-{
-       char *action_name;
-       GtkAction *action;
-
-       /* first rebuild the menu */
-       rebuild_playlist_menu (clipboard);
-
-       /* then remove the 'add to playlist' action for the deleted playlist */
-       action_name = generate_action_name (source, clipboard);
-       action = gtk_action_group_get_action (clipboard->priv->actiongroup, action_name);
-       g_assert (action);
-       gtk_action_group_remove_action (clipboard->priv->actiongroup, action);
-       g_free (action_name);
-}
-
-static void
-rb_shell_clipboard_playlist_renamed_cb (RBStaticPlaylistSource *source,
-                                       GParamSpec *spec,
-                                       RBShellClipboard *clipboard)
-{
-       char *name, *action_name;
-       GtkAction *action;
-
-       g_object_get (source, "name", &name, NULL);
-
-       action_name = generate_action_name (source, clipboard);
-       action = gtk_action_group_get_action (clipboard->priv->actiongroup, action_name);
-       g_assert (action);
-       g_free (action_name);
-
-       g_object_set (action, "label", name, NULL);
-       g_free (name);
-}
-
-static void
-rb_shell_clipboard_playlist_visible_cb (RBStaticPlaylistSource *source,
-                                       GParamSpec *spec,
-                                       RBShellClipboard *clipboard)
-{
-       gboolean visible = FALSE;
-       char *action_name;
-       GtkAction *action;
-
-       g_object_get (source, "visibility", &visible, NULL);
-
-       action_name = generate_action_name (source, clipboard);
-       action = gtk_action_group_get_action (clipboard->priv->actiongroup, action_name);
-       g_assert (action);
-       g_free (action_name);
-
-       gtk_action_set_visible (action, visible);
-       g_object_unref (G_OBJECT (action));
-}
-
-static gboolean
-add_playlist_to_menu (GtkTreeModel *model,
-                     GtkTreePath *path,
-                     GtkTreeIter *iter,
-                     RBShellClipboard *clipboard)
-{
-       RhythmDBEntryType *entry_type;
-       RhythmDBEntryType *source_entry_type;
-       RBDisplayPage *page = NULL;
-       char *action_name;
-       GtkAction *action;
-       int i;
-
-       gtk_tree_model_get (GTK_TREE_MODEL (model), iter,
-                           RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page, -1);
-
-       if (page == NULL) {
-               return FALSE;
-       }
-
-       if (RB_IS_STATIC_PLAYLIST_SOURCE (page) == FALSE) {
-               g_object_unref (page);
-               return FALSE;
-       }
-
-       /* FIXME this isn't quite right; we'd want to be able to add
-        * songs from the library to playlists on devices (transferring
-        * the song to the device first), surely?
-        */
-       g_object_get (clipboard->priv->source, "entry-type", &entry_type, NULL);
-       g_object_get (page, "entry-type", &source_entry_type, NULL);
-       if (source_entry_type != entry_type || source_entry_type == NULL) {
-               g_object_unref (page);
-               if (entry_type)
-                       g_object_unref (entry_type);
-               if (source_entry_type)
-                       g_object_unref (source_entry_type);
-               return FALSE;
-       }
-
-       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 (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", page);
-               g_signal_connect_object (action,
-                                        "activate", G_CALLBACK (rb_shell_clipboard_playlist_add_cb),
-                                        clipboard, 0);
-
-               g_signal_connect_object (page,
-                                        "deleted", G_CALLBACK (rb_shell_clipboard_playlist_deleted_cb),
-                                        clipboard, 0);
-               g_signal_connect_object (page,
-                                        "notify::name", G_CALLBACK (rb_shell_clipboard_playlist_renamed_cb),
-                                        clipboard, 0);
-               g_signal_connect_object (page,
-                                        "notify::visibility", G_CALLBACK 
(rb_shell_clipboard_playlist_visible_cb),
-                                        clipboard, 0);
-       }
-
-       for (i = 0; i < num_playlist_menu_paths; i++) {
-               gtk_ui_manager_add_ui (clipboard->priv->ui_mgr, clipboard->priv->playlist_menu_ui_id,
-                                      playlist_menu_paths[i],
-                                      action_name, action_name,
-                                      GTK_UI_MANAGER_AUTO, FALSE);
-       }
-
-       g_object_unref (source_entry_type);
-       g_object_unref (entry_type);
-       g_free (action_name);
-       g_object_unref (page);
-
-       return FALSE;
-}
-
-static void
-rebuild_playlist_menu (RBShellClipboard *clipboard)
-{
-       GtkTreeModel *model = NULL;
-
-       if (clipboard->priv->source == NULL)
-               return;
-
-       rb_debug ("rebuilding add-to-playlist menu");
-
-       if (clipboard->priv->playlist_menu_ui_id != 0) {
-               gtk_ui_manager_remove_ui (clipboard->priv->ui_mgr,
-                                         clipboard->priv->playlist_menu_ui_id);
-       } else {
-               clipboard->priv->playlist_menu_ui_id =
-                       gtk_ui_manager_new_merge_id (clipboard->priv->ui_mgr);
-       }
-
-       if (clipboard->priv->playlist_manager != NULL) {
-               g_object_get (clipboard->priv->playlist_manager, "display-page-model", &model, NULL);
-       }
-
-       if (model != NULL) {
-               gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc)add_playlist_to_menu, clipboard);
-               g_object_unref (model);
-       }
-}
-
-static gboolean
-rebuild_playlist_menu_idle (RBShellClipboard *clipboard)
-{
-       GDK_THREADS_ENTER ();
-       rebuild_playlist_menu (clipboard);
-       clipboard->priv->idle_playlist_id = 0;
-       GDK_THREADS_LEAVE ();
-       return FALSE;
-}
-
-static void
-rb_shell_clipboard_playlist_added_cb (RBPlaylistManager *mgr,
-                                     RBPlaylistSource *source,
-                                     RBShellClipboard *clipboard)
-{
-       if (!RB_IS_STATIC_PLAYLIST_SOURCE (source))
-               return;
-
-       if (clipboard->priv->idle_playlist_id == 0) {
-               clipboard->priv->idle_playlist_id =
-                       g_idle_add ((GSourceFunc)rebuild_playlist_menu_idle, clipboard);
-       }
-}
diff --git a/shell/rb-shell-clipboard.h b/shell/rb-shell-clipboard.h
index 0366470..b4637a6 100644
--- a/shell/rb-shell-clipboard.h
+++ b/shell/rb-shell-clipboard.h
@@ -60,9 +60,7 @@ struct _RBShellClipboardClass
 
 GType             rb_shell_clipboard_get_type          (void);
 
-RBShellClipboard *rb_shell_clipboard_new               (GtkActionGroup *actiongroup,
-                                                        GtkUIManager *ui_mgr,
-                                                        RhythmDB *db);
+RBShellClipboard *rb_shell_clipboard_new               (RhythmDB *db);
 
 void              rb_shell_clipboard_set_source                (RBShellClipboard *clipboard,
                                                         RBSource *source);
diff --git a/shell/rb-shell-player.c b/shell/rb-shell-player.c
index 88b6c93..671a7f6 100644
--- a/shell/rb-shell-player.c
+++ b/shell/rb-shell-player.c
@@ -62,6 +62,7 @@
 #include <glib/gi18n.h>
 #include <gtk/gtk.h>
 
+#include "rb-application.h"
 #include "rb-property-view.h"
 #include "rb-shell-player.h"
 #include "rb-stock-icons.h"
@@ -112,21 +113,6 @@ static void rb_shell_player_get_property (GObject *object,
                                          guint prop_id,
                                          GValue *value,
                                          GParamSpec *pspec);
-
-static void rb_shell_player_cmd_previous (GtkAction *action,
-                                         RBShellPlayer *player);
-static void rb_shell_player_cmd_play (GtkAction *action,
-                                     RBShellPlayer *player);
-static void rb_shell_player_cmd_next (GtkAction *action,
-                                     RBShellPlayer *player);
-static void rb_shell_player_cmd_volume_up (GtkAction *action,
-                                          RBShellPlayer *player);
-static void rb_shell_player_cmd_volume_down (GtkAction *action,
-                                            RBShellPlayer *player);
-static void rb_shell_player_shuffle_changed_cb (GtkAction *action,
-                                               RBShellPlayer *player);
-static void rb_shell_player_repeat_changed_cb (GtkAction *action,
-                                              RBShellPlayer *player);
 static void rb_shell_player_set_playing_source_internal (RBShellPlayer *player,
                                                         RBSource *source,
                                                         gboolean sync_entry_view);
@@ -224,9 +210,6 @@ struct RBShellPlayerPrivate
        gboolean did_retry;
        GTimeVal last_retry;
 
-       GtkUIManager *ui_manager;
-       GtkActionGroup *actiongroup;
-
        gboolean handling_error;
 
        RBPlayer *mmplayer;
@@ -254,7 +237,6 @@ struct RBShellPlayerPrivate
        float volume;
 
        guint do_next_idle_id;
-       guint unblock_play_id;
 };
 
 #define RB_SHELL_PLAYER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_SHELL_PLAYER, 
RBShellPlayerPrivate))
@@ -292,3578 +274,3568 @@ enum
        LAST_SIGNAL
 };
 
-static GtkActionEntry rb_shell_player_actions [] =
-{
-       { "ControlPrevious", GTK_STOCK_MEDIA_PREVIOUS, N_("Pre_vious"), "<alt>Left",
-         N_("Start playing the previous song"),
-         G_CALLBACK (rb_shell_player_cmd_previous) },
-       { "ControlNext", GTK_STOCK_MEDIA_NEXT, N_("_Next"), "<alt>Right",
-         N_("Start playing the next song"),
-         G_CALLBACK (rb_shell_player_cmd_next) },
-       { "ControlVolumeUp", NULL, N_("_Increase Volume"), "<control>Up",
-         N_("Increase playback volume"),
-         G_CALLBACK (rb_shell_player_cmd_volume_up) },
-       { "ControlVolumeDown", NULL, N_("_Decrease Volume"), "<control>Down",
-         N_("Decrease playback volume"),
-         G_CALLBACK (rb_shell_player_cmd_volume_down) },
-};
-static guint rb_shell_player_n_actions = G_N_ELEMENTS (rb_shell_player_actions);
-
-static GtkToggleActionEntry rb_shell_player_toggle_entries [] =
-{
-       { "ControlPlay", GTK_STOCK_MEDIA_PLAY, N_("_Play"), "<control>space",
-         N_("Start playback"),
-         G_CALLBACK (rb_shell_player_cmd_play) },
-       { "ControlShuffle", GNOME_MEDIA_SHUFFLE, N_("Sh_uffle"), "<control>U",
-         N_("Play songs in a random order"),
-         G_CALLBACK (rb_shell_player_shuffle_changed_cb) },
-       { "ControlRepeat", GNOME_MEDIA_REPEAT, N_("_Repeat"), "<control>R",
-         N_("Play first song again after all songs are played"),
-         G_CALLBACK (rb_shell_player_repeat_changed_cb) },
-};
-static guint rb_shell_player_n_toggle_entries = G_N_ELEMENTS (rb_shell_player_toggle_entries);
 
 static guint rb_shell_player_signals[LAST_SIGNAL] = { 0 };
 
 G_DEFINE_TYPE (RBShellPlayer, rb_shell_player, G_TYPE_OBJECT)
 
 static void
-rb_shell_player_class_init (RBShellPlayerClass *klass)
+volume_pre_unmount_cb (GVolumeMonitor *monitor,
+                      GMount *mount,
+                      RBShellPlayer *player)
 {
-       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       const char *entry_mount_point;
+       GFile *mount_root;
+       RhythmDBEntry *entry;
 
-       object_class->dispose = rb_shell_player_dispose;
-       object_class->finalize = rb_shell_player_finalize;
-       object_class->constructed = rb_shell_player_constructed;
+       entry = rb_shell_player_get_playing_entry (player);
+       if (entry == NULL) {
+               return;
+       }
 
-       object_class->set_property = rb_shell_player_set_property;
-       object_class->get_property = rb_shell_player_get_property;
+       entry_mount_point = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MOUNTPOINT);
+       if (entry_mount_point == NULL) {
+               return;
+       }
 
-       /**
-        * RBShellPlayer:source:
-        *
-        * The current source that is selected for playback.
-        */
-       g_object_class_install_property (object_class,
-                                        PROP_SOURCE,
-                                        g_param_spec_object ("source",
-                                                             "RBSource",
-                                                             "RBSource object",
-                                                             RB_TYPE_SOURCE,
-                                                             G_PARAM_READWRITE));
+       mount_root = g_mount_get_root (mount);
+       if (mount_root != NULL) {
+               char *mount_point;
+               
+               mount_point = g_file_get_uri (mount_root);
+               if (mount_point && entry_mount_point &&
+                   strcmp (entry_mount_point, mount_point) == 0) {
+                       rb_shell_player_stop (player);
+               }
 
-       /**
-        * RBShellPlayer:ui-manager:
-        *
-        * The GtkUIManager
-        */
-       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_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+               g_free (mount_point);
+               g_object_unref (mount_root);
+       }
 
-       /**
-        * RBShellPlayer:db:
-        *
-        * The #RhythmDB
-        */
-       g_object_class_install_property (object_class,
-                                        PROP_DB,
-                                        g_param_spec_object ("db",
-                                                             "RhythmDB",
-                                                             "RhythmDB object",
-                                                             RHYTHMDB_TYPE,
-                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+       rhythmdb_entry_unref (entry);
+}
 
-       /**
-        * RBShellPlayer:action-group:
-        *
-        * The #GtkActionGroup to use for player actions
-        */
-       g_object_class_install_property (object_class,
-                                        PROP_ACTION_GROUP,
-                                        g_param_spec_object ("action-group",
-                                                             "GtkActionGroup",
-                                                             "GtkActionGroup object",
-                                                             GTK_TYPE_ACTION_GROUP,
-                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+static void
+reemit_playing_signal (RBShellPlayer *player,
+                      GParamSpec *pspec,
+                      gpointer data)
+{
+       g_signal_emit (player, rb_shell_player_signals[PLAYING_CHANGED], 0,
+                      rb_player_playing (player->priv->mmplayer));
+}
 
-       /**
-        * RBShellPlayer:queue-source:
-        *
-        * The play queue source
-        */
-       g_object_class_install_property (object_class,
-                                        PROP_QUEUE_SOURCE,
-                                        g_param_spec_object ("queue-source",
-                                                             "RBPlayQueueSource",
-                                                             "RBPlayQueueSource object",
-                                                             RB_TYPE_PLAYLIST_SOURCE,
-                                                             G_PARAM_READWRITE));
+static void
+rb_shell_player_open_playlist_url (RBShellPlayer *player,
+                                  const char *location,
+                                  RhythmDBEntry *entry,
+                                  RBPlayerPlayType play_type)
+{
+       GError *error = NULL;
 
-       /**
-        * RBShellPlayer:queue-only:
-        *
-        * If %TRUE, activating an entry should only add it to the play queue.
-        */
-       g_object_class_install_property (object_class,
-                                        PROP_QUEUE_ONLY,
-                                        g_param_spec_boolean ("queue-only",
-                                                              "Queue only",
-                                                              "Activation only adds to queue",
-                                                              FALSE,
-                                                              G_PARAM_READWRITE));
+       rb_debug ("playing stream url %s", location);
+       rb_player_open (player->priv->mmplayer,
+                       location,
+                       rhythmdb_entry_ref (entry),
+                       (GDestroyNotify) rhythmdb_entry_unref,
+                       &error);
+       if (error == NULL)
+               rb_player_play (player->priv->mmplayer, play_type, player->priv->track_transition_time, 
&error);
 
-       /**
-        * RBShellPlayer:playing-from-queue:
-        *
-        * If %TRUE, the current playing entry came from the play queue.
-        */
-       g_object_class_install_property (object_class,
-                                        PROP_PLAYING_FROM_QUEUE,
-                                        g_param_spec_boolean ("playing-from-queue",
-                                                              "Playing from queue",
-                                                              "Whether playing from the play queue or not",
-                                                              FALSE,
-                                                              G_PARAM_READABLE));
+       if (error) {
+               GDK_THREADS_ENTER ();
+               rb_shell_player_error (player, TRUE, error);
+               g_error_free (error);
+               GDK_THREADS_LEAVE ();
+       }
+}
 
-       /**
-        * RBShellPlayer:player:
-        *
-        * The player backend object (an object implementing the #RBPlayer interface).
-        */
-       g_object_class_install_property (object_class,
-                                        PROP_PLAYER,
-                                        g_param_spec_object ("player",
-                                                             "RBPlayer",
-                                                             "RBPlayer object",
-                                                             G_TYPE_OBJECT,
-                                                             G_PARAM_READABLE));
+static void
+rb_shell_player_handle_eos_unlocked (RBShellPlayer *player, RhythmDBEntry *entry, gboolean allow_stop)
+{
+       RBSource *source;
+       gboolean update_stats;
+       gboolean dragging;
 
-       /**
-        * RBShellPlayer:play-order:
-        *
-        * The current play order object.
-        */
-       g_object_class_install_property (object_class,
-                                        PROP_PLAY_ORDER,
-                                        g_param_spec_string ("play-order",
-                                                             "play-order",
-                                                             "What play order to use",
-                                                             "linear",
-                                                             G_PARAM_READABLE));
-       /**
-        * RBShellPlayer:playing:
-        *
-        * Whether Rhythmbox is currently playing something
-        */
-       g_object_class_install_property (object_class,
-                                        PROP_PLAYING,
-                                        g_param_spec_boolean ("playing",
-                                                              "playing",
-                                                             "Whether Rhythmbox is currently playing",
-                                                              FALSE,
-                                                              G_PARAM_READABLE));
-       /**
-        * RBShellPlayer:volume:
-        *
-        * The current playback volume (between 0.0 and 1.0)
-        */
-       g_object_class_install_property (object_class,
-                                        PROP_VOLUME,
-                                        g_param_spec_float ("volume",
-                                                            "volume",
-                                                            "Current playback volume",
-                                                            0.0f, 1.0f, 1.0f,
-                                                            G_PARAM_READWRITE));
+       source = player->priv->current_playing_source;
 
-       /**
-        * RBShellPlayer:header:
-        *
-        * The #RBHeader object
-        */
-       g_object_class_install_property (object_class,
-                                        PROP_HEADER,
-                                        g_param_spec_object ("header",
-                                                             "RBHeader",
-                                                             "RBHeader object",
-                                                             RB_TYPE_HEADER,
-                                                             G_PARAM_READWRITE));
-       /**
-        * RBShellPlayer:mute:
-        *
-        * Whether playback is currently muted.
-        */
-       g_object_class_install_property (object_class,
-                                        PROP_MUTE,
-                                        g_param_spec_boolean ("mute",
-                                                              "mute",
-                                                              "Whether playback is muted",
-                                                              FALSE,
-                                                              G_PARAM_READWRITE));
-       /**
-        * RBShellPlayer:has-next:
-        *
-        * Whether there is a track to play after the current track.
-        */
-       g_object_class_install_property (object_class,
-                                        PROP_HAS_NEXT,
-                                        g_param_spec_boolean ("has-next",
-                                                              "has-next",
-                                                              "Whether there is a next track",
-                                                              FALSE,
-                                                              G_PARAM_READABLE));
-       /**
-        * RBShellPlayer:has-prev:
-        *
-        * Whether there was a previous track before the current track.
-        */
-       g_object_class_install_property (object_class,
-                                        PROP_HAS_PREV,
-                                        g_param_spec_boolean ("has-prev",
-                                                              "has-prev",
-                                                              "Whether there is a previous track",
-                                                              FALSE,
-                                                              G_PARAM_READABLE));
+       /* nothing to do */
+       if (source == NULL) {
+               return;
+       }
 
-       /**
-        * RBShellPlayer::window-title-changed:
-        * @player: the #RBShellPlayer
-        * @title: the new window title
-        *
-        * Emitted when the main window title text should be changed
-        */
-       rb_shell_player_signals[WINDOW_TITLE_CHANGED] =
-               g_signal_new ("window_title_changed",
-                             G_OBJECT_CLASS_TYPE (object_class),
-                             G_SIGNAL_RUN_LAST,
-                             G_STRUCT_OFFSET (RBShellPlayerClass, window_title_changed),
-                             NULL, NULL,
-                             g_cclosure_marshal_VOID__STRING,
-                             G_TYPE_NONE,
-                             1,
-                             G_TYPE_STRING);
+       if (player->priv->playing_entry_eos) {
+               rb_debug ("playing entry has already EOS'd");
+               return;
+       }
 
-       /**
-        * RBShellPlayer::elapsed-changed:
-        * @player: the #RBShellPlayer
-        * @elapsed: the new playback position in seconds
-        *
-        * Emitted when the playback position changes.
-        */
-       rb_shell_player_signals[ELAPSED_CHANGED] =
-               g_signal_new ("elapsed_changed",
-                             G_OBJECT_CLASS_TYPE (object_class),
-                             G_SIGNAL_RUN_LAST,
-                             G_STRUCT_OFFSET (RBShellPlayerClass, elapsed_changed),
-                             NULL, NULL,
-                             g_cclosure_marshal_VOID__UINT,
-                             G_TYPE_NONE,
-                             1,
-                             G_TYPE_UINT);
+       if (entry != NULL) {
+               if (player->priv->playing_entry != entry) {
+                       rb_debug ("EOS'd entry is not the current playing entry; ignoring");
+                       return;
+               }
 
-       /**
-        * RBShellPlayer::playing-source-changed:
-        * @player: the #RBShellPlayer
-        * @source: the #RBSource that is now playing
-        *
-        * Emitted when a new #RBSource instance starts playing
-        */
-       rb_shell_player_signals[PLAYING_SOURCE_CHANGED] =
-               g_signal_new ("playing-source-changed",
-                             G_OBJECT_CLASS_TYPE (object_class),
-                             G_SIGNAL_RUN_LAST,
-                             G_STRUCT_OFFSET (RBShellPlayerClass, playing_source_changed),
-                             NULL, NULL,
-                             g_cclosure_marshal_VOID__OBJECT,
-                             G_TYPE_NONE,
-                             1,
-                             RB_TYPE_SOURCE);
+               rhythmdb_entry_ref (entry);
+       }
 
-       /**
-        * RBShellPlayer::playing-changed:
-        * @player: the #RBShellPlayer
-        * @playing: flag indicating playback state
-        *
-        * Emitted when playback either stops or starts.
-        */
-       rb_shell_player_signals[PLAYING_CHANGED] =
-               g_signal_new ("playing-changed",
-                             G_OBJECT_CLASS_TYPE (object_class),
-                             G_SIGNAL_RUN_LAST,
-                             G_STRUCT_OFFSET (RBShellPlayerClass, playing_changed),
-                             NULL, NULL,
-                             g_cclosure_marshal_VOID__BOOLEAN,
-                             G_TYPE_NONE,
-                             1,
-                             G_TYPE_BOOLEAN);
+       /* defer EOS handling while the position slider is being dragged */
+       g_object_get (player->priv->header_widget, "slider-dragging", &dragging, NULL);
+       if (dragging) {
+               rb_debug ("slider is dragging, will handle EOS (if applicable) on release");
+               player->priv->playing_entry_eos = TRUE;
+               if (entry != NULL)
+                       rhythmdb_entry_unref (entry);
+               return;
+       }
 
-       /**
-        * RBShellPlayer::playing-song-changed:
-        * @player: the #RBShellPlayer
-        * @entry: the new playing #RhythmDBEntry
-        *
-        * Emitted when the playing database entry changes
-        */
-       rb_shell_player_signals[PLAYING_SONG_CHANGED] =
-               g_signal_new ("playing-song-changed",
-                             G_OBJECT_CLASS_TYPE (object_class),
-                             G_SIGNAL_RUN_LAST,
-                             G_STRUCT_OFFSET (RBShellPlayerClass, playing_song_changed),
-                             NULL, NULL,
-                             g_cclosure_marshal_VOID__BOXED,
-                             G_TYPE_NONE,
-                             1,
-                             RHYTHMDB_TYPE_ENTRY);
+       update_stats = FALSE;
+       switch (rb_source_handle_eos (source)) {
+       case RB_SOURCE_EOF_ERROR:
+               if (allow_stop) {
+                       rb_error_dialog (NULL, _("Stream error"),
+                                        _("Unexpected end of stream!"));
+                       rb_shell_player_stop (player);
+                       player->priv->playing_entry_eos = TRUE;
+                       update_stats = TRUE;
+               }
+               break;
+       case RB_SOURCE_EOF_STOP:
+               if (allow_stop) {
+                       rb_shell_player_stop (player);
+                       player->priv->playing_entry_eos = TRUE;
+                       update_stats = TRUE;
+               }
+               break;
+       case RB_SOURCE_EOF_RETRY: {
+               GTimeVal current;
+               gint diff;
 
-       /**
-        * RBShellPlayer::playing-uri-changed:
-        * @player: the #RBShellPlayer
-        * @uri: the URI of the new playing entry
-        *
-        * Emitted when the playing database entry changes, providing the
-        * URI of the entry.
-        */
-       rb_shell_player_signals[PLAYING_URI_CHANGED] =
-               g_signal_new ("playing-uri-changed",
-                             G_OBJECT_CLASS_TYPE (object_class),
-                             G_SIGNAL_RUN_LAST,
-                             G_STRUCT_OFFSET (RBShellPlayerClass, playing_uri_changed),
-                             NULL, NULL,
-                             g_cclosure_marshal_VOID__STRING,
-                             G_TYPE_NONE,
-                             1,
-                             G_TYPE_STRING);
+               g_get_current_time (&current);
+               diff = current.tv_sec - player->priv->last_retry.tv_sec;
+               player->priv->last_retry = current;
 
-       /**
-        * RBShellPlayer::playing-song-property-changed:
-        * @player: the #RBShellPlayer
-        * @uri: the URI of the playing entry
-        * @property: the name of the property that changed
-        * @old: the previous value for the property
-        * @newvalue: the new value of the property
-        *
-        * Emitted when a property of the playing database entry changes.
-        */
-       rb_shell_player_signals[PLAYING_SONG_PROPERTY_CHANGED] =
-               g_signal_new ("playing-song-property-changed",
-                             G_OBJECT_CLASS_TYPE (object_class),
-                             G_SIGNAL_RUN_LAST,
-                             G_STRUCT_OFFSET (RBShellPlayerClass, playing_song_property_changed),
-                             NULL, NULL,
-                             rb_marshal_VOID__STRING_STRING_POINTER_POINTER,
-                             G_TYPE_NONE,
-                             4,
-                             G_TYPE_STRING, G_TYPE_STRING,
-                             G_TYPE_VALUE, G_TYPE_VALUE);
+               if (rb_source_try_playlist (source) &&
+                   !g_queue_is_empty (player->priv->playlist_urls)) {
+                       char *location = g_queue_pop_head (player->priv->playlist_urls);
+                       rb_debug ("trying next radio stream url: %s", location);
 
-       /**
-        * RBShellPlayer::elapsed-nano-changed:
-        * @player: the #RBShellPlayer
-        * @elapsed: the new playback position in nanoseconds
-        *
-        * Emitted when the playback position changes.  Only use this (as opposed to
-        * elapsed-changed) when you require subsecond precision.  This signal will be
-        * emitted multiple times per second.
-        */
-       rb_shell_player_signals[ELAPSED_NANO_CHANGED] =
-               g_signal_new ("elapsed-nano-changed",
-                             G_OBJECT_CLASS_TYPE (object_class),
-                             G_SIGNAL_RUN_LAST,
-                             G_STRUCT_OFFSET (RBShellPlayerClass, elapsed_nano_changed),
-                             NULL, NULL,
-                             rb_marshal_VOID__INT64,
-                             G_TYPE_NONE,
-                             1,
-                             G_TYPE_INT64);
+                       /* we're handling an unexpected EOS here, so crossfading isn't
+                        * really possible anyway -> specify FALSE.
+                        */
+                       rb_shell_player_open_playlist_url (player, location, entry, FALSE);
+                       g_free (location);
+                       break;
+               }
 
-       g_type_class_add_private (klass, sizeof (RBShellPlayerPrivate));
+               if (allow_stop) {
+                       if (diff < 4) {
+                               rb_debug ("Last retry was less than 4 seconds ago...aborting retry playback");
+                               rb_shell_player_stop (player);
+                       } else {
+                               rb_shell_player_play_entry (player, entry, NULL);
+                       }
+                       player->priv->playing_entry_eos = TRUE;
+                       update_stats = TRUE;
+               }
+       }
+               break;
+       case RB_SOURCE_EOF_NEXT:
+               {
+                       GError *error = NULL;
+
+                       player->priv->playing_entry_eos = TRUE;
+                       update_stats = TRUE;
+                       if (!rb_shell_player_do_next_internal (player, TRUE, allow_stop, &error)) {
+                               if (error->domain != RB_SHELL_PLAYER_ERROR ||
+                                   error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) {
+                                       g_warning ("Unhandled error: %s", error->message);
+                               } else if (allow_stop == FALSE) {
+                                       /* handle the real EOS when it happens */
+                                       player->priv->playing_entry_eos = FALSE;
+                                       update_stats = FALSE;
+                               }
+                       }
+               }
+               break;
+       }
+
+       if (update_stats &&
+           rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_PLAYBACK_ERROR) == NULL) {
+               rb_debug ("updating play statistics");
+               rb_source_update_play_statistics (source,
+                                                 player->priv->db,
+                                                 entry);
+       }
+
+       if (entry != NULL)
+               rhythmdb_entry_unref (entry);
 }
 
 static void
-rb_shell_player_constructed (GObject *object)
+rb_shell_player_slider_dragging_cb (GObject *header, GParamSpec *pspec, RBShellPlayer *player)
 {
-       RBShellPlayer *player;
-       GtkAction *action;
-
-       RB_CHAIN_GOBJECT_METHOD (rb_shell_player_parent_class, constructed, object);
-
-       player = RB_SHELL_PLAYER (object);
+       gboolean drag;
 
-       gtk_action_group_add_actions (player->priv->actiongroup,
-                                     rb_shell_player_actions,
-                                     rb_shell_player_n_actions,
-                                     player);
-       gtk_action_group_add_toggle_actions (player->priv->actiongroup,
-                                            rb_shell_player_toggle_entries,
-                                            rb_shell_player_n_toggle_entries,
-                                            player);
+       g_object_get (player->priv->header_widget, "slider-dragging", &drag, NULL);
+       rb_debug ("slider dragging? %d", drag);
 
-       player_settings_changed_cb (player->priv->settings, "transition-time", player);
-       player_settings_changed_cb (player->priv->settings, "play-order", player);
+       /* if an EOS occurred while dragging, process it now */
+       if (drag == FALSE && player->priv->playing_entry_eos) {
+               rb_debug ("processing EOS delayed due to slider dragging");
+               player->priv->playing_entry_eos = FALSE;
+               rb_shell_player_handle_eos_unlocked (player, rb_shell_player_get_playing_entry (player), 
FALSE);
+       }
+}
 
-       action = gtk_action_group_get_action (player->priv->actiongroup,
-                                             "ControlPlay");
-       g_object_set (action, "is-important", TRUE, NULL);
+static void
+rb_shell_player_handle_eos (RBPlayer *player,
+                           RhythmDBEntry *entry,
+                           gboolean early,
+                           RBShellPlayer *shell_player)
+{
+       const char *location;
+       if (entry == NULL) {
+               /* special case: this is called with entry == NULL to simulate an EOS
+                * from the current playing entry.
+                */
+               entry = shell_player->priv->playing_entry;
+               if (entry == NULL) {
+                       rb_debug ("called to simulate EOS for playing entry, but nothing is playing");
+                       return;
+               }
+       }
 
-       action = gtk_action_group_get_action (player->priv->actiongroup, "ControlPrevious");
-       g_object_bind_property (player, "has-prev", action, "sensitive", G_BINDING_DEFAULT);
-       action = gtk_action_group_get_action (player->priv->actiongroup, "ControlNext");
-       g_object_bind_property (player, "has-next", action, "sensitive", G_BINDING_DEFAULT);
+       GDK_THREADS_ENTER ();
 
-       player->priv->syncing_state = TRUE;
-       rb_shell_player_set_playing_source (player, NULL);
-       rb_shell_player_sync_play_order (player);
-       rb_shell_player_sync_control_state (player);
-       rb_shell_player_sync_volume (player, FALSE, TRUE);
-       player->priv->syncing_state = FALSE;
+       location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
+       if (entry != shell_player->priv->playing_entry) {
+               rb_debug ("got unexpected eos for %s", location);
+       } else {
+               rb_debug ("handling eos for %s", location);
+               /* don't allow playback to be stopped on early EOS notifications */
+               rb_shell_player_handle_eos_unlocked (shell_player, entry, (early == FALSE));
+       }
 
-       g_signal_connect (player,
-                         "notify::playing",
-                         G_CALLBACK (rb_shell_player_playing_changed_cb),
-                         NULL);
+       GDK_THREADS_LEAVE ();
 }
 
+
 static void
-volume_pre_unmount_cb (GVolumeMonitor *monitor,
-                      GMount *mount,
-                      RBShellPlayer *player)
+rb_shell_player_handle_redirect (RBPlayer *player,
+                                RhythmDBEntry *entry,
+                                const gchar *uri,
+                                RBShellPlayer *shell_player)
 {
-       const char *entry_mount_point;
-       GFile *mount_root;
-       RhythmDBEntry *entry;
-
-       entry = rb_shell_player_get_playing_entry (player);
-       if (entry == NULL) {
-               return;
-       }
+       GValue val = { 0 };
 
-       entry_mount_point = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MOUNTPOINT);
-       if (entry_mount_point == NULL) {
-               return;
-       }
+       rb_debug ("redirect to %s", uri);
 
-       mount_root = g_mount_get_root (mount);
-       if (mount_root != NULL) {
-               char *mount_point;
-               
-               mount_point = g_file_get_uri (mount_root);
-               if (mount_point && entry_mount_point &&
-                   strcmp (entry_mount_point, mount_point) == 0) {
-                       rb_shell_player_stop (player);
-               }
+       /* Stop existing stream */
+       rb_player_close (shell_player->priv->mmplayer, NULL, NULL);
 
-               g_free (mount_point);
-               g_object_unref (mount_root);
-       }
+       /* Update entry */
+       g_value_init (&val, G_TYPE_STRING);
+       g_value_set_string (&val, uri);
+       rhythmdb_entry_set (shell_player->priv->db, entry, RHYTHMDB_PROP_LOCATION, &val);
+       g_value_unset (&val);
+       rhythmdb_commit (shell_player->priv->db);
 
-       rhythmdb_entry_unref (entry);
+       /* Play new URI */
+       rb_shell_player_open_location (shell_player, entry, RB_PLAYER_PLAY_REPLACE, NULL);
 }
 
-static void
-reemit_playing_signal (RBShellPlayer *player,
-                      GParamSpec *pspec,
-                      gpointer data)
+
+GQuark
+rb_shell_player_error_quark (void)
 {
-       g_signal_emit (player, rb_shell_player_signals[PLAYING_CHANGED], 0,
-                      rb_player_playing (player->priv->mmplayer));
+       static GQuark quark = 0;
+       if (!quark)
+               quark = g_quark_from_static_string ("rb_shell_player_error");
+
+       return quark;
 }
 
-static void
-rb_shell_player_open_playlist_url (RBShellPlayer *player,
-                                  const char *location,
-                                  RhythmDBEntry *entry,
-                                  RBPlayerPlayType play_type)
+/**
+ * rb_shell_player_set_selected_source:
+ * @player: the #RBShellPlayer
+ * @source: the #RBSource to select
+ *
+ * Updates the player to reflect a new source being selected.
+ */
+void
+rb_shell_player_set_selected_source (RBShellPlayer *player,
+                                    RBSource *source)
 {
-       GError *error = NULL;
+       g_return_if_fail (RB_IS_SHELL_PLAYER (player));
+       g_return_if_fail (source == NULL || RB_IS_SOURCE (source));
 
-       rb_debug ("playing stream url %s", location);
-       rb_player_open (player->priv->mmplayer,
-                       location,
-                       rhythmdb_entry_ref (entry),
-                       (GDestroyNotify) rhythmdb_entry_unref,
-                       &error);
-       if (error == NULL)
-               rb_player_play (player->priv->mmplayer, play_type, player->priv->track_transition_time, 
&error);
+       g_object_set (player, "source", source, NULL);
+}
 
-       if (error) {
-               GDK_THREADS_ENTER ();
-               rb_shell_player_error (player, TRUE, error);
-               g_error_free (error);
-               GDK_THREADS_LEAVE ();
-       }
+/**
+ * rb_shell_player_get_playing_source:
+ * @player: the #RBShellPlayer
+ *
+ * Retrieves the current playing source.  That is, the source from
+ * which the current song was drawn.  This differs from 
+ * #rb_shell_player_get_active_source when the current song came
+ * from the play queue.
+ *
+ * Return value: (transfer none): the current playing #RBSource
+ */
+RBSource *
+rb_shell_player_get_playing_source (RBShellPlayer *player)
+{
+       return player->priv->current_playing_source;
 }
 
-static void
-rb_shell_player_handle_eos_unlocked (RBShellPlayer *player, RhythmDBEntry *entry, gboolean allow_stop)
+/**
+ * rb_shell_player_get_active_source:
+ * @player: the #RBShellPlayer
+ *
+ * Retrieves the active source.  This is the source that the user
+ * selected for playback.
+ *
+ * Return value: (transfer none): the active #RBSource
+ */
+RBSource *
+rb_shell_player_get_active_source (RBShellPlayer *player)
 {
-       RBSource *source;
-       gboolean update_stats;
-       gboolean dragging;
+       return player->priv->source;
+}
 
-       source = player->priv->current_playing_source;
+/**
+ * rb_shell_player_get_playing_entry:
+ * @player: the #RBShellPlayer
+ *
+ * Retrieves the currently playing #RhythmDBEntry, or NULL if
+ * nothing is playing.  The caller must unref the entry
+ * (using #rhythmdb_entry_unref) when it is no longer needed.
+ *
+ * Return value: (transfer full) (allow-none): the currently playing #RhythmDBEntry, or NULL
+ */
+RhythmDBEntry *
+rb_shell_player_get_playing_entry (RBShellPlayer *player)
+{
+       RBPlayOrder *porder;
+       RhythmDBEntry *entry;
 
-       /* nothing to do */
-       if (source == NULL) {
-               return;
+       if (player->priv->current_playing_source == NULL) {
+               return NULL;
        }
 
-       if (player->priv->playing_entry_eos) {
-               rb_debug ("playing entry has already EOS'd");
-               return;
-       }
+       g_object_get (player->priv->current_playing_source, "play-order", &porder, NULL);
+       if (porder == NULL)
+               porder = g_object_ref (player->priv->play_order);
 
-       if (entry != NULL) {
-               if (player->priv->playing_entry != entry) {
-                       rb_debug ("EOS'd entry is not the current playing entry; ignoring");
-                       return;
-               }
+       entry = rb_play_order_get_playing_entry (porder);
+       g_object_unref (porder);
 
-               rhythmdb_entry_ref (entry);
-       }
+       return entry;
+}
 
-       /* defer EOS handling while the position slider is being dragged */
-       g_object_get (player->priv->header_widget, "slider-dragging", &dragging, NULL);
-       if (dragging) {
-               rb_debug ("slider is dragging, will handle EOS (if applicable) on release");
-               player->priv->playing_entry_eos = TRUE;
-               if (entry != NULL)
-                       rhythmdb_entry_unref (entry);
-               return;
+typedef struct {
+       RBShellPlayer *player;
+       char *location;
+       RhythmDBEntry *entry;
+       RBPlayerPlayType play_type;
+       GCancellable *cancellable;
+} OpenLocationThreadData;
+
+static void
+playlist_entry_cb (TotemPlParser *playlist,
+                  const char *uri,
+                  GHashTable *metadata,
+                  OpenLocationThreadData *data)
+{
+       if (g_cancellable_is_cancelled (data->cancellable)) {
+               rb_debug ("playlist parser cancelled");
+       } else {
+               rb_debug ("adding stream url %s (%p)", uri, playlist);
+               g_queue_push_tail (data->player->priv->playlist_urls, g_strdup (uri));
        }
+}
+
+static gpointer
+open_location_thread (OpenLocationThreadData *data)
+{
+       TotemPlParser *playlist;
+       TotemPlParserResult playlist_result;
+
+       playlist = totem_pl_parser_new ();
+
+       g_signal_connect_data (playlist, "entry-parsed",
+                              G_CALLBACK (playlist_entry_cb),
+                              data, NULL, 0);
+
+       totem_pl_parser_add_ignored_mimetype (playlist, "x-directory/normal");
+       totem_pl_parser_add_ignored_mimetype (playlist, "inode/directory");
 
-       update_stats = FALSE;
-       switch (rb_source_handle_eos (source)) {
-       case RB_SOURCE_EOF_ERROR:
-               if (allow_stop) {
-                       rb_error_dialog (NULL, _("Stream error"),
-                                        _("Unexpected end of stream!"));
-                       rb_shell_player_stop (player);
-                       player->priv->playing_entry_eos = TRUE;
-                       update_stats = TRUE;
-               }
-               break;
-       case RB_SOURCE_EOF_STOP:
-               if (allow_stop) {
-                       rb_shell_player_stop (player);
-                       player->priv->playing_entry_eos = TRUE;
-                       update_stats = TRUE;
-               }
-               break;
-       case RB_SOURCE_EOF_RETRY: {
-               GTimeVal current;
-               gint diff;
+       playlist_result = totem_pl_parser_parse (playlist, data->location, FALSE);
+       g_object_unref (playlist);
 
-               g_get_current_time (&current);
-               diff = current.tv_sec - player->priv->last_retry.tv_sec;
-               player->priv->last_retry = current;
+       if (g_cancellable_is_cancelled (data->cancellable)) {
+               playlist_result = TOTEM_PL_PARSER_RESULT_CANCELLED;
+       }
 
-               if (rb_source_try_playlist (source) &&
-                   !g_queue_is_empty (player->priv->playlist_urls)) {
-                       char *location = g_queue_pop_head (player->priv->playlist_urls);
-                       rb_debug ("trying next radio stream url: %s", location);
+       switch (playlist_result) {
+       case TOTEM_PL_PARSER_RESULT_SUCCESS:
+               if (g_queue_is_empty (data->player->priv->playlist_urls)) {
+                       GError *error = g_error_new (RB_SHELL_PLAYER_ERROR,
+                                                    RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST,
+                                                    _("Playlist was empty"));
+                       GDK_THREADS_ENTER ();
+                       rb_shell_player_error (data->player, TRUE, error);
+                       g_error_free (error);
+                       GDK_THREADS_LEAVE ();
+               } else {
+                       char *location;
 
-                       /* we're handling an unexpected EOS here, so crossfading isn't
-                        * really possible anyway -> specify FALSE.
-                        */
-                       rb_shell_player_open_playlist_url (player, location, entry, FALSE);
+                       location = g_queue_pop_head (data->player->priv->playlist_urls);
+                       rb_debug ("playing first stream url %s", location);
+                       rb_shell_player_open_playlist_url (data->player, location, data->entry, 
data->play_type);
                        g_free (location);
-                       break;
-               }
-
-               if (allow_stop) {
-                       if (diff < 4) {
-                               rb_debug ("Last retry was less than 4 seconds ago...aborting retry playback");
-                               rb_shell_player_stop (player);
-                       } else {
-                               rb_shell_player_play_entry (player, entry, NULL);
-                       }
-                       player->priv->playing_entry_eos = TRUE;
-                       update_stats = TRUE;
                }
-       }
                break;
-       case RB_SOURCE_EOF_NEXT:
-               {
-                       GError *error = NULL;
 
-                       player->priv->playing_entry_eos = TRUE;
-                       update_stats = TRUE;
-                       if (!rb_shell_player_do_next_internal (player, TRUE, allow_stop, &error)) {
-                               if (error->domain != RB_SHELL_PLAYER_ERROR ||
-                                   error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) {
-                                       g_warning ("Unhandled error: %s", error->message);
-                               } else if (allow_stop == FALSE) {
-                                       /* handle the real EOS when it happens */
-                                       player->priv->playing_entry_eos = FALSE;
-                                       update_stats = FALSE;
-                               }
-                       }
-               }
+       case TOTEM_PL_PARSER_RESULT_CANCELLED:
+               rb_debug ("playlist parser was cancelled");
                break;
-       }
 
-       if (update_stats &&
-           rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_PLAYBACK_ERROR) == NULL) {
-               rb_debug ("updating play statistics");
-               rb_source_update_play_statistics (source,
-                                                 player->priv->db,
-                                                 entry);
+       default:
+               /* if we can't parse it as a playlist, just try playing it */
+               rb_debug ("playlist parser failed, playing %s directly", data->location);
+               rb_shell_player_open_playlist_url (data->player, data->location, data->entry, 
data->play_type);
+               break;
        }
 
-       if (entry != NULL)
-               rhythmdb_entry_unref (entry);
+       g_object_unref (data->cancellable);
+       g_free (data);
+       return NULL;
 }
 
-static void
-rb_shell_player_slider_dragging_cb (GObject *header, GParamSpec *pspec, RBShellPlayer *player)
+static gboolean
+rb_shell_player_open_location (RBShellPlayer *player,
+                              RhythmDBEntry *entry,
+                              RBPlayerPlayType play_type,
+                              GError **error)
 {
-       gboolean drag;
-
-       g_object_get (player->priv->header_widget, "slider-dragging", &drag, NULL);
-       rb_debug ("slider dragging? %d", drag);
+       char *location;
+       gboolean ret = TRUE;
 
-       /* if an EOS occurred while dragging, process it now */
-       if (drag == FALSE && player->priv->playing_entry_eos) {
-               rb_debug ("processing EOS delayed due to slider dragging");
-               player->priv->playing_entry_eos = FALSE;
-               rb_shell_player_handle_eos_unlocked (player, rb_shell_player_get_playing_entry (player), 
FALSE);
+       /* dispose of any existing playlist urls */
+       if (player->priv->playlist_urls) {
+               g_queue_foreach (player->priv->playlist_urls,
+                                (GFunc) g_free,
+                                NULL);
+               g_queue_free (player->priv->playlist_urls);
+               player->priv->playlist_urls = NULL;
        }
-}
-
-static void
-rb_shell_player_handle_eos (RBPlayer *player,
-                           RhythmDBEntry *entry,
-                           gboolean early,
-                           RBShellPlayer *shell_player)
-{
-       const char *location;
-       if (entry == NULL) {
-               /* special case: this is called with entry == NULL to simulate an EOS
-                * from the current playing entry.
-                */
-               entry = shell_player->priv->playing_entry;
-               if (entry == NULL) {
-                       rb_debug ("called to simulate EOS for playing entry, but nothing is playing");
-                       return;
-               }
+       if (rb_source_try_playlist (player->priv->source)) {
+               player->priv->playlist_urls = g_queue_new ();
        }
 
-       GDK_THREADS_ENTER ();
-
-       location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
-       if (entry != shell_player->priv->playing_entry) {
-               rb_debug ("got unexpected eos for %s", location);
-       } else {
-               rb_debug ("handling eos for %s", location);
-               /* don't allow playback to be stopped on early EOS notifications */
-               rb_shell_player_handle_eos_unlocked (shell_player, entry, (early == FALSE));
+       location = rhythmdb_entry_get_playback_uri (entry);
+       if (location == NULL) {
+               return FALSE;
        }
 
-       GDK_THREADS_LEAVE ();
-}
+       if (rb_source_try_playlist (player->priv->source)) {
+               OpenLocationThreadData *data;
 
+               data = g_new0 (OpenLocationThreadData, 1);
+               data->player = player;
+               data->play_type = play_type;
+               data->entry = entry;
 
-static void
-rb_shell_player_handle_redirect (RBPlayer *player,
-                                RhythmDBEntry *entry,
-                                const gchar *uri,
-                                RBShellPlayer *shell_player)
-{
-       GValue val = { 0 };
+               /* add http:// as a prefix, if it doesn't have a URI scheme */
+               if (strstr (location, "://"))
+                       data->location = g_strdup (location);
+               else
+                       data->location = g_strconcat ("http://";, location, NULL);
 
-       rb_debug ("redirect to %s", uri);
+               if (player->priv->parser_cancellable == NULL) {
+                       player->priv->parser_cancellable = g_cancellable_new ();
+               }
+               data->cancellable = g_object_ref (player->priv->parser_cancellable);
 
-       /* Stop existing stream */
-       rb_player_close (shell_player->priv->mmplayer, NULL, NULL);
+               g_thread_new ("open-location", (GThreadFunc)open_location_thread, data);
+       } else {
+               if (player->priv->parser_cancellable != NULL) {
+                       g_object_unref (player->priv->parser_cancellable);
+                       player->priv->parser_cancellable = NULL;
+               }
 
-       /* Update entry */
-       g_value_init (&val, G_TYPE_STRING);
-       g_value_set_string (&val, uri);
-       rhythmdb_entry_set (shell_player->priv->db, entry, RHYTHMDB_PROP_LOCATION, &val);
-       g_value_unset (&val);
-       rhythmdb_commit (shell_player->priv->db);
+               rhythmdb_entry_ref (entry);
+               ret = ret && rb_player_open (player->priv->mmplayer, location, entry, (GDestroyNotify) 
rhythmdb_entry_unref, error);
 
-       /* Play new URI */
-       rb_shell_player_open_location (shell_player, entry, RB_PLAYER_PLAY_REPLACE, NULL);
+               ret = ret && rb_player_play (player->priv->mmplayer, play_type, 
player->priv->track_transition_time, error);
+       }
+
+       g_free (location);
+       return ret;
 }
 
-static void
-rb_shell_player_init (RBShellPlayer *player)
+/**
+ * rb_shell_player_play:
+ * @player: a #RBShellPlayer
+ * @error: error return
+ *
+ * Starts playback, if it is not already playing.
+ *
+ * Return value: whether playback is now occurring (TRUE when successfully started
+ * or already playing).
+ **/
+gboolean
+rb_shell_player_play (RBShellPlayer *player,
+                     GError **error)
 {
-       GError *error = NULL;
-
-       player->priv = RB_SHELL_PLAYER_GET_PRIVATE (player);
-
-       player->priv->settings = g_settings_new ("org.gnome.rhythmbox.player");
-       player->priv->ui_settings = g_settings_new ("org.gnome.rhythmbox");
-       g_signal_connect_object (player->priv->settings,
-                                "changed",
-                                G_CALLBACK (player_settings_changed_cb),
-                                player, 0);
+       RBEntryView *songs;
 
-       player->priv->play_orders = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, 
(GDestroyNotify)_play_order_description_free);
-       
-       rb_shell_player_add_play_order (player, "linear", N_("Linear"),
-                                       RB_TYPE_LINEAR_PLAY_ORDER, FALSE);
-       rb_shell_player_add_play_order (player, "linear-loop", N_("Linear looping"),
-                                       RB_TYPE_LINEAR_PLAY_ORDER_LOOP, FALSE);
-       rb_shell_player_add_play_order (player, "shuffle", N_("Shuffle"),
-                                       RB_TYPE_SHUFFLE_PLAY_ORDER, FALSE);
-       rb_shell_player_add_play_order (player, "random-equal-weights", N_("Random with equal weights"),
-                                       RB_TYPE_RANDOM_PLAY_ORDER_EQUAL_WEIGHTS, FALSE);
-       rb_shell_player_add_play_order (player, "random-by-age", N_("Random by time since last play"),
-                                       RB_TYPE_RANDOM_PLAY_ORDER_BY_AGE, FALSE);
-       rb_shell_player_add_play_order (player, "random-by-rating", N_("Random by rating"),
-                                       RB_TYPE_RANDOM_PLAY_ORDER_BY_RATING, FALSE);
-       rb_shell_player_add_play_order (player, "random-by-age-and-rating", N_("Random by time since last 
play and rating"),
-                                       RB_TYPE_RANDOM_PLAY_ORDER_BY_AGE_AND_RATING, FALSE);
-       rb_shell_player_add_play_order (player, "queue", N_("Linear, removing entries once played"),
-                                       RB_TYPE_QUEUE_PLAY_ORDER, TRUE);
+       if (player->priv->current_playing_source == NULL) {
+               rb_debug ("current playing source is NULL");
+               g_set_error (error,
+                            RB_SHELL_PLAYER_ERROR,
+                            RB_SHELL_PLAYER_ERROR_NOT_PLAYING,
+                            "Current playing source is NULL");
+               return FALSE;
+       }
 
-       player->priv->mmplayer = rb_player_new (g_settings_get_boolean (player->priv->settings, 
"use-xfade-backend"),
-                                               &error);
-       if (error != NULL) {
-               GtkWidget *dialog;
-               dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL,
-                                                GTK_MESSAGE_ERROR,
-                                                GTK_BUTTONS_CLOSE,
-                                                _("Failed to create the player: %s"),
-                                                error->message);
-               gtk_dialog_run (GTK_DIALOG (dialog));
-               exit (1);
+       if (rb_player_playing (player->priv->mmplayer))
+               return TRUE;
+
+       if (player->priv->parser_cancellable != NULL) {
+               rb_debug ("currently parsing a playlist");
+               return TRUE;
        }
 
-       g_signal_connect_object (player->priv->mmplayer,
-                                "eos",
-                                G_CALLBACK (rb_shell_player_handle_eos),
-                                player, 0);
+       /* we're obviously not playing anything, so crossfading is irrelevant */
+       if (!rb_player_play (player->priv->mmplayer, RB_PLAYER_PLAY_REPLACE, 0.0f, error)) {
+               rb_debug ("player doesn't want to");
+               return FALSE;
+       }
 
-       g_signal_connect_object (player->priv->mmplayer,
-                                "redirect",
-                                G_CALLBACK (rb_shell_player_handle_redirect),
-                                player, 0);
+       songs = rb_source_get_entry_view (player->priv->current_playing_source);
+       if (songs)
+               rb_entry_view_set_state (songs, RB_ENTRY_VIEW_PLAYING);
 
-       g_signal_connect_object (player->priv->mmplayer,
-                                "tick",
-                                G_CALLBACK (tick_cb),
-                                player, 0);
+       return TRUE;
+}
 
-       g_signal_connect_object (player->priv->mmplayer,
-                                "error",
-                                G_CALLBACK (error_cb),
-                                player, 0);
+static void
+rb_shell_player_set_entry_playback_error (RBShellPlayer *player,
+                                         RhythmDBEntry *entry,
+                                         char *message)
+{
+       GValue value = { 0, };
 
-       g_signal_connect_object (player->priv->mmplayer,
-                                "playing-stream",
-                                G_CALLBACK (playing_stream_cb),
-                                player, 0);
+       g_return_if_fail (RB_IS_SHELL_PLAYER (player));
 
-       g_signal_connect_object (player->priv->mmplayer,
-                                "missing-plugins",
-                                G_CALLBACK (missing_plugins_cb),
-                                player, 0);
-       g_signal_connect_object (player->priv->mmplayer,
-                                "volume-changed",
-                                G_CALLBACK (rb_shell_player_volume_changed_cb),
-                                player, 0);
+       g_value_init (&value, G_TYPE_STRING);
+       g_value_set_string (&value, message);
+       rhythmdb_entry_set (player->priv->db,
+                           entry,
+                           RHYTHMDB_PROP_PLAYBACK_ERROR,
+                           &value);
+       g_value_unset (&value);
+       rhythmdb_commit (player->priv->db);
+}
 
-       g_signal_connect_object (player->priv->mmplayer,
-                                "image",
-                                G_CALLBACK (player_image_cb),
-                                player, 0);
+static gboolean
+rb_shell_player_set_playing_entry (RBShellPlayer *player,
+                                  RhythmDBEntry *entry,
+                                  gboolean out_of_order,
+                                  gboolean wait_for_eos,
+                                  GError **error)
+{
+       GError *tmp_error = NULL;
+       GValue val = {0,};
+       RBPlayerPlayType play_type;
 
-       {
-               GVolumeMonitor *monitor = g_volume_monitor_get ();
-               g_signal_connect (G_OBJECT (monitor),
-                                 "mount-pre-unmount",
-                                 G_CALLBACK (volume_pre_unmount_cb),
-                                 player);
-               g_object_unref (monitor);       /* hmm */
-       }
+       g_return_val_if_fail (player->priv->current_playing_source != NULL, TRUE);
+       g_return_val_if_fail (entry != NULL, TRUE);
 
-       player->priv->volume = g_settings_get_double (player->priv->settings, "volume");
+       play_type = wait_for_eos ? RB_PLAYER_PLAY_AFTER_EOS : RB_PLAYER_PLAY_REPLACE;
 
-       g_signal_connect (player, "notify::playing",
-                         G_CALLBACK (reemit_playing_signal), NULL);
-}
+       if (out_of_order) {
+               RBPlayOrder *porder;
 
-static void
-rb_shell_player_set_source_internal (RBShellPlayer *player,
-                                    RBSource      *source)
-{
-       if (player->priv->selected_source != NULL) {
-               RBEntryView *songs = rb_source_get_entry_view (player->priv->selected_source);
-               GList *property_views = rb_source_get_property_views (player->priv->selected_source);
-               GList *l;
+               g_object_get (player->priv->current_playing_source, "play-order", &porder, NULL);
+               if (porder == NULL)
+                       porder = g_object_ref (player->priv->play_order);
+               rb_play_order_set_playing_entry (porder, entry);
+               g_object_unref (porder);
+       }
 
-               if (songs != NULL) {
-                       g_signal_handlers_disconnect_by_func (G_OBJECT (songs),
-                                                             G_CALLBACK (rb_shell_player_entry_activated_cb),
-                                                             player);
-               }
+       if (player->priv->playing_entry != NULL &&
+           player->priv->track_transition_time > 0) {
+               const char *previous_album;
+               const char *album;
 
-               for (l = property_views; l != NULL; l = g_list_next (l)) {
-                       g_signal_handlers_disconnect_by_func (G_OBJECT (l->data),
-                                                             G_CALLBACK 
(rb_shell_player_property_row_activated_cb),
-                                                             player);
+               previous_album = rhythmdb_entry_get_string (player->priv->playing_entry, RHYTHMDB_PROP_ALBUM);
+               album = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM);
+               /* only crossfade if we're not going from the end of one song on an
+                * album to the start of another.  "Unknown" doesn't count as an album.
+                */
+               if (wait_for_eos == FALSE ||
+                   strcmp (album, _("Unknown")) == 0 ||
+                   strcmp (album, previous_album) != 0) {
+                       play_type = RB_PLAYER_PLAY_CROSSFADE;
                }
+       }
 
-               g_list_free (property_views);
+       if (rb_shell_player_open_location (player, entry, play_type, &tmp_error) == FALSE) {
+               goto lose;
        }
 
-       player->priv->selected_source = source;
+       rb_debug ("Success!");
+       /* clear error on successful playback */
+       g_value_init (&val, G_TYPE_STRING);
+       g_value_set_string (&val, NULL);
+       rhythmdb_entry_set (player->priv->db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &val);
+       rhythmdb_commit (player->priv->db);
+       g_value_unset (&val);
 
-       rb_debug ("selected source %p", player->priv->selected_source);
+       return TRUE;
+ lose:
+       /* Ignore errors, shutdown the player */
+       rb_player_close (player->priv->mmplayer, NULL /* XXX specify uri? */, NULL);
 
-       rb_shell_player_sync_with_selected_source (player);
-       rb_shell_player_sync_buttons (player);
+       if (tmp_error == NULL) {
+               tmp_error = g_error_new (RB_SHELL_PLAYER_ERROR,
+                                        RB_SHELL_PLAYER_ERROR_NOT_PLAYING,
+                                        "Problem occurred without error being set. "
+                                        "This is a bug in Rhythmbox or GStreamer.");
+       }
+       /* Mark this song as failed */
+       rb_shell_player_set_entry_playback_error (player, entry, tmp_error->message);
+       g_propagate_error (error, tmp_error);
 
-       if (player->priv->selected_source != NULL) {
-               RBEntryView *songs = rb_source_get_entry_view (player->priv->selected_source);
-               GList *property_views = rb_source_get_property_views (player->priv->selected_source);
-               GList *l;
+       rb_shell_player_sync_with_source (player);
+       rb_shell_player_sync_buttons (player);
+       g_object_notify (G_OBJECT (player), "playing");
 
-               if (songs)
-                       g_signal_connect_object (G_OBJECT (songs),
-                                                "entry-activated",
-                                                G_CALLBACK (rb_shell_player_entry_activated_cb),
-                                                player, 0);
-               for (l = property_views; l != NULL; l = g_list_next (l)) {
-                       g_signal_connect_object (G_OBJECT (l->data),
-                                                "property-activated",
-                                                G_CALLBACK (rb_shell_player_property_row_activated_cb),
-                                                player, 0);
-               }
+       return FALSE;
+}
 
-               g_list_free (property_views);
+static void
+player_settings_changed_cb (GSettings *settings, const char *key, RBShellPlayer *player)
+{
+       if (g_strcmp0 (key, "play-order") == 0) {
+               rb_debug ("play order setting changed");
+               player->priv->syncing_state = TRUE;
+               rb_shell_player_sync_play_order (player);
+               rb_shell_player_sync_buttons (player);
+               rb_shell_player_sync_control_state (player);
+               g_object_notify (G_OBJECT (player), "play-order");
+               player->priv->syncing_state = FALSE;
+       } else if (g_strcmp0 (key, "transition-time") == 0) {
+               double newtime;
+               rb_debug ("track transition time changed");
+               newtime = g_settings_get_double (player->priv->settings, "transition-time");
+               player->priv->track_transition_time = newtime * RB_PLAYER_SECOND;
        }
+}
 
-       /* If we're not playing, change the play order's view of the current source;
-        * if the selected source is the queue, however, set it to NULL so it'll stop
-        * once the queue is empty.
-        */
-       if (player->priv->current_playing_source == NULL) {
-               RBPlayOrder *porder = NULL;
-               RBSource *source = player->priv->selected_source;
-               if (source == RB_SOURCE (player->priv->queue_source)) {
-                       source = NULL;
-               } else if (source != NULL) {
-                       g_object_get (source, "play-order", &porder, NULL);
-               }
+/**
+ * rb_shell_player_get_playback_state:
+ * @player: the #RBShellPlayer
+ * @shuffle: (out): returns the current shuffle setting
+ * @repeat: (out): returns the current repeat setting
+ *
+ * Retrieves the current state of the shuffle and repeat settings.
+ *
+ * Return value: %TRUE if successful.
+ */
+gboolean
+rb_shell_player_get_playback_state (RBShellPlayer *player,
+                                   gboolean *shuffle,
+                                   gboolean *repeat)
+{
+       int i, j;
+       char *play_order;
 
-               if (porder == NULL)
-                       porder = g_object_ref (player->priv->play_order);
+       play_order = g_settings_get_string (player->priv->settings, "play-order");
+       for (i = 0; i < G_N_ELEMENTS(state_to_play_order); i++)
+               for (j = 0; j < G_N_ELEMENTS(state_to_play_order[0]); j++)
+                       if (!strcmp (play_order, state_to_play_order[i][j]))
+                               goto found;
 
-               rb_play_order_playing_source_changed (porder, source);
-               g_object_unref (porder);
+       g_free (play_order);
+       return FALSE;
+
+found:
+       if (shuffle != NULL) {
+               *shuffle = i > 0;
+       }
+       if (repeat != NULL) {
+               *repeat = j > 0;
        }
+       g_free (play_order);
+       return TRUE;
+}
+
+/**
+ * rb_shell_player_set_playback_state:
+ * @player: the #RBShellPlayer
+ * @shuffle: whether to enable the shuffle setting
+ * @repeat: whether to enable the repeat setting
+ *
+ * Sets the state of the shuffle and repeat settings.
+ */
+void
+rb_shell_player_set_playback_state (RBShellPlayer *player,
+                                   gboolean shuffle,
+                                   gboolean repeat)
+{
+       const char *neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
+       g_settings_set_string (player->priv->settings, "play-order", neworder);
 }
 
 static void
-rb_shell_player_set_db_internal (RBShellPlayer *player,
-                                RhythmDB      *db)
+rb_shell_player_sync_play_order (RBShellPlayer *player)
 {
-       if (player->priv->db != NULL) {
-               g_signal_handlers_disconnect_by_func (player->priv->db,
-                                                     G_CALLBACK (rb_shell_player_entry_changed_cb),
-                                                     player);
-               g_signal_handlers_disconnect_by_func (player->priv->db,
-                                                     G_CALLBACK (rb_shell_player_extra_metadata_cb),
+       char *new_play_order;
+       RhythmDBEntry *playing_entry = NULL;
+       RBSource *source;
+
+       new_play_order = g_settings_get_string (player->priv->settings, "play-order");
+       if (player->priv->play_order != NULL) {
+               playing_entry = rb_play_order_get_playing_entry (player->priv->play_order);
+               g_signal_handlers_disconnect_by_func (player->priv->play_order,
+                                                     G_CALLBACK (rb_shell_player_play_order_update_cb),
                                                      player);
+               g_object_unref (player->priv->play_order);
        }
 
-       player->priv->db = db;
+       player->priv->play_order = rb_play_order_new (player, new_play_order);
 
-       if (player->priv->db != NULL) {
-               /* Listen for changed entries to update metadata display */
-               g_signal_connect_object (G_OBJECT (player->priv->db),
-                                        "entry_changed",
-                                        G_CALLBACK (rb_shell_player_entry_changed_cb),
-                                        player, 0);
-               g_signal_connect_object (G_OBJECT (player->priv->db),
-                                        "entry_extra_metadata_notify",
-                                        G_CALLBACK (rb_shell_player_extra_metadata_cb),
-                                        player, 0);
+       g_signal_connect_object (player->priv->play_order,
+                                "have_next_previous_changed",
+                                G_CALLBACK (rb_shell_player_play_order_update_cb),
+                                player, 0);
+       rb_shell_player_play_order_update_cb (player->priv->play_order,
+                                             FALSE, FALSE,
+                                             player);
+
+       source = player->priv->current_playing_source;
+       if (source == NULL) {
+               source = player->priv->selected_source;
+       }
+       rb_play_order_playing_source_changed (player->priv->play_order, source);
+
+       if (playing_entry != NULL) {
+               rb_play_order_set_playing_entry (player->priv->play_order, playing_entry);
+               rhythmdb_entry_unref (playing_entry);
        }
+
+       g_free (new_play_order);
 }
 
 static void
-rb_shell_player_set_queue_source_internal (RBShellPlayer     *player,
-                                          RBPlayQueueSource *source)
+rb_shell_player_play_order_update_cb (RBPlayOrder *porder,
+                                     gboolean _has_next,
+                                     gboolean _has_previous,
+                                     RBShellPlayer *player)
 {
-       if (player->priv->queue_source != NULL) {
-               RBEntryView *sidebar;
-
-               g_object_get (player->priv->queue_source, "sidebar", &sidebar, NULL);
-               g_signal_handlers_disconnect_by_func (sidebar,
-                                                     G_CALLBACK (rb_shell_player_entry_activated_cb),
-                                                     player);
-               g_object_unref (sidebar);
+       /* we cannot depend on the values of has_next, has_previous or porder
+        * since this can be called for the main porder, queue porder, etc
+        */
+       gboolean has_next = FALSE;
+       gboolean has_prev = FALSE;
+       RhythmDBEntry *entry;
 
-               if (player->priv->queue_play_order != NULL) {
-                       g_signal_handlers_disconnect_by_func (player->priv->queue_play_order,
-                                                             G_CALLBACK 
(rb_shell_player_play_order_update_cb),
-                                                             player);
-                       g_object_unref (player->priv->queue_play_order);
+       entry = rb_shell_player_get_playing_entry (player);
+       if (entry != NULL) {
+               has_next = TRUE;
+               has_prev = TRUE;
+               rhythmdb_entry_unref (entry);
+       } else {
+               if (player->priv->current_playing_source &&
+                   (rb_source_handle_eos (player->priv->current_playing_source) == RB_SOURCE_EOF_NEXT)) {
+                       RBPlayOrder *porder;
+                       g_object_get (player->priv->current_playing_source, "play-order", &porder, NULL);
+                       if (porder == NULL)
+                               porder = g_object_ref (player->priv->play_order);
+                       has_next = rb_play_order_has_next (porder);
+                       g_object_unref (porder);
                }
-
+               if (player->priv->queue_play_order) {
+                       has_next |= rb_play_order_has_next (player->priv->queue_play_order);
+               }
+               has_prev = (player->priv->current_playing_source != NULL);
        }
 
-       player->priv->queue_source = source;
+       if (has_prev != player->priv->has_prev) {
+               player->priv->has_prev = has_prev;
+               g_object_notify (G_OBJECT (player), "has-prev");
+       }
+       if (has_next != player->priv->has_next) {
+               player->priv->has_next = has_next;
+               g_object_notify (G_OBJECT (player), "has-next");
+       }
+}
 
-       if (player->priv->queue_source != NULL) {
-               RBEntryView *sidebar;
+/**
+ * rb_shell_player_jump_to_current:
+ * @player: the #RBShellPlayer
+ *
+ * Scrolls the #RBEntryView for the current playing source so that
+ * the current playing entry is visible and selects the row for the
+ * entry.  If there is no current playing entry, the selection is
+ * cleared instead.
+ */
+void
+rb_shell_player_jump_to_current (RBShellPlayer *player)
+{
+       RBSource *source;
+       RhythmDBEntry *entry;
+       RBEntryView *songs;
 
-               g_object_get (player->priv->queue_source, "play-order", &player->priv->queue_play_order, 
NULL);
+       source = player->priv->current_playing_source ? player->priv->current_playing_source :
+               player->priv->selected_source;
 
-               g_signal_connect_object (G_OBJECT (player->priv->queue_play_order),
-                                        "have_next_previous_changed",
-                                        G_CALLBACK (rb_shell_player_play_order_update_cb),
-                                        player, 0);
-               rb_shell_player_play_order_update_cb (player->priv->play_order,
-                                                     FALSE, FALSE,
-                                                     player);
-               rb_play_order_playing_source_changed (player->priv->queue_play_order,
-                                                     RB_SOURCE (player->priv->queue_source));
+       songs = rb_source_get_entry_view (source);
+       entry = rb_shell_player_get_playing_entry (player);
+       if (songs != NULL) {
+               if (entry != NULL) {
+                       rb_entry_view_scroll_to_entry (songs, entry);
+                       rb_entry_view_select_entry (songs, entry);
+               } else {
+                       rb_entry_view_select_none (songs);
+               }
+       }
 
-               g_object_get (player->priv->queue_source, "sidebar", &sidebar, NULL);
-               g_signal_connect_object (G_OBJECT (sidebar),
-                                        "entry-activated",
-                                        G_CALLBACK (rb_shell_player_entry_activated_cb),
-                                        player, 0);
-               g_object_unref (sidebar);
+       if (entry != NULL) {
+               rhythmdb_entry_unref (entry);
        }
 }
 
 static void
-rb_shell_player_dispose (GObject *object)
+swap_playing_source (RBShellPlayer *player,
+                    RBSource *new_source)
 {
-       RBShellPlayer *player;
+       if (player->priv->current_playing_source != NULL) {
+               RBEntryView *old_songs = rb_source_get_entry_view (player->priv->current_playing_source);
+               if (old_songs)
+                       rb_entry_view_set_state (old_songs, RB_ENTRY_VIEW_NOT_PLAYING);
+       }
+       if (new_source != NULL) {
+               RBEntryView *new_songs = rb_source_get_entry_view (new_source);
 
-       g_return_if_fail (object != NULL);
-       g_return_if_fail (RB_IS_SHELL_PLAYER (object));
+               if (new_songs) {
+                       rb_entry_view_set_state (new_songs, RB_ENTRY_VIEW_PLAYING);
+                       rb_shell_player_set_playing_source (player, new_source);
+               }
+       }
+}
 
-       player = RB_SHELL_PLAYER (object);
+/**
+ * rb_shell_player_do_previous:
+ * @player: the #RBShellPlayer
+ * @error: returns any error information
+ *
+ * If the current song has been playing for more than 3 seconds,
+ * restarts it, otherwise, goes back to the previous song.
+ * Fails if there is no current song, or if inside the first
+ * 3 seconds of the first song in the play order.
+ *
+ * Return value: %TRUE if successful
+ */
+gboolean
+rb_shell_player_do_previous (RBShellPlayer *player,
+                            GError **error)
+{
+       RhythmDBEntry *entry = NULL;
+       RBSource *new_source;
 
-       g_return_if_fail (player->priv != NULL);
+       if (player->priv->current_playing_source == NULL) {
+               g_set_error (error,
+                            RB_SHELL_PLAYER_ERROR,
+                            RB_SHELL_PLAYER_ERROR_NOT_PLAYING,
+                            _("Not currently playing"));
+               return FALSE;
+       }
 
-       if (player->priv->ui_settings != NULL) {
-               g_object_unref (player->priv->ui_settings);
-               player->priv->ui_settings = NULL;
+       /* If we're in the first 3 seconds go to the previous song,
+        * else restart the current one.
+        */
+       if (player->priv->current_playing_source != NULL
+           && rb_source_can_pause (player->priv->source)
+           && rb_player_get_time (player->priv->mmplayer) > (G_GINT64_CONSTANT (3) * RB_PLAYER_SECOND)) {
+               rb_debug ("after 3 second previous, restarting song");
+               rb_player_set_time (player->priv->mmplayer, 0);
+               rb_shell_player_sync_with_source (player);
+               return TRUE;
        }
 
-       if (player->priv->settings != NULL) {
-               /* hm, is this really the place to do this? */
-               g_settings_set_double (player->priv->settings,
-                                      "volume",
-                                      player->priv->volume);
+       rb_debug ("going to previous");
 
-               g_object_unref (player->priv->settings);
-               player->priv->settings = NULL;
+       /* hrm, does this actually do anything at all? */
+       if (player->priv->queue_play_order) {
+               entry = rb_play_order_get_previous (player->priv->queue_play_order);
+               if (entry != NULL) {
+                       new_source = RB_SOURCE (player->priv->queue_source);
+                       rb_play_order_go_previous (player->priv->queue_play_order);
+               }
        }
 
-       if (player->priv->mmplayer != NULL) {
-               g_object_unref (player->priv->mmplayer);
-               player->priv->mmplayer = NULL;
-       }
+       if (entry == NULL) {
+               RBPlayOrder *porder;
 
-       if (player->priv->play_order != NULL) {
-               g_object_unref (player->priv->play_order);
-               player->priv->play_order = NULL;
+               new_source = player->priv->source;
+               g_object_get (new_source, "play-order", &porder, NULL);
+               if (porder == NULL)
+                       porder = g_object_ref (player->priv->play_order);
+
+               entry = rb_play_order_get_previous (porder);
+               if (entry)
+                       rb_play_order_go_previous (porder);
+               g_object_unref (porder);
        }
 
-       if (player->priv->queue_play_order != NULL) {
-               g_object_unref (player->priv->queue_play_order);
-               player->priv->queue_play_order = NULL;
-       }
+       if (entry != NULL) {
+               rb_debug ("previous song found, doing previous");
+               if (new_source != player->priv->current_playing_source)
+                       swap_playing_source (player, new_source);
 
-       if (player->priv->do_next_idle_id != 0) {
-               g_source_remove (player->priv->do_next_idle_id);
-               player->priv->do_next_idle_id = 0;
-       }
+               player->priv->jump_to_playing_entry = TRUE;
+               if (!rb_shell_player_set_playing_entry (player, entry, FALSE, FALSE, error)) {
+                       rhythmdb_entry_unref (entry);
+                       return FALSE;
+               }
 
-       if (player->priv->unblock_play_id != 0) {
-               g_source_remove (player->priv->unblock_play_id);
-               player->priv->unblock_play_id = 0;
+               rhythmdb_entry_unref (entry);
+       } else {
+               rb_debug ("no previous song found, signalling error");
+               g_set_error (error,
+                            RB_SHELL_PLAYER_ERROR,
+                            RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST,
+                            _("No previous song"));
+               rb_shell_player_stop (player);
+               return FALSE;
        }
 
-       G_OBJECT_CLASS (rb_shell_player_parent_class)->dispose (object);
+       return TRUE;
 }
 
-static void
-rb_shell_player_finalize (GObject *object)
+static gboolean
+rb_shell_player_do_next_internal (RBShellPlayer *player, gboolean from_eos, gboolean allow_stop, GError 
**error)
 {
-       RBShellPlayer *player;
+       RBSource *new_source = NULL;
+       RhythmDBEntry *entry = NULL;
+       gboolean rv = TRUE;
 
-       g_return_if_fail (object != NULL);
-       g_return_if_fail (RB_IS_SHELL_PLAYER (object));
+       if (player->priv->source == NULL)
+               return TRUE;
 
-       player = RB_SHELL_PLAYER (object);
 
-       g_return_if_fail (player->priv != NULL);
+       /* try the current playing source's play order, if it has one */
+       if (player->priv->current_playing_source != NULL) {
+               RBPlayOrder *porder;
+               g_object_get (player->priv->current_playing_source, "play-order", &porder, NULL);
+               if (porder != NULL) {
+                       entry = rb_play_order_get_next (porder);
+                       if (entry != NULL) {
+                               rb_play_order_go_next (porder);
+                               new_source = player->priv->current_playing_source;
+                       }
+                       g_object_unref (porder);
+               }
+       }
 
-       g_hash_table_destroy (player->priv->play_orders);
-       
-       G_OBJECT_CLASS (rb_shell_player_parent_class)->finalize (object);
-}
+       /* if that's different to the playing source that the user selected
+        * (ie we're playing from the queue), try that too
+        */
+       if (entry == NULL) {
+               RBPlayOrder *porder;
+               g_object_get (player->priv->source, "play-order", &porder, NULL);
+               if (porder == NULL)
+                       porder = g_object_ref (player->priv->play_order);
 
-static void
-rb_shell_player_set_property (GObject *object,
-                             guint prop_id,
-                             const GValue *value,
-                             GParamSpec *pspec)
-{
-       RBShellPlayer *player = RB_SHELL_PLAYER (object);
+               /*
+                * If we interrupted this source to play from something else,
+                * we should go back to whatever it wanted to play before.
+                */
+               if (player->priv->source != player->priv->current_playing_source)
+                       entry = rb_play_order_get_playing_entry (porder);
 
-       switch (prop_id) {
-       case PROP_SOURCE:
-               rb_shell_player_set_source_internal (player, g_value_get_object (value));
-               break;
-       case PROP_UI_MANAGER:
-               player->priv->ui_manager = g_value_get_object (value);
-               break;
-       case PROP_DB:
-               rb_shell_player_set_db_internal (player, g_value_get_object (value));
-               break;
-       case PROP_ACTION_GROUP:
-               player->priv->actiongroup = g_value_get_object (value);
-               break;
-       case PROP_PLAY_ORDER:
-               g_settings_set_string (player->priv->settings,
-                                      "play-order",
-                                      g_value_get_string (value));
-               break;
-       case PROP_VOLUME:
-               player->priv->volume = g_value_get_float (value);
-               rb_shell_player_sync_volume (player, FALSE, TRUE);
-               break;
-       case PROP_HEADER:
-               player->priv->header_widget = g_value_get_object (value);
-               g_signal_connect_object (player->priv->header_widget,
-                                        "notify::slider-dragging",
-                                        G_CALLBACK (rb_shell_player_slider_dragging_cb),
-                                        player, 0);
-               break;
-       case PROP_QUEUE_SOURCE:
-               rb_shell_player_set_queue_source_internal (player, g_value_get_object (value));
-               break;
-       case PROP_QUEUE_ONLY:
-               player->priv->queue_only = g_value_get_boolean (value);
-               break;
-       case PROP_MUTE:
-               player->priv->mute = g_value_get_boolean (value);
-               rb_shell_player_sync_volume (player, FALSE, TRUE);
-       default:
-               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-               break;
+               /* if that didn't help, advance the play order */
+               if (entry == NULL) {
+                       entry = rb_play_order_get_next (porder);
+                       if (entry != NULL) {
+                               rb_debug ("got new entry %s from play order",
+                                         rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
+                               rb_play_order_go_next (porder);
+                       }
+               }
+
+               if (entry != NULL)
+                       new_source = player->priv->source;
+               
+               g_object_unref (porder);
        }
-}
 
-static void
-rb_shell_player_get_property (GObject *object,
-                             guint prop_id,
-                             GValue *value,
-                             GParamSpec *pspec)
-{
-       RBShellPlayer *player = RB_SHELL_PLAYER (object);
+       /* if the new entry isn't from the play queue anyway, let the play queue
+        * override the regular play order.
+        */
+       if (player->priv->queue_play_order &&
+           new_source != RB_SOURCE (player->priv->queue_source)) {
+               RhythmDBEntry *queue_entry;
 
-       switch (prop_id) {
-       case PROP_SOURCE:
-               g_value_set_object (value, player->priv->selected_source);
-               break;
-       case PROP_UI_MANAGER:
-               g_value_set_object (value, player->priv->ui_manager);
-               break;
-       case PROP_DB:
-               g_value_set_object (value, player->priv->db);
-               break;
-       case PROP_ACTION_GROUP:
-               g_value_set_object (value, player->priv->actiongroup);
-               break;
-       case PROP_PLAY_ORDER:
-       {
-               char *play_order = g_settings_get_string (player->priv->settings,
-                                                         "play-order");
-               if (play_order == NULL)
-                       play_order = g_strdup ("linear");
-               g_value_take_string (value, play_order);
-               break;
-       }
-       case PROP_PLAYING:
-               if (player->priv->mmplayer != NULL)
-                       g_value_set_boolean (value, rb_player_playing (player->priv->mmplayer));
-               else
-                       g_value_set_boolean (value, FALSE);
-               break;
-       case PROP_VOLUME:
-               g_value_set_float (value, player->priv->volume);
-               break;
-       case PROP_HEADER:
-               g_value_set_object (value, player->priv->header_widget);
-               break;
-       case PROP_QUEUE_SOURCE:
-               g_value_set_object (value, player->priv->queue_source);
-               break;
-       case PROP_QUEUE_ONLY:
-               g_value_set_boolean (value, player->priv->queue_only);
-               break;
-       case PROP_PLAYING_FROM_QUEUE:
-               g_value_set_boolean (value, player->priv->current_playing_source == RB_SOURCE 
(player->priv->queue_source));
-               break;
-       case PROP_PLAYER:
-               g_value_set_object (value, player->priv->mmplayer);
-               break;
-       case PROP_MUTE:
-               g_value_set_boolean (value, player->priv->mute);
-               break;
-       case PROP_HAS_NEXT:
-               g_value_set_boolean (value, player->priv->has_next);
-               break;
-       case PROP_HAS_PREV:
-               g_value_set_boolean (value, player->priv->has_prev);
-               break;
-       default:
-               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-               break;
+               queue_entry = rb_play_order_get_next (player->priv->queue_play_order);
+               rb_play_order_go_next (player->priv->queue_play_order);
+               if (queue_entry != NULL) {
+                       rb_debug ("got new entry %s from queue play order",
+                                 rhythmdb_entry_get_string (queue_entry, RHYTHMDB_PROP_LOCATION));
+                       if (entry != NULL) {
+                               rhythmdb_entry_unref (entry);
+                       }
+                       entry = queue_entry;
+                       new_source = RB_SOURCE (player->priv->queue_source);
+               } else {
+                       rb_debug ("didn't get a new entry from queue play order");
+               }
        }
-}
 
-GQuark
-rb_shell_player_error_quark (void)
-{
-       static GQuark quark = 0;
-       if (!quark)
-               quark = g_quark_from_static_string ("rb_shell_player_error");
+       /* play the new entry */
+       if (entry != NULL) {
+               /* if the entry view containing the playing entry changed, update it */
+               if (new_source != player->priv->current_playing_source)
+                       swap_playing_source (player, new_source);
 
-       return quark;
-}
+               player->priv->jump_to_playing_entry = TRUE;
+               if (!rb_shell_player_set_playing_entry (player, entry, FALSE, from_eos, error))
+                       rv = FALSE;
+       } else {
+               g_set_error (error,
+                            RB_SHELL_PLAYER_ERROR,
+                            RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST,
+                            _("No next song"));
+               rv = FALSE;
 
-/**
- * rb_shell_player_set_selected_source:
- * @player: the #RBShellPlayer
- * @source: the #RBSource to select
- *
- * Updates the player to reflect a new source being selected.
- */
-void
-rb_shell_player_set_selected_source (RBShellPlayer *player,
-                                    RBSource *source)
-{
-       g_return_if_fail (RB_IS_SHELL_PLAYER (player));
-       g_return_if_fail (source == NULL || RB_IS_SOURCE (source));
+               if (allow_stop) {
+                       rb_debug ("No next entry, stopping playback");
+
+                       /* hmm, need to set playing entry on the playing source's
+                        * play order if it has one?
+                        */
 
-       g_object_set (player, "source", source, NULL);
+                       rb_shell_player_stop (player);
+                       rb_play_order_set_playing_entry (player->priv->play_order, NULL);
+               }
+       }
+
+       if (entry != NULL) {
+               rhythmdb_entry_unref (entry);
+       }
+
+       return rv;
 }
 
 /**
- * rb_shell_player_get_playing_source:
+ * rb_shell_player_do_next:
  * @player: the #RBShellPlayer
+ * @error: returns error information
  *
- * Retrieves the current playing source.  That is, the source from
- * which the current song was drawn.  This differs from 
- * #rb_shell_player_get_active_source when the current song came
- * from the play queue.
+ * Skips to the next song.  Consults the play queue and handles
+ * transitions between the play queue and the active source.
+ * Fails if there is no entry to play after the current one.
  *
- * Return value: (transfer none): the current playing #RBSource
+ * Return value: %TRUE if successful
  */
-RBSource *
-rb_shell_player_get_playing_source (RBShellPlayer *player)
+gboolean
+rb_shell_player_do_next (RBShellPlayer *player,
+                        GError **error)
 {
-       return player->priv->current_playing_source;
+       return rb_shell_player_do_next_internal (player, FALSE, TRUE, error);
 }
 
 /**
- * rb_shell_player_get_active_source:
+ * rb_shell_player_play_entry:
  * @player: the #RBShellPlayer
+ * @entry: the #RhythmDBEntry to play
+ * @source: the new #RBSource to set as playing (or NULL to use the
+ *   selected source)
  *
- * Retrieves the active source.  This is the source that the user
- * selected for playback.
- *
- * Return value: (transfer none): the active #RBSource
+ * Plays a specified entry.
  */
-RBSource *
-rb_shell_player_get_active_source (RBShellPlayer *player)
+void
+rb_shell_player_play_entry (RBShellPlayer *player,
+                           RhythmDBEntry *entry,
+                           RBSource *source)
 {
-       return player->priv->source;
-}
+       GError *error = NULL;
 
-/**
- * rb_shell_player_new:
- * @db: the #RhythmDB
- * @mgr: the #GtkUIManager
- * @actiongroup: the #GtkActionGroup to use
- *
- * Creates the #RBShellPlayer
- * 
- * Return value: the #RBShellPlayer instance
- */
-RBShellPlayer *
-rb_shell_player_new (RhythmDB *db,
-                    GtkUIManager *mgr,
-                    GtkActionGroup *actiongroup)
-{
-       return g_object_new (RB_TYPE_SHELL_PLAYER,
-                            "ui-manager", mgr,
-                            "action-group", actiongroup,
-                            "db", db,
-                            NULL);
+       if (source == NULL)
+               source = player->priv->selected_source;
+       rb_shell_player_set_playing_source (player, source);
+
+       player->priv->jump_to_playing_entry = FALSE;
+       if (!rb_shell_player_set_playing_entry (player, entry, TRUE, FALSE, &error)) {
+               rb_shell_player_error (player, FALSE, error);
+               g_clear_error (&error);
+       }
 }
 
+/* unused parameter can't be removed without breaking dbus interface compatibility */
 /**
- * rb_shell_player_get_playing_entry:
+ * rb_shell_player_playpause:
  * @player: the #RBShellPlayer
+ * @unused: nothing
+ * @error: returns error information
  *
- * Retrieves the currently playing #RhythmDBEntry, or NULL if
- * nothing is playing.  The caller must unref the entry
- * (using #rhythmdb_entry_unref) when it is no longer needed.
+ * Toggles between playing and paused state.  If there is no playing
+ * entry, chooses an entry from (in order of preference) the play queue,
+ * the selection in the current source, or the play order.
  *
- * Return value: (transfer full) (allow-none): the currently playing #RhythmDBEntry, or NULL
+ * Return value: %TRUE if successful
  */
-RhythmDBEntry *
-rb_shell_player_get_playing_entry (RBShellPlayer *player)
-{
-       RBPlayOrder *porder;
-       RhythmDBEntry *entry;
-
-       if (player->priv->current_playing_source == NULL) {
-               return NULL;
-       }
-
-       g_object_get (player->priv->current_playing_source, "play-order", &porder, NULL);
-       if (porder == NULL)
-               porder = g_object_ref (player->priv->play_order);
-
-       entry = rb_play_order_get_playing_entry (porder);
-       g_object_unref (porder);
-
-       return entry;
-}
-
-typedef struct {
-       RBShellPlayer *player;
-       char *location;
-       RhythmDBEntry *entry;
-       RBPlayerPlayType play_type;
-       GCancellable *cancellable;
-} OpenLocationThreadData;
-
-static void
-playlist_entry_cb (TotemPlParser *playlist,
-                  const char *uri,
-                  GHashTable *metadata,
-                  OpenLocationThreadData *data)
-{
-       if (g_cancellable_is_cancelled (data->cancellable)) {
-               rb_debug ("playlist parser cancelled");
-       } else {
-               rb_debug ("adding stream url %s (%p)", uri, playlist);
-               g_queue_push_tail (data->player->priv->playlist_urls, g_strdup (uri));
-       }
-}
-
-static gpointer
-open_location_thread (OpenLocationThreadData *data)
+gboolean
+rb_shell_player_playpause (RBShellPlayer *player,
+                          gboolean unused,
+                          GError **error)
 {
-       TotemPlParser *playlist;
-       TotemPlParserResult playlist_result;
-
-       playlist = totem_pl_parser_new ();
+       gboolean ret;
+       RBEntryView *songs;
 
-       g_signal_connect_data (playlist, "entry-parsed",
-                              G_CALLBACK (playlist_entry_cb),
-                              data, NULL, 0);
+       rb_debug ("doing playpause");
 
-       totem_pl_parser_add_ignored_mimetype (playlist, "x-directory/normal");
-       totem_pl_parser_add_ignored_mimetype (playlist, "inode/directory");
+       g_return_val_if_fail (RB_IS_SHELL_PLAYER (player), TRUE);
 
-       playlist_result = totem_pl_parser_parse (playlist, data->location, FALSE);
-       g_object_unref (playlist);
+       ret = TRUE;
 
-       if (g_cancellable_is_cancelled (data->cancellable)) {
-               playlist_result = TOTEM_PL_PARSER_RESULT_CANCELLED;
-       }
+       if (rb_player_playing (player->priv->mmplayer)) {
+               if (player->priv->source == NULL) {
+                       rb_debug ("playing source is already NULL");
+               } else if (rb_source_can_pause (player->priv->source)) {
+                       rb_debug ("pausing mm player");
+                       if (player->priv->parser_cancellable != NULL) {
+                               g_object_unref (player->priv->parser_cancellable);
+                               player->priv->parser_cancellable = NULL;
+                       }
+                       rb_player_pause (player->priv->mmplayer);
+                       songs = rb_source_get_entry_view (player->priv->current_playing_source);
+                       if (songs)
+                               rb_entry_view_set_state (songs, RB_ENTRY_VIEW_PAUSED);
 
-       switch (playlist_result) {
-       case TOTEM_PL_PARSER_RESULT_SUCCESS:
-               if (g_queue_is_empty (data->player->priv->playlist_urls)) {
-                       GError *error = g_error_new (RB_SHELL_PLAYER_ERROR,
-                                                    RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST,
-                                                    _("Playlist was empty"));
-                       GDK_THREADS_ENTER ();
-                       rb_shell_player_error (data->player, TRUE, error);
-                       g_error_free (error);
-                       GDK_THREADS_LEAVE ();
+                       /* might need a signal for when the player has actually paused here? */
+                       g_object_notify (G_OBJECT (player), "playing");
+                       /* mostly for that */
                } else {
-                       char *location;
-
-                       location = g_queue_pop_head (data->player->priv->playlist_urls);
-                       rb_debug ("playing first stream url %s", location);
-                       rb_shell_player_open_playlist_url (data->player, location, data->entry, 
data->play_type);
-                       g_free (location);
+                       rb_debug ("stopping playback");
+                       rb_shell_player_stop (player);
                }
-               break;
+       } else {
+               RhythmDBEntry *entry;
+               RBSource *new_source;
+               gboolean out_of_order = FALSE;
 
-       case TOTEM_PL_PARSER_RESULT_CANCELLED:
-               rb_debug ("playlist parser was cancelled");
-               break;
+               if (player->priv->source == NULL) {
+                       /* no current stream, pull one in from the currently
+                        * selected source */
+                       rb_debug ("no playing source, using selected source");
+                       rb_shell_player_set_playing_source (player, player->priv->selected_source);
+               }
+               new_source = player->priv->current_playing_source;
 
-       default:
-               /* if we can't parse it as a playlist, just try playing it */
-               rb_debug ("playlist parser failed, playing %s directly", data->location);
-               rb_shell_player_open_playlist_url (data->player, data->location, data->entry, 
data->play_type);
-               break;
-       }
+               entry = rb_shell_player_get_playing_entry (player);
+               if (entry == NULL) {
+                       /* queue takes precedence over selection */
+                       if (player->priv->queue_play_order) {
+                               entry = rb_play_order_get_next (player->priv->queue_play_order);
+                               if (entry != NULL) {
+                                       new_source = RB_SOURCE (player->priv->queue_source);
+                                       rb_play_order_go_next (player->priv->queue_play_order);
+                               }
+                       }
 
-       g_object_unref (data->cancellable);
-       g_free (data);
-       return NULL;
-}
+                       /* selection takes precedence over first item in play order */
+                       if (entry == NULL) {
+                               GList *selection = NULL;
 
-static gboolean
-rb_shell_player_open_location (RBShellPlayer *player,
-                              RhythmDBEntry *entry,
-                              RBPlayerPlayType play_type,
-                              GError **error)
-{
-       char *location;
-       gboolean ret = TRUE;
+                               songs = rb_source_get_entry_view (player->priv->source);
+                               if (songs)
+                                       selection = rb_entry_view_get_selected_entries (songs);
 
-       /* dispose of any existing playlist urls */
-       if (player->priv->playlist_urls) {
-               g_queue_foreach (player->priv->playlist_urls,
-                                (GFunc) g_free,
-                                NULL);
-               g_queue_free (player->priv->playlist_urls);
-               player->priv->playlist_urls = NULL;
-       }
-       if (rb_source_try_playlist (player->priv->source)) {
-               player->priv->playlist_urls = g_queue_new ();
-       }
+                               if (selection != NULL) {
+                                       rb_debug ("choosing first selected entry");
+                                       entry = (RhythmDBEntry*) selection->data;
+                                       if (entry)
+                                               out_of_order = TRUE;
 
-       location = rhythmdb_entry_get_playback_uri (entry);
-       if (location == NULL) {
-               return FALSE;
-       }
+                                       g_list_free (selection);
+                               }
+                       }
 
-       if (rb_source_try_playlist (player->priv->source)) {
-               OpenLocationThreadData *data;
+                       /* play order is last */
+                       if (entry == NULL) {
+                               RBPlayOrder *porder;
 
-               data = g_new0 (OpenLocationThreadData, 1);
-               data->player = player;
-               data->play_type = play_type;
-               data->entry = entry;
+                               rb_debug ("getting entry from play order");
+                               g_object_get (player->priv->source, "play-order", &porder, NULL);
+                               if (porder == NULL)
+                                       porder = g_object_ref (player->priv->play_order);
 
-               /* add http:// as a prefix, if it doesn't have a URI scheme */
-               if (strstr (location, "://"))
-                       data->location = g_strdup (location);
-               else
-                       data->location = g_strconcat ("http://";, location, NULL);
+                               entry = rb_play_order_get_next (porder);
+                               if (entry != NULL)
+                                       rb_play_order_go_next (porder);
+                               g_object_unref (porder);
+                       }
 
-               if (player->priv->parser_cancellable == NULL) {
-                       player->priv->parser_cancellable = g_cancellable_new ();
-               }
-               data->cancellable = g_object_ref (player->priv->parser_cancellable);
+                       if (entry != NULL) {
+                               /* if the entry view containing the playing entry changed, update it */
+                               if (new_source != player->priv->current_playing_source)
+                                       swap_playing_source (player, new_source);
 
-               g_thread_new ("open-location", (GThreadFunc)open_location_thread, data);
-       } else {
-               if (player->priv->parser_cancellable != NULL) {
-                       g_object_unref (player->priv->parser_cancellable);
-                       player->priv->parser_cancellable = NULL;
+                               player->priv->jump_to_playing_entry = TRUE;
+                               if (!rb_shell_player_set_playing_entry (player, entry, out_of_order, FALSE, 
error))
+                                       ret = FALSE;
+                       }
+               } else {
+                       if (!rb_shell_player_play (player, error)) {
+                               rb_shell_player_stop (player);
+                               ret = FALSE;
+                       }
                }
 
-               rhythmdb_entry_ref (entry);
-               ret = ret && rb_player_open (player->priv->mmplayer, location, entry, (GDestroyNotify) 
rhythmdb_entry_unref, error);
-
-               ret = ret && rb_player_play (player->priv->mmplayer, play_type, 
player->priv->track_transition_time, error);
+               if (entry != NULL) {
+                       rhythmdb_entry_unref (entry);
+               }
        }
 
-       g_free (location);
+       rb_shell_player_sync_with_source (player);
+       rb_shell_player_sync_buttons (player);
+
        return ret;
 }
 
-/**
- * rb_shell_player_play:
- * @player: a #RBShellPlayer
- * @error: error return
- *
- * Starts playback, if it is not already playing.
- *
- * Return value: whether playback is now occurring (TRUE when successfully started
- * or already playing).
- **/
-gboolean
-rb_shell_player_play (RBShellPlayer *player,
-                     GError **error)
+static void
+rb_shell_player_sync_control_state (RBShellPlayer *player)
 {
-       RBEntryView *songs;
-
-       if (player->priv->current_playing_source == NULL) {
-               rb_debug ("current playing source is NULL");
-               g_set_error (error,
-                            RB_SHELL_PLAYER_ERROR,
-                            RB_SHELL_PLAYER_ERROR_NOT_PLAYING,
-                            "Current playing source is NULL");
-               return FALSE;
-       }
-
-       if (rb_player_playing (player->priv->mmplayer))
-               return TRUE;
+       gboolean shuffle, repeat;
+       GAction *action;
+       rb_debug ("syncing control state");
 
-       if (player->priv->parser_cancellable != NULL) {
-               rb_debug ("currently parsing a playlist");
-               return TRUE;
-       }
+       if (!rb_shell_player_get_playback_state (player, &shuffle,
+                                                &repeat))
+               return;
 
-       /* we're obviously not playing anything, so crossfading is irrelevant */
-       if (!rb_player_play (player->priv->mmplayer, RB_PLAYER_PLAY_REPLACE, 0.0f, error)) {
-               rb_debug ("player doesn't want to");
-               return FALSE;
-       }
 
-       songs = rb_source_get_entry_view (player->priv->current_playing_source);
-       if (songs)
-               rb_entry_view_set_state (songs, RB_ENTRY_VIEW_PLAYING);
+       action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()),
+                                            "play-shuffle");
+       g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (shuffle));
 
-       return TRUE;
+       action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()),
+                                            "play-repeat");
+       g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (repeat));
 }
 
 static void
-rb_shell_player_set_entry_playback_error (RBShellPlayer *player,
-                                         RhythmDBEntry *entry,
-                                         char *message)
+sync_volume_cb (GSettings *settings, RBShellPlayer *player)
 {
-       GValue value = { 0, };
-
-       g_return_if_fail (RB_IS_SHELL_PLAYER (player));
-
-       g_value_init (&value, G_TYPE_STRING);
-       g_value_set_string (&value, message);
-       rhythmdb_entry_set (player->priv->db,
-                           entry,
-                           RHYTHMDB_PROP_PLAYBACK_ERROR,
-                           &value);
-       g_value_unset (&value);
-       rhythmdb_commit (player->priv->db);
+       g_settings_set_double (player->priv->settings, "volume", player->priv->volume);
 }
 
-static gboolean
-rb_shell_player_set_playing_entry (RBShellPlayer *player,
-                                  RhythmDBEntry *entry,
-                                  gboolean out_of_order,
-                                  gboolean wait_for_eos,
-                                  GError **error)
+static void
+rb_shell_player_sync_volume (RBShellPlayer *player,
+                            gboolean notify,
+                            gboolean set_volume)
 {
-       GError *tmp_error = NULL;
-       GValue val = {0,};
-       RBPlayerPlayType play_type;
-
-       g_return_val_if_fail (player->priv->current_playing_source != NULL, TRUE);
-       g_return_val_if_fail (entry != NULL, TRUE);
-
-       play_type = wait_for_eos ? RB_PLAYER_PLAY_AFTER_EOS : RB_PLAYER_PLAY_REPLACE;
-
-       if (out_of_order) {
-               RBPlayOrder *porder;
+       RhythmDBEntry *entry;
 
-               g_object_get (player->priv->current_playing_source, "play-order", &porder, NULL);
-               if (porder == NULL)
-                       porder = g_object_ref (player->priv->play_order);
-               rb_play_order_set_playing_entry (porder, entry);
-               g_object_unref (porder);
+       if (player->priv->volume <= 0.0){
+               player->priv->volume = 0.0;
+       } else if (player->priv->volume >= 1.0){
+               player->priv->volume = 1.0;
        }
 
-       if (player->priv->playing_entry != NULL &&
-           player->priv->track_transition_time > 0) {
-               const char *previous_album;
-               const char *album;
+       if (set_volume) {
+               rb_player_set_volume (player->priv->mmplayer,
+                                     player->priv->mute ? 0.0 : player->priv->volume);
+       }
 
-               previous_album = rhythmdb_entry_get_string (player->priv->playing_entry, RHYTHMDB_PROP_ALBUM);
-               album = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM);
-               /* only crossfade if we're not going from the end of one song on an
-                * album to the start of another.  "Unknown" doesn't count as an album.
-                */
-               if (wait_for_eos == FALSE ||
-                   strcmp (album, _("Unknown")) == 0 ||
-                   strcmp (album, previous_album) != 0) {
-                       play_type = RB_PLAYER_PLAY_CROSSFADE;
-               }
+       if (player->priv->syncing_state == FALSE) {
+               rb_settings_delayed_sync (player->priv->settings,
+                                         (RBDelayedSyncFunc) sync_volume_cb,
+                                         g_object_ref (player),
+                                         g_object_unref);
        }
 
-       if (rb_shell_player_open_location (player, entry, play_type, &tmp_error) == FALSE) {
-               goto lose;
+       entry = rb_shell_player_get_playing_entry (player);
+       if (entry != NULL) {
+               rhythmdb_entry_unref (entry);
        }
 
-       rb_debug ("Success!");
-       /* clear error on successful playback */
-       g_value_init (&val, G_TYPE_STRING);
-       g_value_set_string (&val, NULL);
-       rhythmdb_entry_set (player->priv->db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &val);
-       rhythmdb_commit (player->priv->db);
-       g_value_unset (&val);
+       if (notify)
+               g_object_notify (G_OBJECT (player), "volume");
+}
 
+/**
+ * rb_shell_player_set_volume:
+ * @player: the #RBShellPlayer
+ * @volume: the volume level (between 0 and 1)
+ * @error: returns the error information
+ *
+ * Sets the playback volume level.
+ *
+ * Return value: %TRUE on success
+ */
+gboolean
+rb_shell_player_set_volume (RBShellPlayer *player,
+                           gdouble volume,
+                           GError **error)
+{
+       player->priv->volume = volume;
+       rb_shell_player_sync_volume (player, TRUE, TRUE);
        return TRUE;
- lose:
-       /* Ignore errors, shutdown the player */
-       rb_player_close (player->priv->mmplayer, NULL /* XXX specify uri? */, NULL);
-
-       if (tmp_error == NULL) {
-               tmp_error = g_error_new (RB_SHELL_PLAYER_ERROR,
-                                        RB_SHELL_PLAYER_ERROR_NOT_PLAYING,
-                                        "Problem occurred without error being set. "
-                                        "This is a bug in Rhythmbox or GStreamer.");
-       }
-       /* Mark this song as failed */
-       rb_shell_player_set_entry_playback_error (player, entry, tmp_error->message);
-       g_propagate_error (error, tmp_error);
+}
 
-       rb_shell_player_sync_with_source (player);
-       rb_shell_player_sync_buttons (player);
-       g_object_notify (G_OBJECT (player), "playing");
+/**
+ * rb_shell_player_set_volume_relative:
+ * @player: the #RBShellPlayer
+ * @delta: difference to apply to the volume level (between -1 and 1)
+ * @error: returns error information
+ *
+ * Adds the specified value to the current volume level.
+ *
+ * Return value: %TRUE on success
+ */
+gboolean
+rb_shell_player_set_volume_relative (RBShellPlayer *player,
+                                    gdouble delta,
+                                    GError **error)
+{
+       /* rb_shell_player_sync_volume does clipping */
+       player->priv->volume += delta;
+       rb_shell_player_sync_volume (player, TRUE, TRUE);
+       return TRUE;
+}
 
-       return FALSE;
+/**
+ * rb_shell_player_get_volume:
+ * @player: the #RBShellPlayer
+ * @volume: (out): returns the volume level
+ * @error: returns error information
+ *
+ * Returns the current volume level
+ *
+ * Return value: the current volume level.
+ */
+gboolean
+rb_shell_player_get_volume (RBShellPlayer *player,
+                           gdouble *volume,
+                           GError **error)
+{
+       *volume = player->priv->volume;
+       return TRUE;
 }
 
 static void
-player_settings_changed_cb (GSettings *settings, const char *key, RBShellPlayer *player)
+rb_shell_player_volume_changed_cb (RBPlayer *player,
+                                  float volume,
+                                  RBShellPlayer *shell_player)
 {
-       if (g_strcmp0 (key, "play-order") == 0) {
-               rb_debug ("play order setting changed");
-               player->priv->syncing_state = TRUE;
-               rb_shell_player_sync_play_order (player);
-               rb_shell_player_sync_buttons (player);
-               rb_shell_player_sync_control_state (player);
-               g_object_notify (G_OBJECT (player), "play-order");
-               player->priv->syncing_state = FALSE;
-       } else if (g_strcmp0 (key, "transition-time") == 0) {
-               double newtime;
-               rb_debug ("track transition time changed");
-               newtime = g_settings_get_double (player->priv->settings, "transition-time");
-               player->priv->track_transition_time = newtime * RB_PLAYER_SECOND;
-       }
+       shell_player->priv->volume = volume;
+       rb_shell_player_sync_volume (shell_player, TRUE, FALSE);
 }
 
 /**
- * rb_shell_player_get_playback_state:
+ * rb_shell_player_set_mute
  * @player: the #RBShellPlayer
- * @shuffle: (out): returns the current shuffle setting
- * @repeat: (out): returns the current repeat setting
+ * @mute: %TRUE to mute playback
+ * @error: returns error information
  *
- * Retrieves the current state of the shuffle and repeat settings.
+ * Updates the mute setting on the player.
  *
- * Return value: %TRUE if successful.
+ * Return value: %TRUE if successful
  */
 gboolean
-rb_shell_player_get_playback_state (RBShellPlayer *player,
-                                   gboolean *shuffle,
-                                   gboolean *repeat)
+rb_shell_player_set_mute (RBShellPlayer *player,
+                         gboolean mute,
+                         GError **error)
 {
-       int i, j;
-       char *play_order;
-
-       play_order = g_settings_get_string (player->priv->settings, "play-order");
-       for (i = 0; i < G_N_ELEMENTS(state_to_play_order); i++)
-               for (j = 0; j < G_N_ELEMENTS(state_to_play_order[0]); j++)
-                       if (!strcmp (play_order, state_to_play_order[i][j]))
-                               goto found;
-
-       g_free (play_order);
-       return FALSE;
-
-found:
-       if (shuffle != NULL) {
-               *shuffle = i > 0;
-       }
-       if (repeat != NULL) {
-               *repeat = j > 0;
-       }
-       g_free (play_order);
+       player->priv->mute = mute;
+       rb_shell_player_sync_volume (player, FALSE, TRUE);
        return TRUE;
 }
 
 /**
- * rb_shell_player_set_playback_state:
+ * rb_shell_player_get_mute:
  * @player: the #RBShellPlayer
- * @shuffle: whether to enable the shuffle setting
- * @repeat: whether to enable the repeat setting
+ * @mute: (out): returns the current mute setting
+ * @error: returns error information
  *
- * Sets the state of the shuffle and repeat settings.
+ * Returns %TRUE if currently muted
+ *
+ * Return value: %TRUE if currently muted
  */
-void
-rb_shell_player_set_playback_state (RBShellPlayer *player,
-                                   gboolean shuffle,
-                                   gboolean repeat)
+gboolean
+rb_shell_player_get_mute (RBShellPlayer *player,
+                         gboolean *mute,
+                         GError **error)
 {
-       const char *neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
-       g_settings_set_string (player->priv->settings, "play-order", neworder);
+       *mute = player->priv->mute;
+       return TRUE;
 }
 
 static void
-rb_shell_player_sync_play_order (RBShellPlayer *player)
+rb_shell_player_entry_activated_cb (RBEntryView *view,
+                                   RhythmDBEntry *entry,
+                                   RBShellPlayer *player)
 {
-       char *new_play_order;
-       RhythmDBEntry *playing_entry = NULL;
-       RBSource *source;
+       gboolean was_from_queue = FALSE;
+       RhythmDBEntry *prev_entry = NULL;
+       GError *error = NULL;
+       gboolean source_set = FALSE;
+       gboolean jump_to_entry = FALSE;
+       char *playback_uri;
 
-       new_play_order = g_settings_get_string (player->priv->settings, "play-order");
-       if (player->priv->play_order != NULL) {
-               playing_entry = rb_play_order_get_playing_entry (player->priv->play_order);
-               g_signal_handlers_disconnect_by_func (player->priv->play_order,
-                                                     G_CALLBACK (rb_shell_player_play_order_update_cb),
-                                                     player);
-               g_object_unref (player->priv->play_order);
-       }
+       g_return_if_fail (entry != NULL);
 
-       player->priv->play_order = rb_play_order_new (player, new_play_order);
+       rb_debug  ("got entry %p activated", entry);
 
-       g_signal_connect_object (player->priv->play_order,
-                                "have_next_previous_changed",
-                                G_CALLBACK (rb_shell_player_play_order_update_cb),
-                                player, 0);
-       rb_shell_player_play_order_update_cb (player->priv->play_order,
-                                             FALSE, FALSE,
-                                             player);
+       /* don't play hidden entries */
+       if (rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN))
+               return;
 
-       source = player->priv->current_playing_source;
-       if (source == NULL) {
-               source = player->priv->selected_source;
-       }
-       rb_play_order_playing_source_changed (player->priv->play_order, source);
+       /* skip entries with no playback uri */
+       playback_uri = rhythmdb_entry_get_playback_uri (entry);
+       if (playback_uri == NULL)
+               return;
 
-       if (playing_entry != NULL) {
-               rb_play_order_set_playing_entry (player->priv->play_order, playing_entry);
-               rhythmdb_entry_unref (playing_entry);
-       }
+       g_free (playback_uri);
 
-       g_free (new_play_order);
-}
+       /* figure out where the previous entry came from */
+       if ((player->priv->queue_source != NULL) &&
+           (player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source))) {
+               prev_entry = rb_shell_player_get_playing_entry (player);
+               was_from_queue = TRUE;
+       }
 
-static void
-rb_shell_player_play_order_update_cb (RBPlayOrder *porder,
-                                     gboolean _has_next,
-                                     gboolean _has_previous,
-                                     RBShellPlayer *player)
-{
-       /* we cannot depend on the values of has_next, has_previous or porder
-        * since this can be called for the main porder, queue porder, etc
-        */
-       gboolean has_next = FALSE;
-       gboolean has_prev = FALSE;
-       RhythmDBEntry *entry;
+       if (player->priv->queue_source) {
+               RBEntryView *queue_sidebar;
 
-       entry = rb_shell_player_get_playing_entry (player);
-       if (entry != NULL) {
-               has_next = TRUE;
-               has_prev = TRUE;
-               rhythmdb_entry_unref (entry);
-       } else {
-               if (player->priv->current_playing_source &&
-                   (rb_source_handle_eos (player->priv->current_playing_source) == RB_SOURCE_EOF_NEXT)) {
-                       RBPlayOrder *porder;
-                       g_object_get (player->priv->current_playing_source, "play-order", &porder, NULL);
-                       if (porder == NULL)
-                               porder = g_object_ref (player->priv->play_order);
-                       has_next = rb_play_order_has_next (porder);
-                       g_object_unref (porder);
-               }
-               if (player->priv->queue_play_order) {
-                       has_next |= rb_play_order_has_next (player->priv->queue_play_order);
-               }
-               has_prev = (player->priv->current_playing_source != NULL);
-       }
+               g_object_get (player->priv->queue_source, "sidebar", &queue_sidebar, NULL);
 
-       if (has_prev != player->priv->has_prev) {
-               player->priv->has_prev = has_prev;
-               g_object_notify (G_OBJECT (player), "has-prev");
-       }
-       if (has_next != player->priv->has_next) {
-               player->priv->has_next = has_next;
-               g_object_notify (G_OBJECT (player), "has-next");
-       }
-}
+               if (view == queue_sidebar || view == rb_source_get_entry_view (RB_SOURCE 
(player->priv->queue_source))) {
 
-/**
- * rb_shell_player_jump_to_current:
- * @player: the #RBShellPlayer
- *
- * Scrolls the #RBEntryView for the current playing source so that
- * the current playing entry is visible and selects the row for the
- * entry.  If there is no current playing entry, the selection is
- * cleared instead.
- */
-void
-rb_shell_player_jump_to_current (RBShellPlayer *player)
-{
-       RBSource *source;
-       RhythmDBEntry *entry;
-       RBEntryView *songs;
+                       /* fall back to the current selected source once the queue is empty */
+                       if (view == queue_sidebar && player->priv->source == NULL) {
+                               /* XXX only do this if the selected source doesn't have its own play order? */
+                               rb_play_order_playing_source_changed (player->priv->play_order,
+                                                                     player->priv->selected_source);
+                               player->priv->source = player->priv->selected_source;
+                       }
 
-       source = player->priv->current_playing_source ? player->priv->current_playing_source :
-               player->priv->selected_source;
+                       rb_shell_player_set_playing_source (player, RB_SOURCE (player->priv->queue_source));
 
-       songs = rb_source_get_entry_view (source);
-       entry = rb_shell_player_get_playing_entry (player);
-       if (songs != NULL) {
-               if (entry != NULL) {
-                       rb_entry_view_scroll_to_entry (songs, entry);
-                       rb_entry_view_select_entry (songs, entry);
+                       was_from_queue = FALSE;
+                       source_set = TRUE;
+                       jump_to_entry = TRUE;
                } else {
-                       rb_entry_view_select_none (songs);
+                       if (player->priv->queue_only) {
+                               rb_source_add_to_queue (player->priv->selected_source,
+                                                       RB_SOURCE (player->priv->queue_source));
+                               rb_shell_player_set_playing_source (player, RB_SOURCE 
(player->priv->queue_source));
+                               source_set = TRUE;
+                       }
                }
+
+               g_object_unref (queue_sidebar);
        }
 
-       if (entry != NULL) {
-               rhythmdb_entry_unref (entry);
+       /* bail out if queue only */
+       if (player->priv->queue_only) {
+               return;
        }
-}
 
-static void
-swap_playing_source (RBShellPlayer *player,
-                    RBSource *new_source)
-{
-       if (player->priv->current_playing_source != NULL) {
-               RBEntryView *old_songs = rb_source_get_entry_view (player->priv->current_playing_source);
-               if (old_songs)
-                       rb_entry_view_set_state (old_songs, RB_ENTRY_VIEW_NOT_PLAYING);
+       if (!source_set) {
+               rb_shell_player_set_playing_source (player, player->priv->selected_source);
+               source_set = TRUE;
+       }
+
+       player->priv->jump_to_playing_entry = jump_to_entry;
+       if (!rb_shell_player_set_playing_entry (player, entry, TRUE, FALSE, &error)) {
+               rb_shell_player_error (player, FALSE, error);
+               g_clear_error (&error);
+       }
+
+       /* if we were previously playing from the queue, clear its playing entry,
+        * so we'll start again from the start.
+        */
+       if (was_from_queue && prev_entry != NULL) {
+               rb_play_order_set_playing_entry (player->priv->queue_play_order, NULL);
        }
-       if (new_source != NULL) {
-               RBEntryView *new_songs = rb_source_get_entry_view (new_source);
 
-               if (new_songs) {
-                       rb_entry_view_set_state (new_songs, RB_ENTRY_VIEW_PLAYING);
-                       rb_shell_player_set_playing_source (player, new_source);
-               }
+       if (prev_entry != NULL) {
+               rhythmdb_entry_unref (prev_entry);
        }
 }
 
-/**
- * rb_shell_player_do_previous:
- * @player: the #RBShellPlayer
- * @error: returns any error information
- *
- * If the current song has been playing for more than 3 seconds,
- * restarts it, otherwise, goes back to the previous song.
- * Fails if there is no current song, or if inside the first
- * 3 seconds of the first song in the play order.
- *
- * Return value: %TRUE if successful
- */
-gboolean
-rb_shell_player_do_previous (RBShellPlayer *player,
-                            GError **error)
+static void
+rb_shell_player_property_row_activated_cb (RBPropertyView *view,
+                                          const char *name,
+                                          RBShellPlayer *player)
 {
+       RBPlayOrder *porder;
        RhythmDBEntry *entry = NULL;
-       RBSource *new_source;
-
-       if (player->priv->current_playing_source == NULL) {
-               g_set_error (error,
-                            RB_SHELL_PLAYER_ERROR,
-                            RB_SHELL_PLAYER_ERROR_NOT_PLAYING,
-                            _("Not currently playing"));
-               return FALSE;
-       }
-
-       /* If we're in the first 3 seconds go to the previous song,
-        * else restart the current one.
-        */
-       if (player->priv->current_playing_source != NULL
-           && rb_source_can_pause (player->priv->source)
-           && rb_player_get_time (player->priv->mmplayer) > (G_GINT64_CONSTANT (3) * RB_PLAYER_SECOND)) {
-               rb_debug ("after 3 second previous, restarting song");
-               rb_player_set_time (player->priv->mmplayer, 0);
-               rb_shell_player_sync_with_source (player);
-               return TRUE;
-       }
-
-       rb_debug ("going to previous");
+       GError *error = NULL;
 
-       /* hrm, does this actually do anything at all? */
-       if (player->priv->queue_play_order) {
-               entry = rb_play_order_get_previous (player->priv->queue_play_order);
-               if (entry != NULL) {
-                       new_source = RB_SOURCE (player->priv->queue_source);
-                       rb_play_order_go_previous (player->priv->queue_play_order);
-               }
-       }
+       rb_debug ("got property activated");
 
-       if (entry == NULL) {
-               RBPlayOrder *porder;
+       rb_shell_player_set_playing_source (player, player->priv->selected_source);
 
-               new_source = player->priv->source;
-               g_object_get (new_source, "play-order", &porder, NULL);
-               if (porder == NULL)
-                       porder = g_object_ref (player->priv->play_order);
+       /* RHYTHMDBFIXME - do we need to wait here until the query is finished?
+        * in theory, yes, but in practice the query is started when the row is
+        * selected (on the first click when doubleclicking, or when using the
+        * keyboard to select then activate) and is pretty much always done by
+        * the time we get in here.
+        */
 
-               entry = rb_play_order_get_previous (porder);
-               if (entry)
-                       rb_play_order_go_previous (porder);
-               g_object_unref (porder);
-       }
+       g_object_get (player->priv->selected_source, "play-order", &porder, NULL);
+       if (porder == NULL)
+               porder = g_object_ref (player->priv->play_order);
 
+       entry = rb_play_order_get_next (porder);
        if (entry != NULL) {
-               rb_debug ("previous song found, doing previous");
-               if (new_source != player->priv->current_playing_source)
-                       swap_playing_source (player, new_source);
+               rb_play_order_go_next (porder);
 
-               player->priv->jump_to_playing_entry = TRUE;
-               if (!rb_shell_player_set_playing_entry (player, entry, FALSE, FALSE, error)) {
-                       rhythmdb_entry_unref (entry);
-                       return FALSE;
+               player->priv->jump_to_playing_entry = TRUE;     /* ? */
+               if (!rb_shell_player_set_playing_entry (player, entry, TRUE, FALSE, &error)) {
+                       rb_shell_player_error (player, FALSE, error);
+                       g_clear_error (&error);
                }
-
-               rhythmdb_entry_unref (entry);
-       } else {
-               rb_debug ("no previous song found, signalling error");
-               g_set_error (error,
-                            RB_SHELL_PLAYER_ERROR,
-                            RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST,
-                            _("No previous song"));
-               rb_shell_player_stop (player);
-               return FALSE;
        }
 
-       return TRUE;
+       rhythmdb_entry_unref (entry);
+       g_object_unref (porder);
 }
 
-static gboolean
-rb_shell_player_do_next_internal (RBShellPlayer *player, gboolean from_eos, gboolean allow_stop, GError 
**error)
+static void
+rb_shell_player_entry_changed_cb (RhythmDB *db,
+                                 RhythmDBEntry *entry,
+                                 GArray *changes,
+                                 RBShellPlayer *player)
 {
-       RBSource *new_source = NULL;
-       RhythmDBEntry *entry = NULL;
-       gboolean rv = TRUE;
+       gboolean synced = FALSE;
+       const char *location;
+       RhythmDBEntry *playing_entry;
+       int i;
 
-       if (player->priv->source == NULL)
-               return TRUE;
+       playing_entry = rb_shell_player_get_playing_entry (player);
+
+       /* We try to update only if the changed entry is currently playing */
+       if (entry != playing_entry) {
+               if (playing_entry != NULL) {
+                       rhythmdb_entry_unref (playing_entry);
+               }
+               return;
+       }
 
+       location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
+       for (i = 0; i < changes->len; i++) {
+               GValue *v = &g_array_index (changes, GValue, i);
+               RhythmDBEntryChange *change = g_value_get_boxed (v);
 
-       /* try the current playing source's play order, if it has one */
-       if (player->priv->current_playing_source != NULL) {
-               RBPlayOrder *porder;
-               g_object_get (player->priv->current_playing_source, "play-order", &porder, NULL);
-               if (porder != NULL) {
-                       entry = rb_play_order_get_next (porder);
-                       if (entry != NULL) {
-                               rb_play_order_go_next (porder);
-                               new_source = player->priv->current_playing_source;
+               /* update UI if the artist, title or album has changed */
+               switch (change->prop) {
+               case RHYTHMDB_PROP_TITLE:
+               case RHYTHMDB_PROP_ARTIST:
+               case RHYTHMDB_PROP_ALBUM:
+                       if (!synced) {
+                               rb_shell_player_sync_with_source (player);
+                               synced = TRUE;
                        }
-                       g_object_unref (porder);
+                       break;
+               default:
+                       break;
+               }
+
+               /* emit dbus signals for changes with easily marshallable types */
+               switch (rhythmdb_get_property_type (db, change->prop)) {
+               case G_TYPE_STRING:
+               case G_TYPE_BOOLEAN:
+               case G_TYPE_ULONG:
+               case G_TYPE_UINT64:
+               case G_TYPE_DOUBLE:
+                       g_signal_emit (G_OBJECT (player),
+                                      rb_shell_player_signals[PLAYING_SONG_PROPERTY_CHANGED], 0,
+                                      location,
+                                      rhythmdb_nice_elt_name_from_propid (db, change->prop),
+                                      &change->old,
+                                      &change->new);
+                       break;
+               default:
+                       break;
                }
        }
 
-       /* if that's different to the playing source that the user selected
-        * (ie we're playing from the queue), try that too
-        */
-       if (entry == NULL) {
-               RBPlayOrder *porder;
-               g_object_get (player->priv->source, "play-order", &porder, NULL);
-               if (porder == NULL)
-                       porder = g_object_ref (player->priv->play_order);
+       if (playing_entry != NULL) {
+               rhythmdb_entry_unref (playing_entry);
+       }
+}
 
-               /*
-                * If we interrupted this source to play from something else,
-                * we should go back to whatever it wanted to play before.
-                */
-               if (player->priv->source != player->priv->current_playing_source)
-                       entry = rb_play_order_get_playing_entry (porder);
+static void
+rb_shell_player_extra_metadata_cb (RhythmDB *db,
+                                  RhythmDBEntry *entry,
+                                  const char *field,
+                                  GValue *metadata,
+                                  RBShellPlayer *player)
+{
 
-               /* if that didn't help, advance the play order */
-               if (entry == NULL) {
-                       entry = rb_play_order_get_next (porder);
-                       if (entry != NULL) {
-                               rb_debug ("got new entry %s from play order",
-                                         rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
-                               rb_play_order_go_next (porder);
-                       }
-               }
+       RhythmDBEntry *playing_entry;
 
-               if (entry != NULL)
-                       new_source = player->priv->source;
-               
-               g_object_unref (porder);
+       playing_entry = rb_shell_player_get_playing_entry (player);
+       if (entry != playing_entry) {
+               if (playing_entry != NULL) {
+                       rhythmdb_entry_unref (playing_entry);
+               }
+               return;
        }
 
-       /* if the new entry isn't from the play queue anyway, let the play queue
-        * override the regular play order.
-        */
-       if (player->priv->queue_play_order &&
-           new_source != RB_SOURCE (player->priv->queue_source)) {
-               RhythmDBEntry *queue_entry;
+       rb_shell_player_sync_with_source (player);
 
-               queue_entry = rb_play_order_get_next (player->priv->queue_play_order);
-               rb_play_order_go_next (player->priv->queue_play_order);
-               if (queue_entry != NULL) {
-                       rb_debug ("got new entry %s from queue play order",
-                                 rhythmdb_entry_get_string (queue_entry, RHYTHMDB_PROP_LOCATION));
-                       if (entry != NULL) {
-                               rhythmdb_entry_unref (entry);
-                       }
-                       entry = queue_entry;
-                       new_source = RB_SOURCE (player->priv->queue_source);
-               } else {
-                       rb_debug ("didn't get a new entry from queue play order");
+       /* emit dbus signals for changes with easily marshallable types */
+       switch (G_VALUE_TYPE (metadata)) {
+       case G_TYPE_STRING:
+               /* make sure it's valid utf8, otherwise dbus barfs */
+               if (g_utf8_validate (g_value_get_string (metadata), -1, NULL) == FALSE) {
+                       rb_debug ("not emitting extra metadata field %s as value is not valid utf8", field);
+                       return;
                }
+       case G_TYPE_BOOLEAN:
+       case G_TYPE_ULONG:
+       case G_TYPE_UINT64:
+       case G_TYPE_DOUBLE:
+               g_signal_emit (G_OBJECT (player),
+                              rb_shell_player_signals[PLAYING_SONG_PROPERTY_CHANGED], 0,
+                              rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
+                              field,
+                              metadata,                /* slightly silly */
+                              metadata);
+               break;
+       default:
+               break;
        }
+}
+
+
+static void
+rb_shell_player_sync_with_source (RBShellPlayer *player)
+{
+       const char *entry_title = NULL;
+       const char *artist = NULL;
+       const char *stream_name = NULL;
+       char *streaming_title = NULL;
+       char *streaming_artist = NULL;
+       RhythmDBEntry *entry;
+       char *title = NULL;
+       gint64 elapsed;
+
+       entry = rb_shell_player_get_playing_entry (player);
+       rb_debug ("playing source: %p, active entry: %p", player->priv->current_playing_source, entry);
 
-       /* play the new entry */
        if (entry != NULL) {
-               /* if the entry view containing the playing entry changed, update it */
-               if (new_source != player->priv->current_playing_source)
-                       swap_playing_source (player, new_source);
+               GValue *value;
 
-               player->priv->jump_to_playing_entry = TRUE;
-               if (!rb_shell_player_set_playing_entry (player, entry, FALSE, from_eos, error))
-                       rv = FALSE;
-       } else {
-               g_set_error (error,
-                            RB_SHELL_PLAYER_ERROR,
-                            RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST,
-                            _("No next song"));
-               rv = FALSE;
+               entry_title = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE);
+               artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST);
 
-               if (allow_stop) {
-                       rb_debug ("No next entry, stopping playback");
+               value = rhythmdb_entry_request_extra_metadata (player->priv->db,
+                                                              entry,
+                                                              RHYTHMDB_PROP_STREAM_SONG_TITLE);
+               if (value != NULL) {
+                       streaming_title = g_value_dup_string (value);
+                       g_value_unset (value);
+                       g_free (value);
 
-                       /* hmm, need to set playing entry on the playing source's
-                        * play order if it has one?
-                        */
+                       rb_debug ("got streaming title \"%s\"", streaming_title);
+                       /* use entry title for stream name */
+                       stream_name = entry_title;
+                       entry_title = streaming_title;
+               }
 
-                       rb_shell_player_stop (player);
-                       rb_play_order_set_playing_entry (player->priv->play_order, NULL);
+               value = rhythmdb_entry_request_extra_metadata (player->priv->db,
+                                                              entry,
+                                                              RHYTHMDB_PROP_STREAM_SONG_ARTIST);
+               if (value != NULL) {
+                       streaming_artist = g_value_dup_string (value);
+                       g_value_unset (value);
+                       g_free (value);
+
+                       rb_debug ("got streaming artist \"%s\"", streaming_artist);
+                       /* override artist from entry */
+                       artist = streaming_artist;
                }
-       }
 
-       if (entry != NULL) {
                rhythmdb_entry_unref (entry);
        }
 
-       return rv;
-}
+       if ((artist && artist[0] != '\0') || entry_title || stream_name) {
 
-/**
- * rb_shell_player_do_next:
- * @player: the #RBShellPlayer
- * @error: returns error information
- *
- * Skips to the next song.  Consults the play queue and handles
- * transitions between the play queue and the active source.
- * Fails if there is no entry to play after the current one.
- *
- * Return value: %TRUE if successful
- */
-gboolean
-rb_shell_player_do_next (RBShellPlayer *player,
-                        GError **error)
-{
-       return rb_shell_player_do_next_internal (player, FALSE, TRUE, error);
-}
+               GString *title_str = g_string_sized_new (100);
+               if (artist && artist[0] != '\0') {
+                       g_string_append (title_str, artist);
+                       g_string_append (title_str, " - ");
+               }
+               if (entry_title != NULL)
+                       g_string_append (title_str, entry_title);
 
-static void
-rb_shell_player_cmd_previous (GtkAction *action,
-                             RBShellPlayer *player)
-{
-       GError *error = NULL;
+               if (stream_name != NULL)
+                       g_string_append_printf (title_str, " (%s)", stream_name);
 
-       if (!rb_shell_player_do_previous (player, &error)) {
-               if (error->domain != RB_SHELL_PLAYER_ERROR ||
-                   error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) {
-                       g_warning ("cmd_previous: Unhandled error: %s", error->message);
-               } else if (error->code == RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) {
-                       rb_shell_player_stop (player);
-               }
+               title = g_string_free (title_str, FALSE);
        }
+
+       elapsed = rb_player_get_time (player->priv->mmplayer);
+       if (elapsed < 0)
+               elapsed = 0;
+       player->priv->elapsed = elapsed / RB_PLAYER_SECOND;
+
+       g_signal_emit (G_OBJECT (player), rb_shell_player_signals[WINDOW_TITLE_CHANGED], 0,
+                      title);
+       g_free (title);
+
+       g_signal_emit (G_OBJECT (player), rb_shell_player_signals[ELAPSED_CHANGED], 0,
+                      player->priv->elapsed);
+
+       g_free (streaming_artist);
+       g_free (streaming_title);
 }
 
 static void
-rb_shell_player_cmd_next (GtkAction *action,
-                         RBShellPlayer *player)
+rb_shell_player_sync_buttons (RBShellPlayer *player)
 {
-       GError *error = NULL;
+       GActionMap *map;
+       GAction *action;
+       RBSource *source;
+       RBEntryView *view;
+       int entry_view_state;
+       RhythmDBEntry *entry;
 
-       if (!rb_shell_player_do_next (player, &error)) {
-               if (error->domain != RB_SHELL_PLAYER_ERROR ||
-                   error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) {
-                       g_warning ("cmd_next: Unhandled error: %s", error->message);
-               } else if (error->code == RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) {
-                       rb_shell_player_stop (player);
-               }
+       entry = rb_shell_player_get_playing_entry (player);
+       if (entry != NULL) {
+               source = player->priv->current_playing_source;
+               entry_view_state = rb_player_playing (player->priv->mmplayer) ?
+                       RB_ENTRY_VIEW_PLAYING : RB_ENTRY_VIEW_PAUSED;
+       } else {
+               source = player->priv->selected_source;
+               entry_view_state = RB_ENTRY_VIEW_NOT_PLAYING;
        }
-}
 
-/**
- * rb_shell_player_play_entry:
- * @player: the #RBShellPlayer
- * @entry: the #RhythmDBEntry to play
- * @source: the new #RBSource to set as playing (or NULL to use the
- *   selected source)
- *
- * Plays a specified entry.
- */
-void
-rb_shell_player_play_entry (RBShellPlayer *player,
-                           RhythmDBEntry *entry,
-                           RBSource *source)
-{
-       GError *error = NULL;
+       source = (entry == NULL) ? player->priv->selected_source : player->priv->current_playing_source;
 
-       if (source == NULL)
-               source = player->priv->selected_source;
-       rb_shell_player_set_playing_source (player, source);
+       rb_debug ("syncing with source %p", source);
 
-       player->priv->jump_to_playing_entry = FALSE;
-       if (!rb_shell_player_set_playing_entry (player, entry, TRUE, FALSE, &error)) {
-               rb_shell_player_error (player, FALSE, error);
-               g_clear_error (&error);
-       }
-}
+       /* meh
+       action = gtk_action_group_get_action (player->priv->actiongroup,
+                                             "ViewJumpToPlaying");
+       g_object_set (action, "sensitive", entry != NULL, NULL);
+       */
 
-static void
-rb_shell_player_cmd_volume_up (GtkAction *action,
-                              RBShellPlayer *player)
-{
-       rb_shell_player_set_volume_relative (player, 0.1, NULL);
-}
+       map = G_ACTION_MAP (g_application_get_default ());
+       action = g_action_map_lookup_action (map, "play");
+       g_simple_action_set_enabled (G_SIMPLE_ACTION (action), entry != NULL || source != NULL);
 
-static void
-rb_shell_player_cmd_volume_down (GtkAction *action,
-                                RBShellPlayer *player)
-{
-       rb_shell_player_set_volume_relative (player, -0.1, NULL);
-}
+       if (source != NULL) {
+               view = rb_source_get_entry_view (source);
+               if (view)
+                       rb_entry_view_set_state (view, entry_view_state);
+       }
 
-static void
-rb_shell_player_cmd_play (GtkAction *action,
-                         RBShellPlayer *player)
-{
-       GError *error = NULL;
-       rb_debug ("play!");
-       if (!rb_shell_player_playpause (player, FALSE, &error))
-               rb_error_dialog (NULL,
-                                _("Couldn't start playback"),
-                                "%s", (error) ? error->message : "(null)");
-       g_clear_error (&error);
+       if (entry != NULL) {
+               rhythmdb_entry_unref (entry);
+       }
 }
 
-/* unused parameter can't be removed without breaking dbus interface compatibility */
 /**
- * rb_shell_player_playpause:
+ * rb_shell_player_set_playing_source:
  * @player: the #RBShellPlayer
- * @unused: nothing
- * @error: returns error information
- *
- * Toggles between playing and paused state.  If there is no playing
- * entry, chooses an entry from (in order of preference) the play queue,
- * the selection in the current source, or the play order.
+ * @source: the new playing #RBSource
  *
- * Return value: %TRUE if successful
+ * Replaces the current playing source.
  */
-gboolean
-rb_shell_player_playpause (RBShellPlayer *player,
-                          gboolean unused,
-                          GError **error)
+void
+rb_shell_player_set_playing_source (RBShellPlayer *player,
+                                   RBSource *source)
 {
-       gboolean ret;
-       RBEntryView *songs;
-
-       rb_debug ("doing playpause");
-
-       g_return_val_if_fail (RB_IS_SHELL_PLAYER (player), TRUE);
-
-       ret = TRUE;
-
-       if (rb_player_playing (player->priv->mmplayer)) {
-               if (player->priv->source == NULL) {
-                       rb_debug ("playing source is already NULL");
-               } else if (rb_source_can_pause (player->priv->source)) {
-                       rb_debug ("pausing mm player");
-                       if (player->priv->parser_cancellable != NULL) {
-                               g_object_unref (player->priv->parser_cancellable);
-                               player->priv->parser_cancellable = NULL;
-                       }
-                       rb_player_pause (player->priv->mmplayer);
-                       songs = rb_source_get_entry_view (player->priv->current_playing_source);
-                       if (songs)
-                               rb_entry_view_set_state (songs, RB_ENTRY_VIEW_PAUSED);
+       rb_shell_player_set_playing_source_internal (player, source, TRUE);
+}
 
-                       /* might need a signal for when the player has actually paused here? */
-                       g_object_notify (G_OBJECT (player), "playing");
-                       /* mostly for that */
-               } else {
-                       rb_debug ("stopping playback");
-                       rb_shell_player_stop (player);
-               }
-       } else {
-               RhythmDBEntry *entry;
-               RBSource *new_source;
-               gboolean out_of_order = FALSE;
+static void
+actually_set_playing_source (RBShellPlayer *player,
+                            RBSource *source,
+                            gboolean sync_entry_view)
+{
+       RBPlayOrder *porder;
 
-               if (player->priv->source == NULL) {
-                       /* no current stream, pull one in from the currently
-                        * selected source */
-                       rb_debug ("no playing source, using selected source");
-                       rb_shell_player_set_playing_source (player, player->priv->selected_source);
+       player->priv->source = source;
+       player->priv->current_playing_source = source;
+
+       if (source != NULL) {
+               RBEntryView *songs = rb_source_get_entry_view (player->priv->source);
+               if (sync_entry_view && songs) {
+                       rb_entry_view_set_state (songs, RB_ENTRY_VIEW_PLAYING);
                }
-               new_source = player->priv->current_playing_source;
+       }
 
-               entry = rb_shell_player_get_playing_entry (player);
-               if (entry == NULL) {
-                       /* queue takes precedence over selection */
-                       if (player->priv->queue_play_order) {
-                               entry = rb_play_order_get_next (player->priv->queue_play_order);
-                               if (entry != NULL) {
-                                       new_source = RB_SOURCE (player->priv->queue_source);
-                                       rb_play_order_go_next (player->priv->queue_play_order);
-                               }
-                       }
+       if (source != RB_SOURCE (player->priv->queue_source)) {
+               if (source == NULL)
+                       source = player->priv->selected_source;
 
-                       /* selection takes precedence over first item in play order */
-                       if (entry == NULL) {
-                               GList *selection = NULL;
+               if (source != NULL) {
+                       g_object_get (source, "play-order", &porder, NULL);
+                       if (porder == NULL)
+                               porder = g_object_ref (player->priv->play_order);
 
-                               songs = rb_source_get_entry_view (player->priv->source);
-                               if (songs)
-                                       selection = rb_entry_view_get_selected_entries (songs);
+                       rb_play_order_playing_source_changed (porder, source);
+                       g_object_unref (porder);
+               }
+       }
 
-                               if (selection != NULL) {
-                                       rb_debug ("choosing first selected entry");
-                                       entry = (RhythmDBEntry*) selection->data;
-                                       if (entry)
-                                               out_of_order = TRUE;
+       rb_shell_player_play_order_update_cb (player->priv->play_order,
+                                             FALSE, FALSE,
+                                             player);
+}
 
-                                       g_list_free (selection);
-                               }
-                       }
+static void
+rb_shell_player_set_playing_source_internal (RBShellPlayer *player,
+                                            RBSource *source,
+                                            gboolean sync_entry_view)
 
-                       /* play order is last */
-                       if (entry == NULL) {
-                               RBPlayOrder *porder;
+{
+       gboolean emit_source_changed = TRUE;
+       gboolean emit_playing_from_queue_changed = FALSE;
 
-                               rb_debug ("getting entry from play order");
-                               g_object_get (player->priv->source, "play-order", &porder, NULL);
-                               if (porder == NULL)
-                                       porder = g_object_ref (player->priv->play_order);
+       if (player->priv->source == source &&
+           player->priv->current_playing_source == source &&
+           source != NULL)
+               return;
 
-                               entry = rb_play_order_get_next (porder);
-                               if (entry != NULL)
-                                       rb_play_order_go_next (porder);
-                               g_object_unref (porder);
-                       }
+       rb_debug ("setting playing source to %p", source);
 
-                       if (entry != NULL) {
-                               /* if the entry view containing the playing entry changed, update it */
-                               if (new_source != player->priv->current_playing_source)
-                                       swap_playing_source (player, new_source);
+       if (RB_SOURCE (player->priv->queue_source) == source) {
 
-                               player->priv->jump_to_playing_entry = TRUE;
-                               if (!rb_shell_player_set_playing_entry (player, entry, out_of_order, FALSE, 
error))
-                                       ret = FALSE;
-                       }
+               if (player->priv->current_playing_source != source)
+                       emit_playing_from_queue_changed = TRUE;
+
+               if (player->priv->source == NULL) {
+                       actually_set_playing_source (player, source, sync_entry_view);
                } else {
-                       if (!rb_shell_player_play (player, error)) {
-                               rb_shell_player_stop (player);
-                               ret = FALSE;
-                       }
+                       emit_source_changed = FALSE;
+                       player->priv->current_playing_source = source;
                }
 
-               if (entry != NULL) {
-                       rhythmdb_entry_unref (entry);
-               }
-       }
+       } else {
+               if (player->priv->current_playing_source != source) {
+                       if (player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source))
+                               emit_playing_from_queue_changed = TRUE;
 
-       rb_shell_player_sync_with_source (player);
-       rb_shell_player_sync_buttons (player);
+                       /* stop the old source */
+                       if (player->priv->current_playing_source != NULL) {
+                               if (sync_entry_view) {
+                                       RBEntryView *songs = rb_source_get_entry_view 
(player->priv->current_playing_source);
+                                       rb_debug ("source is already playing, stopping it");
 
-       return ret;
-}
+                                       /* clear the playing entry if we're switching between non-queue 
sources */
+                                       if (player->priv->current_playing_source != RB_SOURCE 
(player->priv->queue_source))
+                                               rb_play_order_set_playing_entry (player->priv->play_order, 
NULL);
 
-static void
-rb_shell_player_sync_control_state (RBShellPlayer *player)
-{
-       gboolean shuffle, repeat;
-       GtkAction *action;
-       rb_debug ("syncing control state");
+                                       if (songs)
+                                               rb_entry_view_set_state (songs, RB_ENTRY_VIEW_NOT_PLAYING);
+                               }
+                       }
+               }
+               actually_set_playing_source (player, source, sync_entry_view);
+       }
 
-       if (!rb_shell_player_get_playback_state (player, &shuffle,
-                                                &repeat))
-               return;
+       rb_shell_player_sync_with_source (player);
+       /*g_object_notify (G_OBJECT (player), "playing");*/
+       if (player->priv->selected_source)
+               rb_shell_player_sync_buttons (player);
 
-       action = gtk_action_group_get_action (player->priv->actiongroup,
-                                             "ControlShuffle");
-       gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), shuffle);
-       action = gtk_action_group_get_action (player->priv->actiongroup,
-                                             "ControlRepeat");
-       gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), repeat);
+       if (emit_source_changed) {
+               g_signal_emit (G_OBJECT (player), rb_shell_player_signals[PLAYING_SOURCE_CHANGED],
+                              0, player->priv->source);
+       }
+       if (emit_playing_from_queue_changed) {
+               g_object_notify (G_OBJECT (player), "playing-from-queue");
+       }
 }
 
-static void
-sync_volume_cb (GSettings *settings, RBShellPlayer *player)
+/**
+ * rb_shell_player_stop:
+ * @player: a #RBShellPlayer.
+ *
+ * Completely stops playback, freeing resources and unloading the file.
+ *
+ * In general rb_shell_player_pause() should be used instead, as it stops the
+ * audio, but does not completely free resources.
+ **/
+void
+rb_shell_player_stop (RBShellPlayer *player)
 {
-       g_settings_set_double (player->priv->settings, "volume", player->priv->volume);
-}
+       GError *error = NULL;
+       rb_debug ("stopping");
 
-static void
-rb_shell_player_sync_volume (RBShellPlayer *player,
-                            gboolean notify,
-                            gboolean set_volume)
-{
-       RhythmDBEntry *entry;
+       g_return_if_fail (RB_IS_SHELL_PLAYER (player));
 
-       if (player->priv->volume <= 0.0){
-               player->priv->volume = 0.0;
-       } else if (player->priv->volume >= 1.0){
-               player->priv->volume = 1.0;
+       if (error == NULL)
+               rb_player_close (player->priv->mmplayer, NULL, &error);
+       if (error) {
+               rb_error_dialog (NULL,
+                                _("Couldn't stop playback"),
+                                "%s", error->message);
+               g_error_free (error);
        }
 
-       if (set_volume) {
-               rb_player_set_volume (player->priv->mmplayer,
-                                     player->priv->mute ? 0.0 : player->priv->volume);
+       if (player->priv->parser_cancellable != NULL) {
+               rb_debug ("cancelling playlist parser");
+               g_cancellable_cancel (player->priv->parser_cancellable);
+               g_object_unref (player->priv->parser_cancellable);
+               player->priv->parser_cancellable = NULL;
        }
 
-       if (player->priv->syncing_state == FALSE) {
-               rb_settings_delayed_sync (player->priv->settings,
-                                         (RBDelayedSyncFunc) sync_volume_cb,
-                                         g_object_ref (player),
-                                         g_object_unref);
+       if (player->priv->playing_entry != NULL) {
+               rhythmdb_entry_unref (player->priv->playing_entry);
+               player->priv->playing_entry = NULL;
        }
 
-       entry = rb_shell_player_get_playing_entry (player);
-       if (entry != NULL) {
-               rhythmdb_entry_unref (entry);
-       }
+       rb_shell_player_set_playing_source (player, NULL);
+       rb_shell_player_sync_with_source (player);
+       g_signal_emit (G_OBJECT (player),
+                      rb_shell_player_signals[PLAYING_SONG_CHANGED], 0,
+                      NULL);
+       g_signal_emit (G_OBJECT (player),
+                      rb_shell_player_signals[PLAYING_URI_CHANGED], 0,
+                      NULL);
+       g_object_notify (G_OBJECT (player), "playing");
+       rb_shell_player_sync_buttons (player);
+}
 
-       if (notify)
-               g_object_notify (G_OBJECT (player), "volume");
+/**
+ * rb_shell_player_pause:
+ * @player: a #RBShellPlayer
+ * @error: error return
+ *
+ * Pauses playback if possible, completely stopping if not.
+ *
+ * Return value: whether playback is not occurring (TRUE when successfully
+ * paused/stopped or playback was not occurring).
+ **/
+
+gboolean
+rb_shell_player_pause (RBShellPlayer *player,
+                      GError **error)
+{
+       if (rb_player_playing (player->priv->mmplayer))
+               return rb_shell_player_playpause (player, FALSE, error);
+       else
+               return TRUE;
+}
+
+/**
+ * rb_shell_player_get_playing:
+ * @player: a #RBShellPlayer
+ * @playing: (out): playback state return
+ * @error: error return
+ *
+ * Reports whether playback is occuring by setting #playing.
+ *
+ * Return value: %TRUE if successful
+ **/
+gboolean
+rb_shell_player_get_playing (RBShellPlayer *player,
+                            gboolean *playing,
+                            GError **error)
+{
+       if (playing != NULL)
+               *playing = rb_player_playing (player->priv->mmplayer);
+
+       return TRUE;
 }
 
 /**
- * rb_shell_player_set_volume:
+ * rb_shell_player_get_playing_time_string:
  * @player: the #RBShellPlayer
- * @volume: the volume level (between 0 and 1)
- * @error: returns the error information
- *
- * Sets the playback volume level.
+ * 
+ * Constructs a string showing the current playback position,
+ * taking the time display settings into account.
  *
- * Return value: %TRUE on success
+ * Return value: allocated playing time string
  */
-gboolean
-rb_shell_player_set_volume (RBShellPlayer *player,
-                           gdouble volume,
-                           GError **error)
+char *
+rb_shell_player_get_playing_time_string (RBShellPlayer *player)
 {
-       player->priv->volume = volume;
-       rb_shell_player_sync_volume (player, TRUE, TRUE);
-       return TRUE;
+       gboolean elapsed;
+       elapsed = g_settings_get_boolean (player->priv->ui_settings, "time-display");
+       return rb_make_elapsed_time_string (player->priv->elapsed,
+                                           rb_shell_player_get_playing_song_duration (player),
+                                           elapsed);
 }
 
 /**
- * rb_shell_player_set_volume_relative:
+ * rb_shell_player_get_playing_time:
  * @player: the #RBShellPlayer
- * @delta: difference to apply to the volume level (between -1 and 1)
+ * @time: (out): returns the current playback position
  * @error: returns error information
  *
- * Adds the specified value to the current volume level.
+ * Retrieves the current playback position.  Fails if
+ * the player currently cannot provide the playback
+ * position.
  *
- * Return value: %TRUE on success
+ * Return value: %TRUE if successful
  */
 gboolean
-rb_shell_player_set_volume_relative (RBShellPlayer *player,
-                                    gdouble delta,
-                                    GError **error)
+rb_shell_player_get_playing_time (RBShellPlayer *player,
+                                 guint *time,
+                                 GError **error)
 {
-       /* rb_shell_player_sync_volume does clipping */
-       player->priv->volume += delta;
-       rb_shell_player_sync_volume (player, TRUE, TRUE);
-       return TRUE;
+       gint64 ptime;
+
+       ptime = rb_player_get_time (player->priv->mmplayer);
+       if (ptime >= 0) {
+               if (time != NULL) {
+                       *time = (guint)(ptime / RB_PLAYER_SECOND);
+               }
+               return TRUE;
+       } else {
+               g_set_error (error,
+                            RB_SHELL_PLAYER_ERROR,
+                            RB_SHELL_PLAYER_ERROR_POSITION_NOT_AVAILABLE,
+                            _("Playback position not available"));
+               return FALSE;
+       }
 }
 
 /**
- * rb_shell_player_get_volume:
+ * rb_shell_player_set_playing_time:
  * @player: the #RBShellPlayer
- * @volume: (out): returns the volume level
+ * @time: the target playback position (in seconds)
  * @error: returns error information
  *
- * Returns the current volume level
+ * Attempts to set the playback position.  Fails if the
+ * current song is not seekable.
  *
- * Return value: the current volume level.
+ * Return value: %TRUE if successful
  */
 gboolean
-rb_shell_player_get_volume (RBShellPlayer *player,
-                           gdouble *volume,
-                           GError **error)
-{
-       *volume = player->priv->volume;
-       return TRUE;
-}
-
-static void
-rb_shell_player_volume_changed_cb (RBPlayer *player,
-                                  float volume,
-                                  RBShellPlayer *shell_player)
+rb_shell_player_set_playing_time (RBShellPlayer *player,
+                                 guint time,
+                                 GError **error)
 {
-       shell_player->priv->volume = volume;
-       rb_shell_player_sync_volume (shell_player, TRUE, FALSE);
+       if (rb_player_seekable (player->priv->mmplayer)) {
+               if (player->priv->playing_entry_eos) {
+                       rb_debug ("forgetting that playing entry had EOS'd due to seek");
+                       player->priv->playing_entry_eos = FALSE;
+               }
+               rb_player_set_time (player->priv->mmplayer, ((gint64) time) * RB_PLAYER_SECOND);
+               return TRUE;
+       } else {
+               g_set_error (error,
+                            RB_SHELL_PLAYER_ERROR,
+                            RB_SHELL_PLAYER_ERROR_NOT_SEEKABLE,
+                            _("Current song is not seekable"));
+               return FALSE;
+       }
 }
 
 /**
- * rb_shell_player_set_mute:
+ * rb_shell_player_seek:
  * @player: the #RBShellPlayer
- * @mute: %TRUE to mute playback
+ * @offset: relative seek target (in seconds)
  * @error: returns error information
  *
- * Updates the mute setting on the player.
+ * Seeks forwards or backwards in the current playing
+ * song. Fails if the current song is not seekable.
  *
  * Return value: %TRUE if successful
  */
 gboolean
-rb_shell_player_set_mute (RBShellPlayer *player,
-                         gboolean mute,
-                         GError **error)
+rb_shell_player_seek (RBShellPlayer *player,
+                     gint32 offset,
+                     GError **error)
 {
-       player->priv->mute = mute;
-       rb_shell_player_sync_volume (player, FALSE, TRUE);
-       return TRUE;
+       g_return_val_if_fail (RB_IS_SHELL_PLAYER (player), FALSE);
+
+       if (rb_player_seekable (player->priv->mmplayer)) {
+               gint64 target_time = rb_player_get_time (player->priv->mmplayer) +
+                       (((gint64)offset) * RB_PLAYER_SECOND);
+               if (target_time < 0)
+                       target_time = 0;
+               rb_player_set_time (player->priv->mmplayer, target_time);
+               return TRUE;
+       } else {
+               g_set_error (error,
+                            RB_SHELL_PLAYER_ERROR,
+                            RB_SHELL_PLAYER_ERROR_NOT_SEEKABLE,
+                            _("Current song is not seekable"));
+               return FALSE;
+       }
 }
 
 /**
- * rb_shell_player_get_mute:
+ * rb_shell_player_get_playing_song_duration:
  * @player: the #RBShellPlayer
- * @mute: (out): returns the current mute setting
- * @error: returns error information
  *
- * Returns %TRUE if currently muted
+ * Retrieves the duration of the current playing song.
  *
- * Return value: %TRUE if currently muted
+ * Return value: duration, or -1 if not playing
  */
-gboolean
-rb_shell_player_get_mute (RBShellPlayer *player,
-                         gboolean *mute,
-                         GError **error)
+long
+rb_shell_player_get_playing_song_duration (RBShellPlayer *player)
 {
-       *mute = player->priv->mute;
-       return TRUE;
+       RhythmDBEntry *current_entry;
+       long val;
+
+       g_return_val_if_fail (RB_IS_SHELL_PLAYER (player), -1);
+
+       current_entry = rb_shell_player_get_playing_entry (player);
+
+       if (current_entry == NULL) {
+               rb_debug ("Did not get playing entry : return -1 as length");
+               return -1;
+       }
+
+       val = rhythmdb_entry_get_ulong (current_entry, RHYTHMDB_PROP_DURATION);
+
+       rhythmdb_entry_unref (current_entry);
+
+       return val;
 }
 
 static void
-rb_shell_player_shuffle_changed_cb (GtkAction *action,
-                                   RBShellPlayer *player)
+rb_shell_player_sync_with_selected_source (RBShellPlayer *player)
 {
-       const char *neworder;
-       gboolean shuffle = FALSE;
-       gboolean repeat = FALSE;
+       rb_debug ("syncing with selected source: %p", player->priv->selected_source);
+       if (player->priv->source == NULL)
+       {
+               rb_debug ("no playing source, new source is %p", player->priv->selected_source);
+               rb_shell_player_sync_with_source (player);
+       }
+}
 
-       if (player->priv->syncing_state)
-               return;
+static gboolean
+do_next_idle (RBShellPlayer *player)
+{
+       /* use the EOS callback, so that EOF_SOURCE_ conditions are handled properly */
+       rb_shell_player_handle_eos (NULL, NULL, FALSE, player);
+       player->priv->do_next_idle_id = 0;
 
-       rb_debug ("shuffle changed");
+       return FALSE;
+}
 
-       rb_shell_player_get_playback_state (player, &shuffle, &repeat);
+static gboolean
+do_next_not_found_idle (RBShellPlayer *player)
+{
+       RhythmDBEntry *entry;
+       entry = rb_shell_player_get_playing_entry (player);
 
-       shuffle = !shuffle;
-       neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
-       g_settings_set_string (player->priv->settings, "play-order", neworder);
+       do_next_idle (player);
+
+       if (entry != NULL) {
+               rhythmdb_entry_update_availability (entry, RHYTHMDB_ENTRY_AVAIL_NOT_FOUND);
+               rhythmdb_commit (player->priv->db);
+               rhythmdb_entry_unref (entry);
+       }
+
+       return FALSE;
 }
 
 static void
-rb_shell_player_repeat_changed_cb (GtkAction *action,
-                                  RBShellPlayer *player)
+rb_shell_player_error (RBShellPlayer *player,
+                      gboolean async,
+                      const GError *err)
 {
-       const char *neworder;
-       gboolean shuffle = FALSE;
-       gboolean repeat = FALSE;
-       rb_debug ("repeat changed");
+       RhythmDBEntry *entry;
+       gboolean do_next;
+
+       g_return_if_fail (player->priv->handling_error == FALSE);
+
+       player->priv->handling_error = TRUE;
+
+       entry = rb_shell_player_get_playing_entry (player);
+
+       rb_debug ("playback error while playing: %s", err->message);
+       /* For synchronous errors the entry playback error has already been set */
+       if (entry && async)
+               rb_shell_player_set_entry_playback_error (player, entry, err->message);
+
+       if (entry == NULL) {
+               do_next = TRUE;
+       } else if (err->domain == RB_PLAYER_ERROR && err->code == RB_PLAYER_ERROR_NOT_FOUND) {
+               /* process not found errors after we've started the next track */
+               if (player->priv->do_next_idle_id != 0) {
+                       g_source_remove (player->priv->do_next_idle_id);
+               }
+               player->priv->do_next_idle_id = g_idle_add ((GSourceFunc)do_next_not_found_idle, player);
+               do_next = FALSE;
+       } else if (err->domain == RB_PLAYER_ERROR && err->code == RB_PLAYER_ERROR_NO_AUDIO) {
+
+               /* stream has completely ended */
+               rb_shell_player_stop (player);
+               do_next = FALSE;
+       } else if ((player->priv->current_playing_source != NULL) &&
+                  (rb_source_handle_eos (player->priv->current_playing_source) == RB_SOURCE_EOF_RETRY)) {
+               /* receiving an error means a broken stream or non-audio stream, so abort
+                * unless we've got more URLs to try */
+               if (g_queue_is_empty (player->priv->playlist_urls)) {
+                       rb_error_dialog (NULL,
+                                        _("Couldn't start playback"),
+                                        "%s", (err) ? err->message : "(null)");
+                       rb_shell_player_stop (player);
+                       do_next = FALSE;
+               } else {
+                       rb_debug ("haven't yet exhausted the URLs from the playlist");
+                       do_next = TRUE;
+               }
+       } else {
+               do_next = TRUE;
+       }
 
-       if (player->priv->syncing_state)
-               return;
+       if (do_next && player->priv->do_next_idle_id == 0) {
+               player->priv->do_next_idle_id = g_idle_add ((GSourceFunc)do_next_idle, player);
+       }
 
-       rb_shell_player_get_playback_state (player, &shuffle, &repeat);
+       player->priv->handling_error = FALSE;
 
-       repeat = !repeat;
-       neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
-       g_settings_set_string (player->priv->settings, "play-order", neworder);
+       if (entry != NULL) {
+               rhythmdb_entry_unref (entry);
+       }
 }
 
 static void
-rb_shell_player_entry_activated_cb (RBEntryView *view,
-                                   RhythmDBEntry *entry,
-                                   RBShellPlayer *player)
+playing_stream_cb (RBPlayer *mmplayer,
+                  RhythmDBEntry *entry,
+                  RBShellPlayer *player)
 {
-       gboolean was_from_queue = FALSE;
-       RhythmDBEntry *prev_entry = NULL;
-       GError *error = NULL;
-       gboolean source_set = FALSE;
-       gboolean jump_to_entry = FALSE;
-       char *playback_uri;
+       gboolean entry_changed;
 
        g_return_if_fail (entry != NULL);
 
-       rb_debug  ("got entry %p activated", entry);
-
-       /* don't play hidden entries */
-       if (rhythmdb_entry_get_boolean (entry, RHYTHMDB_PROP_HIDDEN))
-               return;
-
-       /* skip entries with no playback uri */
-       playback_uri = rhythmdb_entry_get_playback_uri (entry);
-       if (playback_uri == NULL)
-               return;
-
-       g_free (playback_uri);
-
-       /* figure out where the previous entry came from */
-       if ((player->priv->queue_source != NULL) &&
-           (player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source))) {
-               prev_entry = rb_shell_player_get_playing_entry (player);
-               was_from_queue = TRUE;
-       }
-
-       if (player->priv->queue_source) {
-               RBEntryView *queue_sidebar;
-
-               g_object_get (player->priv->queue_source, "sidebar", &queue_sidebar, NULL);
-
-               if (view == queue_sidebar || view == rb_source_get_entry_view (RB_SOURCE 
(player->priv->queue_source))) {
-
-                       /* fall back to the current selected source once the queue is empty */
-                       if (view == queue_sidebar && player->priv->source == NULL) {
-                               /* XXX only do this if the selected source doesn't have its own play order? */
-                               rb_play_order_playing_source_changed (player->priv->play_order,
-                                                                     player->priv->selected_source);
-                               player->priv->source = player->priv->selected_source;
-                       }
-
-                       rb_shell_player_set_playing_source (player, RB_SOURCE (player->priv->queue_source));
+       GDK_THREADS_ENTER ();
 
-                       was_from_queue = FALSE;
-                       source_set = TRUE;
-                       jump_to_entry = TRUE;
-               } else {
-                       if (player->priv->queue_only) {
-                               rb_source_add_to_queue (player->priv->selected_source,
-                                                       RB_SOURCE (player->priv->queue_source));
-                               rb_shell_player_set_playing_source (player, RB_SOURCE 
(player->priv->queue_source));
-                               source_set = TRUE;
-                       }
-               }
+       entry_changed = (player->priv->playing_entry != entry);
 
-               g_object_unref (queue_sidebar);
-       }
+       /* update playing entry */
+       if (player->priv->playing_entry)
+               rhythmdb_entry_unref (player->priv->playing_entry);
+       player->priv->playing_entry = rhythmdb_entry_ref (entry);
+       player->priv->playing_entry_eos = FALSE;
 
-       /* bail out if queue only */
-       if (player->priv->queue_only) {
-               return;
-       }
+       if (entry_changed) {
+               const char *location;
 
-       if (!source_set) {
-               rb_shell_player_set_playing_source (player, player->priv->selected_source);
-               source_set = TRUE;
+               location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
+               rb_debug ("new playing stream: %s", location);
+               g_signal_emit (G_OBJECT (player),
+                              rb_shell_player_signals[PLAYING_SONG_CHANGED], 0,
+                              entry);
+               g_signal_emit (G_OBJECT (player),
+                              rb_shell_player_signals[PLAYING_URI_CHANGED], 0,
+                              location);
        }
 
-       player->priv->jump_to_playing_entry = jump_to_entry;
-       if (!rb_shell_player_set_playing_entry (player, entry, TRUE, FALSE, &error)) {
-               rb_shell_player_error (player, FALSE, error);
-               g_clear_error (&error);
-       }
+       /* resync UI */
+       rb_shell_player_sync_with_source (player);
+       rb_shell_player_sync_buttons (player);
+       g_object_notify (G_OBJECT (player), "playing");
 
-       /* if we were previously playing from the queue, clear its playing entry,
-        * so we'll start again from the start.
-        */
-       if (was_from_queue && prev_entry != NULL) {
-               rb_play_order_set_playing_entry (player->priv->queue_play_order, NULL);
+       if (player->priv->jump_to_playing_entry) {
+               rb_shell_player_jump_to_current (player);
+               player->priv->jump_to_playing_entry = FALSE;
        }
 
-       if (prev_entry != NULL) {
-               rhythmdb_entry_unref (prev_entry);
-       }
+       GDK_THREADS_LEAVE ();
 }
 
 static void
-rb_shell_player_property_row_activated_cb (RBPropertyView *view,
-                                          const char *name,
-                                          RBShellPlayer *player)
+error_cb (RBPlayer *mmplayer,
+         RhythmDBEntry *entry,
+         const GError *err,
+         gpointer data)
 {
-       RBPlayOrder *porder;
-       RhythmDBEntry *entry = NULL;
-       GError *error = NULL;
-
-       rb_debug ("got property activated");
-
-       rb_shell_player_set_playing_source (player, player->priv->selected_source);
+       RBShellPlayer *player = RB_SHELL_PLAYER (data);
 
-       /* RHYTHMDBFIXME - do we need to wait here until the query is finished?
-        * in theory, yes, but in practice the query is started when the row is
-        * selected (on the first click when doubleclicking, or when using the
-        * keyboard to select then activate) and is pretty much always done by
-        * the time we get in here.
-        */
+       if (player->priv->handling_error)
+               return;
 
-       g_object_get (player->priv->selected_source, "play-order", &porder, NULL);
-       if (porder == NULL)
-               porder = g_object_ref (player->priv->play_order);
+       if (player->priv->source == NULL) {
+               rb_debug ("ignoring error (no source): %s", err->message);
+               return;
+       }
 
-       entry = rb_play_order_get_next (porder);
-       if (entry != NULL) {
-               rb_play_order_go_next (porder);
+       GDK_THREADS_ENTER ();
 
-               player->priv->jump_to_playing_entry = TRUE;     /* ? */
-               if (!rb_shell_player_set_playing_entry (player, entry, TRUE, FALSE, &error)) {
-                       rb_shell_player_error (player, FALSE, error);
-                       g_clear_error (&error);
-               }
+       if (entry != player->priv->playing_entry) {
+               rb_debug ("got error for unexpected entry %p (expected %p)", entry, 
player->priv->playing_entry);
+       } else {
+               rb_shell_player_error (player, TRUE, err);
+               rb_debug ("exiting error hander");
        }
 
-       rhythmdb_entry_unref (entry);
-       g_object_unref (porder);
+       GDK_THREADS_LEAVE ();
 }
 
 static void
-rb_shell_player_entry_changed_cb (RhythmDB *db,
-                                 RhythmDBEntry *entry,
-                                 GArray *changes,
-                                 RBShellPlayer *player)
+tick_cb (RBPlayer *mmplayer,
+        RhythmDBEntry *entry,
+        gint64 elapsed,
+        gint64 duration,
+        gpointer data)
 {
-       gboolean synced = FALSE;
-       const char *location;
-       RhythmDBEntry *playing_entry;
-       int i;
+       RBShellPlayer *player = RB_SHELL_PLAYER (data);
+       gint64 remaining_check = 0;
+       gboolean duration_from_player = TRUE;
+       const char *uri;
+       long elapsed_sec;
 
-       playing_entry = rb_shell_player_get_playing_entry (player);
+       GDK_THREADS_ENTER ();
 
-       /* We try to update only if the changed entry is currently playing */
-       if (entry != playing_entry) {
-               if (playing_entry != NULL) {
-                       rhythmdb_entry_unref (playing_entry);
-               }
+       if (player->priv->playing_entry != entry) {
+               rb_debug ("got tick for unexpected entry %p (expected %p)", entry, 
player->priv->playing_entry);
+               GDK_THREADS_LEAVE ();
                return;
        }
 
-       location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
-       for (i = 0; i < changes->len; i++) {
-               GValue *v = &g_array_index (changes, GValue, i);
-               RhythmDBEntryChange *change = g_value_get_boxed (v);
+       /* if we aren't getting a duration value from the player, use the
+        * value from the entry, if any.
+        */
+       if (duration < 1) {
+               duration = ((gint64)rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION)) * 
RB_PLAYER_SECOND;
+               duration_from_player = FALSE;
+       }
 
-               /* update UI if the artist, title or album has changed */
-               switch (change->prop) {
-               case RHYTHMDB_PROP_TITLE:
-               case RHYTHMDB_PROP_ARTIST:
-               case RHYTHMDB_PROP_ALBUM:
-                       if (!synced) {
-                               rb_shell_player_sync_with_source (player);
-                               synced = TRUE;
-                       }
-                       break;
-               default:
-                       break;
-               }
+       uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
+       rb_debug ("tick: [%s, %" G_GINT64_FORMAT ":%" G_GINT64_FORMAT "(%d)]",
+                 uri,
+                 elapsed,
+                 duration,
+                 duration_from_player);
 
-               /* emit dbus signals for changes with easily marshallable types */
-               switch (rhythmdb_get_property_type (db, change->prop)) {
-               case G_TYPE_STRING:
-               case G_TYPE_BOOLEAN:
-               case G_TYPE_ULONG:
-               case G_TYPE_UINT64:
-               case G_TYPE_DOUBLE:
-                       g_signal_emit (G_OBJECT (player),
-                                      rb_shell_player_signals[PLAYING_SONG_PROPERTY_CHANGED], 0,
-                                      location,
-                                      rhythmdb_nice_elt_name_from_propid (db, change->prop),
-                                      &change->old,
-                                      &change->new);
-                       break;
-               default:
-                       break;
+       if (elapsed < 0) {
+               elapsed_sec = 0;
+       } else {
+               elapsed_sec = elapsed / RB_PLAYER_SECOND;
+       }
+
+       if (player->priv->elapsed != elapsed_sec) {
+               player->priv->elapsed = elapsed_sec;
+               g_signal_emit (G_OBJECT (player), rb_shell_player_signals[ELAPSED_CHANGED],
+                              0, player->priv->elapsed);
+       }
+       g_signal_emit (player, rb_shell_player_signals[ELAPSED_NANO_CHANGED], 0, elapsed);
+
+       if (duration_from_player) {
+               /* XXX update duration in various things? */
+       }
+
+       /* check if we should start a crossfade */
+       if (rb_player_multiple_open (mmplayer)) {
+               if (player->priv->track_transition_time < PREROLL_TIME) {
+                       remaining_check = PREROLL_TIME;
+               } else {
+                       remaining_check = player->priv->track_transition_time;
                }
        }
 
-       if (playing_entry != NULL) {
-               rhythmdb_entry_unref (playing_entry);
+       /*
+        * just pretending we got an EOS will do exactly what we want
+        * here.  if we don't want to crossfade, we'll just leave the stream
+        * prerolled until the current stream really ends.
+        */
+       if (remaining_check > 0 &&
+           duration > 0 &&
+           elapsed > 0 &&
+           ((duration - elapsed) <= remaining_check)) {
+               rb_debug ("%" G_GINT64_FORMAT " ns remaining in stream %s; need %" G_GINT64_FORMAT " for 
transition",
+                         duration - elapsed,
+                         uri,
+                         remaining_check);
+               rb_shell_player_handle_eos_unlocked (player, entry, FALSE);
        }
+
+       GDK_THREADS_LEAVE ();
 }
 
+typedef struct {
+       RhythmDBEntry *entry;
+       RBShellPlayer *player;
+} MissingPluginRetryData;
+
 static void
-rb_shell_player_extra_metadata_cb (RhythmDB *db,
-                                  RhythmDBEntry *entry,
-                                  const char *field,
-                                  GValue *metadata,
-                                  RBShellPlayer *player)
+missing_plugins_retry_cb (gpointer inst,
+                         gboolean retry,
+                         MissingPluginRetryData *retry_data)
 {
-
-       RhythmDBEntry *playing_entry;
-
-       playing_entry = rb_shell_player_get_playing_entry (player);
-       if (entry != playing_entry) {
-               if (playing_entry != NULL) {
-                       rhythmdb_entry_unref (playing_entry);
-               }
+       GError *error = NULL;
+       if (retry == FALSE) {
+               /* next?  or stop playback? */
+               rb_debug ("not retrying playback; stopping player");
+               rb_shell_player_stop (retry_data->player);
                return;
        }
 
-       rb_shell_player_sync_with_source (player);
-
-       /* emit dbus signals for changes with easily marshallable types */
-       switch (G_VALUE_TYPE (metadata)) {
-       case G_TYPE_STRING:
-               /* make sure it's valid utf8, otherwise dbus barfs */
-               if (g_utf8_validate (g_value_get_string (metadata), -1, NULL) == FALSE) {
-                       rb_debug ("not emitting extra metadata field %s as value is not valid utf8", field);
-                       return;
-               }
-       case G_TYPE_BOOLEAN:
-       case G_TYPE_ULONG:
-       case G_TYPE_UINT64:
-       case G_TYPE_DOUBLE:
-               g_signal_emit (G_OBJECT (player),
-                              rb_shell_player_signals[PLAYING_SONG_PROPERTY_CHANGED], 0,
-                              rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
-                              field,
-                              metadata,                /* slightly silly */
-                              metadata);
-               break;
-       default:
-               break;
+       rb_debug ("retrying playback");
+       rb_shell_player_set_playing_entry (retry_data->player,
+                                          retry_data->entry,
+                                          FALSE, FALSE,
+                                          &error);
+       if (error != NULL) {
+               rb_shell_player_error (retry_data->player, FALSE, error);
+               g_clear_error (&error);
        }
 }
 
-
 static void
-rb_shell_player_sync_with_source (RBShellPlayer *player)
+missing_plugins_retry_cleanup (MissingPluginRetryData *retry)
 {
-       const char *entry_title = NULL;
-       const char *artist = NULL;
-       const char *stream_name = NULL;
-       char *streaming_title = NULL;
-       char *streaming_artist = NULL;
-       RhythmDBEntry *entry;
-       char *title = NULL;
-       gint64 elapsed;
-
-       entry = rb_shell_player_get_playing_entry (player);
-       rb_debug ("playing source: %p, active entry: %p", player->priv->current_playing_source, entry);
-
-       if (entry != NULL) {
-               GValue *value;
-
-               entry_title = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE);
-               artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST);
-
-               value = rhythmdb_entry_request_extra_metadata (player->priv->db,
-                                                              entry,
-                                                              RHYTHMDB_PROP_STREAM_SONG_TITLE);
-               if (value != NULL) {
-                       streaming_title = g_value_dup_string (value);
-                       g_value_unset (value);
-                       g_free (value);
-
-                       rb_debug ("got streaming title \"%s\"", streaming_title);
-                       /* use entry title for stream name */
-                       stream_name = entry_title;
-                       entry_title = streaming_title;
-               }
-
-               value = rhythmdb_entry_request_extra_metadata (player->priv->db,
-                                                              entry,
-                                                              RHYTHMDB_PROP_STREAM_SONG_ARTIST);
-               if (value != NULL) {
-                       streaming_artist = g_value_dup_string (value);
-                       g_value_unset (value);
-                       g_free (value);
+       retry->player->priv->handling_error = FALSE;
 
-                       rb_debug ("got streaming artist \"%s\"", streaming_artist);
-                       /* override artist from entry */
-                       artist = streaming_artist;
-               }
+       g_object_unref (retry->player);
+       rhythmdb_entry_unref (retry->entry);
+       g_free (retry);
+}
 
-               rhythmdb_entry_unref (entry);
-       }
 
-       if ((artist && artist[0] != '\0') || entry_title || stream_name) {
+static void
+missing_plugins_cb (RBPlayer *player,
+                   RhythmDBEntry *entry,
+                   const char **details,
+                   const char **descriptions,
+                   RBShellPlayer *sp)
+{
+       gboolean processing;
+       GClosure *retry;
+       MissingPluginRetryData *retry_data;
 
-               GString *title_str = g_string_sized_new (100);
-               if (artist && artist[0] != '\0') {
-                       g_string_append (title_str, artist);
-                       g_string_append (title_str, " - ");
-               }
-               if (entry_title != NULL)
-                       g_string_append (title_str, entry_title);
+       retry_data = g_new0 (MissingPluginRetryData, 1);
+       retry_data->player = g_object_ref (sp);
+       retry_data->entry = rhythmdb_entry_ref (entry);
 
-               if (stream_name != NULL)
-                       g_string_append_printf (title_str, " (%s)", stream_name);
+       retry = g_cclosure_new ((GCallback) missing_plugins_retry_cb,
+                               retry_data,
+                               (GClosureNotify) missing_plugins_retry_cleanup);
+       g_closure_set_marshal (retry, g_cclosure_marshal_VOID__BOOLEAN);
+       processing = rb_missing_plugins_install (details, FALSE, retry);
+       if (processing) {
+               /* don't handle any further errors */
+               sp->priv->handling_error = TRUE;
 
-               title = g_string_free (title_str, FALSE);
+               /* probably specify the URI here.. */
+               rb_debug ("stopping player while processing missing plugins");
+               rb_player_close (retry_data->player->priv->mmplayer, NULL, NULL);
+       } else {
+               rb_debug ("not processing missing plugins; simulating EOS");
+               rb_shell_player_handle_eos (NULL, NULL, FALSE, retry_data->player);
        }
 
-       elapsed = rb_player_get_time (player->priv->mmplayer);
-       if (elapsed < 0)
-               elapsed = 0;
-       player->priv->elapsed = elapsed / RB_PLAYER_SECOND;
-
-       g_signal_emit (G_OBJECT (player), rb_shell_player_signals[WINDOW_TITLE_CHANGED], 0,
-                      title);
-       g_free (title);
-
-       g_signal_emit (G_OBJECT (player), rb_shell_player_signals[ELAPSED_CHANGED], 0,
-                      player->priv->elapsed);
-
-       g_free (streaming_artist);
-       g_free (streaming_title);
+       g_closure_sink (retry);
 }
 
 static void
-rb_shell_player_sync_buttons (RBShellPlayer *player)
+player_image_cb (RBPlayer *player,
+                RhythmDBEntry *entry,
+                GdkPixbuf *image,
+                RBShellPlayer *shell_player)
 {
-       GtkAction *action;
-       RBSource *source;
-       RBEntryView *view;
-       int entry_view_state;
-       RhythmDBEntry *entry;
+       RBExtDB *store;
+       RBExtDBKey *key;
+       const char *artist;
+       GValue v = G_VALUE_INIT;
 
-       entry = rb_shell_player_get_playing_entry (player);
-       if (entry != NULL) {
-               source = player->priv->current_playing_source;
-               entry_view_state = rb_player_playing (player->priv->mmplayer) ?
-                       RB_ENTRY_VIEW_PLAYING : RB_ENTRY_VIEW_PAUSED;
-       } else {
-               source = player->priv->selected_source;
-               entry_view_state = RB_ENTRY_VIEW_NOT_PLAYING;
+       if (image == NULL)
+               return;
+
+       artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM_ARTIST);
+       if (artist == NULL || artist[0] == '\0' || strcmp (artist, _("Unknown")) == 0) {
+               artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST);
+               if (artist == NULL || artist[0] == '\0' || strcmp (artist, _("Unknown")) == 0) {
+                       return;
+               }
        }
 
-       source = (entry == NULL) ? player->priv->selected_source : player->priv->current_playing_source;
+       store = rb_ext_db_new ("album-art");
 
-       rb_debug ("syncing with source %p", source);
+       key = rb_ext_db_key_create_storage ("album", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM));
+       rb_ext_db_key_add_field (key, "artist", artist);
 
-       action = gtk_action_group_get_action (player->priv->actiongroup,
-                                             "ViewJumpToPlaying");
-       g_object_set (action, "sensitive", entry != NULL, NULL);
+       g_value_init (&v, GDK_TYPE_PIXBUF);
+       g_value_set_object (&v, image);
+       rb_ext_db_store (store, key, RB_EXT_DB_SOURCE_EMBEDDED, &v);
+       g_value_unset (&v);
 
-       action = gtk_action_group_get_action (player->priv->actiongroup,
-                                             "ControlPlay");
-       g_object_set (action, "sensitive", entry != NULL || source != NULL, NULL);
+       g_object_unref (store);
+       rb_ext_db_key_free (key);
+}
+
+/**
+ * rb_shell_player_get_playing_path:
+ * @player: the #RBShellPlayer
+ * @path: (out callee-allocates) (transfer full): returns the URI of the current playing entry
+ * @error: returns error information
+ *
+ * Retrieves the URI of the current playing entry.  The
+ * caller must not free the returned string.
+ *
+ * Return value: %TRUE if successful
+ */
+gboolean
+rb_shell_player_get_playing_path (RBShellPlayer *player,
+                                 const gchar **path,
+                                 GError **error)
+{
+       RhythmDBEntry *entry;
 
-       if (source != NULL) {
-               view = rb_source_get_entry_view (source);
-               if (view)
-                       rb_entry_view_set_state (view, entry_view_state);
+       entry = rb_shell_player_get_playing_entry (player);
+       if (entry != NULL) {
+               *path = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
+       } else {
+               *path = NULL;
        }
 
        if (entry != NULL) {
                rhythmdb_entry_unref (entry);
        }
-}
 
-/**
- * rb_shell_player_set_playing_source:
- * @player: the #RBShellPlayer
- * @source: the new playing #RBSource
- *
- * Replaces the current playing source.
- */
-void
-rb_shell_player_set_playing_source (RBShellPlayer *player,
-                                   RBSource *source)
-{
-       rb_shell_player_set_playing_source_internal (player, source, TRUE);
+       return TRUE;
 }
 
 static void
-actually_set_playing_source (RBShellPlayer *player,
-                            RBSource *source,
-                            gboolean sync_entry_view)
+rb_shell_player_playing_changed_cb (RBShellPlayer *player,
+                                   GParamSpec *arg1,
+                                   gpointer user_data)
 {
-       RBPlayOrder *porder;
-
-       player->priv->source = source;
-       player->priv->current_playing_source = source;
-
-       if (source != NULL) {
-               RBEntryView *songs = rb_source_get_entry_view (player->priv->source);
-               if (sync_entry_view && songs) {
-                       rb_entry_view_set_state (songs, RB_ENTRY_VIEW_PLAYING);
-               }
-       }
+       gboolean playing;
+       GActionMap *map;
+       GAction *action;
+       /*char *tooltip;*/
 
-       if (source != RB_SOURCE (player->priv->queue_source)) {
-               if (source == NULL)
-                       source = player->priv->selected_source;
+       /* sync play action state */
+       g_object_get (player, "playing", &playing, NULL);
 
-               if (source != NULL) {
-                       g_object_get (source, "play-order", &porder, NULL);
-                       if (porder == NULL)
-                               porder = g_object_ref (player->priv->play_order);
+       map = G_ACTION_MAP (g_application_get_default ());
+       action = g_action_map_lookup_action (map, "play");
+       g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_boolean (playing));
 
-                       rb_play_order_playing_source_changed (porder, source);
-                       g_object_unref (porder);
+#if 0
+       /* -> header, set tooltip on button directly */
+       action = gtk_action_group_get_action (player->priv->actiongroup,
+                                             "ControlPlay");
+       if (playing) {
+               if (rb_source_can_pause (player->priv->source)) {
+                       tooltip = g_strdup (_("Pause playback"));
+               } else {
+                       tooltip = g_strdup (_("Stop playback"));
                }
+       } else {
+               tooltip = g_strdup (_("Start playback"));
        }
-
-       rb_shell_player_play_order_update_cb (player->priv->play_order,
-                                             FALSE, FALSE,
-                                             player);
+       g_object_set (action, "tooltip", tooltip, NULL);
+       g_free (tooltip);
+#endif
 }
 
 static void
-rb_shell_player_set_playing_source_internal (RBShellPlayer *player,
-                                            RBSource *source,
-                                            gboolean sync_entry_view)
-
+play_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
 {
-       gboolean emit_source_changed = TRUE;
-       gboolean emit_playing_from_queue_changed = FALSE;
-
-       if (player->priv->source == source &&
-           player->priv->current_playing_source == source &&
-           source != NULL)
-               return;
-
-       rb_debug ("setting playing source to %p", source);
-
-       if (RB_SOURCE (player->priv->queue_source) == source) {
-
-               if (player->priv->current_playing_source != source)
-                       emit_playing_from_queue_changed = TRUE;
-
-               if (player->priv->source == NULL) {
-                       actually_set_playing_source (player, source, sync_entry_view);
-               } else {
-                       emit_source_changed = FALSE;
-                       player->priv->current_playing_source = source;
-               }
-
-       } else {
-               if (player->priv->current_playing_source != source) {
-                       if (player->priv->current_playing_source == RB_SOURCE (player->priv->queue_source))
-                               emit_playing_from_queue_changed = TRUE;
+       RBShellPlayer *player = RB_SHELL_PLAYER (user_data);
+       GError *error = NULL;
 
-                       /* stop the old source */
-                       if (player->priv->current_playing_source != NULL) {
-                               if (sync_entry_view) {
-                                       RBEntryView *songs = rb_source_get_entry_view 
(player->priv->current_playing_source);
-                                       rb_debug ("source is already playing, stopping it");
+       rb_debug ("play!");
+       if (rb_shell_player_playpause (player, FALSE, &error) == FALSE) {
+               rb_error_dialog (NULL,
+                                _("Couldn't start playback"),
+                                "%s", (error) ? error->message : "(null)");
+       }
+       g_clear_error (&error);
+}
 
-                                       /* clear the playing entry if we're switching between non-queue 
sources */
-                                       if (player->priv->current_playing_source != RB_SOURCE 
(player->priv->queue_source))
-                                               rb_play_order_set_playing_entry (player->priv->play_order, 
NULL);
+static void
+play_previous_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
+{
+       RBShellPlayer *player = RB_SHELL_PLAYER (user_data);
+       GError *error = NULL;
 
-                                       if (songs)
-                                               rb_entry_view_set_state (songs, RB_ENTRY_VIEW_NOT_PLAYING);
-                               }
-                       }
+       if (!rb_shell_player_do_previous (player, &error)) {
+               if (error->domain != RB_SHELL_PLAYER_ERROR ||
+                   error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) {
+                       g_warning ("cmd_previous: Unhandled error: %s", error->message);
+               } else if (error->code == RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) {
+                       rb_shell_player_stop (player);
                }
-               actually_set_playing_source (player, source, sync_entry_view);
        }
+}
 
-       rb_shell_player_sync_with_source (player);
-       /*g_object_notify (G_OBJECT (player), "playing");*/
-       if (player->priv->selected_source)
-               rb_shell_player_sync_buttons (player);
+static void
+play_next_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
+{
+       RBShellPlayer *player = RB_SHELL_PLAYER (user_data);
+       GError *error = NULL;
 
-       if (emit_source_changed) {
-               g_signal_emit (G_OBJECT (player), rb_shell_player_signals[PLAYING_SOURCE_CHANGED],
-                              0, player->priv->source);
-       }
-       if (emit_playing_from_queue_changed) {
-               g_object_notify (G_OBJECT (player), "playing-from-queue");
+       if (!rb_shell_player_do_next (player, &error)) {
+               if (error->domain != RB_SHELL_PLAYER_ERROR ||
+                   error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) {
+                       g_warning ("cmd_next: Unhandled error: %s", error->message);
+               } else if (error->code == RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST) {
+                       rb_shell_player_stop (player);
+               }
        }
 }
 
-/**
- * rb_shell_player_stop:
- * @player: a #RBShellPlayer.
- *
- * Completely stops playback, freeing resources and unloading the file.
- *
- * In general rb_shell_player_pause() should be used instead, as it stops the
- * audio, but does not completely free resources.
- **/
-void
-rb_shell_player_stop (RBShellPlayer *player)
+static void
+play_repeat_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
 {
-       GError *error = NULL;
-       rb_debug ("stopping");
+       RBShellPlayer *player = RB_SHELL_PLAYER (user_data);
+       const char *neworder;
+       gboolean shuffle = FALSE;
+       gboolean repeat = FALSE;
+       rb_debug ("repeat changed");
 
-       g_return_if_fail (RB_IS_SHELL_PLAYER (player));
+       if (player->priv->syncing_state)
+               return;
 
-       if (error == NULL)
-               rb_player_close (player->priv->mmplayer, NULL, &error);
-       if (error) {
-               rb_error_dialog (NULL,
-                                _("Couldn't stop playback"),
-                                "%s", error->message);
-               g_error_free (error);
-       }
+       rb_shell_player_get_playback_state (player, &shuffle, &repeat);
 
-       if (player->priv->parser_cancellable != NULL) {
-               rb_debug ("cancelling playlist parser");
-               g_cancellable_cancel (player->priv->parser_cancellable);
-               g_object_unref (player->priv->parser_cancellable);
-               player->priv->parser_cancellable = NULL;
-       }
+       repeat = !repeat;
+       neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
+       g_settings_set_string (player->priv->settings, "play-order", neworder);
+}
 
-       if (player->priv->playing_entry != NULL) {
-               rhythmdb_entry_unref (player->priv->playing_entry);
-               player->priv->playing_entry = NULL;
-       }
+static void
+play_shuffle_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
+{
+       RBShellPlayer *player = RB_SHELL_PLAYER (user_data);
+       const char *neworder;
+       gboolean shuffle = FALSE;
+       gboolean repeat = FALSE;
 
-       rb_shell_player_set_playing_source (player, NULL);
-       rb_shell_player_sync_with_source (player);
-       g_signal_emit (G_OBJECT (player),
-                      rb_shell_player_signals[PLAYING_SONG_CHANGED], 0,
-                      NULL);
-       g_signal_emit (G_OBJECT (player),
-                      rb_shell_player_signals[PLAYING_URI_CHANGED], 0,
-                      NULL);
-       g_object_notify (G_OBJECT (player), "playing");
-       rb_shell_player_sync_buttons (player);
-}
+       if (player->priv->syncing_state)
+               return;
 
-/**
- * rb_shell_player_pause:
- * @player: a #RBShellPlayer
- * @error: error return
- *
- * Pauses playback if possible, completely stopping if not.
- *
- * Return value: whether playback is not occurring (TRUE when successfully
- * paused/stopped or playback was not occurring).
- **/
+       rb_debug ("shuffle changed");
 
-gboolean
-rb_shell_player_pause (RBShellPlayer *player,
-                      GError **error)
-{
-       if (rb_player_playing (player->priv->mmplayer))
-               return rb_shell_player_playpause (player, FALSE, error);
-       else
-               return TRUE;
+       rb_shell_player_get_playback_state (player, &shuffle, &repeat);
+
+       shuffle = !shuffle;
+       neworder = state_to_play_order[shuffle ? 1 : 0][repeat ? 1 : 0];
+       g_settings_set_string (player->priv->settings, "play-order", neworder);
 }
 
-/**
- * rb_shell_player_get_playing:
- * @player: a #RBShellPlayer
- * @playing: (out): playback state return
- * @error: error return
- *
- * Reports whether playback is occuring by setting #playing.
- *
- * Return value: %TRUE if successful
- **/
-gboolean
-rb_shell_player_get_playing (RBShellPlayer *player,
-                            gboolean *playing,
-                            GError **error)
+static void
+play_volume_up_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
 {
-       if (playing != NULL)
-               *playing = rb_player_playing (player->priv->mmplayer);
+       RBShellPlayer *player = RB_SHELL_PLAYER (user_data);
+       rb_shell_player_set_volume_relative (player, 0.1, NULL);
+}
 
-       return TRUE;
+static void
+play_volume_down_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
+{
+       RBShellPlayer *player = RB_SHELL_PLAYER (user_data);
+       rb_shell_player_set_volume_relative (player, -0.1, NULL);
 }
 
-/**
- * rb_shell_player_get_playing_time_string:
- * @player: the #RBShellPlayer
- * 
- * Constructs a string showing the current playback position,
- * taking the time display settings into account.
- *
- * Return value: allocated playing time string
- */
-char *
-rb_shell_player_get_playing_time_string (RBShellPlayer *player)
+
+static void
+_play_order_description_free (RBPlayOrderDescription *order)
 {
-       gboolean elapsed;
-       elapsed = g_settings_get_boolean (player->priv->ui_settings, "time-display");
-       return rb_make_elapsed_time_string (player->priv->elapsed,
-                                           rb_shell_player_get_playing_song_duration (player),
-                                           elapsed);
+       g_free (order->name);
+       g_free (order->description);
+       g_free (order);
 }
 
 /**
- * rb_shell_player_get_playing_time:
- * @player: the #RBShellPlayer
- * @time: (out): returns the current playback position
- * @error: returns error information
+ * rb_play_order_new:
+ * @porder_name: Play order type name
+ * @player: #RBShellPlayer instance to attach to
  *
- * Retrieves the current playback position.  Fails if
- * the player currently cannot provide the playback
- * position.
+ * Creates a new #RBPlayOrder of the specified type.
  *
- * Return value: %TRUE if successful
- */
-gboolean
-rb_shell_player_get_playing_time (RBShellPlayer *player,
-                                 guint *time,
-                                 GError **error)
+ * Returns: #RBPlayOrder instance
+ **/
+
+#define DEFAULT_PLAY_ORDER "linear"
+
+static RBPlayOrder *
+rb_play_order_new (RBShellPlayer *player, const char* porder_name)
 {
-       gint64 ptime;
+       RBPlayOrderDescription *order;
 
-       ptime = rb_player_get_time (player->priv->mmplayer);
-       if (ptime >= 0) {
-               if (time != NULL) {
-                       *time = (guint)(ptime / RB_PLAYER_SECOND);
-               }
-               return TRUE;
-       } else {
-               g_set_error (error,
-                            RB_SHELL_PLAYER_ERROR,
-                            RB_SHELL_PLAYER_ERROR_POSITION_NOT_AVAILABLE,
-                            _("Playback position not available"));
-               return FALSE;
+       g_return_val_if_fail (porder_name != NULL, NULL);
+       g_return_val_if_fail (player != NULL, NULL);
+
+       order = g_hash_table_lookup (player->priv->play_orders, porder_name);
+
+       if (order == NULL) {
+               g_warning ("Unknown value \"%s\" in GSettings key \"play-order"
+                               "\". Using %s play order.", porder_name, DEFAULT_PLAY_ORDER);
+               order = g_hash_table_lookup (player->priv->play_orders, DEFAULT_PLAY_ORDER);
        }
+
+       return RB_PLAY_ORDER (g_object_new (order->order_type, "player", player, NULL));
 }
 
 /**
- * rb_shell_player_set_playing_time:
+ * rb_shell_player_add_play_order:
  * @player: the #RBShellPlayer
- * @time: the target playback position (in seconds)
- * @error: returns error information
- *
- * Attempts to set the playback position.  Fails if the
- * current song is not seekable.
+ * @name: name of the new play order
+ * @description: description of the new play order
+ * @order_type: the #GType of the play order class
+ * @hidden: if %TRUE, don't display the play order in the UI
  *
- * Return value: %TRUE if successful
+ * Adds a new play order to the set of available play orders.
  */
-gboolean
-rb_shell_player_set_playing_time (RBShellPlayer *player,
-                                 guint time,
-                                 GError **error)
+void
+rb_shell_player_add_play_order (RBShellPlayer *player, const char *name,
+                               const char *description, GType order_type, gboolean hidden)
 {
-       if (rb_player_seekable (player->priv->mmplayer)) {
-               if (player->priv->playing_entry_eos) {
-                       rb_debug ("forgetting that playing entry had EOS'd due to seek");
-                       player->priv->playing_entry_eos = FALSE;
-               }
-               rb_player_set_time (player->priv->mmplayer, ((gint64) time) * RB_PLAYER_SECOND);
-               return TRUE;
-       } else {
-               g_set_error (error,
-                            RB_SHELL_PLAYER_ERROR,
-                            RB_SHELL_PLAYER_ERROR_NOT_SEEKABLE,
-                            _("Current song is not seekable"));
-               return FALSE;
-       }
+       RBPlayOrderDescription *order;
+
+       g_return_if_fail (g_type_is_a (order_type, RB_TYPE_PLAY_ORDER));
+
+       order = g_new0(RBPlayOrderDescription, 1);
+       order->name = g_strdup (name);
+       order->description = g_strdup (description);
+       order->order_type = order_type;
+       order->is_in_dropdown = !hidden;
+
+       g_hash_table_insert (player->priv->play_orders, order->name, order);
 }
 
 /**
- * rb_shell_player_seek:
+ * rb_shell_player_remove_play_order:
  * @player: the #RBShellPlayer
- * @offset: relative seek target (in seconds)
- * @error: returns error information
- *
- * Seeks forwards or backwards in the current playing
- * song. Fails if the current song is not seekable.
+ * @name: name of the play order to remove
  *
- * Return value: %TRUE if successful
+ * Removes a play order previously added with #rb_shell_player_add_play_order
+ * from the set of available play orders.
  */
-gboolean
-rb_shell_player_seek (RBShellPlayer *player,
-                     gint32 offset,
-                     GError **error)
+void
+rb_shell_player_remove_play_order (RBShellPlayer *player, const char *name)
 {
-       g_return_val_if_fail (RB_IS_SHELL_PLAYER (player), FALSE);
-
-       if (rb_player_seekable (player->priv->mmplayer)) {
-               gint64 target_time = rb_player_get_time (player->priv->mmplayer) +
-                       (((gint64)offset) * RB_PLAYER_SECOND);
-               if (target_time < 0)
-                       target_time = 0;
-               rb_player_set_time (player->priv->mmplayer, target_time);
-               return TRUE;
-       } else {
-               g_set_error (error,
-                            RB_SHELL_PLAYER_ERROR,
-                            RB_SHELL_PLAYER_ERROR_NOT_SEEKABLE,
-                            _("Current song is not seekable"));
-               return FALSE;
-       }
+       g_hash_table_remove (player->priv->play_orders, name);
 }
 
-/**
- * rb_shell_player_get_playing_song_duration:
- * @player: the #RBShellPlayer
- *
- * Retrieves the duration of the current playing song.
- *
- * Return value: duration, or -1 if not playing
- */
-long
-rb_shell_player_get_playing_song_duration (RBShellPlayer *player)
+static void
+rb_shell_player_constructed (GObject *object)
 {
-       RhythmDBEntry *current_entry;
-       long val;
+       RBApplication *app;
+       RBShellPlayer *player;
+       GAction *action;
+
+       GActionEntry actions[] = {
+               { "play", play_action_cb, "b", "false" },
+               { "play-previous", play_previous_action_cb },
+               { "play-next", play_next_action_cb },
+               { "play-repeat", play_repeat_action_cb, "b", "false" },
+               { "play-shuffle", play_shuffle_action_cb, "b", "false" },
+               { "volume-up", play_volume_up_action_cb },
+               { "volume-down", play_volume_down_action_cb }
+       };
 
-       g_return_val_if_fail (RB_IS_SHELL_PLAYER (player), -1);
+       RB_CHAIN_GOBJECT_METHOD (rb_shell_player_parent_class, constructed, object);
 
-       current_entry = rb_shell_player_get_playing_entry (player);
+       player = RB_SHELL_PLAYER (object);
 
-       if (current_entry == NULL) {
-               rb_debug ("Did not get playing entry : return -1 as length");
-               return -1;
-       }
+       app = RB_APPLICATION (g_application_get_default ());
+       g_action_map_add_action_entries (G_ACTION_MAP (app),
+                                        actions,
+                                        G_N_ELEMENTS (actions),
+                                        player);
 
-       val = rhythmdb_entry_get_ulong (current_entry, RHYTHMDB_PROP_DURATION);
+       gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>p", "app.play", g_variant_new_boolean 
(TRUE));
+       gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Left", "app.play-previous", NULL);
+       gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Right", "app.play-next", NULL);
+       gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Up", "app.volume-up", NULL);
+       gtk_application_add_accelerator (GTK_APPLICATION (app), "<Ctrl>Down", "app.volume-down", NULL);
 
-       rhythmdb_entry_unref (current_entry);
+       player_settings_changed_cb (player->priv->settings, "transition-time", player);
+       player_settings_changed_cb (player->priv->settings, "play-order", player);
 
-       return val;
+       action = g_action_map_lookup_action (G_ACTION_MAP (app), "play-previous");
+       g_object_bind_property (player, "has-prev", action, "enabled", G_BINDING_DEFAULT);
+       action = g_action_map_lookup_action (G_ACTION_MAP (app), "play-next");
+       g_object_bind_property (player, "has-next", action, "enabled", G_BINDING_DEFAULT);
+
+       player->priv->syncing_state = TRUE;
+       rb_shell_player_set_playing_source (player, NULL);
+       rb_shell_player_sync_play_order (player);
+       rb_shell_player_sync_control_state (player);
+       rb_shell_player_sync_volume (player, FALSE, TRUE);
+       player->priv->syncing_state = FALSE;
+
+       g_signal_connect (player,
+                         "notify::playing",
+                         G_CALLBACK (rb_shell_player_playing_changed_cb),
+                         NULL);
 }
 
 static void
-rb_shell_player_sync_with_selected_source (RBShellPlayer *player)
+rb_shell_player_set_source_internal (RBShellPlayer *player,
+                                    RBSource      *source)
 {
-       rb_debug ("syncing with selected source: %p", player->priv->selected_source);
-       if (player->priv->source == NULL)
-       {
-               rb_debug ("no playing source, new source is %p", player->priv->selected_source);
-               rb_shell_player_sync_with_source (player);
+       if (player->priv->selected_source != NULL) {
+               RBEntryView *songs = rb_source_get_entry_view (player->priv->selected_source);
+               GList *property_views = rb_source_get_property_views (player->priv->selected_source);
+               GList *l;
+
+               if (songs != NULL) {
+                       g_signal_handlers_disconnect_by_func (G_OBJECT (songs),
+                                                             G_CALLBACK (rb_shell_player_entry_activated_cb),
+                                                             player);
+               }
+
+               for (l = property_views; l != NULL; l = g_list_next (l)) {
+                       g_signal_handlers_disconnect_by_func (G_OBJECT (l->data),
+                                                             G_CALLBACK 
(rb_shell_player_property_row_activated_cb),
+                                                             player);
+               }
+
+               g_list_free (property_views);
        }
-}
 
-static gboolean
-do_next_idle (RBShellPlayer *player)
-{
-       /* use the EOS callback, so that EOF_SOURCE_ conditions are handled properly */
-       rb_shell_player_handle_eos (NULL, NULL, FALSE, player);
-       player->priv->do_next_idle_id = 0;
+       player->priv->selected_source = source;
+
+       rb_debug ("selected source %p", player->priv->selected_source);
+
+       rb_shell_player_sync_with_selected_source (player);
+       rb_shell_player_sync_buttons (player);
+
+       if (player->priv->selected_source != NULL) {
+               RBEntryView *songs = rb_source_get_entry_view (player->priv->selected_source);
+               GList *property_views = rb_source_get_property_views (player->priv->selected_source);
+               GList *l;
+
+               if (songs)
+                       g_signal_connect_object (G_OBJECT (songs),
+                                                "entry-activated",
+                                                G_CALLBACK (rb_shell_player_entry_activated_cb),
+                                                player, 0);
+               for (l = property_views; l != NULL; l = g_list_next (l)) {
+                       g_signal_connect_object (G_OBJECT (l->data),
+                                                "property-activated",
+                                                G_CALLBACK (rb_shell_player_property_row_activated_cb),
+                                                player, 0);
+               }
+
+               g_list_free (property_views);
+       }
+
+       /* If we're not playing, change the play order's view of the current source;
+        * if the selected source is the queue, however, set it to NULL so it'll stop
+        * once the queue is empty.
+        */
+       if (player->priv->current_playing_source == NULL) {
+               RBPlayOrder *porder = NULL;
+               RBSource *source = player->priv->selected_source;
+               if (source == RB_SOURCE (player->priv->queue_source)) {
+                       source = NULL;
+               } else if (source != NULL) {
+                       g_object_get (source, "play-order", &porder, NULL);
+               }
+
+               if (porder == NULL)
+                       porder = g_object_ref (player->priv->play_order);
 
-       return FALSE;
+               rb_play_order_playing_source_changed (porder, source);
+               g_object_unref (porder);
+       }
 }
-
-static gboolean
-do_next_not_found_idle (RBShellPlayer *player)
+static void
+rb_shell_player_set_db_internal (RBShellPlayer *player,
+                                RhythmDB      *db)
 {
-       RhythmDBEntry *entry;
-       entry = rb_shell_player_get_playing_entry (player);
+       if (player->priv->db != NULL) {
+               g_signal_handlers_disconnect_by_func (player->priv->db,
+                                                     G_CALLBACK (rb_shell_player_entry_changed_cb),
+                                                     player);
+               g_signal_handlers_disconnect_by_func (player->priv->db,
+                                                     G_CALLBACK (rb_shell_player_extra_metadata_cb),
+                                                     player);
+       }
 
-       do_next_idle (player);
+       player->priv->db = db;
 
-       if (entry != NULL) {
-               rhythmdb_entry_update_availability (entry, RHYTHMDB_ENTRY_AVAIL_NOT_FOUND);
-               rhythmdb_commit (player->priv->db);
-               rhythmdb_entry_unref (entry);
+       if (player->priv->db != NULL) {
+               /* Listen for changed entries to update metadata display */
+               g_signal_connect_object (G_OBJECT (player->priv->db),
+                                        "entry_changed",
+                                        G_CALLBACK (rb_shell_player_entry_changed_cb),
+                                        player, 0);
+               g_signal_connect_object (G_OBJECT (player->priv->db),
+                                        "entry_extra_metadata_notify",
+                                        G_CALLBACK (rb_shell_player_extra_metadata_cb),
+                                        player, 0);
        }
-
-       return FALSE;
 }
 
 static void
-rb_shell_player_error (RBShellPlayer *player,
-                      gboolean async,
-                      const GError *err)
+rb_shell_player_set_queue_source_internal (RBShellPlayer     *player,
+                                          RBPlayQueueSource *source)
 {
-       RhythmDBEntry *entry;
-       gboolean do_next;
+       if (player->priv->queue_source != NULL) {
+               RBEntryView *sidebar;
 
-       g_return_if_fail (player->priv->handling_error == FALSE);
+               g_object_get (player->priv->queue_source, "sidebar", &sidebar, NULL);
+               g_signal_handlers_disconnect_by_func (sidebar,
+                                                     G_CALLBACK (rb_shell_player_entry_activated_cb),
+                                                     player);
+               g_object_unref (sidebar);
 
-       player->priv->handling_error = TRUE;
+               if (player->priv->queue_play_order != NULL) {
+                       g_signal_handlers_disconnect_by_func (player->priv->queue_play_order,
+                                                             G_CALLBACK 
(rb_shell_player_play_order_update_cb),
+                                                             player);
+                       g_object_unref (player->priv->queue_play_order);
+               }
 
-       entry = rb_shell_player_get_playing_entry (player);
+       }
 
-       rb_debug ("playback error while playing: %s", err->message);
-       /* For synchronous errors the entry playback error has already been set */
-       if (entry && async)
-               rb_shell_player_set_entry_playback_error (player, entry, err->message);
+       player->priv->queue_source = source;
 
-       if (entry == NULL) {
-               do_next = TRUE;
-       } else if (err->domain == RB_PLAYER_ERROR && err->code == RB_PLAYER_ERROR_NOT_FOUND) {
-               /* process not found errors after we've started the next track */
-               if (player->priv->do_next_idle_id != 0) {
-                       g_source_remove (player->priv->do_next_idle_id);
-               }
-               player->priv->do_next_idle_id = g_idle_add ((GSourceFunc)do_next_not_found_idle, player);
-               do_next = FALSE;
-       } else if (err->domain == RB_PLAYER_ERROR && err->code == RB_PLAYER_ERROR_NO_AUDIO) {
+       if (player->priv->queue_source != NULL) {
+               RBEntryView *sidebar;
 
-               /* stream has completely ended */
-               rb_shell_player_stop (player);
-               do_next = FALSE;
-       } else if ((player->priv->current_playing_source != NULL) &&
-                  (rb_source_handle_eos (player->priv->current_playing_source) == RB_SOURCE_EOF_RETRY)) {
-               /* receiving an error means a broken stream or non-audio stream, so abort
-                * unless we've got more URLs to try */
-               if (g_queue_is_empty (player->priv->playlist_urls)) {
-                       rb_error_dialog (NULL,
-                                        _("Couldn't start playback"),
-                                        "%s", (err) ? err->message : "(null)");
-                       rb_shell_player_stop (player);
-                       do_next = FALSE;
-               } else {
-                       rb_debug ("haven't yet exhausted the URLs from the playlist");
-                       do_next = TRUE;
-               }
-       } else {
-               do_next = TRUE;
-       }
+               g_object_get (player->priv->queue_source, "play-order", &player->priv->queue_play_order, 
NULL);
 
-       if (do_next && player->priv->do_next_idle_id == 0) {
-               player->priv->do_next_idle_id = g_idle_add ((GSourceFunc)do_next_idle, player);
+               g_signal_connect_object (G_OBJECT (player->priv->queue_play_order),
+                                        "have_next_previous_changed",
+                                        G_CALLBACK (rb_shell_player_play_order_update_cb),
+                                        player, 0);
+               rb_shell_player_play_order_update_cb (player->priv->play_order,
+                                                     FALSE, FALSE,
+                                                     player);
+               rb_play_order_playing_source_changed (player->priv->queue_play_order,
+                                                     RB_SOURCE (player->priv->queue_source));
+
+               g_object_get (player->priv->queue_source, "sidebar", &sidebar, NULL);
+               g_signal_connect_object (G_OBJECT (sidebar),
+                                        "entry-activated",
+                                        G_CALLBACK (rb_shell_player_entry_activated_cb),
+                                        player, 0);
+               g_object_unref (sidebar);
        }
+}
 
-       player->priv->handling_error = FALSE;
+static void
+rb_shell_player_set_property (GObject *object,
+                             guint prop_id,
+                             const GValue *value,
+                             GParamSpec *pspec)
+{
+       RBShellPlayer *player = RB_SHELL_PLAYER (object);
 
-       if (entry != NULL) {
-               rhythmdb_entry_unref (entry);
+       switch (prop_id) {
+       case PROP_SOURCE:
+               rb_shell_player_set_source_internal (player, g_value_get_object (value));
+               break;
+       case PROP_DB:
+               rb_shell_player_set_db_internal (player, g_value_get_object (value));
+               break;
+       case PROP_PLAY_ORDER:
+               g_settings_set_string (player->priv->settings,
+                                      "play-order",
+                                      g_value_get_string (value));
+               break;
+       case PROP_VOLUME:
+               player->priv->volume = g_value_get_float (value);
+               rb_shell_player_sync_volume (player, FALSE, TRUE);
+               break;
+       case PROP_HEADER:
+               player->priv->header_widget = g_value_get_object (value);
+               g_signal_connect_object (player->priv->header_widget,
+                                        "notify::slider-dragging",
+                                        G_CALLBACK (rb_shell_player_slider_dragging_cb),
+                                        player, 0);
+               break;
+       case PROP_QUEUE_SOURCE:
+               rb_shell_player_set_queue_source_internal (player, g_value_get_object (value));
+               break;
+       case PROP_QUEUE_ONLY:
+               player->priv->queue_only = g_value_get_boolean (value);
+               break;
+       case PROP_MUTE:
+               player->priv->mute = g_value_get_boolean (value);
+               rb_shell_player_sync_volume (player, FALSE, TRUE);
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
        }
 }
 
 static void
-playing_stream_cb (RBPlayer *mmplayer,
-                  RhythmDBEntry *entry,
-                  RBShellPlayer *player)
+rb_shell_player_get_property (GObject *object,
+                             guint prop_id,
+                             GValue *value,
+                             GParamSpec *pspec)
 {
-       gboolean entry_changed;
-
-       g_return_if_fail (entry != NULL);
+       RBShellPlayer *player = RB_SHELL_PLAYER (object);
 
-       GDK_THREADS_ENTER ();
+       switch (prop_id) {
+       case PROP_SOURCE:
+               g_value_set_object (value, player->priv->selected_source);
+               break;
+       case PROP_DB:
+               g_value_set_object (value, player->priv->db);
+               break;
+       case PROP_PLAY_ORDER:
+       {
+               char *play_order = g_settings_get_string (player->priv->settings,
+                                                         "play-order");
+               if (play_order == NULL)
+                       play_order = g_strdup ("linear");
+               g_value_take_string (value, play_order);
+               break;
+       }
+       case PROP_PLAYING:
+               if (player->priv->mmplayer != NULL)
+                       g_value_set_boolean (value, rb_player_playing (player->priv->mmplayer));
+               else
+                       g_value_set_boolean (value, FALSE);
+               break;
+       case PROP_VOLUME:
+               g_value_set_float (value, player->priv->volume);
+               break;
+       case PROP_HEADER:
+               g_value_set_object (value, player->priv->header_widget);
+               break;
+       case PROP_QUEUE_SOURCE:
+               g_value_set_object (value, player->priv->queue_source);
+               break;
+       case PROP_QUEUE_ONLY:
+               g_value_set_boolean (value, player->priv->queue_only);
+               break;
+       case PROP_PLAYING_FROM_QUEUE:
+               g_value_set_boolean (value, player->priv->current_playing_source == RB_SOURCE 
(player->priv->queue_source));
+               break;
+       case PROP_PLAYER:
+               g_value_set_object (value, player->priv->mmplayer);
+               break;
+       case PROP_MUTE:
+               g_value_set_boolean (value, player->priv->mute);
+               break;
+       case PROP_HAS_NEXT:
+               g_value_set_boolean (value, player->priv->has_next);
+               break;
+       case PROP_HAS_PREV:
+               g_value_set_boolean (value, player->priv->has_prev);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+static void
+rb_shell_player_init (RBShellPlayer *player)
+{
+       GError *error = NULL;
 
-       entry_changed = (player->priv->playing_entry != entry);
+       player->priv = RB_SHELL_PLAYER_GET_PRIVATE (player);
 
-       /* update playing entry */
-       if (player->priv->playing_entry)
-               rhythmdb_entry_unref (player->priv->playing_entry);
-       player->priv->playing_entry = rhythmdb_entry_ref (entry);
-       player->priv->playing_entry_eos = FALSE;
+       player->priv->settings = g_settings_new ("org.gnome.rhythmbox.player");
+       player->priv->ui_settings = g_settings_new ("org.gnome.rhythmbox");
+       g_signal_connect_object (player->priv->settings,
+                                "changed",
+                                G_CALLBACK (player_settings_changed_cb),
+                                player, 0);
 
-       if (entry_changed) {
-               const char *location;
+       player->priv->play_orders = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, 
(GDestroyNotify)_play_order_description_free);
+       
+       rb_shell_player_add_play_order (player, "linear", N_("Linear"),
+                                       RB_TYPE_LINEAR_PLAY_ORDER, FALSE);
+       rb_shell_player_add_play_order (player, "linear-loop", N_("Linear looping"),
+                                       RB_TYPE_LINEAR_PLAY_ORDER_LOOP, FALSE);
+       rb_shell_player_add_play_order (player, "shuffle", N_("Shuffle"),
+                                       RB_TYPE_SHUFFLE_PLAY_ORDER, FALSE);
+       rb_shell_player_add_play_order (player, "random-equal-weights", N_("Random with equal weights"),
+                                       RB_TYPE_RANDOM_PLAY_ORDER_EQUAL_WEIGHTS, FALSE);
+       rb_shell_player_add_play_order (player, "random-by-age", N_("Random by time since last play"),
+                                       RB_TYPE_RANDOM_PLAY_ORDER_BY_AGE, FALSE);
+       rb_shell_player_add_play_order (player, "random-by-rating", N_("Random by rating"),
+                                       RB_TYPE_RANDOM_PLAY_ORDER_BY_RATING, FALSE);
+       rb_shell_player_add_play_order (player, "random-by-age-and-rating", N_("Random by time since last 
play and rating"),
+                                       RB_TYPE_RANDOM_PLAY_ORDER_BY_AGE_AND_RATING, FALSE);
+       rb_shell_player_add_play_order (player, "queue", N_("Linear, removing entries once played"),
+                                       RB_TYPE_QUEUE_PLAY_ORDER, TRUE);
 
-               location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
-               rb_debug ("new playing stream: %s", location);
-               g_signal_emit (G_OBJECT (player),
-                              rb_shell_player_signals[PLAYING_SONG_CHANGED], 0,
-                              entry);
-               g_signal_emit (G_OBJECT (player),
-                              rb_shell_player_signals[PLAYING_URI_CHANGED], 0,
-                              location);
+       player->priv->mmplayer = rb_player_new (g_settings_get_boolean (player->priv->settings, 
"use-xfade-backend"),
+                                               &error);
+       if (error != NULL) {
+               GtkWidget *dialog;
+               dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL,
+                                                GTK_MESSAGE_ERROR,
+                                                GTK_BUTTONS_CLOSE,
+                                                _("Failed to create the player: %s"),
+                                                error->message);
+               gtk_dialog_run (GTK_DIALOG (dialog));
+               exit (1);
        }
 
-       /* resync UI */
-       rb_shell_player_sync_with_source (player);
-       rb_shell_player_sync_buttons (player);
-       g_object_notify (G_OBJECT (player), "playing");
+       g_signal_connect_object (player->priv->mmplayer,
+                                "eos",
+                                G_CALLBACK (rb_shell_player_handle_eos),
+                                player, 0);
 
-       if (player->priv->jump_to_playing_entry) {
-               rb_shell_player_jump_to_current (player);
-               player->priv->jump_to_playing_entry = FALSE;
-       }
+       g_signal_connect_object (player->priv->mmplayer,
+                                "redirect",
+                                G_CALLBACK (rb_shell_player_handle_redirect),
+                                player, 0);
 
-       GDK_THREADS_LEAVE ();
-}
+       g_signal_connect_object (player->priv->mmplayer,
+                                "tick",
+                                G_CALLBACK (tick_cb),
+                                player, 0);
 
-static void
-error_cb (RBPlayer *mmplayer,
-         RhythmDBEntry *entry,
-         const GError *err,
-         gpointer data)
-{
-       RBShellPlayer *player = RB_SHELL_PLAYER (data);
+       g_signal_connect_object (player->priv->mmplayer,
+                                "error",
+                                G_CALLBACK (error_cb),
+                                player, 0);
 
-       if (player->priv->handling_error)
-               return;
+       g_signal_connect_object (player->priv->mmplayer,
+                                "playing-stream",
+                                G_CALLBACK (playing_stream_cb),
+                                player, 0);
 
-       if (player->priv->source == NULL) {
-               rb_debug ("ignoring error (no source): %s", err->message);
-               return;
-       }
+       g_signal_connect_object (player->priv->mmplayer,
+                                "missing-plugins",
+                                G_CALLBACK (missing_plugins_cb),
+                                player, 0);
+       g_signal_connect_object (player->priv->mmplayer,
+                                "volume-changed",
+                                G_CALLBACK (rb_shell_player_volume_changed_cb),
+                                player, 0);
 
-       GDK_THREADS_ENTER ();
+       g_signal_connect_object (player->priv->mmplayer,
+                                "image",
+                                G_CALLBACK (player_image_cb),
+                                player, 0);
 
-       if (entry != player->priv->playing_entry) {
-               rb_debug ("got error for unexpected entry %p (expected %p)", entry, 
player->priv->playing_entry);
-       } else {
-               rb_shell_player_error (player, TRUE, err);
-               rb_debug ("exiting error hander");
+       {
+               GVolumeMonitor *monitor = g_volume_monitor_get ();
+               g_signal_connect (G_OBJECT (monitor),
+                                 "mount-pre-unmount",
+                                 G_CALLBACK (volume_pre_unmount_cb),
+                                 player);
+               g_object_unref (monitor);       /* hmm */
        }
 
-       GDK_THREADS_LEAVE ();
+       player->priv->volume = g_settings_get_double (player->priv->settings, "volume");
+
+       g_signal_connect (player, "notify::playing",
+                         G_CALLBACK (reemit_playing_signal), NULL);
 }
 
 static void
-tick_cb (RBPlayer *mmplayer,
-        RhythmDBEntry *entry,
-        gint64 elapsed,
-        gint64 duration,
-        gpointer data)
+rb_shell_player_dispose (GObject *object)
 {
-       RBShellPlayer *player = RB_SHELL_PLAYER (data);
-       gint64 remaining_check = 0;
-       gboolean duration_from_player = TRUE;
-       const char *uri;
-       long elapsed_sec;
-
-       GDK_THREADS_ENTER ();
+       RBShellPlayer *player;
 
-       if (player->priv->playing_entry != entry) {
-               rb_debug ("got tick for unexpected entry %p (expected %p)", entry, 
player->priv->playing_entry);
-               GDK_THREADS_LEAVE ();
-               return;
-       }
+       g_return_if_fail (object != NULL);
+       g_return_if_fail (RB_IS_SHELL_PLAYER (object));
 
-       /* if we aren't getting a duration value from the player, use the
-        * value from the entry, if any.
-        */
-       if (duration < 1) {
-               duration = ((gint64)rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION)) * 
RB_PLAYER_SECOND;
-               duration_from_player = FALSE;
-       }
+       player = RB_SHELL_PLAYER (object);
 
-       uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
-       rb_debug ("tick: [%s, %" G_GINT64_FORMAT ":%" G_GINT64_FORMAT "(%d)]",
-                 uri,
-                 elapsed,
-                 duration,
-                 duration_from_player);
+       g_return_if_fail (player->priv != NULL);
 
-       if (elapsed < 0) {
-               elapsed_sec = 0;
-       } else {
-               elapsed_sec = elapsed / RB_PLAYER_SECOND;
+       if (player->priv->ui_settings != NULL) {
+               g_object_unref (player->priv->ui_settings);
+               player->priv->ui_settings = NULL;
        }
 
-       if (player->priv->elapsed != elapsed_sec) {
-               player->priv->elapsed = elapsed_sec;
-               g_signal_emit (G_OBJECT (player), rb_shell_player_signals[ELAPSED_CHANGED],
-                              0, player->priv->elapsed);
-       }
-       g_signal_emit (player, rb_shell_player_signals[ELAPSED_NANO_CHANGED], 0, elapsed);
+       if (player->priv->settings != NULL) {
+               /* hm, is this really the place to do this? */
+               g_settings_set_double (player->priv->settings,
+                                      "volume",
+                                      player->priv->volume);
 
-       if (duration_from_player) {
-               /* XXX update duration in various things? */
+               g_object_unref (player->priv->settings);
+               player->priv->settings = NULL;
        }
 
-       /* check if we should start a crossfade */
-       if (rb_player_multiple_open (mmplayer)) {
-               if (player->priv->track_transition_time < PREROLL_TIME) {
-                       remaining_check = PREROLL_TIME;
-               } else {
-                       remaining_check = player->priv->track_transition_time;
-               }
+       if (player->priv->mmplayer != NULL) {
+               g_object_unref (player->priv->mmplayer);
+               player->priv->mmplayer = NULL;
        }
 
-       /*
-        * just pretending we got an EOS will do exactly what we want
-        * here.  if we don't want to crossfade, we'll just leave the stream
-        * prerolled until the current stream really ends.
-        */
-       if (remaining_check > 0 &&
-           duration > 0 &&
-           elapsed > 0 &&
-           ((duration - elapsed) <= remaining_check)) {
-               rb_debug ("%" G_GINT64_FORMAT " ns remaining in stream %s; need %" G_GINT64_FORMAT " for 
transition",
-                         duration - elapsed,
-                         uri,
-                         remaining_check);
-               rb_shell_player_handle_eos_unlocked (player, entry, FALSE);
+       if (player->priv->play_order != NULL) {
+               g_object_unref (player->priv->play_order);
+               player->priv->play_order = NULL;
        }
 
-       GDK_THREADS_LEAVE ();
-}
-
-typedef struct {
-       RhythmDBEntry *entry;
-       RBShellPlayer *player;
-} MissingPluginRetryData;
-
-static void
-missing_plugins_retry_cb (gpointer inst,
-                         gboolean retry,
-                         MissingPluginRetryData *retry_data)
-{
-       GError *error = NULL;
-       if (retry == FALSE) {
-               /* next?  or stop playback? */
-               rb_debug ("not retrying playback; stopping player");
-               rb_shell_player_stop (retry_data->player);
-               return;
+       if (player->priv->queue_play_order != NULL) {
+               g_object_unref (player->priv->queue_play_order);
+               player->priv->queue_play_order = NULL;
        }
 
-       rb_debug ("retrying playback");
-       rb_shell_player_set_playing_entry (retry_data->player,
-                                          retry_data->entry,
-                                          FALSE, FALSE,
-                                          &error);
-       if (error != NULL) {
-               rb_shell_player_error (retry_data->player, FALSE, error);
-               g_clear_error (&error);
+       if (player->priv->do_next_idle_id != 0) {
+               g_source_remove (player->priv->do_next_idle_id);
+               player->priv->do_next_idle_id = 0;
        }
-}
-
-static void
-missing_plugins_retry_cleanup (MissingPluginRetryData *retry)
-{
-       retry->player->priv->handling_error = FALSE;
 
-       g_object_unref (retry->player);
-       rhythmdb_entry_unref (retry->entry);
-       g_free (retry);
+       G_OBJECT_CLASS (rb_shell_player_parent_class)->dispose (object);
 }
 
-
 static void
-missing_plugins_cb (RBPlayer *player,
-                   RhythmDBEntry *entry,
-                   const char **details,
-                   const char **descriptions,
-                   RBShellPlayer *sp)
+rb_shell_player_finalize (GObject *object)
 {
-       gboolean processing;
-       GClosure *retry;
-       MissingPluginRetryData *retry_data;
+       RBShellPlayer *player;
 
-       retry_data = g_new0 (MissingPluginRetryData, 1);
-       retry_data->player = g_object_ref (sp);
-       retry_data->entry = rhythmdb_entry_ref (entry);
+       g_return_if_fail (object != NULL);
+       g_return_if_fail (RB_IS_SHELL_PLAYER (object));
 
-       retry = g_cclosure_new ((GCallback) missing_plugins_retry_cb,
-                               retry_data,
-                               (GClosureNotify) missing_plugins_retry_cleanup);
-       g_closure_set_marshal (retry, g_cclosure_marshal_VOID__BOOLEAN);
-       processing = rb_missing_plugins_install (details, FALSE, retry);
-       if (processing) {
-               /* don't handle any further errors */
-               sp->priv->handling_error = TRUE;
+       player = RB_SHELL_PLAYER (object);
 
-               /* probably specify the URI here.. */
-               rb_debug ("stopping player while processing missing plugins");
-               rb_player_close (retry_data->player->priv->mmplayer, NULL, NULL);
-       } else {
-               rb_debug ("not processing missing plugins; simulating EOS");
-               rb_shell_player_handle_eos (NULL, NULL, FALSE, retry_data->player);
-       }
+       g_return_if_fail (player->priv != NULL);
 
-       g_closure_sink (retry);
+       g_hash_table_destroy (player->priv->play_orders);
+       
+       G_OBJECT_CLASS (rb_shell_player_parent_class)->finalize (object);
 }
 
 static void
-player_image_cb (RBPlayer *player,
-                RhythmDBEntry *entry,
-                GdkPixbuf *image,
-                RBShellPlayer *shell_player)
+rb_shell_player_class_init (RBShellPlayerClass *klass)
 {
-       RBExtDB *store;
-       RBExtDBKey *key;
-       const char *artist;
-       GValue v = G_VALUE_INIT;
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-       if (image == NULL)
-               return;
+       object_class->dispose = rb_shell_player_dispose;
+       object_class->finalize = rb_shell_player_finalize;
+       object_class->constructed = rb_shell_player_constructed;
 
-       artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM_ARTIST);
-       if (artist == NULL || artist[0] == '\0' || strcmp (artist, _("Unknown")) == 0) {
-               artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST);
-               if (artist == NULL || artist[0] == '\0' || strcmp (artist, _("Unknown")) == 0) {
-                       return;
-               }
-       }
+       object_class->set_property = rb_shell_player_set_property;
+       object_class->get_property = rb_shell_player_get_property;
 
-       store = rb_ext_db_new ("album-art");
+       /**
+        * RBShellPlayer:source:
+        *
+        * The current source that is selected for playback.
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_SOURCE,
+                                        g_param_spec_object ("source",
+                                                             "RBSource",
+                                                             "RBSource object",
+                                                             RB_TYPE_SOURCE,
+                                                             G_PARAM_READWRITE));
+       /**
+        * RBShellPlayer:db:
+        *
+        * The #RhythmDB
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_DB,
+                                        g_param_spec_object ("db",
+                                                             "RhythmDB",
+                                                             "RhythmDB object",
+                                                             RHYTHMDB_TYPE,
+                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
-       key = rb_ext_db_key_create_storage ("album", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM));
-       rb_ext_db_key_add_field (key, "artist", artist);
+       /**
+        * RBShellPlayer:queue-source:
+        *
+        * The play queue source
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_QUEUE_SOURCE,
+                                        g_param_spec_object ("queue-source",
+                                                             "RBPlayQueueSource",
+                                                             "RBPlayQueueSource object",
+                                                             RB_TYPE_PLAYLIST_SOURCE,
+                                                             G_PARAM_READWRITE));
 
-       g_value_init (&v, GDK_TYPE_PIXBUF);
-       g_value_set_object (&v, image);
-       rb_ext_db_store (store, key, RB_EXT_DB_SOURCE_EMBEDDED, &v);
-       g_value_unset (&v);
+       /**
+        * RBShellPlayer:queue-only:
+        *
+        * If %TRUE, activating an entry should only add it to the play queue.
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_QUEUE_ONLY,
+                                        g_param_spec_boolean ("queue-only",
+                                                              "Queue only",
+                                                              "Activation only adds to queue",
+                                                              FALSE,
+                                                              G_PARAM_READWRITE));
 
-       g_object_unref (store);
-       rb_ext_db_key_free (key);
-}
+       /**
+        * RBShellPlayer:playing-from-queue:
+        *
+        * If %TRUE, the current playing entry came from the play queue.
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_PLAYING_FROM_QUEUE,
+                                        g_param_spec_boolean ("playing-from-queue",
+                                                              "Playing from queue",
+                                                              "Whether playing from the play queue or not",
+                                                              FALSE,
+                                                              G_PARAM_READABLE));
 
-/**
- * rb_shell_player_get_playing_path:
- * @player: the #RBShellPlayer
- * @path: (out callee-allocates) (transfer full): returns the URI of the current playing entry
- * @error: returns error information
- *
- * Retrieves the URI of the current playing entry.  The
- * caller must not free the returned string.
- *
- * Return value: %TRUE if successful
- */
-gboolean
-rb_shell_player_get_playing_path (RBShellPlayer *player,
-                                 const gchar **path,
-                                 GError **error)
-{
-       RhythmDBEntry *entry;
+       /**
+        * RBShellPlayer:player:
+        *
+        * The player backend object (an object implementing the #RBPlayer interface).
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_PLAYER,
+                                        g_param_spec_object ("player",
+                                                             "RBPlayer",
+                                                             "RBPlayer object",
+                                                             G_TYPE_OBJECT,
+                                                             G_PARAM_READABLE));
 
-       entry = rb_shell_player_get_playing_entry (player);
-       if (entry != NULL) {
-               *path = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
-       } else {
-               *path = NULL;
-       }
+       /**
+        * RBShellPlayer:play-order:
+        *
+        * The current play order object.
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_PLAY_ORDER,
+                                        g_param_spec_string ("play-order",
+                                                             "play-order",
+                                                             "What play order to use",
+                                                             "linear",
+                                                             G_PARAM_READABLE));
+       /**
+        * RBShellPlayer:playing:
+        *
+        * Whether Rhythmbox is currently playing something
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_PLAYING,
+                                        g_param_spec_boolean ("playing",
+                                                              "playing",
+                                                             "Whether Rhythmbox is currently playing",
+                                                              FALSE,
+                                                              G_PARAM_READABLE));
+       /**
+        * RBShellPlayer:volume:
+        *
+        * The current playback volume (between 0.0 and 1.0)
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_VOLUME,
+                                        g_param_spec_float ("volume",
+                                                            "volume",
+                                                            "Current playback volume",
+                                                            0.0f, 1.0f, 1.0f,
+                                                            G_PARAM_READWRITE));
 
-       if (entry != NULL) {
-               rhythmdb_entry_unref (entry);
-       }
+       /**
+        * RBShellPlayer:header:
+        *
+        * The #RBHeader object
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_HEADER,
+                                        g_param_spec_object ("header",
+                                                             "RBHeader",
+                                                             "RBHeader object",
+                                                             RB_TYPE_HEADER,
+                                                             G_PARAM_READWRITE));
+       /**
+        * RBShellPlayer:mute:
+        *
+        * Whether playback is currently muted.
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_MUTE,
+                                        g_param_spec_boolean ("mute",
+                                                              "mute",
+                                                              "Whether playback is muted",
+                                                              FALSE,
+                                                              G_PARAM_READWRITE));
+       /**
+        * RBShellPlayer:has-next:
+        *
+        * Whether there is a track to play after the current track.
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_HAS_NEXT,
+                                        g_param_spec_boolean ("has-next",
+                                                              "has-next",
+                                                              "Whether there is a next track",
+                                                              FALSE,
+                                                              G_PARAM_READABLE));
+       /**
+        * RBShellPlayer:has-prev:
+        *
+        * Whether there was a previous track before the current track.
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_HAS_PREV,
+                                        g_param_spec_boolean ("has-prev",
+                                                              "has-prev",
+                                                              "Whether there is a previous track",
+                                                              FALSE,
+                                                              G_PARAM_READABLE));
+
+       /**
+        * RBShellPlayer::window-title-changed:
+        * @player: the #RBShellPlayer
+        * @title: the new window title
+        *
+        * Emitted when the main window title text should be changed
+        */
+       rb_shell_player_signals[WINDOW_TITLE_CHANGED] =
+               g_signal_new ("window_title_changed",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (RBShellPlayerClass, window_title_changed),
+                             NULL, NULL,
+                             g_cclosure_marshal_VOID__STRING,
+                             G_TYPE_NONE,
+                             1,
+                             G_TYPE_STRING);
 
-       return TRUE;
-}
+       /**
+        * RBShellPlayer::elapsed-changed:
+        * @player: the #RBShellPlayer
+        * @elapsed: the new playback position in seconds
+        *
+        * Emitted when the playback position changes.
+        */
+       rb_shell_player_signals[ELAPSED_CHANGED] =
+               g_signal_new ("elapsed_changed",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (RBShellPlayerClass, elapsed_changed),
+                             NULL, NULL,
+                             g_cclosure_marshal_VOID__UINT,
+                             G_TYPE_NONE,
+                             1,
+                             G_TYPE_UINT);
 
-static gboolean
-_idle_unblock_signal_cb (gpointer data)
-{
-       RBShellPlayer *player = (RBShellPlayer *)data;
-       GtkAction *action;
-       gboolean playing;
+       /**
+        * RBShellPlayer::playing-source-changed:
+        * @player: the #RBShellPlayer
+        * @source: the #RBSource that is now playing
+        *
+        * Emitted when a new #RBSource instance starts playing
+        */
+       rb_shell_player_signals[PLAYING_SOURCE_CHANGED] =
+               g_signal_new ("playing-source-changed",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (RBShellPlayerClass, playing_source_changed),
+                             NULL, NULL,
+                             g_cclosure_marshal_VOID__OBJECT,
+                             G_TYPE_NONE,
+                             1,
+                             RB_TYPE_SOURCE);
 
-       GDK_THREADS_ENTER ();
+       /**
+        * RBShellPlayer::playing-changed:
+        * @player: the #RBShellPlayer
+        * @playing: flag indicating playback state
+        *
+        * Emitted when playback either stops or starts.
+        */
+       rb_shell_player_signals[PLAYING_CHANGED] =
+               g_signal_new ("playing-changed",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (RBShellPlayerClass, playing_changed),
+                             NULL, NULL,
+                             g_cclosure_marshal_VOID__BOOLEAN,
+                             G_TYPE_NONE,
+                             1,
+                             G_TYPE_BOOLEAN);
 
-       player->priv->unblock_play_id = 0;
+       /**
+        * RBShellPlayer::playing-song-changed:
+        * @player: the #RBShellPlayer
+        * @entry: the new playing #RhythmDBEntry
+        *
+        * Emitted when the playing database entry changes
+        */
+       rb_shell_player_signals[PLAYING_SONG_CHANGED] =
+               g_signal_new ("playing-song-changed",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (RBShellPlayerClass, playing_song_changed),
+                             NULL, NULL,
+                             g_cclosure_marshal_VOID__BOXED,
+                             G_TYPE_NONE,
+                             1,
+                             RHYTHMDB_TYPE_ENTRY);
 
-       action = gtk_action_group_get_action (player->priv->actiongroup,
-                                             "ControlPlay");
+       /**
+        * RBShellPlayer::playing-uri-changed:
+        * @player: the #RBShellPlayer
+        * @uri: the URI of the new playing entry
+        *
+        * Emitted when the playing database entry changes, providing the
+        * URI of the entry.
+        */
+       rb_shell_player_signals[PLAYING_URI_CHANGED] =
+               g_signal_new ("playing-uri-changed",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (RBShellPlayerClass, playing_uri_changed),
+                             NULL, NULL,
+                             g_cclosure_marshal_VOID__STRING,
+                             G_TYPE_NONE,
+                             1,
+                             G_TYPE_STRING);
 
-       /* sync the active state of the action again */
-       g_object_get (player, "playing", &playing, NULL);
-       gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), playing);
+       /**
+        * RBShellPlayer::playing-song-property-changed:
+        * @player: the #RBShellPlayer
+        * @uri: the URI of the playing entry
+        * @property: the name of the property that changed
+        * @old: the previous value for the property
+        * @newvalue: the new value of the property
+        *
+        * Emitted when a property of the playing database entry changes.
+        */
+       rb_shell_player_signals[PLAYING_SONG_PROPERTY_CHANGED] =
+               g_signal_new ("playing-song-property-changed",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (RBShellPlayerClass, playing_song_property_changed),
+                             NULL, NULL,
+                             rb_marshal_VOID__STRING_STRING_POINTER_POINTER,
+                             G_TYPE_NONE,
+                             4,
+                             G_TYPE_STRING, G_TYPE_STRING,
+                             G_TYPE_VALUE, G_TYPE_VALUE);
 
-       g_signal_handlers_unblock_by_func (action, rb_shell_player_cmd_play, player);
+       /**
+        * RBShellPlayer::elapsed-nano-changed:
+        * @player: the #RBShellPlayer
+        * @elapsed: the new playback position in nanoseconds
+        *
+        * Emitted when the playback position changes.  Only use this (as opposed to
+        * elapsed-changed) when you require subsecond precision.  This signal will be
+        * emitted multiple times per second.
+        */
+       rb_shell_player_signals[ELAPSED_NANO_CHANGED] =
+               g_signal_new ("elapsed-nano-changed",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (RBShellPlayerClass, elapsed_nano_changed),
+                             NULL, NULL,
+                             rb_marshal_VOID__INT64,
+                             G_TYPE_NONE,
+                             1,
+                             G_TYPE_INT64);
 
-       GDK_THREADS_LEAVE ();
-       return FALSE;
+       g_type_class_add_private (klass, sizeof (RBShellPlayerPrivate));
 }
 
-static void
-rb_shell_player_playing_changed_cb (RBShellPlayer *player,
-                                   GParamSpec *arg1,
-                                   gpointer user_data)
+/**
+ * rb_shell_player_new:
+ * @db: the #RhythmDB
+ *
+ * Creates the #RBShellPlayer
+ * 
+ * Return value: the #RBShellPlayer instance
+ */
+RBShellPlayer *
+rb_shell_player_new (RhythmDB *db)
 {
-       GtkAction *action;
-       gboolean playing;
-       char *tooltip;
-
-       g_object_get (player, "playing", &playing, NULL);
-       action = gtk_action_group_get_action (player->priv->actiongroup,
-                                             "ControlPlay");
-       if (playing) {
-               if (rb_source_can_pause (player->priv->source)) {
-                       tooltip = g_strdup (_("Pause playback"));
-               } else {
-                       tooltip = g_strdup (_("Stop playback"));
-               }
-       } else {
-               tooltip = g_strdup (_("Start playback"));
-       }
-       g_object_set (action, "tooltip", tooltip, NULL);
-       g_free (tooltip);
-
-       /* block the signal, so that it doesn't get stuck by triggering recursively,
-        * and don't unblock it until whatever else is happening has finished.
-        * don't block it again if it's already blocked, though.
-        */
-       if (player->priv->unblock_play_id == 0) {
-               g_signal_handlers_block_by_func (action, rb_shell_player_cmd_play, player);
-       }
-       gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), playing);
-
-       if (player->priv->unblock_play_id == 0) {
-               player->priv->unblock_play_id = g_idle_add (_idle_unblock_signal_cb, player);
-       }
+       return g_object_new (RB_TYPE_SHELL_PLAYER,
+                            "db", db,
+                            NULL);
 }
 
 /* This should really be standard. */
@@ -3890,83 +3862,3 @@ rb_shell_player_error_get_type (void)
        return etype;
 }
 
-static void
-_play_order_description_free (RBPlayOrderDescription *order)
-{
-       g_free (order->name);
-       g_free (order->description);
-       g_free (order);
-}
-
-/**
- * rb_play_order_new:
- * @porder_name: Play order type name
- * @player: #RBShellPlayer instance to attach to
- *
- * Creates a new #RBPlayOrder of the specified type.
- *
- * Returns: #RBPlayOrder instance
- **/
-
-#define DEFAULT_PLAY_ORDER "linear"
-
-static RBPlayOrder *
-rb_play_order_new (RBShellPlayer *player, const char* porder_name)
-{
-       RBPlayOrderDescription *order;
-
-       g_return_val_if_fail (porder_name != NULL, NULL);
-       g_return_val_if_fail (player != NULL, NULL);
-
-       order = g_hash_table_lookup (player->priv->play_orders, porder_name);
-
-       if (order == NULL) {
-               g_warning ("Unknown value \"%s\" in GSettings key \"play-order"
-                               "\". Using %s play order.", porder_name, DEFAULT_PLAY_ORDER);
-               order = g_hash_table_lookup (player->priv->play_orders, DEFAULT_PLAY_ORDER);
-       }
-
-       return RB_PLAY_ORDER (g_object_new (order->order_type, "player", player, NULL));
-}
-
-/**
- * rb_shell_player_add_play_order:
- * @player: the #RBShellPlayer
- * @name: name of the new play order
- * @description: description of the new play order
- * @order_type: the #GType of the play order class
- * @hidden: if %TRUE, don't display the play order in the UI
- *
- * Adds a new play order to the set of available play orders.
- */
-void
-rb_shell_player_add_play_order (RBShellPlayer *player, const char *name,
-                               const char *description, GType order_type, gboolean hidden)
-{
-       RBPlayOrderDescription *order;
-
-       g_return_if_fail (g_type_is_a (order_type, RB_TYPE_PLAY_ORDER));
-
-       order = g_new0(RBPlayOrderDescription, 1);
-       order->name = g_strdup (name);
-       order->description = g_strdup (description);
-       order->order_type = order_type;
-       order->is_in_dropdown = !hidden;
-
-       g_hash_table_insert (player->priv->play_orders, order->name, order);
-}
-
-/**
- * rb_shell_player_remove_play_order:
- * @player: the #RBShellPlayer
- * @name: name of the play order to remove
- *
- * Removes a play order previously added with #rb_shell_player_add_play_order
- * from the set of available play orders.
- */
-void
-rb_shell_player_remove_play_order (RBShellPlayer *player, const char *name)
-{
-       g_hash_table_remove (player->priv->play_orders, name);
-}
-
diff --git a/shell/rb-shell-player.h b/shell/rb-shell-player.h
index 217e7df..3b8a5f7 100644
--- a/shell/rb-shell-player.h
+++ b/shell/rb-shell-player.h
@@ -89,9 +89,7 @@ struct _RBShellPlayerClass
 
 GType                  rb_shell_player_get_type   (void);
 
-RBShellPlayer *                rb_shell_player_new             (RhythmDB *db,
-                                                        GtkUIManager *mgr,
-                                                        GtkActionGroup *actiongroup);
+RBShellPlayer *                rb_shell_player_new             (RhythmDB *db);
 
 void                   rb_shell_player_set_selected_source     (RBShellPlayer *player,
                                                                 RBSource *source);
diff --git a/shell/rb-shell.c b/shell/rb-shell.c
index 57da123..6c34f37 100644
--- a/shell/rb-shell.c
+++ b/shell/rb-shell.c
@@ -58,6 +58,7 @@
 #include <X11/XF86keysym.h>
 #endif /* HAVE_MMKEYS */
 
+#include "rb-application.h"
 #include "rb-shell.h"
 #include "rb-debug.h"
 #include "rb-dialog.h"
@@ -98,8 +99,8 @@
 #include "rb-podcast-entry-types.h"
 #include "rb-ext-db.h"
 #include "rb-auto-playlist-source.h"
-
-#include "eggsmclient.h"
+#include "rb-builder-helpers.h"
+#include "rb-display-page-menu.h"
 
 #define UNINSTALLED_PLUGINS_LOCATION "plugins"
 
@@ -117,9 +118,6 @@ static void rb_shell_get_property (GObject *object,
                                   guint prop_id,
                                   GValue *value,
                                   GParamSpec *pspec);
-static void rb_shell_activate (GApplication *app);
-static void rb_shell_open (GApplication *app, GFile **files, int n_files, const char *hint);
-static gboolean rb_shell_local_command_line (GApplication *app, gchar ***args, int *exit_status);
 static gboolean rb_shell_get_visibility (RBShell *shell);
 static gboolean rb_shell_window_state_cb (GtkWidget *widget,
                                          GdkEventWindowState *event,
@@ -158,35 +156,13 @@ 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,
                                                     RBShell *shell);
-static void rb_shell_cmd_about (GtkAction *action,
-                               RBShell *shell);
-static void rb_shell_cmd_contents (GtkAction *action,
-                                  RBShell *shell);
-static void rb_shell_cmd_quit (GtkAction *action,
-                              RBShell *shell);
-static void rb_shell_cmd_preferences (GtkAction *action,
-                                     RBShell *shell);
-static void rb_shell_cmd_plugins (GtkAction *action,
-                                 RBShell *shell);
-static void rb_shell_cmd_add_music (GtkAction *action, RBShell *shell);
-
-static void rb_shell_cmd_current_song (GtkAction *action,
-                                      RBShell *shell);
+static void rb_shell_playing_changed_cb (RBShellPlayer *player, gboolean playing, RBShell *shell);
+
 static void rb_shell_jump_to_current (RBShell *shell);
 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);
-static void rb_shell_view_party_mode_changed_cb (GtkAction *action,
-                                                RBShell *shell);
-static void rb_shell_view_statusbar_changed_cb (GtkAction *action,
-                                               RBShell *shell);
-static void rb_shell_view_queue_as_sidebar_changed_cb (GtkAction *action,
-                                                      RBShell *shell);
 static void rb_shell_load_complete_cb (RhythmDB *db, RBShell *shell);
-static void rb_shell_sync_pane_visibility (RBShell *shell);
-static void rb_shell_sync_statusbar_visibility (RBShell *shell);
 static void rb_shell_set_visibility (RBShell *shell,
                                     gboolean initial,
                                     gboolean visible);
@@ -198,20 +174,18 @@ static void display_page_tree_drag_received_cb (RBDisplayPageTree *display_page_
 static void paned_size_allocate_cb (GtkWidget *widget,
                                    GtkAllocation *allocation,
                                    RBShell *shell);
-static void rb_shell_volume_widget_changed_cb (GtkScaleButton *vol,
-                                              gdouble volume,
-                                              RBShell *shell);
-static void rb_shell_player_volume_changed_cb (RBShellPlayer *player,
-                                              GParamSpec *arg,
-                                              RBShell *shell);
 
-static void rb_shell_session_init (RBShell *shell);
+/*static void view_all_action_cb (GSimpleAction *, GVariant *, gpointer);*/
+static void jump_to_playing_action_cb (GSimpleAction *, GVariant *, gpointer);
+static void add_music_action_cb (GAction *action, GVariant *parameter, RBShell *shell);
+static void view_party_mode_changed_cb (GAction *action, GVariant *parameter, RBShell *shell);
 
 static gboolean rb_shell_visibility_changing (RBShell *shell, gboolean initial, gboolean visible);
 
 enum
 {
        PROP_NONE,
+       PROP_APPLICATION,
        PROP_NO_REGISTRATION,
        PROP_NO_UPDATE,
        PROP_DRY_RUN,
@@ -219,7 +193,7 @@ enum
        PROP_PLAYLISTS_FILE,
        PROP_SELECTED_PAGE,
        PROP_DB,
-       PROP_UI_MANAGER,
+       PROP_ACCEL_GROUP,
        PROP_CLIPBOARD,
        PROP_PLAYLIST_MANAGER,
        PROP_REMOVABLE_MEDIA_MANAGER,
@@ -249,16 +223,15 @@ enum
 
 static guint rb_shell_signals[LAST_SIGNAL] = { 0 };
 
-G_DEFINE_TYPE (RBShell, rb_shell, GTK_TYPE_APPLICATION)
+G_DEFINE_TYPE (RBShell, rb_shell, G_TYPE_OBJECT)
 
 struct _RBShellPrivate
 {
+       RBApplication *application;
        GtkWidget *window;
        gboolean iconified;
 
-       GtkUIManager *ui_manager;
-       GtkActionGroup *actiongroup;
-       guint source_ui_merge_id;
+       GtkAccelGroup *accel_group;
 
        GtkWidget *main_vbox;
        GtkWidget *paned;
@@ -315,9 +288,6 @@ struct _RBShellPrivate
        GtkWidget *prefs;
        GtkWidget *plugins;
 
-       GtkWidget *volume_button;
-       gboolean syncing_volume;
-
        char *cached_title;
        gboolean cached_playing;
 
@@ -330,119 +300,6 @@ struct _RBShellPrivate
        PeasExtensionSet *activatable;
 };
 
-
-static GtkActionEntry rb_shell_actions [] =
-{
-       { "Music", NULL, N_("_Music") },
-       { "Edit", NULL, N_("_Edit") },
-       { "View", NULL, N_("_View") },
-       { "Control", NULL, N_("_Control") },
-       { "Tools", NULL, N_("_Tools") },
-       { "Help", NULL, N_("_Help") },
-
-       { "MusicAdd", GTK_STOCK_OPEN, N_("Add Music..."), "<control>O",
-         N_("Add music to the library"),
-         G_CALLBACK (rb_shell_cmd_add_music) },
-       { "HelpAbout", GTK_STOCK_ABOUT, N_("_About"), NULL,
-         N_("Show information about Rhythmbox"),
-         G_CALLBACK (rb_shell_cmd_about) },
-       { "HelpContents", GTK_STOCK_HELP, N_("_Contents"), "F1",
-         N_("Display Rhythmbox help"),
-         G_CALLBACK (rb_shell_cmd_contents) },
-       { "MusicQuit", GTK_STOCK_QUIT, N_("_Quit"), "<control>Q",
-         N_("Quit Rhythmbox"),
-         G_CALLBACK (rb_shell_cmd_quit) },
-       { "EditPreferences", GTK_STOCK_PREFERENCES, N_("Prefere_nces"), NULL,
-         N_("Edit Rhythmbox preferences"),
-         G_CALLBACK (rb_shell_cmd_preferences) },
-       { "EditPlugins", NULL, N_("Plu_gins"), NULL,
-         N_("Change and configure plugins"),
-         G_CALLBACK (rb_shell_cmd_plugins) },
-       { "ViewAll", NULL, N_("Show _All Tracks"), "<control>Y",
-         N_("Show all tracks in this music source"),
-         G_CALLBACK (rb_shell_cmd_view_all) },
-       { "ViewJumpToPlaying", GTK_STOCK_JUMP_TO, N_("_Jump to Playing Song"), "<control>J",
-         N_("Scroll the view to the currently playing song"),
-         G_CALLBACK (rb_shell_cmd_current_song) },
-};
-static guint rb_shell_n_actions = G_N_ELEMENTS (rb_shell_actions);
-
-static GtkToggleActionEntry rb_shell_toggle_entries [] =
-{
-       { "ViewSidePane", NULL, N_("Side _Pane"), "F9",
-         N_("Change the visibility of the side pane"),
-         NULL, TRUE },
-       { "ViewPartyMode", NULL, N_("Party _Mode"), "F11",
-         N_("Change the status of the party mode"),
-         G_CALLBACK (rb_shell_view_party_mode_changed_cb), FALSE },
-       { "ViewQueueAsSidebar", NULL, N_("Play _Queue as Side Pane"), "<control>K",
-         N_("Change whether the queue is visible as a source or a sidebar"),
-         G_CALLBACK (rb_shell_view_queue_as_sidebar_changed_cb) },
-        { "ViewStatusbar", NULL, N_("S_tatusbar"), NULL,
-         N_("Change the visibility of the statusbar"),
-         G_CALLBACK (rb_shell_view_statusbar_changed_cb), TRUE },
-       { "ViewSongPositionSlider", NULL, N_("_Song Position Slider"), NULL,
-         N_("Change the visibility of the song position slider"),
-         NULL, TRUE },
-       { "ViewAlbumArt", NULL, N_("_Album Art"), NULL,
-         N_("Change the visibility of the album art display"),
-         NULL, TRUE },
-        { "ViewBrowser", NULL, N_("_Browse"), "<control>B",
-         N_("Change the visibility of the browser"),
-         NULL, TRUE }
-};
-static guint rb_shell_n_toggle_entries = G_N_ELEMENTS (rb_shell_toggle_entries);
-
-static void
-rb_shell_activate (GApplication *app)
-{
-       rb_shell_present (RB_SHELL (app), gtk_get_current_event_time (), NULL);
-}
-
-static void
-rb_shell_open (GApplication *app, GFile **files, int n_files, const char *hint)
-{
-       int i;
-
-       for (i = 0; i < n_files; i++) {
-               char *uri;
-
-               uri = g_file_get_uri (files[i]);
-
-               /*
-                * rb_uri_exists won't work if the location isn't mounted.
-                * however, things that are interesting to mount are generally
-                * non-local, so we'll process them anyway.
-                */
-               if (rb_uri_is_local (uri) == FALSE || rb_uri_exists (uri)) {
-                       rb_shell_load_uri (RB_SHELL (app), uri, TRUE, NULL);
-               }
-               g_free (uri);
-       }
-}
-
-static void
-load_state_changed_cb (GActionGroup *action_group, const char *action_name, GVariant *state, GPtrArray 
*files)
-{
-       gboolean loaded;
-       gboolean scanned;
-
-       if (g_strcmp0 (action_name, "LoadURI") != 0) {
-               return;
-       }
-
-       g_variant_get (state, "(bb)", &loaded, &scanned);
-       if (loaded) {
-               rb_debug ("opening files now");
-               g_signal_handlers_disconnect_by_func (action_group, load_state_changed_cb, files);
-
-               g_application_open (G_APPLICATION (action_group), (GFile **)files->pdata, files->len, "");
-               g_ptr_array_free (files, TRUE);
-       }
-}
-
-
-
 static GMountOperation *
 rb_shell_create_mount_op_cb (RhythmDB *db, RBShell *shell)
 {
@@ -597,6 +454,23 @@ construct_db (RBShell *shell)
        g_signal_connect (shell->priv->art_store, "store", G_CALLBACK (store_external_art_cb), shell);
 
        rb_profile_end ("creating database object");
+
+       /* do the playlist manager too, since we need this before creating the play queue */
+       if (shell->priv->playlists_file) {
+               pathname = g_strdup (shell->priv->playlists_file);
+       } else {
+               pathname = rb_find_user_data_file ("playlists.xml");
+       }
+
+       shell->priv->playlist_manager = rb_playlist_manager_new (shell, pathname);
+
+       g_free (pathname);
+
+       g_signal_connect_object (G_OBJECT (shell->priv->playlist_manager), "playlist_added",
+                                G_CALLBACK (rb_shell_playlist_added_cb), shell, 0);
+       g_signal_connect_object (G_OBJECT (shell->priv->playlist_manager), "playlist_created",
+                                G_CALLBACK (rb_shell_playlist_created_cb), shell, 0);
+
 }
 
 static void
@@ -607,7 +481,7 @@ construct_widgets (RBShell *shell)
        rb_profile_start ("constructing widgets");
 
        /* initialize UI */
-       win = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL));
+       win = GTK_WINDOW (gtk_application_window_new (GTK_APPLICATION (shell->priv->application)));
        gtk_window_set_title (win, _("Rhythmbox"));
 
        shell->priv->window = GTK_WIDGET (win);
@@ -633,27 +507,27 @@ construct_widgets (RBShell *shell)
 
        shell->priv->podcast_manager = rb_podcast_manager_new (shell->priv->db);
        shell->priv->track_transfer_queue = rb_track_transfer_queue_new (shell);
-       shell->priv->ui_manager = gtk_ui_manager_new ();
-       shell->priv->source_ui_merge_id = gtk_ui_manager_new_merge_id (shell->priv->ui_manager);
+       shell->priv->accel_group = gtk_accel_group_new ();
+       gtk_window_add_accel_group (win, shell->priv->accel_group);
 
-       shell->priv->player_shell = rb_shell_player_new (shell->priv->db,
-                                                        shell->priv->ui_manager,
-                                                        shell->priv->actiongroup);
-       g_signal_connect_object (G_OBJECT (shell->priv->player_shell),
+       shell->priv->player_shell = rb_shell_player_new (shell->priv->db);
+       g_signal_connect_object (shell->priv->player_shell,
                                 "playing-source-changed",
                                 G_CALLBACK (rb_shell_playing_source_changed_cb),
                                 shell, 0);
-       g_signal_connect_object (G_OBJECT (shell->priv->player_shell),
+       g_signal_connect_object (shell->priv->player_shell,
                                 "notify::playing-from-queue",
                                 G_CALLBACK (rb_shell_playing_from_queue_cb),
                                 shell, 0);
-       g_signal_connect_object (G_OBJECT (shell->priv->player_shell),
+       g_signal_connect_object (shell->priv->player_shell,
                                 "window_title_changed",
                                 G_CALLBACK (rb_shell_player_window_title_changed_cb),
                                 shell, 0);
-       shell->priv->clipboard_shell = rb_shell_clipboard_new (shell->priv->actiongroup,
-                                                              shell->priv->ui_manager,
-                                                              shell->priv->db);
+       g_signal_connect_object (shell->priv->player_shell,
+                                "playing-changed",
+                                G_CALLBACK (rb_shell_playing_changed_cb),
+                                shell, 0);
+       shell->priv->clipboard_shell = rb_shell_clipboard_new (shell->priv->db);
 
        shell->priv->display_page_tree = rb_display_page_tree_new (shell);
        gtk_widget_show_all (GTK_WIDGET (shell->priv->display_page_tree));
@@ -662,13 +536,13 @@ construct_widgets (RBShell *shell)
        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->header = rb_header_new (shell->priv->player_shell, shell->priv->db);
        g_object_set (shell->priv->player_shell, "header", shell->priv->header, NULL);
        gtk_widget_show (GTK_WIDGET (shell->priv->header));
        g_settings_bind (shell->priv->settings, "time-display", shell->priv->header, "show-remaining", 
G_SETTINGS_BIND_DEFAULT);
 
        shell->priv->statusbar = rb_statusbar_new (shell->priv->db,
-                                                  shell->priv->ui_manager,
                                                   shell->priv->track_transfer_queue);
        gtk_widget_show (GTK_WIDGET (shell->priv->statusbar));
 
@@ -772,7 +646,6 @@ static void
 construct_sources (RBShell *shell)
 {
        RBDisplayPage *page_group;
-       char *pathname;
 
        rb_profile_start ("constructing sources");
 
@@ -791,34 +664,8 @@ construct_sources (RBShell *shell)
        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_auto_playlist_source_create_actions (shell);
-       rb_static_playlist_source_create_actions (shell);
-
        rb_podcast_main_source_add_subsources (RB_PODCAST_MAIN_SOURCE (shell->priv->podcast_source));
 
-       /* Find the playlist name if none supplied */
-       if (shell->priv->playlists_file) {
-               pathname = g_strdup (shell->priv->playlists_file);
-       } else {
-               pathname = rb_find_user_data_file ("playlists.xml");
-       }
-
-       /* Initialize playlist manager */
-       rb_debug ("shell: creating playlist manager");
-       shell->priv->playlist_manager = rb_playlist_manager_new (shell,
-                                                                shell->priv->display_page_model,
-                                                                shell->priv->display_page_tree,
-                                                                pathname);
-
-       g_object_set (shell->priv->clipboard_shell,
-                     "playlist-manager", shell->priv->playlist_manager,
-                     NULL);
-
-       g_signal_connect_object (G_OBJECT (shell->priv->playlist_manager), "playlist_added",
-                                G_CALLBACK (rb_shell_playlist_added_cb), shell, 0);
-       g_signal_connect_object (G_OBJECT (shell->priv->playlist_manager), "playlist_created",
-                                G_CALLBACK (rb_shell_playlist_created_cb), shell, 0);
-
        /* Initialize removable media manager */
        rb_debug ("shell: creating removable media manager");
        shell->priv->removable_media_manager = rb_removable_media_manager_new (shell);
@@ -826,51 +673,37 @@ construct_sources (RBShell *shell)
        g_signal_connect_object (G_OBJECT (shell->priv->removable_media_manager), "medium_added",
                                 G_CALLBACK (rb_shell_medium_added_cb), shell, 0);
 
-
-       g_free (pathname);
-
        rb_profile_end ("constructing sources");
 }
 
 static void
 construct_load_ui (RBShell *shell)
 {
-       GtkWidget *menubar;
        GtkWidget *toolbar;
        GtkToolItem *tool_item;
-       GError *error = NULL;
+       GtkBuilder *builder;
+       gboolean shell_shows_app_menu;
 
        rb_debug ("shell: loading ui");
        rb_profile_start ("loading ui");
 
-       gtk_ui_manager_insert_action_group (shell->priv->ui_manager,
-                                           shell->priv->actiongroup, 0);
-       gtk_ui_manager_add_ui_from_file (shell->priv->ui_manager,
-                                        rb_file ("rhythmbox-ui.xml"), &error);
-
-       gtk_ui_manager_ensure_update (shell->priv->ui_manager);
-       gtk_window_add_accel_group (GTK_WINDOW (shell->priv->window),
-                                   gtk_ui_manager_get_accel_group (shell->priv->ui_manager));
-       menubar = gtk_ui_manager_get_widget (shell->priv->ui_manager, "/MenuBar");
+       builder = rb_builder_load ("main-toolbar.ui", NULL);
+       toolbar = GTK_WIDGET (gtk_builder_get_object (builder, "main-toolbar"));
 
-       gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), menubar, FALSE, FALSE, 0);
-       gtk_box_reorder_child (GTK_BOX (shell->priv->main_vbox), menubar, 0);
+       /* this seems a bit unnecessary */
+       gtk_actionable_set_action_target_value (GTK_ACTIONABLE (gtk_builder_get_object (builder, 
"play-button")),
+                                               g_variant_new_boolean (TRUE));
+       gtk_actionable_set_action_target_value (GTK_ACTIONABLE (gtk_builder_get_object (builder, 
"shuffle-button")),
+                                               g_variant_new_boolean (TRUE));
+       gtk_actionable_set_action_target_value (GTK_ACTIONABLE (gtk_builder_get_object (builder, 
"repeat-button")),
+                                               g_variant_new_boolean (TRUE));
 
-       toolbar = gtk_ui_manager_get_widget (shell->priv->ui_manager, "/ToolBar");
        gtk_style_context_add_class (gtk_widget_get_style_context (toolbar),
                                     GTK_STYLE_CLASS_PRIMARY_TOOLBAR);
-       gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_BOTH);
        gtk_box_pack_start (GTK_BOX (shell->priv->main_vbox), toolbar, FALSE, FALSE, 0);
        gtk_box_reorder_child (GTK_BOX (shell->priv->main_vbox), toolbar, 1);
 
-       shell->priv->volume_button = gtk_volume_button_new ();
-       g_signal_connect (shell->priv->volume_button, "value-changed",
-                         G_CALLBACK (rb_shell_volume_widget_changed_cb),
-                         shell);
-       g_signal_connect (shell->priv->player_shell, "notify::volume",
-                         G_CALLBACK (rb_shell_player_volume_changed_cb),
-                         shell);
-       rb_shell_player_volume_changed_cb (shell->priv->player_shell, NULL, shell);
+       g_object_unref (builder);
 
        tool_item = gtk_tool_item_new ();
        gtk_tool_item_set_expand (tool_item, TRUE);
@@ -878,18 +711,27 @@ construct_load_ui (RBShell *shell)
        gtk_widget_show_all (GTK_WIDGET (tool_item));
        gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tool_item, -1);
 
-       tool_item = gtk_tool_item_new ();
-       gtk_container_add (GTK_CONTAINER (tool_item), shell->priv->volume_button);
-       gtk_widget_show_all (GTK_WIDGET (tool_item));
-       gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tool_item, -1);
+       g_object_get (gtk_settings_get_default (),
+                     "gtk-shell-shows-app-menu", &shell_shows_app_menu,
+                     NULL);
+       if (shell_shows_app_menu == FALSE) {
+               GtkWidget *menu_button;
+               GtkWidget *image;
+               GMenuModel *model;
+               GApplication *app = g_application_get_default ();
 
-       gtk_widget_set_tooltip_text (shell->priv->volume_button,
-                                    _("Change the music volume"));
+               menu_button = gtk_menu_button_new ();
 
-       if (error != NULL) {
-               g_warning ("Couldn't merge %s: %s",
-                          rb_file ("rhythmbox-ui.xml"), error->message);
-               g_clear_error (&error);
+               model = rb_application_get_shared_menu (RB_APPLICATION (app), "app-menu");
+               gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (menu_button), model);
+
+               image = gtk_image_new_from_icon_name ("emblem-system-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
+               gtk_container_add (GTK_CONTAINER (menu_button), image);
+
+               tool_item = gtk_tool_item_new ();
+               gtk_container_add (GTK_CONTAINER (tool_item), menu_button);
+               gtk_widget_show_all (GTK_WIDGET (tool_item));
+               gtk_toolbar_insert (GTK_TOOLBAR (toolbar), tool_item, -1);
        }
 
        rb_profile_end ("loading ui");
@@ -1075,178 +917,51 @@ construct_plugins (RBShell *shell)
 static gboolean
 _scan_idle (RBShell *shell)
 {
-       gboolean loaded, scanned;
 
        GDK_THREADS_ENTER ();
        rb_removable_media_manager_scan (shell->priv->removable_media_manager);
        GDK_THREADS_LEAVE ();
 
        if (shell->priv->no_registration == FALSE) {
-               g_variant_get (g_action_group_get_action_state (G_ACTION_GROUP (shell), "LoadURI"), "(bb)", 
&loaded, &scanned);
-               g_action_group_change_action_state (G_ACTION_GROUP (shell), "LoadURI", g_variant_new ("(bb)", 
loaded, TRUE));
-       }
-
-       return FALSE;
-}
-
-static void
-rb_shell_startup (GApplication *app)
-{
-       RBShell *shell = RB_SHELL (app);
-       GtkAction *gtkaction;
-       RBEntryView *view;
-
-       rb_debug ("Constructing shell");
-       rb_profile_start ("constructing shell");
-
-       gtkaction = gtk_action_group_get_action (shell->priv->actiongroup, "ViewSidePane");
-       g_settings_bind (shell->priv->settings, "display-page-tree-visible",
-                        gtkaction, "active",
-                        G_SETTINGS_BIND_DEFAULT);
-       g_settings_bind (shell->priv->settings, "display-page-tree-visible",
-                        shell->priv->sidebar_container, "visible",
-                        G_SETTINGS_BIND_DEFAULT);
-
-       gtkaction = gtk_action_group_get_action (shell->priv->actiongroup, "ViewSongPositionSlider");
-       g_settings_bind (shell->priv->settings, "show-song-position-slider",
-                        gtkaction, "active",
-                        G_SETTINGS_BIND_DEFAULT);
-       g_settings_bind (shell->priv->settings, "show-song-position-slider",
-                        shell->priv->header, "show-position-slider",
-                        G_SETTINGS_BIND_DEFAULT);
-
-       gtkaction = gtk_action_group_get_action (shell->priv->actiongroup, "ViewAlbumArt");
-       g_settings_bind (shell->priv->settings, "show-album-art",
-                        gtkaction, "active",
-                        G_SETTINGS_BIND_DEFAULT);
-       g_settings_bind (shell->priv->settings, "show-album-art",
-                        shell->priv->header, "show-album-art",
-                        G_SETTINGS_BIND_DEFAULT);
-
-       rb_debug ("shell: syncing with settings");
-       rb_shell_sync_pane_visibility (shell);
-
-       g_signal_connect_object (G_OBJECT (shell->priv->db), "save-error",
-                                G_CALLBACK (rb_shell_db_save_error_cb), shell, 0);
-
-       construct_sources (shell);
+               gboolean loaded, scanned;
+               GVariant *state;
 
-       construct_load_ui (shell);
+               state = g_action_group_get_action_state (G_ACTION_GROUP (shell->priv->application), 
"load-uri");
+               g_variant_get (state, "(bb)", &loaded, &scanned);
+               g_action_group_change_action_state (G_ACTION_GROUP (shell->priv->application),
+                                                   "load-uri",
+                                                   g_variant_new ("(bb)", loaded, TRUE));
 
-       construct_plugins (shell);
-
-       rb_shell_sync_window_state (shell, FALSE);
-       rb_shell_sync_party_mode (shell);
-
-       rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source));
-
-       /* 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);
-
-       /* GO GO GO! */
-       rb_debug ("loading database");
-       rhythmdb_load (shell->priv->db);
-
-       rb_debug ("shell: syncing window state");
-       rb_shell_sync_paned (shell);
-
-       /* set initial visibility */
-       rb_shell_set_visibility (shell, TRUE, TRUE);
-
-       gdk_notify_startup_complete ();
-
-       view = rb_source_get_entry_view (RB_SOURCE (shell->priv->library_source));
-       if (view != NULL) {
-               gtk_widget_grab_focus (GTK_WIDGET (view));
+               g_variant_unref (state);
        }
 
-       rb_profile_end ("constructing shell");
-
-       /* window-based usage counting doesn't work for us, just hold the app until
-        * we're asked to quit.
-        */
-       g_application_hold (app);
-
-       (* G_APPLICATION_CLASS (rb_shell_parent_class)->startup) (app);
+       return FALSE;
 }
 
-static gboolean
-rb_shell_local_command_line (GApplication *app, gchar ***args, int *exit_status)
-{
-       RBShell *shell;
-       GError *error = NULL;
-       gboolean scanned;
-       gboolean loaded;
-       GPtrArray *files;
-       int n_files;
-       int i;
-
-       n_files = g_strv_length (*args) - 1;
-
-       shell = RB_SHELL (app);
-       if (shell->priv->no_registration) {
-               if (n_files > 0) {
-                       g_warning ("Unable to open files on the commandline with --no-registration");
-               }
-               rb_shell_startup (app);
-               return TRUE;
-       }
-
-       if (!g_application_register (app, NULL, &error)) {
-               g_critical ("%s", error->message);
-               g_error_free (error);
-               *exit_status = 1;
-               return TRUE;
-       }
-
-       if (n_files <= 0) {
-               g_application_activate (app);
-               *exit_status = 0;
-               return TRUE;
-       }
-
-       files = g_ptr_array_new_with_free_func (g_object_unref);
-       for (i = 0; i < n_files; i++) {
-               g_ptr_array_add (files, g_file_new_for_commandline_arg ((*args)[i + 1]));
-       }
-
-       g_variant_get (g_action_group_get_action_state (G_ACTION_GROUP (app), "LoadURI"), "(bb)", &loaded, 
&scanned);
-       if (loaded) {
-               rb_debug ("opening files immediately");
-               g_application_open (app, (GFile **)files->pdata, files->len, "");
-               g_ptr_array_free (files, TRUE);
-       } else {
-               rb_debug ("opening files once db is loaded");
-               g_signal_connect (app, "action-state-changed::LoadURI", G_CALLBACK (load_state_changed_cb), 
files);
-       }
-
-       return TRUE;
-}
 static void
 rb_shell_class_init (RBShellClass *klass)
 {
        GObjectClass *object_class = G_OBJECT_CLASS (klass);
-       GApplicationClass *app_class = G_APPLICATION_CLASS (klass);
 
        object_class->set_property = rb_shell_set_property;
        object_class->get_property = rb_shell_get_property;
         object_class->finalize = rb_shell_finalize;
        object_class->constructed = rb_shell_constructed;
 
-       app_class->activate = rb_shell_activate;
-       app_class->open = rb_shell_open;
-       app_class->local_command_line = rb_shell_local_command_line;
-       app_class->startup = rb_shell_startup;
-
        klass->visibility_changing = rb_shell_visibility_changing;
 
+       /*
+        * RBShell:application:
+        *
+        * The #RBApplication instance
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_APPLICATION,
+                                        g_param_spec_object ("application",
+                                                             "application",
+                                                             "RBApplication instance",
+                                                             RB_TYPE_APPLICATION,
+                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
        /**
         * RBShell:no-registration:
         *
@@ -1333,16 +1048,16 @@ rb_shell_class_init (RBShellClass *klass)
                                                              RHYTHMDB_TYPE,
                                                              G_PARAM_READABLE));
        /**
-        * RBShell:ui-manager:
+        * RBShell:accel-group:
         *
-        * The #GtkUIManager instance
+        * A #GtkAccelGroup instance to use for additional accelerator keys
         */
        g_object_class_install_property (object_class,
-                                        PROP_UI_MANAGER,
-                                        g_param_spec_object ("ui-manager",
-                                                             "GtkUIManager",
-                                                             "GtkUIManager object",
-                                                             GTK_TYPE_UI_MANAGER,
+                                        PROP_ACCEL_GROUP,
+                                        g_param_spec_object ("accel-group",
+                                                             "GtkAccelGroup",
+                                                             "GtkAccelGroup object",
+                                                             GTK_TYPE_ACCEL_GROUP,
                                                              G_PARAM_READABLE));
        /**
         * RBShell:clipboard:
@@ -1620,20 +1335,6 @@ static void
 rb_shell_init (RBShell *shell)
 {
        shell->priv = G_TYPE_INSTANCE_GET_PRIVATE (shell, RB_TYPE_SHELL, RBShellPrivate);
-
-       rb_user_data_dir ();
-       rb_refstring_system_init ();
-
-#ifdef USE_UNINSTALLED_DIRS
-       rb_file_helpers_init (TRUE);
-#else
-       rb_file_helpers_init (FALSE);
-#endif
-       rb_stock_icons_init ();
-
-        rb_shell_session_init (shell);
-
-       g_setenv ("PULSE_PROP_media.role", "music", TRUE);
 }
 
 static void
@@ -1646,6 +1347,9 @@ rb_shell_set_property (GObject *object,
 
        switch (prop_id)
        {
+       case PROP_APPLICATION:
+               shell->priv->application = g_value_get_object (value);
+               break;
        case PROP_NO_REGISTRATION:
                shell->priv->no_registration = g_value_get_boolean (value);
                break;
@@ -1690,6 +1394,9 @@ rb_shell_get_property (GObject *object,
 
        switch (prop_id)
        {
+       case PROP_APPLICATION:
+               g_value_set_object (value, shell->priv->application);
+               break;
        case PROP_NO_REGISTRATION:
                g_value_set_boolean (value, shell->priv->no_registration);
                break;
@@ -1708,8 +1415,8 @@ rb_shell_get_property (GObject *object,
        case PROP_DB:
                g_value_set_object (value, shell->priv->db);
                break;
-       case PROP_UI_MANAGER:
-               g_value_set_object (value, shell->priv->ui_manager);
+       case PROP_ACCEL_GROUP:
+               g_value_set_object (value, shell->priv->accel_group);
                break;
        case PROP_CLIPBOARD:
                g_value_set_object (value, shell->priv->clipboard_shell);
@@ -1914,163 +1621,130 @@ rb_shell_finalize (GObject *object)
                shell->priv->art_store = NULL;
        }
 
-       rb_file_helpers_shutdown ();
-       rb_stock_icons_shutdown ();
-       rb_refstring_system_shutdown ();
-
         G_OBJECT_CLASS (rb_shell_parent_class)->finalize (object);
 
        rb_debug ("shell shutdown complete");
 }
 
-/**
- * rb_shell_new:
- * @autostarted: %TRUE if autostarted by the session manager
- * @argc: a pointer to the number of command line arguments
- * @argv: a pointer to the array of command line arguments
- *
- * Creates the Rhythmbox shell.  This is effectively a singleton, so it doesn't
- * make sense to call this from anywhere other than main.c.
- *
- * Return value: the #RBShell instance
- */
-RBShell *
-rb_shell_new (gboolean autostarted, int *argc, char ***argv)
-{
-       GOptionContext *context;
-       gboolean debug = FALSE;
-       char *debug_match = NULL;
-       gboolean no_update = FALSE;
-       gboolean no_registration = FALSE;
-       gboolean dry_run = FALSE;
-       gboolean disable_plugins = FALSE;
-       char *rhythmdb_file = NULL;
-       char *playlists_file = NULL;
-       GError *error = NULL;
-
-       const GOptionEntry options []  = {
-               { "debug",           'd', 0, G_OPTION_ARG_NONE,         &debug,           N_("Enable debug 
output"), NULL },
-               { "debug-match",     'D', 0, G_OPTION_ARG_STRING,       &debug_match,     N_("Enable debug 
output matching a specified string"), NULL },
-               { "no-update",         0, 0, G_OPTION_ARG_NONE,         &no_update,       N_("Do not update 
the library with file changes"), NULL },
-               { "no-registration", 'n', 0, G_OPTION_ARG_NONE,         &no_registration, N_("Do not register 
the shell"), NULL },
-               { "dry-run",           0, 0, G_OPTION_ARG_NONE,         &dry_run,         N_("Don't save any 
data permanently (implies --no-registration)"), NULL },
-               { "disable-plugins",   0, 0, G_OPTION_ARG_NONE,         &disable_plugins, N_("Disable loading 
of plugins"), NULL },
-               { "rhythmdb-file",     0, 0, G_OPTION_ARG_STRING,       &rhythmdb_file,   N_("Path for 
database file to use"), NULL },
-               { "playlists-file",    0, 0, G_OPTION_ARG_STRING,       &playlists_file,   N_("Path for 
playlists file to use"), NULL },
-               { NULL }
-       };
-
-       context = g_option_context_new (NULL);
-       g_option_context_add_main_entries (context, options, GETTEXT_PACKAGE);
-       g_option_context_add_group (context, gst_init_get_option_group ());
-       g_option_context_add_group (context, egg_sm_client_get_option_group ());
-       g_option_context_add_group (context, gtk_get_option_group (TRUE));
-
-       if (g_option_context_parse (context, argc, argv, &error) == FALSE) {
-               g_print (_("%s\nRun '%s --help' to see a full list of available command line options.\n"),
-                        error->message, (*argv)[0]);
-               g_error_free (error);
-               g_option_context_free (context);
-               exit (1);
-       }
-       g_option_context_free (context);
-
-       if (!debug && debug_match)
-               rb_debug_init_match (debug_match);
-       else
-               rb_debug_init (debug);
-
-       return g_object_new (RB_TYPE_SHELL,
-                            "application-id", "org.gnome.Rhythmbox3",
-                            "flags", G_APPLICATION_HANDLES_OPEN,
-                            "autostarted", autostarted,
-                            "no-registration", no_registration,
-                            "no-update", no_update,
-                            "dry-run", dry_run,
-                            "rhythmdb-file", rhythmdb_file,
-                            "playlists-file", playlists_file,
-                            "disable-plugins", disable_plugins,
-                            NULL);
-}
 
 static void
-load_uri_action_cb (GSimpleAction *action, GVariant *parameters, RBShell *shell)
+rb_shell_constructed (GObject *object)
 {
-       const char *uri;
-       gboolean play;
+       RBShell *shell;
+       GAction *action;
+       RBEntryView *view;
 
-       g_variant_get (parameters, "(&sb)", &uri, &play);
+       /* need this? */
+       gtk_init (NULL, NULL);
 
-       rb_shell_load_uri (shell, uri, play, NULL);
-}
+       RB_CHAIN_GOBJECT_METHOD (rb_shell_parent_class, constructed, object);
 
-static void
-activate_source_action_cb (GSimpleAction *action, GVariant *parameters, RBShell *shell)
-{
-       const char *source;
-       guint play;
+       shell = RB_SHELL (object);
 
-       g_variant_get (parameters, "(&su)", &source, &play);
-       rb_shell_activate_source_by_uri (shell, source, play, NULL);
-}
+       /* construct enough of the rest of it to display the window if required */
 
-static void
-quit_action_cb (GSimpleAction *action, GVariant *parameters, RBShell *shell)
-{
-       rb_shell_quit (shell, NULL);
-}
+       shell->priv->settings = g_settings_new ("org.gnome.rhythmbox");
 
-static void
-rb_shell_constructed (GObject *object)
-{
-       RBShell *shell;
-       GSimpleAction *action;
+       construct_db (shell);
 
-       gtk_init (NULL, NULL);
+       rb_debug ("Constructing shell");
+       rb_profile_start ("constructing shell");
 
-       RB_CHAIN_GOBJECT_METHOD (rb_shell_parent_class, constructed, object);
+       construct_widgets (shell);
 
-       shell = RB_SHELL (object);
+       action = g_settings_create_action (shell->priv->settings, "display-page-tree-visible");
+       g_action_map_add_action (G_ACTION_MAP (shell->priv->window), action);
+       g_settings_bind (shell->priv->settings, "display-page-tree-visible",
+                        shell->priv->sidebar_container, "visible",
+                        G_SETTINGS_BIND_DEFAULT);
 
-       /* create application actions */
-       action = g_simple_action_new_stateful ("LoadURI", G_VARIANT_TYPE ("(sb)"), g_variant_new ("(bb)", 
FALSE, FALSE));
-       g_signal_connect_object (action, "activate", G_CALLBACK (load_uri_action_cb), shell, 0);
-       g_action_map_add_action (G_ACTION_MAP (shell), G_ACTION (action));
-       g_object_unref (action);
+       action = g_settings_create_action (shell->priv->settings, "show-song-position-slider");
+       g_action_map_add_action (G_ACTION_MAP (shell->priv->window), action);
+       g_settings_bind (shell->priv->settings, "show-song-position-slider",
+                        shell->priv->header, "show-position-slider",
+                        G_SETTINGS_BIND_DEFAULT);
 
-       action = g_simple_action_new ("ActivateSource", G_VARIANT_TYPE ("(su)"));
-       g_signal_connect_object (action, "activate", G_CALLBACK (activate_source_action_cb), shell, 0);
-       g_action_map_add_action (G_ACTION_MAP (shell), G_ACTION (action));
-       g_object_unref (action);
+       action = g_settings_create_action (shell->priv->settings, "show-album-art");
+       g_action_map_add_action (G_ACTION_MAP (shell->priv->window), action);
+       g_settings_bind (shell->priv->settings, "show-album-art",
+                        shell->priv->header, "show-album-art",
+                        G_SETTINGS_BIND_DEFAULT);
 
-       action = g_simple_action_new ("Quit", NULL);
-       g_signal_connect_object (action, "activate", G_CALLBACK (quit_action_cb), shell, 0);
-       g_action_map_add_action (G_ACTION_MAP (shell), G_ACTION (action));
-       g_object_unref (action);
+       action = g_settings_create_action (shell->priv->settings, "queue-as-sidebar");
+       g_action_map_add_action (G_ACTION_MAP (shell->priv->window), action);
+       g_settings_bind (shell->priv->settings, "queue-as-sidebar",
+                        shell->priv->queue_sidebar, "visible",
+                        G_SETTINGS_BIND_DEFAULT);
+       g_settings_bind (shell->priv->settings, "queue-as-sidebar",
+                        shell->priv->queue_source, "visibility",
+                        G_SETTINGS_BIND_INVERT_BOOLEAN);
+
+       action = g_settings_create_action (shell->priv->settings, "statusbar-visible");
+       g_action_map_add_action (G_ACTION_MAP (shell->priv->window), action);
+       g_settings_bind (shell->priv->settings, "statusbar-visible",
+                        shell->priv->statusbar, "visible",
+                        G_SETTINGS_BIND_DEFAULT);
 
-       /* construct enough of the rest of it to display the window if required */
+       action = G_ACTION (g_simple_action_new_stateful ("party-mode",
+                                                        NULL,
+                                                        g_variant_new_boolean (FALSE)));
+       g_action_map_add_action (G_ACTION_MAP (shell->priv->window), action);
+       g_signal_connect (action, "activate", G_CALLBACK (view_party_mode_changed_cb), shell);
 
-       shell->priv->settings = g_settings_new ("org.gnome.rhythmbox");
+       action = G_ACTION (g_simple_action_new ("library-import", NULL));
+       g_signal_connect (action, "activate", G_CALLBACK (add_music_action_cb), shell);
+       g_action_map_add_action (G_ACTION_MAP (g_application_get_default ()), action);
 
-       shell->priv->actiongroup = gtk_action_group_new ("MainActions");
-       gtk_action_group_set_translation_domain (shell->priv->actiongroup,
-                                                GETTEXT_PACKAGE);
-       gtk_action_group_add_actions (shell->priv->actiongroup,
-                                     rb_shell_actions,
-                                     rb_shell_n_actions, shell);
-       gtk_action_group_add_toggle_actions (shell->priv->actiongroup,
-                                            rb_shell_toggle_entries,
-                                            rb_shell_n_toggle_entries,
-                                            shell);
-
-       /* Translators: this is the short label for the 'add music' action */
-       gtk_action_set_short_label (gtk_action_group_get_action (shell->priv->actiongroup, "MusicAdd"), 
C_("Library", "Import"));
-       /* Translators: this is the short label for the 'view all tracks' action */
-       gtk_action_set_short_label (gtk_action_group_get_action (shell->priv->actiongroup, "ViewAll"), 
_("Show All"));
+       action = G_ACTION (g_simple_action_new ("jump-to-playing", NULL));
+       g_signal_connect (action, "activate", G_CALLBACK (jump_to_playing_action_cb), shell);
+       g_action_map_add_action (G_ACTION_MAP (shell->priv->window), action);
 
-       construct_db (shell);
 
-       construct_widgets (shell);
+       rb_debug ("shell: syncing with settings");
+
+       g_signal_connect_object (G_OBJECT (shell->priv->db), "save-error",
+                                G_CALLBACK (rb_shell_db_save_error_cb), shell, 0);
+
+
+       construct_sources (shell);
+
+       construct_load_ui (shell);
+
+       construct_plugins (shell);
+
+       rb_shell_sync_window_state (shell, FALSE);
+       rb_shell_sync_party_mode (shell);
+
+       rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source));
+
+       /* 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);
+
+       /* GO GO GO! */
+       rb_debug ("loading database");
+       rhythmdb_load (shell->priv->db);
+
+       rb_debug ("shell: syncing window state");
+       rb_shell_sync_paned (shell);
+
+       /* set initial visibility */
+       rb_shell_set_visibility (shell, TRUE, TRUE);
+
+       gdk_notify_startup_complete ();
+
+       view = rb_source_get_entry_view (RB_SOURCE (shell->priv->library_source));
+       if (view != NULL) {
+               gtk_widget_grab_focus (GTK_WIDGET (view));
+       }
+
+       rb_profile_end ("constructing shell");
 }
 
 static gboolean
@@ -2543,6 +2217,12 @@ rb_shell_playing_from_queue_cb (RBShellPlayer *player,
 }
 
 static void
+rb_shell_playing_changed_cb (RBShellPlayer *player, gboolean playing, RBShell *shell)
+{
+       /* update tooltip on play/pause button */
+}
+
+static void
 rb_shell_select_page (RBShell *shell, RBDisplayPage *page)
 {
        int pagenum;
@@ -2554,7 +2234,6 @@ rb_shell_select_page (RBShell *shell, RBDisplayPage *page)
 
        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_page = page;
@@ -2579,13 +2258,11 @@ rb_shell_select_page (RBShell *shell, RBDisplayPage *page)
                rb_shell_clipboard_set_source (shell->priv->clipboard_shell, source);
                rb_shell_player_set_selected_source (shell->priv->player_shell, 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);  /* ? */
 
                /* clear playlist-manager:source? */
-               /* clear removable-media-manager:source? */
        }
        rb_statusbar_set_page (shell->priv->statusbar, page);
 
@@ -2643,114 +2320,17 @@ rb_shell_set_window_title (RBShell *shell,
 }
 
 static void
-rb_shell_view_statusbar_changed_cb (GtkAction *action,
-                                   RBShell *shell)
-{
-       g_settings_set_boolean (shell->priv->settings,
-                               "statusbar-hidden",
-                               !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
-
-       rb_shell_sync_statusbar_visibility (shell);
-}
-
-static void
-rb_shell_view_queue_as_sidebar_changed_cb (GtkAction *action,
-                                          RBShell *shell)
+view_party_mode_changed_cb (GAction *action, GVariant *parameter, RBShell *shell)
 {
-       gboolean queue_as_sidebar = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
-       /* maybe use a settings binding? */
-       g_settings_set_boolean (shell->priv->settings,
-                               "queue-as-sidebar",
-                               queue_as_sidebar);
-
-       if (queue_as_sidebar &&
-           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_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source));
-       }
-
-       rb_shell_playing_from_queue_cb (shell->priv->player_shell, NULL, shell);
-
-       rb_shell_sync_pane_visibility (shell);
-}
-
-static void
-rb_shell_view_party_mode_changed_cb (GtkAction *action,
-                                    RBShell *shell)
-{
-       shell->priv->party_mode = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+       shell->priv->party_mode = (shell->priv->party_mode == FALSE);
        rb_shell_sync_party_mode (shell);
 }
 
 static void
-rb_shell_cmd_about (GtkAction *action,
-                   RBShell *shell)
-{
-       const char **tem;
-       GString *comment;
-
-       const char *authors[] = {
-               "",
-#include "MAINTAINERS.tab"
-               "",
-               NULL,
-#include "MAINTAINERS.old.tab"
-               "",
-               NULL,
-#include "AUTHORS.tab"
-               NULL
-       };
-
-       const char *documenters[] = {
-#include "DOCUMENTERS.tab"
-               NULL
-       };
-
-       const char *translator_credits = _("translator-credits");
-
-       const char *license[] = {
-               N_("Rhythmbox is free software; you can redistribute it and/or modify\n"
-                  "it under the terms of the GNU General Public License as published by\n"
-                  "the Free Software Foundation; either version 2 of the License, or\n"
-                  "(at your option) any later version.\n"),
-               N_("Rhythmbox is distributed in the hope that it will be useful,\n"
-                  "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
-                  "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
-                  "GNU General Public License for more details.\n"),
-               N_("You should have received a copy of the GNU General Public License\n"
-                  "along with Rhythmbox; if not, write to the Free Software Foundation, Inc.,\n"
-                  "51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA\n")
-       };
-
-       char *license_trans;
-
-       authors[0] = _("Maintainers:");
-       for (tem = authors; *tem != NULL; tem++)
-               ;
-       *tem = _("Former Maintainers:");
-       for (; *tem != NULL; tem++)
-               ;
-       *tem = _("Contributors:");
-
-       comment = g_string_new (_("Music management and playback software for GNOME."));
-
-       license_trans = g_strconcat (_(license[0]), "\n", _(license[1]), "\n",
-                                    _(license[2]), "\n", NULL);
-
-       gtk_show_about_dialog (GTK_WINDOW (shell->priv->window),
-                              "version", VERSION,
-                              "copyright", "Copyright \xc2\xa9 2005 - 2009 The Rhythmbox authors\nCopyright 
\xc2\xa9 2003 - 2005 Colin Walters\nCopyright \xc2\xa9 2002, 2003 Jorn Baayen",
-                              "license", license_trans,
-                              "website-label", _("Rhythmbox Website"),
-                              "website", "http://www.gnome.org/projects/rhythmbox";,
-                              "comments", comment->str,
-                              "authors", (const char **) authors,
-                              "documenters", (const char **) documenters,
-                              "translator-credits", strcmp (translator_credits, "translator-credits") != 0 ? 
translator_credits : NULL,
-                              "logo-icon-name", "rhythmbox",
-                              NULL);
-       g_string_free (comment, TRUE);
-       g_free (license_trans);
+add_music_action_cb (GAction *action, GVariant *parameter, RBShell *shell)
+{
+       rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source));
+       rb_library_source_show_import_dialog (shell->priv->library_source);
 }
 
 /**
@@ -2769,105 +2349,6 @@ rb_shell_toggle_visibility (RBShell *shell)
        rb_shell_set_visibility (shell, FALSE, !visible);
 }
 
-static void
-rb_shell_cmd_quit (GtkAction *action,
-                  RBShell *shell)
-{
-       rb_shell_quit (shell, NULL);
-}
-
-static void
-rb_shell_cmd_contents (GtkAction *action,
-                      RBShell *shell)
-{
-       GError *error = NULL;
-
-       gtk_show_uri (gtk_widget_get_screen (shell->priv->window),
-                     "help:rhythmbox",
-                     gtk_get_current_event_time (),
-                     &error);
-
-       if (error != NULL) {
-               rb_error_dialog (NULL, _("Couldn't display help"),
-                                "%s", error->message);
-               g_error_free (error);
-       }
-}
-
-static void
-rb_shell_cmd_preferences (GtkAction *action,
-                         RBShell *shell)
-{
-       RBShellPreferences *prefs;
-
-       g_object_get (shell, "prefs", &prefs, NULL);
-
-       gtk_window_present (GTK_WINDOW (prefs));
-       g_object_unref (prefs);
-}
-
-static gboolean
-rb_shell_plugins_window_delete_cb (GtkWidget *window,
-                                  GdkEventAny *event,
-                                  gpointer data)
-{
-       gtk_widget_hide (window);
-
-       return TRUE;
-}
-
-static void
-rb_shell_plugins_response_cb (GtkDialog *dialog,
-                             int response_id,
-                             gpointer data)
-{
-       if (response_id == GTK_RESPONSE_CLOSE)
-               gtk_widget_hide (GTK_WIDGET (dialog));
-}
-
-static void
-rb_shell_cmd_plugins (GtkAction *action,
-                     RBShell *shell)
-{
-       if (shell->priv->plugins == NULL) {
-               GtkWidget *content_area;
-               GtkWidget *manager;
-
-               shell->priv->plugins = gtk_dialog_new_with_buttons (_("Configure Plugins"),
-                                                                   GTK_WINDOW (shell->priv->window),
-                                                                   GTK_DIALOG_DESTROY_WITH_PARENT,
-                                                                   GTK_STOCK_CLOSE,
-                                                                   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_box_set_spacing (GTK_BOX (content_area), 2);
-
-               g_signal_connect_object (G_OBJECT (shell->priv->plugins),
-                                        "delete_event",
-                                        G_CALLBACK (rb_shell_plugins_window_delete_cb),
-                                        NULL, 0);
-               g_signal_connect_object (G_OBJECT (shell->priv->plugins),
-                                        "response",
-                                        G_CALLBACK (rb_shell_plugins_response_cb),
-                                        NULL, 0);
-
-               manager = peas_gtk_plugin_manager_new (NULL);
-               gtk_widget_show_all (GTK_WIDGET (manager));
-               gtk_box_pack_start (GTK_BOX (content_area), manager, TRUE, TRUE, 0);
-               gtk_window_set_default_size (GTK_WINDOW (shell->priv->plugins), 600, 400);
-       }
-
-       gtk_window_present (GTK_WINDOW (shell->priv->plugins));
-}
-
-static void
-rb_shell_cmd_add_music (GtkAction *action, RBShell *shell)
-{
-       rb_shell_select_page (shell, RB_DISPLAY_PAGE (shell->priv->library_source));
-       rb_library_source_show_import_dialog (shell->priv->library_source);
-}
-
 static gboolean
 quit_timeout (gpointer dummy)
 {
@@ -2903,7 +2384,10 @@ rb_shell_quit (RBShell *shell,
        rb_shell_shutdown (shell);
        rb_shell_sync_state (shell);
 
-       g_application_release (G_APPLICATION (shell));
+       /* or maybe just _quit */
+       /* g_application_release (G_APPLICATION (shell->priv->application)); */
+
+       gtk_widget_destroy (GTK_WIDGET (shell->priv->window));
 
        g_timeout_add_seconds (10, quit_timeout, NULL);
        return TRUE;
@@ -2912,7 +2396,6 @@ rb_shell_quit (RBShell *shell,
 static gboolean
 idle_handle_load_complete (RBShell *shell)
 {
-       gboolean loaded, scanned;
        GDK_THREADS_ENTER ();
 
        rb_debug ("load complete");
@@ -2923,8 +2406,17 @@ idle_handle_load_complete (RBShell *shell)
        shell->priv->save_playlist_id = g_timeout_add_seconds (10, (GSourceFunc) idle_save_playlist_manager, 
shell);
 
        if (shell->priv->no_registration == FALSE) {
-               g_variant_get (g_action_group_get_action_state (G_ACTION_GROUP (shell), "LoadURI"), "(bb)", 
&loaded, &scanned);
-               g_action_group_change_action_state (G_ACTION_GROUP (shell), "LoadURI", g_variant_new ("(bb)", 
TRUE, scanned));
+               GVariant *state;
+               gboolean loaded, scanned;
+
+               state = g_action_group_get_action_state (G_ACTION_GROUP (shell->priv->application), 
"load-uri");
+               g_variant_get (state, "(bb)", &loaded, &scanned);
+
+               g_action_group_change_action_state (G_ACTION_GROUP (shell->priv->application),
+                                                   "load-uri",
+                                                   g_variant_new ("(bb)", TRUE, scanned));
+
+               g_variant_unref (state);
        }
 
        rhythmdb_start_action_thread (shell->priv->db);
@@ -2941,27 +2433,6 @@ rb_shell_load_complete_cb (RhythmDB *db,
        g_idle_add ((GSourceFunc) idle_handle_load_complete, shell);
 }
 
-static void
-rb_shell_sync_pane_visibility (RBShell *shell)
-{
-       GtkAction *action;
-       gboolean queue_as_sidebar = g_settings_get_boolean (shell->priv->settings, "queue-as-sidebar");
-
-       if (shell->priv->queue_source != NULL) {
-               g_object_set (shell->priv->queue_source, "visibility", !queue_as_sidebar, NULL);
-       }
-
-       if (queue_as_sidebar) {
-               gtk_widget_show (shell->priv->queue_sidebar);
-       } else {
-               gtk_widget_hide (shell->priv->queue_sidebar);
-       }
-
-       action = gtk_action_group_get_action (shell->priv->actiongroup,
-                                             "ViewQueueAsSidebar");
-       gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), queue_as_sidebar);
-}
-
 static gboolean
 window_state_event_cb (GtkWidget           *widget,
                       GdkEventWindowState *event,
@@ -2977,14 +2448,14 @@ window_state_event_cb (GtkWidget           *widget,
 static void
 rb_shell_sync_party_mode (RBShell *shell)
 {
-       GtkAction *action;
+       GAction *action;
 
        /* party mode does not use gsettings as a model since it
           should not be persistent */
 
        /* disable/enable quit action */
-       action = gtk_action_group_get_action (shell->priv->actiongroup, "MusicQuit");
-       g_object_set (action, "sensitive", !shell->priv->party_mode, NULL);
+       action = g_action_map_lookup_action (G_ACTION_MAP (shell->priv->application), "quit");
+       g_simple_action_set_enabled (G_SIMPLE_ACTION (action), !shell->priv->party_mode);
 
        /* show/hide queue as sidebar ? */
 
@@ -3010,20 +2481,6 @@ rb_shell_sync_party_mode (RBShell *shell)
 }
 
 static void
-rb_shell_sync_statusbar_visibility (RBShell *shell)
-{
-       gboolean visible;
-       GtkAction *action;
-
-       visible = !g_settings_get_boolean (shell->priv->settings, "statusbar-hidden");
-
-       action = gtk_action_group_get_action (shell->priv->actiongroup, "ViewStatusbar");
-       gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), visible);
-
-       gtk_widget_set_visible (GTK_WIDGET (shell->priv->statusbar), visible);
-}
-
-static void
 rb_shell_sync_paned (RBShell *shell)
 {
        gtk_paned_set_position (GTK_PANED (shell->priv->right_paned),
@@ -3065,18 +2522,18 @@ display_page_tree_drag_received_cb (RBDisplayPageTree *display_page_tree,
 }
 
 static void
-rb_shell_cmd_current_song (GtkAction *action,
-                          RBShell *shell)
+jump_to_playing_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
 {
+       RBShell *shell = RB_SHELL (data);
        rb_debug ("current song");
-
        rb_shell_jump_to_current (shell);
 }
 
+/*
 static void
-rb_shell_cmd_view_all (GtkAction *action,
-                      RBShell *shell)
+view_all_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
 {
+       RBShell *shell = RB_SHELL (data);
        if (RB_IS_SOURCE (shell->priv->selected_page)) {
                RBSource *source = RB_SOURCE (shell->priv->selected_page);
                rb_debug ("view all");
@@ -3084,6 +2541,7 @@ rb_shell_cmd_view_all (GtkAction *action,
                rb_source_reset_filters (source);
        }
 }
+*/
 
 static void
 rb_shell_jump_to_entry_with_source (RBShell *shell,
@@ -3183,33 +2641,6 @@ rb_shell_error_quark (void)
        return quark;
 }
 
-static void
-session_save_state_cb (EggSMClient *client,
-                      GKeyFile *key_file,
-                      RBShell *shell)
-{
-       rb_debug ("session save-state");
-       rb_shell_sync_state (shell);
-}
-
-static void
-session_quit_cb (EggSMClient *client,
-                RBShell *shell)
-{
-       rb_debug ("session quit");
-       rb_shell_quit (shell, NULL);
-}
-
-static void
-rb_shell_session_init (RBShell *shell)
-{
-       EggSMClient *sm_client;
-
-       sm_client = egg_sm_client_get ();
-       g_signal_connect (sm_client, "save-state", G_CALLBACK (session_save_state_cb), shell);
-       g_signal_connect (sm_client, "quit", G_CALLBACK (session_quit_cb), shell);
-}
-
 /**
  * rb_shell_guess_source_for_uri:
  * @shell: the #RBSource
@@ -3728,30 +3159,6 @@ rb_shell_set_song_property (RBShell *shell,
        return TRUE;
 }
 
-static void
-rb_shell_volume_widget_changed_cb (GtkScaleButton *vol,
-                                  gdouble volume,
-                                  RBShell *shell)
-{
-       if (!shell->priv->syncing_volume) {
-               g_object_set (shell->priv->player_shell, "volume", volume, NULL);
-       }
-}
-
-static void
-rb_shell_player_volume_changed_cb (RBShellPlayer *player,
-                                  GParamSpec *arg,
-                                  RBShell *shell)
-{
-       float volume;
-
-       g_object_get (player, "volume", &volume, NULL);
-       shell->priv->syncing_volume = TRUE;
-       gtk_scale_button_set_value (GTK_SCALE_BUTTON (shell->priv->volume_button), volume);
-       shell->priv->syncing_volume = FALSE;
-
-}
-
 static GtkBox*
 rb_shell_get_box_for_ui_location (RBShell *shell, RBShellUILocation location)
 {
diff --git a/shell/rb-shell.h b/shell/rb-shell.h
index e5c92f9..e784cbc 100644
--- a/shell/rb-shell.h
+++ b/shell/rb-shell.h
@@ -103,8 +103,6 @@ struct _RBShellClass
 
 GType          rb_shell_get_type       (void);
 
-RBShell *      rb_shell_new            (gboolean autostarted, int *argc, char ***argv);
-
 gboolean        rb_shell_present        (RBShell *shell, guint32 timestamp, GError **error);
 
 RBSource *     rb_shell_guess_source_for_uri (RBShell *shell, const char *uri);
diff --git a/shell/rb-statusbar.c b/shell/rb-statusbar.c
index 489ef91..10e92a9 100644
--- a/shell/rb-statusbar.c
+++ b/shell/rb-statusbar.c
@@ -91,8 +91,6 @@ struct RBStatusbarPrivate
 
         RhythmDB *db;
 
-        GtkUIManager *ui_manager;
-
         GtkWidget *progress;
 
         guint status_poll_id;
@@ -102,7 +100,6 @@ enum
 {
         PROP_0,
         PROP_DB,
-        PROP_UI_MANAGER,
         PROP_PAGE,
        PROP_TRANSFER_QUEUE
 };
@@ -145,19 +142,6 @@ rb_statusbar_class_init (RBStatusbarClass *klass)
                                                               RB_TYPE_DISPLAY_PAGE,
                                                               G_PARAM_READWRITE));
        /**
-        * RBStatusbar:ui-manager:
-        *
-        * The #GtkUIManager instance
-        */
-        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_READWRITE | G_PARAM_CONSTRUCT_ONLY));
-
-       /**
         * RBStatusbar::transfer-queue:
         *
         * The #RBTrackTransferQueue instance
@@ -212,11 +196,6 @@ rb_statusbar_dispose (GObject *object)
                statusbar->priv->db = NULL;
        }
 
-       if (statusbar->priv->ui_manager != NULL) {
-               g_object_unref (statusbar->priv->ui_manager);
-               statusbar->priv->ui_manager = NULL;
-       }
-
        if (statusbar->priv->selected_page != NULL) {
                g_object_unref (statusbar->priv->selected_page);
                statusbar->priv->selected_page = NULL;
@@ -245,72 +224,6 @@ rb_statusbar_finalize (GObject *object)
         G_OBJECT_CLASS (rb_statusbar_parent_class)->finalize (object);
 }
 
-typedef struct {
-        GtkWidget *statusbar;
-        char      *tooltip;
-} StatusTip;
-
-static void
-statustip_free (StatusTip *tip)
-{
-        g_object_unref (tip->statusbar);
-        g_free (tip->tooltip);
-        g_free (tip);
-}
-
-static void
-set_statusbar_tooltip (GtkWidget *widget,
-                       StatusTip *data)
-{
-        guint context_id;
-
-        context_id = gtk_statusbar_get_context_id (GTK_STATUSBAR (data->statusbar),
-                                                   "rb_statusbar_tooltip");
-        gtk_statusbar_push (GTK_STATUSBAR (data->statusbar),
-                            context_id,
-                            data->tooltip ? data->tooltip: "");
-}
-
-static void
-unset_statusbar_tooltip (GtkWidget *widget,
-                         GtkWidget *statusbar)
-{
-        guint context_id;
-
-        context_id = gtk_statusbar_get_context_id (GTK_STATUSBAR (statusbar),
-                                                   "rb_statusbar_tooltip");
-        gtk_statusbar_pop (GTK_STATUSBAR (statusbar), context_id);
-}
-
-static void
-rb_statusbar_connect_ui_manager (RBStatusbar    *statusbar,
-                                GtkAction      *action,
-                                GtkWidget      *proxy,
-                                GtkUIManager   *ui_manager)
-{
-        char *tooltip;
-
-        if (! GTK_IS_MENU_ITEM (proxy))
-                return;
-
-        g_object_get (action, "tooltip", &tooltip, NULL);
-
-        if (tooltip) {
-                StatusTip *statustip;
-
-                statustip = g_new (StatusTip, 1);
-                statustip->statusbar = g_object_ref (statusbar);
-                statustip->tooltip = tooltip;
-                g_signal_connect_data (proxy, "select",
-                                       G_CALLBACK (set_statusbar_tooltip),
-                                       statustip, (GClosureNotify)statustip_free, 0);
-
-                g_signal_connect (proxy, "deselect",
-                                  G_CALLBACK (unset_statusbar_tooltip),
-                                  statusbar);
-        }
-}
-
 static void
 rb_statusbar_set_property (GObject *object,
                            guint prop_id,
@@ -347,22 +260,6 @@ rb_statusbar_set_property (GObject *object,
                rb_statusbar_sync_status (statusbar);
 
                 break;
-        case PROP_UI_MANAGER:
-                if (statusbar->priv->ui_manager) {
-                        g_signal_handlers_disconnect_by_func (G_OBJECT (statusbar->priv->ui_manager),
-                                                              G_CALLBACK (rb_statusbar_connect_ui_manager),
-                                                              statusbar);
-                       g_object_unref (statusbar->priv->ui_manager);
-                }
-                statusbar->priv->ui_manager = g_value_get_object (value);
-               g_object_ref (statusbar->priv->ui_manager);
-
-                g_signal_connect_object (statusbar->priv->ui_manager,
-                                         "connect-proxy",
-                                         G_CALLBACK (rb_statusbar_connect_ui_manager),
-                                         statusbar,
-                                         G_CONNECT_SWAPPED);
-                break;
        case PROP_TRANSFER_QUEUE:
                statusbar->priv->transfer_queue = g_value_dup_object (value);
                g_signal_connect_object (G_OBJECT (statusbar->priv->transfer_queue),
@@ -393,9 +290,6 @@ rb_statusbar_get_property (GObject *object,
         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);
-                break;
         case PROP_TRANSFER_QUEUE:
                 g_value_set_object (value, statusbar->priv->transfer_queue);
                 break;
@@ -507,7 +401,6 @@ rb_statusbar_sync_status (RBStatusbar *status)
 /**
  * rb_statusbar_new:
  * @db: the #RhythmDB instance
- * @ui_manager: the #GtkUIManager
  * @transfer_queue: the #RBTrackTransferQueue
  *
  * Creates the status bar widget.
@@ -516,12 +409,10 @@ rb_statusbar_sync_status (RBStatusbar *status)
  */
 RBStatusbar *
 rb_statusbar_new (RhythmDB *db,
-                  GtkUIManager *ui_manager,
                  RBTrackTransferQueue *queue)
 {
         RBStatusbar *statusbar = g_object_new (RB_TYPE_STATUSBAR,
                                                "db", db,
-                                               "ui-manager", ui_manager,
                                               "transfer-queue", queue,
                                                NULL);
 
diff --git a/shell/rb-statusbar.h b/shell/rb-statusbar.h
index 935f055..d5a5847 100644
--- a/shell/rb-statusbar.h
+++ b/shell/rb-statusbar.h
@@ -63,7 +63,6 @@ struct _RBStatusbarClass
 GType                  rb_statusbar_get_type   (void);
 
 RBStatusbar *          rb_statusbar_new        (RhythmDB *db,
-                                                GtkUIManager *ui_manager,
                                                 RBTrackTransferQueue *transfer_queue);
 
 void                   rb_statusbar_set_page   (RBStatusbar *statusbar,
diff --git a/sources/Makefile.am b/sources/Makefile.am
index 7020d48..0405d58 100644
--- a/sources/Makefile.am
+++ b/sources/Makefile.am
@@ -12,6 +12,7 @@ sourceinclude_HEADERS =               \
        rb-display-page.h               \
        rb-display-page-group.h         \
        rb-display-page-tree.h          \
+       rb-display-page-menu.h          \
        rb-display-page-model.h         \
        rb-browser-source.h             \
        rb-media-player-source.h        \
@@ -31,6 +32,7 @@ libsources_la_SOURCES =               \
        rb-display-page.c               \
        rb-display-page-group.c         \
        rb-display-page-tree.c          \
+       rb-display-page-menu.c          \
        rb-display-page-model.c         \
        rb-browser-source.c             \
        rb-library-source.c             \
diff --git a/sources/rb-auto-playlist-source.c b/sources/rb-auto-playlist-source.c
index bd799ed..47f930b 100644
--- a/sources/rb-auto-playlist-source.c
+++ b/sources/rb-auto-playlist-source.c
@@ -42,6 +42,8 @@
 #include "rb-playlist-xml.h"
 #include "rb-source-search-basic.h"
 #include "rb-source-toolbar.h"
+#include "rb-application.h"
+#include "rb-builder-helpers.h"
 
 /**
  * SECTION:rb-auto-playlist-source
@@ -75,7 +77,6 @@ static void rb_auto_playlist_source_get_property (GObject *object,
                                                  GParamSpec *pspec);
 
 /* source methods */
-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);
@@ -98,14 +99,6 @@ static void rb_auto_playlist_source_browser_changed_cb (RBLibraryBrowser *entry,
                                                        GParamSpec *pspec,
                                                        RBAutoPlaylistSource *source);
 
-static GtkRadioActionEntry rb_auto_playlist_source_radio_actions [] =
-{
-       { "AutoPlaylistSearchAll", NULL, N_("Search all fields"), NULL, NULL, RHYTHMDB_PROP_SEARCH_MATCH },
-       { "AutoPlaylistSearchArtists", NULL, N_("Search artists"), NULL, NULL, RHYTHMDB_PROP_ARTIST_FOLDED },
-       { "AutoPlaylistSearchAlbums", NULL, N_("Search albums"), NULL, NULL, RHYTHMDB_PROP_ALBUM_FOLDED },
-       { "AutoPlaylistSearchTitles", NULL, N_("Search titles"), NULL, NULL, RHYTHMDB_PROP_TITLE_FOLDED }
-};
-
 enum
 {
        PROP_0,
@@ -113,8 +106,6 @@ enum
        PROP_SHOW_BROWSER
 };
 
-#define AUTO_PLAYLIST_SOURCE_POPUP_PATH "/AutoPlaylistSourcePopup"
-
 typedef struct _RBAutoPlaylistSourcePrivate RBAutoPlaylistSourcePrivate;
 
 struct _RBAutoPlaylistSourcePrivate
@@ -134,6 +125,8 @@ struct _RBAutoPlaylistSourcePrivate
 
        RBSourceSearch *default_search;
        RhythmDBQuery *search_query;
+       GMenu *search_popup;
+       GAction *search_action;
 };
 
 static gpointer playlist_pixbuf = NULL;
@@ -155,7 +148,6 @@ 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;
@@ -205,15 +197,10 @@ rb_auto_playlist_source_dispose (GObject *object)
 {
        RBAutoPlaylistSourcePrivate *priv = GET_PRIVATE (object);
 
-       if (priv->cached_all_query != NULL) {
-               g_object_unref (priv->cached_all_query);
-               priv->cached_all_query = NULL;
-       }
-
-       if (priv->default_search != NULL) {
-               g_object_unref (priv->default_search);
-               priv->default_search = NULL;
-       }
+       g_clear_object (&priv->cached_all_query);
+       g_clear_object (&priv->default_search);
+       g_clear_object (&priv->search_popup);
+       g_clear_object (&priv->search_action);
 
        G_OBJECT_CLASS (rb_auto_playlist_source_parent_class)->dispose (object);
 }
@@ -238,34 +225,6 @@ rb_auto_playlist_source_finalize (GObject *object)
        G_OBJECT_CLASS (rb_auto_playlist_source_parent_class)->finalize (object);
 }
 
-void
-rb_auto_playlist_source_create_actions (RBShell *shell)
-{
-       RBAutoPlaylistSourceClass *klass;
-       GtkUIManager *uimanager;
-
-       klass = RB_AUTO_PLAYLIST_SOURCE_CLASS (g_type_class_ref (RB_TYPE_AUTO_PLAYLIST_SOURCE));
-
-       klass->action_group = gtk_action_group_new ("AutoPlaylistActions");
-       gtk_action_group_set_translation_domain (klass->action_group, GETTEXT_PACKAGE);
-
-       g_object_get (shell, "ui-manager", &uimanager, NULL);
-       gtk_ui_manager_insert_action_group (uimanager, klass->action_group, 0);
-       g_object_unref (uimanager);
-
-       gtk_action_group_add_radio_actions (klass->action_group,
-                                           rb_auto_playlist_source_radio_actions,
-                                           G_N_ELEMENTS (rb_auto_playlist_source_radio_actions),
-                                           0,
-                                           NULL,
-                                           NULL);
-       rb_source_search_basic_create_for_actions (klass->action_group,
-                                                  rb_auto_playlist_source_radio_actions,
-                                                  G_N_ELEMENTS (rb_auto_playlist_source_radio_actions));
-
-       g_type_class_unref (klass);
-}
-
 static void
 rb_auto_playlist_source_constructed (GObject *object)
 {
@@ -274,8 +233,9 @@ rb_auto_playlist_source_constructed (GObject *object)
        RBAutoPlaylistSourcePrivate *priv;
        RBShell *shell;
        RhythmDBEntryType *entry_type;
-       GtkUIManager *ui_manager;
+       GtkAccelGroup *accel_group;
        GtkWidget *grid;
+       GMenu *section;
 
        RB_CHAIN_GOBJECT_METHOD (rb_auto_playlist_source_parent_class, constructed, object);
 
@@ -300,17 +260,35 @@ rb_auto_playlist_source_constructed (GObject *object)
                                 G_CALLBACK (rb_auto_playlist_source_songs_sort_order_changed_cb),
                                 source, 0);
 
-       priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH);
+       priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH, NULL);
 
        /* set up toolbar */
        g_object_get (source, "shell", &shell, NULL);
-       g_object_get (shell, "ui-manager", &ui_manager, NULL);
-       priv->toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (source), ui_manager);
-       rb_source_toolbar_add_search_entry (priv->toolbar, "/AutoPlaylistSourceSearchMenu", NULL);
+       g_object_get (shell, "accel-group", &accel_group, NULL);
+       priv->toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (source), accel_group);
 
-       g_object_unref (ui_manager);
+       g_object_unref (accel_group);
        g_object_unref (shell);
 
+       priv->search_action = rb_source_create_search_action (RB_SOURCE (source));
+       g_action_change_state (priv->search_action, g_variant_new_string ("search-match"));
+       g_action_map_add_action (G_ACTION_MAP (g_application_get_default ()), priv->search_action);
+
+       rb_source_search_basic_register (RHYTHMDB_PROP_SEARCH_MATCH, "search-match", _("Search all fields"));
+       rb_source_search_basic_register (RHYTHMDB_PROP_ARTIST_FOLDED, "artist", _("Search artists"));
+       rb_source_search_basic_register (RHYTHMDB_PROP_ALBUM_FOLDED, "album", _("Search albums"));
+       rb_source_search_basic_register (RHYTHMDB_PROP_TITLE_FOLDED, "title", _("Search titles"));
+       
+       section = g_menu_new ();
+       rb_source_search_add_to_menu (section, "app", priv->search_action, "search-match");
+       rb_source_search_add_to_menu (section, "app", priv->search_action, "artist");
+       rb_source_search_add_to_menu (section, "app", priv->search_action, "album");
+       rb_source_search_add_to_menu (section, "app", priv->search_action, "title");
+
+       priv->search_popup = g_menu_new ();
+       g_menu_append_section (priv->search_popup, NULL, G_MENU_MODEL (section));
+       rb_source_toolbar_add_search_entry_menu (priv->toolbar, G_MENU_MODEL (priv->search_popup), 
priv->search_action);
+
        /* reparent the entry view */
        g_object_ref (songs);
        gtk_container_remove (GTK_CONTAINER (source), GTK_WIDGET (songs));
@@ -343,16 +321,26 @@ rb_auto_playlist_source_constructed (GObject *object)
 RBSource *
 rb_auto_playlist_source_new (RBShell *shell, const char *name, gboolean local)
 {
+       RBSource *source;
+       GtkBuilder *builder;
+       GMenu *toolbar;
+
        if (name == NULL)
                name = "";
 
-       return RB_SOURCE (g_object_new (RB_TYPE_AUTO_PLAYLIST_SOURCE,
-                                       "name", name,
-                                       "shell", shell,
-                                       "is-local", local,
-                                       "entry-type", RHYTHMDB_ENTRY_TYPE_SONG,
-                                       "toolbar-path", "/AutoPlaylistSourceToolBar",
-                                       NULL));
+       builder = rb_builder_load ("playlist-toolbar.ui", NULL);
+       toolbar = G_MENU (gtk_builder_get_object (builder, "playlist-toolbar"));
+       rb_application_link_shared_menus (RB_APPLICATION (g_application_get_default ()), toolbar);
+
+       source = RB_SOURCE (g_object_new (RB_TYPE_AUTO_PLAYLIST_SOURCE,
+                                         "name", name,
+                                         "shell", shell,
+                                         "is-local", local,
+                                         "entry-type", RHYTHMDB_ENTRY_TYPE_SONG,
+                                         "toolbar-menu", toolbar,
+                                         NULL));
+       g_object_unref (builder);
+       return source;
 }
 
 static void
@@ -501,13 +489,6 @@ rb_auto_playlist_source_new_from_xml (RBShell *shell, xmlNodePtr node)
        return RB_SOURCE (source);
 }
 
-static gboolean
-impl_show_popup (RBDisplayPage *page)
-{
-       _rb_display_page_show_popup (page, AUTO_PLAYLIST_SOURCE_POPUP_PATH);
-       return TRUE;
-}
-
 static void
 impl_reset_filters (RBSource *source)
 {
diff --git a/sources/rb-auto-playlist-source.h b/sources/rb-auto-playlist-source.h
index 68954c6..2e7e91c 100644
--- a/sources/rb-auto-playlist-source.h
+++ b/sources/rb-auto-playlist-source.h
@@ -55,14 +55,10 @@ struct _RBAutoPlaylistSource
 struct _RBAutoPlaylistSourceClass
 {
        RBPlaylistSourceClass parent;
-
-       GtkActionGroup *action_group;
 };
 
 GType          rb_auto_playlist_source_get_type        (void);
 
-void           rb_auto_playlist_source_create_actions  (RBShell *shell);
-
 RBSource *     rb_auto_playlist_source_new             (RBShell *shell,
                                                         const char *name,
                                                         gboolean local);
diff --git a/sources/rb-browser-source.c b/sources/rb-browser-source.c
index 253f897..225021e 100644
--- a/sources/rb-browser-source.c
+++ b/sources/rb-browser-source.c
@@ -62,6 +62,8 @@
 #include "rb-search-entry.h"
 #include "rb-source-toolbar.h"
 #include "rb-shell-preferences.h"
+#include "rb-builder-helpers.h"
+#include "rb-application.h"
 
 static void rb_browser_source_class_init (RBBrowserSourceClass *klass);
 static void rb_browser_source_init (RBBrowserSource *source);
@@ -76,9 +78,10 @@ static void rb_browser_source_get_property (GObject *object,
                                          guint prop_id,
                                          GValue *value,
                                          GParamSpec *pspec);
-static void rb_browser_source_cmd_choose_genre (GtkAction *action, RBSource *source);
-static void rb_browser_source_cmd_choose_artist (GtkAction *action, RBSource *source);
-static void rb_browser_source_cmd_choose_album (GtkAction *action, RBSource *source);
+static void select_genre_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data);
+static void select_artist_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data);
+static void select_album_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data);
+
 static void songs_view_sort_order_changed_cb (GObject *object, GParamSpec *pspec, RBBrowserSource *source);
 static void rb_browser_source_browser_changed_cb (RBLibraryBrowser *entry,
                                                  GParamSpec *param,
@@ -122,35 +125,13 @@ struct RBBrowserSourcePrivate
        gboolean search_on_completion;
        RBSourceSearch *default_search;
 
-       GtkActionGroup *action_group;
-       GtkActionGroup *search_action_group;
-
-       gboolean dispose_has_run;
+       GMenu *popup;
+       GMenu *search_popup;
+       GAction *search_action;
 };
 
 #define RB_BROWSER_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_BROWSER_SOURCE, 
RBBrowserSourcePrivate))
 
-static GtkActionEntry rb_browser_source_actions [] =
-{
-       { "BrowserSrcChooseGenre", NULL, N_("Browse This _Genre"), NULL,
-         N_("Set the browser to view only this genre"),
-         G_CALLBACK (rb_browser_source_cmd_choose_genre) },
-       { "BrowserSrcChooseArtist", NULL , N_("Browse This _Artist"), NULL,
-         N_("Set the browser to view only this artist"),
-         G_CALLBACK (rb_browser_source_cmd_choose_artist) },
-       { "BrowserSrcChooseAlbum", NULL, N_("Browse This A_lbum"), NULL,
-         N_("Set the browser to view only this album"),
-         G_CALLBACK (rb_browser_source_cmd_choose_album) }
-};
-
-static GtkRadioActionEntry rb_browser_source_radio_actions [] =
-{
-       { "BrowserSourceSearchAll", NULL, N_("Search all fields"), NULL, NULL, RHYTHMDB_PROP_SEARCH_MATCH },
-       { "BrowserSourceSearchArtists", NULL, N_("Search artists"), NULL, NULL, RHYTHMDB_PROP_ARTIST_FOLDED },
-       { "BrowserSourceSearchAlbums", NULL, N_("Search albums"), NULL, NULL, RHYTHMDB_PROP_ALBUM_FOLDED },
-       { "BrowserSourceSearchTitles", NULL, N_("Search titles"), NULL, NULL, RHYTHMDB_PROP_TITLE_FOLDED }
-};
-
 static const GtkTargetEntry songs_view_drag_types[] = {
        { "application/x-rhythmbox-entry", 0, 0 },
        { "text/uri-list", 0, 1 }
@@ -226,37 +207,13 @@ rb_browser_source_dispose (GObject *object)
        RBBrowserSource *source;
        source = RB_BROWSER_SOURCE (object);
 
-       if (source->priv->dispose_has_run) {
-               /* If dispose did already run, return. */
-               return;
-       }
-       /* Make sure dispose does not run twice. */
-       source->priv->dispose_has_run = TRUE;
-
-       if (source->priv->db != NULL) {
-               g_object_unref (source->priv->db);
-               source->priv->db = NULL;
-       }
-
-       if (source->priv->search_query != NULL) {
-               rhythmdb_query_free (source->priv->search_query);
-               source->priv->search_query = NULL;
-       }
-
-       if (source->priv->cached_all_query != NULL) {
-               g_object_unref (source->priv->cached_all_query);
-               source->priv->cached_all_query = NULL;
-       }
-
-       if (source->priv->action_group != NULL) {
-               g_object_unref (source->priv->action_group);
-               source->priv->action_group = NULL;
-       }
-
-       if (source->priv->default_search != NULL) {
-               g_object_unref (source->priv->default_search);
-               source->priv->default_search = NULL;
-       }
+       g_clear_object (&source->priv->db);
+       g_clear_object (&source->priv->search_query);
+       g_clear_object (&source->priv->cached_all_query);
+       g_clear_object (&source->priv->default_search);
+       g_clear_object (&source->priv->popup);
+       g_clear_object (&source->priv->search_popup);
+       g_clear_object (&source->priv->search_action);
 
        G_OBJECT_CLASS (rb_browser_source_parent_class)->dispose (object);
 }
@@ -285,15 +242,29 @@ rb_browser_source_songs_show_popup_cb (RBEntryView *view,
                RBBrowserSourceClass *klass = RB_BROWSER_SOURCE_GET_CLASS (source);
 
                klass->show_entry_popup (source);
-       } else {
-               rb_display_page_show_popup (RB_DISPLAY_PAGE (source));
        }
 }
 
 static void
 default_show_entry_popup (RBBrowserSource *source)
 {
-       _rb_display_page_show_popup (RB_DISPLAY_PAGE (source), "/BrowserSourceViewPopup");
+       GtkWidget *menu;
+       GMenuModel *playlist_menu;
+
+       /* update add to playlist menu links */
+       g_object_get (source, "playlist-menu", &playlist_menu, NULL);
+       rb_menu_update_link (source->priv->popup, "rb-playlist-menu-link", playlist_menu);
+       g_object_unref (playlist_menu);
+
+       menu = gtk_menu_new_from_model (G_MENU_MODEL (source->priv->popup));
+       gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (source), NULL);
+       gtk_menu_popup (GTK_MENU (menu),
+                       NULL,
+                       NULL,
+                       NULL,
+                       NULL,
+                       3,
+                       gtk_get_current_event_time ());
 }
 
 static void
@@ -303,10 +274,17 @@ rb_browser_source_constructed (GObject *object)
        RBBrowserSourceClass *klass;
        RBShell *shell;
        GObject *shell_player;
-       GtkUIManager *ui_manager;
+       GtkAccelGroup *accel_group;
        RhythmDBEntryType *entry_type;
        GtkWidget *content;
        GtkWidget *paned;
+       GtkBuilder *builder;
+       GMenu *section;
+       GActionEntry actions[] = {
+               { "browser-select-genre", select_genre_action_cb },
+               { "browser-select-artist", select_artist_action_cb },
+               { "browser-select-album", select_album_action_cb }
+       };
 
        RB_CHAIN_GOBJECT_METHOD (rb_browser_source_parent_class, constructed, object);
 
@@ -319,34 +297,35 @@ rb_browser_source_constructed (GObject *object)
        g_object_get (shell,
                      "db", &source->priv->db,
                      "shell-player", &shell_player,
-                     "ui-manager", &ui_manager,
+                     "accel-group", &accel_group,
                      NULL);
 
-       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,
-                                        rb_browser_source_radio_actions[0].name) == NULL) {
-               gtk_action_group_add_radio_actions (source->priv->action_group,
-                                                   rb_browser_source_radio_actions,
-                                                   G_N_ELEMENTS (rb_browser_source_radio_actions),
-                                                   0,
-                                                   NULL,
-                                                   NULL);
-
-               rb_source_search_basic_create_for_actions (source->priv->action_group,
-                                                          rb_browser_source_radio_actions,
-                                                          G_N_ELEMENTS (rb_browser_source_radio_actions));
-       }
+       _rb_add_display_page_actions (G_ACTION_MAP (g_application_get_default ()),
+                                     G_OBJECT (shell),
+                                     actions,
+                                     G_N_ELEMENTS (actions));
        g_object_unref (shell);
 
-       source->priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH);
+
+       source->priv->search_action = rb_source_create_search_action (RB_SOURCE (source));
+       g_action_map_add_action (G_ACTION_MAP (g_application_get_default ()), source->priv->search_action);
+
+       /* ensure search instances exist */
+       rb_source_search_basic_register (RHYTHMDB_PROP_SEARCH_MATCH, "search-match", _("Search all fields"));
+       rb_source_search_basic_register (RHYTHMDB_PROP_ARTIST_FOLDED, "artist", _("Search artists"));
+       rb_source_search_basic_register (RHYTHMDB_PROP_ALBUM_FOLDED, "album", _("Search albums"));
+       rb_source_search_basic_register (RHYTHMDB_PROP_TITLE_FOLDED, "title", _("Search titles"));
+       
+       section = g_menu_new ();
+       rb_source_search_add_to_menu (section, "app", source->priv->search_action, "search-match");
+       rb_source_search_add_to_menu (section, "app", source->priv->search_action, "artist");
+       rb_source_search_add_to_menu (section, "app", source->priv->search_action, "album");
+       rb_source_search_add_to_menu (section, "app", source->priv->search_action, "title");
+
+       source->priv->search_popup = g_menu_new ();
+       g_menu_append_section (source->priv->search_popup, NULL, G_MENU_MODEL (section));
+
+       source->priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH, _("Search all 
fields"));
 
        paned = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
 
@@ -410,8 +389,8 @@ rb_browser_source_constructed (GObject *object)
        gtk_paned_pack2 (GTK_PANED (paned), GTK_WIDGET (source->priv->songs), TRUE, FALSE);
 
        /* set up toolbar */
-       source->priv->toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (source), ui_manager);
-       rb_source_toolbar_add_search_entry (source->priv->toolbar, "/BrowserSourceSearchMenu", NULL);
+       source->priv->toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (source), accel_group);
+       rb_source_toolbar_add_search_entry_menu (source->priv->toolbar, G_MENU_MODEL 
(source->priv->search_popup), source->priv->search_action);
 
        content = gtk_grid_new ();
        gtk_grid_set_column_spacing (GTK_GRID (content), 6);
@@ -435,8 +414,15 @@ rb_browser_source_constructed (GObject *object)
        source->priv->cached_all_query = rhythmdb_query_model_new_empty (source->priv->db);
        rb_browser_source_populate (source);
 
+       builder = rb_builder_load ("browser-popup.ui", NULL);
+       source->priv->popup = G_MENU (gtk_builder_get_object (builder, "browser-popup"));
+       rb_application_link_shared_menus (RB_APPLICATION (g_application_get_default ()),
+                                         source->priv->popup);
+       g_object_unref (builder);
+
        g_object_unref (entry_type);
        g_object_unref (shell_player);
+       g_object_unref (accel_group);
 }
 
 static void
@@ -542,32 +528,32 @@ browse_property (RBBrowserSource *source, RhythmDBPropType prop)
 }
 
 static void
-rb_browser_source_cmd_choose_genre (GtkAction *action, RBSource *source)
+select_genre_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
 {
        rb_debug ("choosing genre");
 
-       if (RB_IS_BROWSER_SOURCE (source)) {
-               browse_property (RB_BROWSER_SOURCE (source), RHYTHMDB_PROP_GENRE);
+       if (RB_IS_BROWSER_SOURCE (data)) {
+               browse_property (RB_BROWSER_SOURCE (data), RHYTHMDB_PROP_GENRE);
        }
 }
 
 static void
-rb_browser_source_cmd_choose_artist (GtkAction *action, RBSource *source)
+select_artist_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
 {
        rb_debug ("choosing artist");
 
-       if (RB_IS_BROWSER_SOURCE (source)) {
-               browse_property (RB_BROWSER_SOURCE (source), RHYTHMDB_PROP_ARTIST);
+       if (RB_IS_BROWSER_SOURCE (data)) {
+               browse_property (RB_BROWSER_SOURCE (data), RHYTHMDB_PROP_ARTIST);
        }
 }
 
 static void
-rb_browser_source_cmd_choose_album (GtkAction *action, RBSource *source)
+select_album_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
 {
        rb_debug ("choosing album");
 
-       if (RB_IS_BROWSER_SOURCE (source)) {
-               browse_property (RB_BROWSER_SOURCE (source), RHYTHMDB_PROP_ALBUM);
+       if (RB_IS_BROWSER_SOURCE (data)) {
+               browse_property (RB_BROWSER_SOURCE (data), RHYTHMDB_PROP_ALBUM);
        }
 }
 
diff --git a/sources/rb-display-page-menu.c b/sources/rb-display-page-menu.c
new file mode 100644
index 0000000..62b0324
--- /dev/null
+++ b/sources/rb-display-page-menu.c
@@ -0,0 +1,459 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2013 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 <sources/rb-display-page-menu.h>
+#include <lib/rb-util.h>
+#include <lib/rb-debug.h>
+
+static void rb_display_page_menu_class_init (RBDisplayPageMenuClass *klass);
+static void rb_display_page_menu_init (RBDisplayPageMenu *menu);
+
+struct _RBDisplayPageMenuPrivate
+{
+       RBDisplayPageModel *model;
+       RBDisplayPage *root_page;
+       GType page_type;
+       char *action;
+
+       int item_count;
+};
+
+G_DEFINE_TYPE (RBDisplayPageMenu, rb_display_page_menu, G_TYPE_MENU_MODEL);
+
+/**
+ * SECTION:rb-display-page-menu
+ * @short_description: #GMenu populated with a portion of the display page model
+ *
+ */
+
+enum
+{
+       PROP_0,
+       PROP_MODEL,
+       PROP_ROOT_PAGE,
+       PROP_PAGE_TYPE,
+       PROP_ACTION
+};
+
+
+static gboolean
+get_page_iter (RBDisplayPageMenu *menu, GtkTreeIter *iter)
+{
+       GtkTreeIter parent;
+
+       if (rb_display_page_model_find_page (menu->priv->model, menu->priv->root_page, &parent) == FALSE)
+               return FALSE;
+
+       if (gtk_tree_model_iter_children (GTK_TREE_MODEL (menu->priv->model), iter, &parent) == FALSE) {
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static gboolean
+consider_page (RBDisplayPageMenu *menu, RBDisplayPage *page)
+{
+       gboolean visible;
+
+       if (G_TYPE_CHECK_INSTANCE_TYPE (page, menu->priv->page_type) == FALSE)
+               return FALSE;
+
+       g_object_get (page, "visibility", &visible, NULL);
+       return visible;
+}
+
+static RBDisplayPage *
+get_page_at_index (RBDisplayPageMenu *menu, int index, GtkTreeIter *iter)
+{
+       int i;
+
+       if (get_page_iter (menu, iter) == FALSE)
+               return NULL;
+
+       i = 0;
+       do {
+               RBDisplayPage *page;
+               gboolean counted;
+
+               gtk_tree_model_get (GTK_TREE_MODEL (menu->priv->model),
+                                   iter,
+                                   RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
+                                   -1);
+
+               counted = consider_page (menu, page);
+               if (counted && index == i) {
+                       return page;
+               } else if (counted) {
+                       i++;
+               }
+
+               g_object_unref (page);
+       } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (menu->priv->model), iter));
+
+       return NULL;
+}
+
+static int
+count_items (RBDisplayPageMenu *menu)
+{
+       GtkTreeIter iter;
+       int i;
+
+       if (get_page_iter (menu, &iter) == FALSE)
+               return 0;
+
+       i = 0;
+       do {
+               RBDisplayPage *page;
+               gboolean counted;
+
+               gtk_tree_model_get (GTK_TREE_MODEL (menu->priv->model),
+                                   &iter,
+                                   RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
+                                   -1);
+
+               counted = consider_page (menu, page);
+               g_object_unref (page);
+               if (counted)
+                       i++;
+       } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (menu->priv->model), &iter));
+
+       return i;
+}
+
+
+static gboolean
+impl_is_mutable (GMenuModel *menu_model)
+{
+       return TRUE;
+}
+
+static int
+impl_get_n_items (GMenuModel *menu_model)
+{
+       RBDisplayPageMenu *menu = RB_DISPLAY_PAGE_MENU (menu_model);
+       return menu->priv->item_count;
+}
+
+static void
+impl_get_item_attributes (GMenuModel *menu_model, int item_index, GHashTable **attrs)
+{
+       RBDisplayPageMenu *menu = RB_DISPLAY_PAGE_MENU (menu_model);
+       RBDisplayPage *page;
+       GtkTreeIter iter;
+
+       *attrs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
+
+       page = get_page_at_index (menu, item_index, &iter);
+       if (page != NULL) {
+               char *name;
+               char *path;
+               GVariant *v;
+
+               g_object_get (page, "name", &name, NULL);
+               rb_debug ("page at %d is %s", item_index, name);
+               g_hash_table_insert (*attrs, g_strdup ("label"), g_variant_new_string (name));
+               g_free (name);
+
+               g_hash_table_insert (*attrs, g_strdup ("action"), g_variant_new_string (menu->priv->action));
+
+               path = gtk_tree_model_get_string_from_iter (GTK_TREE_MODEL (menu->priv->model), &iter);
+               /* this is a bit awkward.. */
+               v = g_variant_new_string (path);
+               g_hash_table_insert (*attrs, g_strdup ("target"), g_variant_ref_sink (v));
+               g_free (path);
+       } else {
+               rb_debug ("no page at %d", item_index);
+       }
+}
+
+static void
+impl_get_item_links (GMenuModel *menu_model, int item_index, GHashTable **links)
+{
+       /* we never have any links */
+       *links = g_hash_table_new (g_str_hash, g_str_equal);
+}
+
+static void
+rebuild_menu (RBDisplayPageMenu *menu)
+{
+       int oldnum;
+       oldnum = menu->priv->item_count;
+       menu->priv->item_count = count_items (menu);
+       rb_debug ("building menu, %d => %d items", oldnum, menu->priv->item_count);
+       g_menu_model_items_changed (G_MENU_MODEL (menu), 0, oldnum, menu->priv->item_count);
+}
+
+static void
+row_changed_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, RBDisplayPageMenu *menu)
+{
+       rebuild_menu (menu);
+}
+
+static void
+row_inserted_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, RBDisplayPageMenu *menu)
+{
+       rebuild_menu (menu);
+}
+
+static void
+row_deleted_cb (GtkTreeModel *model, GtkTreePath *path, RBDisplayPageMenu *menu)
+{
+       rebuild_menu (menu);
+}
+
+static void
+rows_reordered_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer new_order, 
RBDisplayPageMenu *menu)
+{
+       rebuild_menu (menu);
+}
+
+
+static void
+impl_finalize (GObject *object)
+{
+       RBDisplayPageMenu *menu;
+
+       menu = RB_DISPLAY_PAGE_MENU (object);
+       g_free (menu->priv->action);
+
+       G_OBJECT_CLASS (rb_display_page_menu_parent_class)->finalize (object);
+}
+
+static void
+impl_dispose (GObject *object)
+{
+       RBDisplayPageMenu *menu;
+
+       menu = RB_DISPLAY_PAGE_MENU (object);
+       if (menu->priv->model) {
+               g_signal_handlers_disconnect_by_data (menu->priv->model, menu);
+               g_clear_object (&menu->priv->model);
+       }
+
+       g_clear_object (&menu->priv->root_page);
+
+       G_OBJECT_CLASS (rb_display_page_menu_parent_class)->dispose (object);
+}
+
+static void
+impl_constructed (GObject *object)
+{
+       RBDisplayPageMenu *menu;
+       GtkTreeModel *real_model;
+
+       RB_CHAIN_GOBJECT_METHOD (rb_display_page_menu_parent_class, constructed, object);
+
+       menu = RB_DISPLAY_PAGE_MENU (object);
+
+       real_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (menu->priv->model));
+       g_signal_connect (real_model, "row-inserted", G_CALLBACK (row_inserted_cb), menu);
+       g_signal_connect (real_model, "row-deleted", G_CALLBACK (row_deleted_cb), menu);
+       g_signal_connect (real_model, "row-changed", G_CALLBACK (row_changed_cb), menu);
+       g_signal_connect (real_model, "rows-reordered", G_CALLBACK (rows_reordered_cb), menu);
+
+       rebuild_menu (menu);
+}
+
+static void
+impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+       RBDisplayPageMenu *menu = RB_DISPLAY_PAGE_MENU (object);
+
+       switch (prop_id) {
+       case PROP_MODEL:
+               g_value_set_object (value, menu->priv->model);
+               break;
+       case PROP_ROOT_PAGE:
+               g_value_set_object (value, menu->priv->root_page);
+               break;
+       case PROP_PAGE_TYPE:
+               g_value_set_gtype (value, menu->priv->page_type);
+               break;
+       case PROP_ACTION:
+               g_value_set_string (value, menu->priv->action);
+               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)
+{
+       RBDisplayPageMenu *menu = RB_DISPLAY_PAGE_MENU (object);
+
+       switch (prop_id) {
+       case PROP_MODEL:
+               menu->priv->model = g_value_get_object (value);
+               break;
+       case PROP_ROOT_PAGE:
+               menu->priv->root_page = g_value_get_object (value);
+               break;
+       case PROP_PAGE_TYPE:
+               menu->priv->page_type = g_value_get_gtype (value);
+               break;
+       case PROP_ACTION:
+               menu->priv->action = g_value_dup_string (value);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+rb_display_page_menu_init (RBDisplayPageMenu *menu)
+{
+       menu->priv = G_TYPE_INSTANCE_GET_PRIVATE (menu, RB_TYPE_DISPLAY_PAGE_MENU, RBDisplayPageMenuPrivate);
+}
+
+static void
+rb_display_page_menu_class_init (RBDisplayPageMenuClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GMenuModelClass *menu_class = G_MENU_MODEL_CLASS (klass);
+
+       object_class->constructed = impl_constructed;
+       object_class->finalize = impl_finalize;
+       object_class->dispose = impl_dispose;
+       object_class->set_property = impl_set_property;
+       object_class->get_property = impl_get_property;
+
+       menu_class->is_mutable = impl_is_mutable;
+       menu_class->get_n_items = impl_get_n_items;
+       menu_class->get_item_attributes = impl_get_item_attributes;
+       menu_class->get_item_links = impl_get_item_links;
+
+
+       g_object_class_install_property (object_class,
+                                        PROP_MODEL,
+                                        g_param_spec_object ("model",
+                                                             "model",
+                                                             "display page model",
+                                                             RB_TYPE_DISPLAY_PAGE_MODEL,
+                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+       g_object_class_install_property (object_class,
+                                        PROP_ROOT_PAGE,
+                                        g_param_spec_object ("root-page",
+                                                             "root page",
+                                                             "root page",
+                                                             RB_TYPE_DISPLAY_PAGE,
+                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+       g_object_class_install_property (object_class,
+                                        PROP_PAGE_TYPE,
+                                        g_param_spec_gtype ("page-type",
+                                                            "page type",
+                                                            "page type",
+                                                            RB_TYPE_DISPLAY_PAGE,
+                                                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+       g_object_class_install_property (object_class,
+                                        PROP_ACTION,
+                                        g_param_spec_string ("action",
+                                                             "action",
+                                                             "action name",
+                                                             NULL,
+                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+       g_type_class_add_private (klass, sizeof (RBDisplayPageMenuPrivate));
+}
+
+
+/**
+ * rb_display_page_menu_new:
+ * @model: the #RBDisplayPageModel
+ * @root: the page below which to search for pages to build the menu
+ * @page_type: type of pages to add to the menu
+ * @action: action name for the menu items
+ *
+ * Creates a menu from pages of type @page_type that are located 
+ * below @root in the model.  The menu items are associated with
+ * the given action name, and include a path string to the selected
+ * page as the action target.  Use @rb_display_page_menu_get_page
+ * to retrieve the page object.
+ *
+ * The menu is kept up to date as pages are added, removed, hidden
+ * and shown in the model.
+ *
+ * Return value: new menu
+ */
+GMenuModel *
+rb_display_page_menu_new (RBDisplayPageModel *model,
+                         RBDisplayPage *root,
+                         GType page_type,
+                         const char *action)
+{
+       return G_MENU_MODEL (g_object_new (RB_TYPE_DISPLAY_PAGE_MENU,
+                                          "model", model,
+                                          "root-page", root,
+                                          "page-type", page_type,
+                                          "action", action,
+                                          NULL));
+}
+
+/**
+ * rb_display_page_menu_get_page:
+ * @model: the #RBDisplayPageModel
+ * @parameters: action parameters
+ *
+ * Retrieves the page instance for an action invocation
+ * given the action parameters.
+ *
+ * Return value: (transfer full): page instance
+ */
+RBDisplayPage *
+rb_display_page_menu_get_page (RBDisplayPageModel *model, GVariant *parameters)
+{
+       GtkTreeIter iter;
+       RBDisplayPage *page;
+
+       if (g_variant_is_of_type (parameters, G_VARIANT_TYPE_STRING) == FALSE) {
+               rb_debug ("can't find page, variant type is %s", g_variant_get_type_string (parameters));
+               return NULL;
+       }
+
+       rb_debug ("trying to find page for %s", g_variant_get_string (parameters, NULL));
+
+       if (gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (model),
+                                                &iter,
+                                                g_variant_get_string (parameters, NULL)) == FALSE) {
+               return NULL;
+       }
+
+       gtk_tree_model_get (GTK_TREE_MODEL (model),
+                           &iter,
+                           RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
+                           -1);
+       return page;
+}
diff --git a/sources/rb-display-page-menu.h b/sources/rb-display-page-menu.h
new file mode 100644
index 0000000..93ccac5
--- /dev/null
+++ b/sources/rb-display-page-menu.h
@@ -0,0 +1,71 @@
+/*
+ *  Copyright (C) 2013 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_MENU_H
+#define RB_DISPLAY_PAGE_MENU_H
+
+#include <gtk/gtk.h>
+
+#include <sources/rb-display-page-model.h>
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_DISPLAY_PAGE_MENU         (rb_display_page_menu_get_type ())
+#define RB_DISPLAY_PAGE_MENU(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_DISPLAY_PAGE_MENU, 
RBDisplayPageMenu))
+#define RB_DISPLAY_PAGE_MENU_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_DISPLAY_PAGE_MENU, 
RBDisplayPageMenuClass))
+#define RB_IS_DISPLAY_PAGE_MENU(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_DISPLAY_PAGE_MENU))
+#define RB_IS_DISPLAY_PAGE_MENU_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_DISPLAY_PAGE_MENU))
+#define RB_DISPLAY_PAGE_MENU_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_DISPLAY_PAGE_MENU, 
RBDisplayPageMenuClass))
+
+typedef struct _RBDisplayPageMenu RBDisplayPageMenu;
+typedef struct _RBDisplayPageMenuClass RBDisplayPageMenuClass;
+typedef struct _RBDisplayPageMenuPrivate RBDisplayPageMenuPrivate;
+
+struct _RBDisplayPageMenu
+{
+       GMenuModel parent;
+       RBDisplayPageMenuPrivate *priv;
+};
+
+struct _RBDisplayPageMenuClass
+{
+       GMenuModelClass parent;
+};
+
+GType          rb_display_page_menu_get_type           (void);
+
+GMenuModel *   rb_display_page_menu_new                (RBDisplayPageModel *model,
+                                                        RBDisplayPage *root,
+                                                        GType page_type,
+                                                        const char *action);
+
+RBDisplayPage *        rb_display_page_menu_get_page           (RBDisplayPageModel *model,
+                                                        GVariant *parameters);
+
+G_END_DECLS
+
+#endif /* RB_DISPLAY_PAGE_MENU_H */
diff --git a/sources/rb-display-page-tree.c b/sources/rb-display-page-tree.c
index 071ab96..6556c9f 100644
--- a/sources/rb-display-page-tree.c
+++ b/sources/rb-display-page-tree.c
@@ -50,6 +50,10 @@
 #include "rb-util.h"
 #include "rb-auto-playlist-source.h"
 #include "rb-static-playlist-source.h"
+#include "rb-play-queue-source.h"
+#include "rb-device-source.h"
+#include "rb-builder-helpers.h"
+#include "rb-application.h"
 
 /**
  * SECTION:rb-display-page-tree
@@ -71,10 +75,14 @@
 
 struct _RBDisplayPageTreePrivate
 {
+       GtkWidget *scrolled;
        GtkWidget *treeview;
        GtkCellRenderer *title_renderer;
        GtkCellRenderer *expander_renderer;
 
+       GtkWidget *toolbar;
+       GtkWidget *add_menubutton;
+
        RBDisplayPageModel *page_model;
        GtkTreeSelection *selection;
 
@@ -88,6 +96,9 @@ struct _RBDisplayPageTreePrivate
        guint expand_rows_id;
 
        GSettings *settings;
+
+       GSimpleAction *remove_action;
+       GSimpleAction *eject_action;
 };
 
 
@@ -107,8 +118,24 @@ enum
 
 static guint signals[LAST_SIGNAL] = { 0 };
 
-G_DEFINE_TYPE (RBDisplayPageTree, rb_display_page_tree, GTK_TYPE_SCROLLED_WINDOW)
+G_DEFINE_TYPE (RBDisplayPageTree, rb_display_page_tree, GTK_TYPE_GRID)
+
+static RBDisplayPage *
+get_selected_page (RBDisplayPageTree *display_page_tree)
+{
+       GtkTreeIter iter;
+       GtkTreeModel *model;
+       RBDisplayPage *page;
+
+       if (!gtk_tree_selection_get_selected (display_page_tree->priv->selection, &model, &iter))
+               return NULL;
 
+       gtk_tree_model_get (model,
+                           &iter,
+                           RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
+                           -1);
+       return page;
+}
 
 static gboolean
 retrieve_expander_state (RBDisplayPageTree *display_page_tree, RBDisplayPageGroup *group)
@@ -176,7 +203,7 @@ set_cell_background (RBDisplayPageTree  *display_page_tree,
        g_return_if_fail (cell != NULL);
 
        gtk_style_context_get_color (gtk_widget_get_style_context (GTK_WIDGET (display_page_tree)),
-                                    GTK_STATE_SELECTED,
+                                    GTK_STATE_FLAG_SELECTED,
                                     &color);
 
        if (!is_group) {
@@ -217,7 +244,7 @@ indent_level1_cell_data_func (GtkTreeViewColumn *tree_column,
        depth = gtk_tree_path_get_depth (path);
        gtk_tree_path_free (path);
        g_object_set (cell,
-                     "text", "    ",
+                     "text", "  ",
                      "visible", depth > 1,
                      NULL);
 }
@@ -236,7 +263,7 @@ indent_level2_cell_data_func (GtkTreeViewColumn *tree_column,
        depth = gtk_tree_path_get_depth (path);
        gtk_tree_path_free (path);
        g_object_set (cell,
-                     "text", "    ",
+                     "text", "  ",
                      "visible", depth > 2,
                      NULL);
 }
@@ -482,76 +509,10 @@ model_row_inserted_cb (GtkTreeModel *model,
 }
 
 static gboolean
-emit_show_popup (GtkTreeView *treeview,
-                RBDisplayPageTree *display_page_tree)
-{
-       GtkTreeIter iter;
-       RBDisplayPage *page;
-
-       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 TRUE;
-}
-
-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;
 
@@ -560,15 +521,11 @@ key_release_cb (GtkTreeView *treeview,
                return FALSE;
        }
 
-       if (!gtk_tree_selection_get_selected (display_page_tree->priv->selection, NULL, &iter)) {
+       page = get_selected_page (display_page_tree);
+       if (page == NULL) {
                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) {
+       } else if (RB_IS_SOURCE (page) == FALSE) {
+               g_object_unref (page);
                return FALSE;
        }
 
@@ -582,14 +539,6 @@ key_release_cb (GtkTreeView *treeview,
        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
@@ -735,21 +684,24 @@ 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;
+       page = get_selected_page (display_page_tree);
+       if (page != NULL) {
+               g_signal_emit (display_page_tree, signals[SELECTED], 0, page);
 
-       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);
+               if (RB_IS_DEVICE_SOURCE (page) && rb_device_source_can_eject (RB_DEVICE_SOURCE (page))) {
+                       g_simple_action_set_enabled (display_page_tree->priv->eject_action, TRUE);
+               } else {
+                       g_simple_action_set_enabled (display_page_tree->priv->eject_action, FALSE);
+               }
+
+               g_simple_action_set_enabled (display_page_tree->priv->remove_action, 
rb_display_page_can_remove (page));
+               g_object_unref (page);
+       } else {
+               g_simple_action_set_enabled (display_page_tree->priv->remove_action, FALSE);
+               g_simple_action_set_enabled (display_page_tree->priv->eject_action, FALSE);
+       }
 }
 
 static void
@@ -782,6 +734,32 @@ source_name_edited_cb (GtkCellRendererText *renderer,
        g_object_unref (page);
 }
 
+static void
+remove_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
+{
+       RBDisplayPage *page = get_selected_page (RB_DISPLAY_PAGE_TREE (user_data));
+       if (page) {
+               rb_display_page_delete_thyself (page);
+               g_object_unref (page);
+       }
+}
+
+static void
+eject_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
+{
+       RBDisplayPage *page = get_selected_page (RB_DISPLAY_PAGE_TREE (user_data));
+       if (page == NULL) {
+               /* nothing */
+       } else if (RB_IS_DEVICE_SOURCE (page) && rb_device_source_can_eject (RB_DEVICE_SOURCE (page))) {
+               rb_device_source_eject (RB_DEVICE_SOURCE (page));
+               g_object_unref (page);
+       } else {
+               rb_debug ("why are we here?");
+               g_object_unref (page);
+       }
+}
+
+
 static gboolean
 display_page_search_equal_func (GtkTreeModel *model,
                                gint column,
@@ -827,11 +805,6 @@ 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));
 }
@@ -896,24 +869,36 @@ static void
 impl_constructed (GObject *object)
 {
        RBDisplayPageTree *display_page_tree;
+       GtkCellRenderer *renderer;
+       GtkWidget *scrolled;
+       GtkStyleContext *context;
+       GtkToolItem *button;
+       GtkWidget *image;
+       GIcon *icon;
+       GMenuModel *menu;
+       GtkBuilder *builder;
+       GApplication *app;
+
+       GActionEntry actions[] = {
+               { "display-page-remove", remove_action_cb },
+               { "display-page-eject", eject_action_cb }
+       };
 
        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);
+       gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (object)),
+                                    GTK_STYLE_CLASS_SIDEBAR);
 
-       display_page_tree->priv->settings = g_settings_new ("org.gnome.rhythmbox.display-page-tree");
-}
-
-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);
+       scrolled = gtk_scrolled_window_new (NULL, NULL);
+       g_object_set (scrolled,
+                     "hscrollbar_policy", GTK_POLICY_AUTOMATIC,
+                     "vscrollbar_policy", GTK_POLICY_AUTOMATIC,
+                     "shadow_type", GTK_SHADOW_IN,
+                     "hexpand", TRUE,
+                     "vexpand", TRUE,
+                     NULL);
+       gtk_grid_attach (GTK_GRID (display_page_tree), scrolled, 0, 0, 1, 1);
 
        display_page_tree->priv->page_model = rb_display_page_model_new ();
        g_signal_connect_object (display_page_tree->priv->page_model,
@@ -954,19 +939,10 @@ rb_display_page_tree_init (RBDisplayPageTree *display_page_tree)
                                 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);
 
@@ -1039,6 +1015,61 @@ rb_display_page_tree_init (RBDisplayPageTree *display_page_tree)
                                                 NULL);
        display_page_tree->priv->expander_renderer = renderer;
 
+       /* toolbar actions */
+       app = g_application_get_default ();
+       g_action_map_add_action_entries (G_ACTION_MAP (app), actions, G_N_ELEMENTS (actions), 
display_page_tree);
+
+       /* disable the remove and eject actions initially */
+       display_page_tree->priv->remove_action = G_SIMPLE_ACTION (g_action_map_lookup_action (G_ACTION_MAP 
(app), "display-page-remove"));
+       display_page_tree->priv->eject_action = G_SIMPLE_ACTION (g_action_map_lookup_action (G_ACTION_MAP 
(app), "display-page-eject"));
+       g_simple_action_set_enabled (display_page_tree->priv->remove_action, FALSE);
+       g_simple_action_set_enabled (display_page_tree->priv->eject_action, FALSE);
+
+       /* toolbar */
+       display_page_tree->priv->toolbar = gtk_toolbar_new ();
+       gtk_toolbar_set_style (GTK_TOOLBAR (display_page_tree->priv->toolbar), GTK_TOOLBAR_ICONS);
+       gtk_toolbar_set_icon_size (GTK_TOOLBAR (display_page_tree->priv->toolbar), GTK_ICON_SIZE_MENU);
+
+       context = gtk_widget_get_style_context (display_page_tree->priv->toolbar);
+       gtk_style_context_set_junction_sides (context, GTK_JUNCTION_BOTTOM);
+       gtk_style_context_add_class (context, GTK_STYLE_CLASS_INLINE_TOOLBAR);
+
+       gtk_grid_attach (GTK_GRID (display_page_tree), display_page_tree->priv->toolbar, 0, 1, 1, 1);
+
+       button = gtk_tool_item_new ();
+       display_page_tree->priv->add_menubutton = gtk_menu_button_new ();
+       icon = g_themed_icon_new_with_default_fallbacks ("list-add-symbolic");
+       image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_SMALL_TOOLBAR);
+       gtk_button_set_image (GTK_BUTTON (display_page_tree->priv->add_menubutton), image);
+       gtk_container_add (GTK_CONTAINER (button), display_page_tree->priv->add_menubutton);
+       gtk_toolbar_insert (GTK_TOOLBAR (display_page_tree->priv->toolbar), button, -1);
+       g_object_unref (icon);
+
+       builder = rb_builder_load ("display-page-add-menu.ui", NULL);
+       menu = G_MENU_MODEL (gtk_builder_get_object (builder, "display-page-add-menu"));
+       rb_application_link_shared_menus (RB_APPLICATION (g_application_get_default ()), G_MENU (menu));
+       gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (display_page_tree->priv->add_menubutton), menu);
+       g_object_unref (builder);
+
+       button = gtk_tool_button_new (NULL, NULL);
+       icon = g_themed_icon_new_with_default_fallbacks ("list-remove-symbolic");
+       image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_SMALL_TOOLBAR);
+       gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (button), image);
+       gtk_toolbar_insert (GTK_TOOLBAR (display_page_tree->priv->toolbar), button, -1);
+       g_object_unref (icon);
+
+       gtk_actionable_set_action_name (GTK_ACTIONABLE (button), "app.display-page-remove");
+
+       /* maybe this should be a column in the tree instead.. */
+       button = gtk_tool_button_new (NULL, NULL);
+       icon = g_themed_icon_new_with_default_fallbacks ("media-eject-symbolic");
+       image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_SMALL_TOOLBAR);
+       gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (button), image);
+       gtk_toolbar_insert (GTK_TOOLBAR (display_page_tree->priv->toolbar), button, -1);
+       g_object_unref (icon);
+       
+       gtk_actionable_set_action_name (GTK_ACTIONABLE (button), "app.display-page-eject");
+
        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",
@@ -1049,6 +1080,19 @@ rb_display_page_tree_init (RBDisplayPageTree *display_page_tree)
                                                (GtkTreeSelectionFunc) selection_check_cb,
                                                display_page_tree,
                                                NULL);
+
+       gtk_container_add (GTK_CONTAINER (scrolled), display_page_tree->priv->treeview);
+
+       display_page_tree->priv->settings = g_settings_new ("org.gnome.rhythmbox.display-page-tree");
+}
+
+static void
+rb_display_page_tree_init (RBDisplayPageTree *display_page_tree)
+{
+       display_page_tree->priv =
+               G_TYPE_INSTANCE_GET_PRIVATE (display_page_tree,
+                                            RB_TYPE_DISPLAY_PAGE_TREE,
+                                            RBDisplayPageTreePrivate);
 }
 
 static void
diff --git a/sources/rb-display-page-tree.h b/sources/rb-display-page-tree.h
index 91fafa9..5d947e2 100644
--- a/sources/rb-display-page-tree.h
+++ b/sources/rb-display-page-tree.h
@@ -49,14 +49,14 @@ typedef struct _RBDisplayPageTreePrivate RBDisplayPageTreePrivate;
 
 struct _RBDisplayPageTree
 {
-       GtkScrolledWindow    parent;
+       GtkGrid    parent;
 
        RBDisplayPageTreePrivate *priv;
 };
 
 struct _RBDisplayPageTreeClass
 {
-       GtkScrolledWindowClass parent_class;
+       GtkGridClass parent_class;
 
        /* signals */
        void (*selected) (RBDisplayPageTree *tree, RBDisplayPage *page);
diff --git a/sources/rb-display-page.c b/sources/rb-display-page.c
index 51babeb..5669fad 100644
--- a/sources/rb-display-page.c
+++ b/sources/rb-display-page.c
@@ -71,7 +71,6 @@ enum
 {
        PROP_0,
        PROP_SHELL,
-       PROP_UI_MANAGER,
        PROP_NAME,
        PROP_PIXBUF,
        PROP_VISIBLE,
@@ -134,7 +133,7 @@ rb_display_page_receive_drag (RBDisplayPage *page, GtkSelectionData *data)
  * 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)
 {
@@ -145,6 +144,7 @@ rb_display_page_show_popup (RBDisplayPage *page)
        else
                return FALSE;
 }
+*/
 
 /**
  * rb_display_page_delete_thyself:
@@ -175,6 +175,40 @@ rb_display_page_delete_thyself (RBDisplayPage *page)
 }
 
 /**
+ * rb_display_page_can_remove:
+ * @page: a #RBDisplayPage
+ *
+ * Called to check whether the user is able to remove the page
+ *
+ * Return value: %TRUE if the page can be removed
+ */
+gboolean
+rb_display_page_can_remove (RBDisplayPage *page)
+{
+       RBDisplayPageClass *klass;
+       klass = RB_DISPLAY_PAGE_GET_CLASS (page);
+       if (klass->can_remove)
+               return klass->can_remove (page);
+
+       return FALSE;
+}
+
+/**
+ * rb_display_page_remove:
+ * @page: a #RBDisplayPage
+ *
+ * Called when the user requests removal of a page.
+ */
+void
+rb_display_page_remove (RBDisplayPage *page)
+{
+       RBDisplayPageClass *klass;
+       klass = RB_DISPLAY_PAGE_GET_CLASS (page);
+       g_assert (klass->remove != NULL);
+       klass->remove (page);
+}
+
+/**
  * rb_display_page_selectable:
  * @page: a #RBDisplayPage
  *
@@ -303,92 +337,14 @@ 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 void (*DisplayPageActionActivateCallback) (GSimpleAction *action, GVariant *parameters, 
RBDisplayPage *page);
+typedef void (*DisplayPageActionChangeStateCallback) (GSimpleAction *action, GVariant *value, RBDisplayPage 
*page);
 
 typedef struct {
-       DisplayPageActionCallback callback;
+       union {
+               DisplayPageActionActivateCallback gaction;
+               DisplayPageActionChangeStateCallback gactionstate;
+       } u;
        gpointer shell;
 } DisplayPageActionData;
 
@@ -402,7 +358,7 @@ display_page_action_data_destroy (DisplayPageActionData *data)
 }
 
 static void
-display_page_action_cb (GtkAction *action, DisplayPageActionData *data)
+display_page_action_activate_cb (GSimpleAction *action, GVariant *parameters, DisplayPageActionData *data)
 {
        RBDisplayPage *page;
 
@@ -410,69 +366,94 @@ display_page_action_cb (GtkAction *action, DisplayPageActionData *data)
                return;
        }
 
-       /* get current page */
        g_object_get (data->shell, "selected-page", &page, NULL);
        if (page != NULL) {
-               data->callback (action, page);
+               data->u.gaction (action, parameters, page);
+               g_object_unref (page);
+       }
+}
+
+static void
+display_page_action_change_state_cb (GSimpleAction *action, GVariant *value, DisplayPageActionData *data)
+{
+       RBDisplayPage *page;
+
+       if (data->shell == NULL) {
+               return;
+       }
+
+       g_object_get (data->shell, "selected-page", &page, NULL);
+       if (page != NULL) {
+               data->u.gactionstate (action, value, 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)
+_rb_add_display_page_actions (GActionMap *map, GObject *shell, const GActionEntry *actions, gint n_entries)
 {
        int i;
-       for (i = 0; i < num_actions; i++) {
-               GtkAction *action;
-               const char *label;
-               const char *tooltip;
+       for (i = 0; i < n_entries; i++) {
+               GSimpleAction *action;
+               const GVariantType *parameter_type;
                DisplayPageActionData *page_action_data;
 
-               if (gtk_action_group_get_action (group, actions[i].name) != NULL) {
+               if (g_action_map_lookup_action (map, 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);
+               if (actions[i].parameter_type) {
+                       parameter_type = G_VARIANT_TYPE (actions[i].parameter_type);
+               } else {
+                       parameter_type = NULL;
+               }
 
-               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].state) {
+                       GVariant *state;
+                       GError *error = NULL;
+                       state = g_variant_parse (NULL, actions[i].state, NULL, NULL, &error);
+                       if (state == NULL) {
+                               g_critical ("could not parse state value '%s' for action "
+                                           "%s: %s",
+                                           actions[i].state, actions[i].name, error->message);
+                               g_error_free (error);
+                               continue;
                        }
+                       action = g_simple_action_new_stateful (actions[i].name,
+                                                              parameter_type,
+                                                              state);
+               } else {
+                       action = g_simple_action_new (actions[i].name, parameter_type);
                }
 
-               if (actions[i].callback) {
+               if (actions[i].activate) {
                        GClosure *closure;
                        page_action_data = g_slice_new0 (DisplayPageActionData);
-                       page_action_data->callback = (DisplayPageActionCallback) actions[i].callback;
+                       page_action_data->u.gaction = (DisplayPageActionActivateCallback) actions[i].activate;
                        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),
+                       closure = g_cclosure_new (G_CALLBACK (display_page_action_activate_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);
+               if (actions[i].change_state) {
+                       GClosure *closure;
+                       page_action_data = g_slice_new0 (DisplayPageActionData);
+                       page_action_data->u.gactionstate = (DisplayPageActionChangeStateCallback) 
actions[i].change_state;
+                       page_action_data->shell = shell;
+                       g_object_add_weak_pointer (shell, &page_action_data->shell);
+
+                       closure = g_cclosure_new (G_CALLBACK (display_page_action_change_state_cb),
+                                                 page_action_data,
+                                                 (GClosureNotify) display_page_action_data_destroy);
+                       g_signal_connect_closure (action, "change-state", closure, FALSE);
+               }
+
+               g_action_map_add_action (map, G_ACTION (action));
                g_object_unref (action);
        }
 }
@@ -486,14 +467,6 @@ impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *ps
        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;
@@ -637,18 +610,6 @@ rb_display_page_class_init (RBDisplayPageClass *klass)
                                                              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
@@ -753,21 +714,3 @@ rb_display_page_class_init (RBDisplayPageClass *klass)
 
        g_type_class_add_private (object_class, sizeof (RBDisplayPagePrivate));
 }
-
-/* introspection annotations for vmethods */
-
-/**
- * impl_get_status:
- * @source: a #RBSource
- * @text: (inout) (allow-none) (transfer full): holds the returned status text
- * @progress_text: (inout) (allow-none) (transfer full): holds the returned text for the progress bar
- * @progress: (inout): holds the progress value
- */
-
-/**
- * impl_get_config_widget:
- * @source: a #RBSource
- * @prefs: a #RBShellPreferences
- *
- * Return value: (transfer none): configuration widget
- */
diff --git a/sources/rb-display-page.h b/sources/rb-display-page.h
index 3c072ee..32e425b 100644
--- a/sources/rb-display-page.h
+++ b/sources/rb-display-page.h
@@ -72,14 +72,17 @@ struct _RBDisplayPageClass
 
        void    (*get_status)           (RBDisplayPage *page, char **text, char **progress_text, float 
*progress);
        gboolean (*receive_drag)        (RBDisplayPage *page, GtkSelectionData *data);
-       gboolean (*show_popup)          (RBDisplayPage *page);
+       /*gboolean (*show_popup)                (RBDisplayPage *page);*/
        void    (*delete_thyself)       (RBDisplayPage *page);
+
+       gboolean (*can_remove)          (RBDisplayPage *page);
+       void    (*remove)               (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_show_popup              (RBDisplayPage *page);*/
 
 gboolean       rb_display_page_selectable              (RBDisplayPage *page);
 void           rb_display_page_selected                (RBDisplayPage *page);
@@ -91,19 +94,16 @@ void                rb_display_page_get_status              (RBDisplayPage *page, char 
**text, char **prog
 
 void           rb_display_page_delete_thyself          (RBDisplayPage *page);
 
+gboolean       rb_display_page_can_remove              (RBDisplayPage *page);
+void           rb_display_page_remove                  (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,
+
+void           _rb_add_display_page_actions            (GActionMap *map,
                                                         GObject *shell,
-                                                        GtkActionEntry *actions,
+                                                        const GActionEntry *actions,
                                                         int num_actions);
 
 /* things for the display page model */
diff --git a/sources/rb-import-errors-source.c b/sources/rb-import-errors-source.c
index 2eae5e6..ac70a76 100644
--- a/sources/rb-import-errors-source.c
+++ b/sources/rb-import-errors-source.c
@@ -35,6 +35,7 @@
 #include "rb-util.h"
 #include "rb-debug.h"
 #include "rb-missing-plugins.h"
+#include "rb-builder-helpers.h"
 
 static void rb_import_errors_source_class_init (RBImportErrorsSourceClass *klass);
 static void rb_import_errors_source_init (RBImportErrorsSource *source);
@@ -77,6 +78,8 @@ struct _RBImportErrorsSourcePrivate
 
        RhythmDBEntryType *normal_entry_type;
        RhythmDBEntryType *ignore_entry_type;
+
+       GMenuModel *popup;
 };
 
 G_DEFINE_TYPE (RBImportErrorsSource, rb_import_errors_source, RB_TYPE_SOURCE);
@@ -397,7 +400,28 @@ rb_import_errors_source_songs_show_popup_cb (RBEntryView *view,
                                             gboolean over_entry,
                                             RBImportErrorsSource *source)
 {
-       _rb_display_page_show_popup (RB_DISPLAY_PAGE (source), "/ImportErrorsViewPopup");
+       GtkWidget *menu;
+       GtkBuilder *builder;
+
+       if (over_entry == FALSE)
+               return;
+
+       if (source->priv->popup == NULL) {
+               builder = rb_builder_load ("import-errors-popup.ui", NULL);
+               source->priv->popup = G_MENU_MODEL (gtk_builder_get_object (builder, "import-errors-popup"));
+               g_object_ref (source->priv->popup);
+               g_object_unref (builder);
+       }
+
+       menu = gtk_menu_new_from_model (source->priv->popup);
+       gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (source), NULL);
+       gtk_menu_popup (GTK_MENU (menu),
+                       NULL,
+                       NULL,
+                       NULL,
+                       NULL,
+                       3,
+                       gtk_get_current_event_time ());
 }
 
 static void
diff --git a/sources/rb-library-source.c b/sources/rb-library-source.c
index 061b4bd..a4f3ccb 100644
--- a/sources/rb-library-source.c
+++ b/sources/rb-library-source.c
@@ -69,6 +69,10 @@
 #include "rb-gst-media-types.h"
 #include "rb-object-property-editor.h"
 #include "rb-import-dialog.h"
+#include "rb-application.h"
+#include "rb-display-page-menu.h"
+#include "rb-display-page-group.h"
+#include "rb-static-playlist-source.h"
 
 #define SOURCE_PAGE            0
 #define IMPORT_DIALOG_PAGE     1
@@ -79,7 +83,6 @@ static void rb_library_source_constructed (GObject *object);
 static void rb_library_source_dispose (GObject *object);
 static void rb_library_source_finalize (GObject *object);
 
-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);
@@ -195,7 +198,6 @@ rb_library_source_class_init (RBLibrarySourceClass *klass)
        object_class->finalize = rb_library_source_finalize;
        object_class->constructed = rb_library_source_constructed;
 
-       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;
@@ -328,6 +330,10 @@ rb_library_source_constructed (GObject *object)
        RBShell *shell;
        RBEntryView *songs;
        char **locations;
+       RBDisplayPageModel *model;
+       GMenuModel *playlist_menu;
+       GMenu *playlist_add_menu;
+       GMenu *playlist_add_section;
 
        source = RB_LIBRARY_SOURCE (object);
        source->priv->notebook = gtk_notebook_new ();
@@ -381,6 +387,23 @@ rb_library_source_constructed (GObject *object)
        rb_entry_view_append_column (songs, RB_ENTRY_VIEW_COL_LAST_PLAYED, FALSE);
        rb_entry_view_append_column (songs, RB_ENTRY_VIEW_COL_FIRST_SEEN, FALSE);
 
+       /* set up playlist menu */
+       g_object_get (shell, "display-page-model", &model, NULL);
+       playlist_add_menu = g_menu_new ();
+       playlist_add_section = g_menu_new ();
+       g_menu_append (playlist_add_section, _("Add to New Playlist"), "app.playlist-add-to-new");
+       playlist_menu = rb_display_page_menu_new (model,
+                                                 RB_DISPLAY_PAGE_GROUP_PLAYLISTS,
+                                                 RB_TYPE_STATIC_PLAYLIST_SOURCE,
+                                                 "app.playlist-add-to");
+       g_menu_append_section (playlist_add_menu, NULL, G_MENU_MODEL (playlist_add_section));
+       g_menu_append_section (playlist_add_menu, NULL, G_MENU_MODEL (playlist_menu));
+       rb_application_add_shared_menu (RB_APPLICATION (g_application_get_default ()),
+                                       "playlist-page-menu",
+                                       G_MENU_MODEL (playlist_add_menu));
+       g_object_set (source, "playlist-menu", playlist_add_menu, NULL);
+       g_object_unref (model);
+
        rb_library_source_sync_child_sources (source);
 
        g_object_unref (shell);
@@ -400,6 +423,8 @@ rb_library_source_new (RBShell *shell)
        RBSource *source;
        GdkPixbuf *icon;
        GSettings *settings;
+       GtkBuilder *builder;
+       GMenu *toolbar;
        gint size;
 
        gtk_icon_size_lookup (RB_SOURCE_ICON_SIZE, &size, NULL);
@@ -408,19 +433,25 @@ rb_library_source_new (RBShell *shell)
                                         size,
                                         0, NULL);
        settings = g_settings_new ("org.gnome.rhythmbox.library");
+
+       builder = rb_builder_load ("library-toolbar.ui", NULL);
+       toolbar = G_MENU (gtk_builder_get_object (builder, "library-toolbar"));
+       rb_application_link_shared_menus (RB_APPLICATION (g_application_get_default ()), toolbar);
+
        source = RB_SOURCE (g_object_new (RB_TYPE_LIBRARY_SOURCE,
                                          "name", _("Music"),
                                          "entry-type", RHYTHMDB_ENTRY_TYPE_SONG,
                                          "shell", shell,
                                          "pixbuf", icon,
                                          "populate", FALSE,            /* wait until the database is loaded 
*/
-                                         "toolbar-path", "/LibrarySourceToolBar",
+                                         "toolbar-menu", toolbar,
                                          "settings", g_settings_get_child (settings, "source"),
                                          NULL));
        if (icon != NULL) {
                g_object_unref (icon);
        }
        g_object_unref (settings);
+       g_object_unref (builder);
 
        rb_shell_register_entry_type_for_source (shell, source, RHYTHMDB_ENTRY_TYPE_SONG);
 
@@ -967,13 +998,6 @@ impl_receive_drag (RBDisplayPage *asource, GtkSelectionData *data)
        return TRUE;
 }
 
-static gboolean
-impl_show_popup (RBDisplayPage *source)
-{
-       _rb_display_page_show_popup (source, "/LibrarySourcePopup");
-       return TRUE;
-}
-
 static void
 rb_library_source_path_changed_cb (GtkComboBox *box, RBLibrarySource *source)
 {
@@ -1865,10 +1889,13 @@ rb_library_source_add_child_source (const char *path, RBLibrarySource *library_s
        char *sort_column;
        int sort_order;
        GFile *file;
+       GMenuModel *playlist_menu;
 
        g_object_get (library_source,
                      "shell", &shell,
                      "entry-type", &entry_type,
+                     "playlist-menu", &playlist_menu,
+                     "pixbuf", &icon,
                      NULL);
 
        file = g_file_new_for_uri (path);
@@ -1889,15 +1916,16 @@ 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, "pixbuf", &icon, NULL);
-       g_object_set (source, "pixbuf", icon, NULL);
-       if (icon != NULL) {
-               g_object_unref (icon);
-       }
+       g_object_set (source,
+                     "pixbuf", icon,
+                     "playlist-menu", playlist_menu,
+                     NULL);
 
        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_clear_object (&icon);
+       g_object_unref (playlist_menu);
        g_object_unref (entry_type);
        g_object_unref (shell);
        g_free (name);
diff --git a/sources/rb-media-player-source.c b/sources/rb-media-player-source.c
index 20ac983..2e0273b 100644
--- a/sources/rb-media-player-source.c
+++ b/sources/rb-media-player-source.c
@@ -50,9 +50,6 @@
 typedef struct {
        RBSyncSettings *sync_settings;
 
-       GtkActionGroup *action_group;
-       GtkAction *sync_action;
-
        /* properties dialog bits */
        GtkDialog *properties_dialog;
        RBSyncBarData volume_usage;
@@ -66,6 +63,10 @@ typedef struct {
        /* sync state */
        RBSyncState *sync_state;
 
+       GAction *sync_action;
+       GAction *properties_action;
+       gboolean syncing;
+
        GstEncodingTarget *encoding_target;
 } RBMediaPlayerSourcePrivate;
 
@@ -87,19 +88,14 @@ static void rb_media_player_source_get_property (GObject *object,
                                         GParamSpec *pspec);
 static void rb_media_player_source_constructed (GObject *object);
 
-static void sync_cmd (GtkAction *action, RBSource *source);
+static void sync_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data);
+static void properties_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data);
 static gboolean sync_idle_delete_entries (RBMediaPlayerSource *source);
 
 static gboolean impl_receive_drag (RBDisplayPage *page, GtkSelectionData *data);
 static void impl_delete_thyself (RBDisplayPage *page);
 
-static char *impl_get_delete_action (RBSource *source);
-
-static GtkActionEntry rb_media_player_source_actions[] = {
-       { "MediaPlayerSourceSync", GTK_STOCK_REFRESH, N_("Sync with Library"), NULL,
-         N_("Synchronize media player with the library"),
-         G_CALLBACK (sync_cmd) },
-};
+static char *impl_get_delete_label (RBSource *source);
 
 enum
 {
@@ -108,30 +104,6 @@ enum
        PROP_ENCODING_TARGET
 };
 
-static GtkActionGroup *action_group = NULL;
-
-void
-rb_media_player_source_init_actions (RBShell *shell)
-{
-       GtkUIManager *uimanager;
-
-       if (action_group != NULL) {
-               return;
-       }
-
-       action_group = gtk_action_group_new ("MediaPlayerActions");
-       gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
-
-       g_object_get (shell, "ui-manager", &uimanager, NULL);
-       gtk_ui_manager_insert_action_group (uimanager, action_group, 0);
-       g_object_unref (uimanager);
-
-       _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
 rb_media_player_source_class_init (RBMediaPlayerSourceClass *klass)
 {
@@ -152,7 +124,7 @@ rb_media_player_source_class_init (RBMediaPlayerSourceClass *klass)
        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_get_delete_action = impl_get_delete_action;
+       source_class->impl_get_delete_label = impl_get_delete_label;
        source_class->impl_delete = NULL;
 
        browser_source_class->has_drop_support = (RBBrowserSourceFeatureFunc) rb_false_function;
@@ -256,14 +228,29 @@ rb_media_player_source_get_property (GObject *object,
 }
 
 static void
-load_status_changed_cb (GObject *object, GParamSpec *pspec, gpointer whatever)
+update_actions (RBMediaPlayerSource *source)
 {
-       RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (object);
+       RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (source);
        RBSourceLoadStatus status;
+       gboolean selected;
+
+       g_object_get (source,
+                     "load-status", &status,
+                     "selected", &selected,
+                     NULL);
+
+       if (selected) {
+               g_simple_action_set_enabled (G_SIMPLE_ACTION (priv->sync_action),
+                                            (status == RB_SOURCE_LOAD_STATUS_LOADED) && (priv->syncing == 
FALSE));
+               g_simple_action_set_enabled (G_SIMPLE_ACTION (priv->properties_action),
+                                            (status == RB_SOURCE_LOAD_STATUS_LOADED));
+       }
+}
 
-       g_object_get (object, "load-status", &status, NULL);
-
-       gtk_action_set_sensitive (priv->sync_action, (status == RB_SOURCE_LOAD_STATUS_LOADED));
+static void
+load_status_changed_cb (GObject *object, GParamSpec *pspec, gpointer whatever)
+{
+       update_actions (RB_MEDIA_PLAYER_SOURCE (object));
 }
 
 static void
@@ -271,16 +258,23 @@ rb_media_player_source_constructed (GObject *object)
 {
        RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (object);
        RBShell *shell;
+       GApplication *app;
+       GActionEntry actions[] = {
+               { "media-player-sync", sync_action_cb },
+               { "media-player-properties", properties_action_cb }
+       };
 
        RB_CHAIN_GOBJECT_METHOD (rb_media_player_source_parent_class, constructed, object);
 
+       app = g_application_get_default ();
        g_object_get (object, "shell", &shell, NULL);
-       rb_media_player_source_init_actions (shell);
+       _rb_add_display_page_actions (G_ACTION_MAP (app), G_OBJECT (shell), actions, G_N_ELEMENTS (actions));
        g_object_unref (shell);
 
-       priv->sync_action = gtk_action_group_get_action (action_group, "MediaPlayerSourceSync");
+       priv->sync_action = g_action_map_lookup_action (G_ACTION_MAP (app), "media-player-sync");
+       priv->properties_action = g_action_map_lookup_action (G_ACTION_MAP (app), "media-player-properties");
        g_signal_connect (object, "notify::load-status", G_CALLBACK (load_status_changed_cb), NULL);
-       load_status_changed_cb (object, NULL, NULL);
+       update_actions (RB_MEDIA_PLAYER_SOURCE (object));
 }
 
 /* must be called once device information is available via source properties */
@@ -565,7 +559,8 @@ sync_idle_cb_cleanup (RBMediaPlayerSource *source)
 
        rb_debug ("cleaning up after sync process");
 
-       gtk_action_set_sensitive (priv->sync_action, TRUE);
+       priv->syncing = FALSE;
+       update_actions (source);
 
        /* release the ref taken at the start of the sync */
        g_object_unref (source);
@@ -805,7 +800,8 @@ rb_media_player_source_sync (RBMediaPlayerSource *source)
 {
        RBMediaPlayerSourcePrivate *priv = MEDIA_PLAYER_SOURCE_GET_PRIVATE (source);
 
-       gtk_action_set_sensitive (priv->sync_action, FALSE);
+       priv->syncing = TRUE;
+       update_actions (source);
 
        /* ref the source for the duration of the sync operation */
        g_idle_add ((GSourceFunc)sync_idle_cb_update_sync, g_object_ref (source));
@@ -820,9 +816,15 @@ _rb_media_player_source_add_to_map (GHashTable *map, RhythmDBEntry *entry)
 }
 
 static void
-sync_cmd (GtkAction *action, RBSource *source)
+sync_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
+{
+       rb_media_player_source_sync (RB_MEDIA_PLAYER_SOURCE (data));
+}
+
+static void
+properties_action_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
-       rb_media_player_source_sync (RB_MEDIA_PLAYER_SOURCE (source));
+       rb_media_player_source_show_properties (RB_MEDIA_PLAYER_SOURCE (data));
 }
 
 static RhythmDB *
@@ -912,9 +914,9 @@ impl_receive_drag (RBDisplayPage *page, GtkSelectionData *data)
 }
 
 static char *
-impl_get_delete_action (RBSource *source)
+impl_get_delete_label (RBSource *source)
 {
-       return g_strdup ("EditDelete");
+       return g_strdup (_("Delete"));
 }
 
 static void
@@ -936,21 +938,3 @@ impl_delete_thyself (RBDisplayPage *page)
        rhythmdb_commit (db);
        g_object_unref (db);
 }
-
-/* annotations for methods */
-
-/**
- * impl_delete_entries:
- * @source: the source
- * @entries: (element-type RB.RhythmDBEntry) (transfer full): list of entries to delete
- * @callback: callback to call on completion
- * @data: (closure) (scope notified): callback data
- * @destroy_data: callback to free callback data
- */
-
-/**
- * impl_add_playlist:
- * @source: the source
- * @name: new playlist name
- * @entries: (element-type RB.RhythmDBEntry) (transfer full): list of entries to add
- */
diff --git a/sources/rb-media-player-source.h b/sources/rb-media-player-source.h
index eef8835..5b6e846 100644
--- a/sources/rb-media-player-source.h
+++ b/sources/rb-media-player-source.h
@@ -73,8 +73,6 @@ struct _RBMediaPlayerSourceClass
 
 GType  rb_media_player_source_get_type (void);
 
-void   rb_media_player_source_init_actions     (RBShell *shell);
-
 void   rb_media_player_source_load             (RBMediaPlayerSource *source);
 
 guint64 rb_media_player_source_get_capacity    (RBMediaPlayerSource *source);
diff --git a/sources/rb-missing-files-source.c b/sources/rb-missing-files-source.c
index 70efbec..86b3a1f 100644
--- a/sources/rb-missing-files-source.c
+++ b/sources/rb-missing-files-source.c
@@ -36,6 +36,7 @@
 #include "rb-song-info.h"
 #include "rb-util.h"
 #include "rb-debug.h"
+#include "rb-builder-helpers.h"
 
 /**
  * SECTION:rb-missing-files-source
@@ -76,12 +77,11 @@ static void rb_missing_files_source_songs_sort_order_changed_cb (GObject *object
                                                                 GParamSpec *pspec,
                                                                 RBMissingFilesSource *source);
 
-#define MISSING_FILES_SOURCE_SONGS_POPUP_PATH "/MissingFilesViewPopup"
-
 struct RBMissingFilesSourcePrivate
 {
        RhythmDB *db;
        RBEntryView *view;
+       GMenuModel *popup;
 };
 
 G_DEFINE_TYPE (RBMissingFilesSource, rb_missing_files_source, RB_TYPE_SOURCE);
@@ -297,8 +297,28 @@ rb_missing_files_source_songs_show_popup_cb (RBEntryView *view,
                                             gboolean over_entry,
                                             RBMissingFilesSource *source)
 {
-       if (over_entry)
-               _rb_display_page_show_popup (RB_DISPLAY_PAGE (source), MISSING_FILES_SOURCE_SONGS_POPUP_PATH);
+       GtkWidget *menu;
+       GtkBuilder *builder;
+
+       if (over_entry == FALSE)
+               return;
+
+       if (source->priv->popup == NULL) {
+               builder = rb_builder_load ("missing-files-popup.ui", NULL);
+               source->priv->popup = G_MENU_MODEL (gtk_builder_get_object (builder, "missing-files-popup"));
+               g_object_ref (source->priv->popup);
+               g_object_unref (builder);
+       }
+
+       menu = gtk_menu_new_from_model (source->priv->popup);
+       gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (source), NULL);
+       gtk_menu_popup (GTK_MENU (menu),
+                       NULL,
+                       NULL,
+                       NULL,
+                       NULL,
+                       3,
+                       gtk_get_current_event_time ());
 }
 
 static void
diff --git a/sources/rb-play-queue-source.c b/sources/rb-play-queue-source.c
index 3bcdb53..c26029d 100644
--- a/sources/rb-play-queue-source.c
+++ b/sources/rb-play-queue-source.c
@@ -40,6 +40,8 @@
 #include "rb-util.h"
 #include "rb-debug.h"
 #include "rb-play-order-queue.h"
+#include "rb-builder-helpers.h"
+#include "rb-application.h"
 
 /**
  * SECTION:rb-play-queue-source
@@ -94,11 +96,13 @@ static void impl_show_entry_view_popup (RBPlaylistSource *source,
                                        gboolean over_entry);
 static void impl_save_contents_to_xml (RBPlaylistSource *source,
                                       xmlNodePtr node);
-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 gboolean impl_show_popup (RBDisplayPage *page);
+
+static void queue_clear_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data);
+static void queue_shuffle_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data);
+static void queue_delete_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data);
+static void queue_properties_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data);
+static void queue_save_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data);
+
 
 static void rb_play_queue_dbus_method_call (GDBusConnection *connection,
                                            const char *sender,
@@ -109,21 +113,19 @@ static void rb_play_queue_dbus_method_call (GDBusConnection *connection,
                                            GDBusMethodInvocation *invocation,
                                            RBPlayQueueSource *source);
 
-#define PLAY_QUEUE_SOURCE_SONGS_POPUP_PATH "/QueuePlaylistViewPopup"
-#define PLAY_QUEUE_SOURCE_SIDEBAR_POPUP_PATH "/QueueSidebarViewPopup"
-#define PLAY_QUEUE_SOURCE_POPUP_PATH "/QueueSourcePopup"
-
 typedef struct _RBPlayQueueSourcePrivate RBPlayQueueSourcePrivate;
 
 struct _RBPlayQueueSourcePrivate
 {
        RBEntryView *sidebar;
        GtkTreeViewColumn *sidebar_column;
-       GtkActionGroup *action_group;
        RBPlayOrder *queue_play_order;
 
        guint dbus_object_id;
        GDBusConnection *bus;
+
+       GMenuModel *popup;
+       GMenuModel *sidepane_popup;
 };
 
 enum
@@ -136,16 +138,6 @@ enum
 G_DEFINE_TYPE (RBPlayQueueSource, rb_play_queue_source, RB_TYPE_STATIC_PLAYLIST_SOURCE)
 #define RB_PLAY_QUEUE_SOURCE_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), 
RB_TYPE_PLAY_QUEUE_SOURCE, RBPlayQueueSourcePrivate))
 
-static GtkActionEntry rb_play_queue_source_actions [] =
-{
-       { "ClearQueue", GTK_STOCK_CLEAR, N_("Clear _Queue"), NULL,
-         N_("Remove all songs from the play queue"),
-         G_CALLBACK (rb_play_queue_source_cmd_clear) },
-       { "ShuffleQueue", GNOME_MEDIA_SHUFFLE, N_("Shuffle Queue"), NULL,
-         N_("Shuffle the tracks in the play queue"),
-         G_CALLBACK (rb_play_queue_source_cmd_shuffle) }
-};
-
 static const GDBusInterfaceVTable play_queue_vtable = {
        (GDBusInterfaceMethodCallFunc) rb_play_queue_dbus_method_call,
        NULL,
@@ -168,15 +160,7 @@ rb_play_queue_source_dispose (GObject *object)
 {
        RBPlayQueueSourcePrivate *priv = RB_PLAY_QUEUE_SOURCE_GET_PRIVATE (object);
 
-       if (priv->action_group != NULL) {
-               g_object_unref (priv->action_group);
-               priv->action_group = NULL;
-       }
-
-       if (priv->queue_play_order != NULL) {
-               g_object_unref (priv->queue_play_order);
-               priv->queue_play_order = NULL;
-       }
+       g_clear_object (&priv->queue_play_order);
 
        if (priv->bus != NULL) {
                if (priv->dbus_object_id) {
@@ -201,7 +185,6 @@ 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);
 
@@ -210,8 +193,6 @@ 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->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;
 
@@ -257,8 +238,15 @@ rb_play_queue_source_constructed (GObject *object)
        RBShell *shell;
        RhythmDB *db;
        GtkCellRenderer *renderer;
+       GtkBuilder *builder;
        RhythmDBQueryModel *model;
-       GtkAction *action;
+       GActionEntry actions[] = {
+               { "queue-clear", queue_clear_action_cb },
+               { "queue-shuffle", queue_shuffle_action_cb },
+               { "queue-delete", queue_delete_action_cb },
+               { "queue-properties", queue_properties_action_cb },
+               { "queue-save", queue_save_action_cb }
+       };
 
        RB_CHAIN_GOBJECT_METHOD (rb_play_queue_source_parent_class, constructed, object);
 
@@ -272,18 +260,10 @@ 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_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 */
-       g_object_set (G_OBJECT (action), "short-label", _("Clear"), NULL);
-
-       /* Translators: this is the toolbutton label for the 'shuffle queue' action */
-       gtk_action_set_short_label (gtk_action_group_get_action (priv->action_group, "ShuffleQueue"), 
C_("Queue", "Shuffle"));
+       g_action_map_add_action_entries (G_ACTION_MAP (g_application_get_default ()),
+                                        actions,
+                                        G_N_ELEMENTS (actions),
+                                        source);
 
        priv->sidebar = rb_entry_view_new (db, shell_player, TRUE, TRUE);
        g_object_unref (shell_player);
@@ -324,6 +304,14 @@ rb_play_queue_source_constructed (GObject *object)
 
        rb_play_queue_source_update_count (source, GTK_TREE_MODEL (model), 0);
 
+       /* load popup menus */
+       builder = rb_builder_load ("queue-popups.ui", NULL);
+       priv->popup = G_MENU_MODEL (gtk_builder_get_object (builder, "queue-source-popup"));
+       priv->sidepane_popup = G_MENU_MODEL (gtk_builder_get_object (builder, "queue-sidepane-popup"));
+       g_object_ref (priv->popup);
+       g_object_ref (priv->sidepane_popup);
+       g_object_unref (builder);
+
        /* register dbus interface */
        priv->bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
        if (priv->bus) {
@@ -384,57 +372,24 @@ rb_play_queue_source_get_property (GObject *object,
 RBSource *
 rb_play_queue_source_new (RBShell *shell)
 {
-       return RB_SOURCE (g_object_new (RB_TYPE_PLAY_QUEUE_SOURCE,
-                                       "name", _("Play Queue"),
-                                       "shell", shell,
-                                       "is-local", TRUE,
-                                       "entry-type", NULL,
-                                       "toolbar-path", "/QueueSourceToolBar",
-                                       "show-browser", FALSE,
-                                       NULL));
-}
-
-/**
- * rb_play_queue_source_sidebar_song_info:
- * @source: the #RBPlayQueueSource
- *
- * Creates and displays a #RBSongInfo for the currently selected
- * entry in the side pane play queue view
- */
-void
-rb_play_queue_source_sidebar_song_info (RBPlayQueueSource *source)
-{
-       RBPlayQueueSourcePrivate *priv = RB_PLAY_QUEUE_SOURCE_GET_PRIVATE (source);
-       GtkWidget *song_info = NULL;
-
-       g_return_if_fail (priv->sidebar != NULL);
-
-       song_info = rb_song_info_new (RB_SOURCE (source), priv->sidebar);
-       if (song_info)
-               gtk_widget_show_all (song_info);
-       else
-               rb_debug ("failed to create dialog, or no selection!");
-}
-
-/**
- * rb_play_queue_source_sidebar_delete:
- * @source: the #RBPlayQueueSource
- *
- * Deletes the selected entries from the play queue side pane.
- * This is called by the #RBShellClipboard.
- */
-void
-rb_play_queue_source_sidebar_delete (RBPlayQueueSource *source)
-{
-       RBPlayQueueSourcePrivate *priv = RB_PLAY_QUEUE_SOURCE_GET_PRIVATE (source);
-       RBEntryView *sidebar = priv->sidebar;
-       GList *sel, *tem;
-
-       sel = rb_entry_view_get_selected_entries (sidebar);
-       for (tem = sel; tem != NULL; tem = tem->next)
-               rb_static_playlist_source_remove_entry (RB_STATIC_PLAYLIST_SOURCE (source),
-                                                       (RhythmDBEntry *) tem->data);
-       g_list_free (sel);
+       RBSource *source;
+       GtkBuilder *builder;
+       GMenu *toolbar;
+
+       builder = rb_builder_load ("queue-toolbar.ui", NULL);
+       toolbar = G_MENU (gtk_builder_get_object (builder, "queue-toolbar"));
+       rb_application_link_shared_menus (RB_APPLICATION (g_application_get_default ()), toolbar);
+
+       source = RB_SOURCE (g_object_new (RB_TYPE_PLAY_QUEUE_SOURCE,
+                                         "name", _("Play Queue"),
+                                         "shell", shell,
+                                         "is-local", TRUE,
+                                         "entry-type", NULL,
+                                         "toolbar-menu", toolbar,
+                                         "show-browser", FALSE,
+                                         NULL));
+       g_object_unref (builder);
+       return source;
 }
 
 /**
@@ -467,12 +422,21 @@ impl_show_entry_view_popup (RBPlaylistSource *source,
                            gboolean over_entry)
 {
        RBPlayQueueSourcePrivate *priv = RB_PLAY_QUEUE_SOURCE_GET_PRIVATE (source);
-       const char *popup = PLAY_QUEUE_SOURCE_SONGS_POPUP_PATH;
-       if (view == priv->sidebar)
-               popup = PLAY_QUEUE_SOURCE_SIDEBAR_POPUP_PATH;
-       else if (!over_entry)
-               return;
-       _rb_display_page_show_popup (RB_DISPLAY_PAGE (source), popup);
+       GtkWidget *menu;
+
+       if (view == priv->sidebar) {
+               menu = gtk_menu_new_from_model (priv->sidepane_popup);
+       } else {
+               menu = gtk_menu_new_from_model (priv->popup);
+       }
+       gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (source), NULL);
+       gtk_menu_popup (GTK_MENU (menu),
+                       NULL,
+                       NULL,
+                       NULL,
+                       NULL,
+                       3,
+                       gtk_get_current_event_time ());
 }
 
 static void
@@ -526,10 +490,10 @@ rb_play_queue_source_update_count (RBPlayQueueSource *source,
                                   GtkTreeModel *model,
                                   gint offset)
 {
+       GAction *action;
        gint count = gtk_tree_model_iter_n_children (model, NULL) + offset;
        RBPlayQueueSourcePrivate *priv = RB_PLAY_QUEUE_SOURCE_GET_PRIVATE (source);
        char *name = _("Play Queue");
-       GtkAction *action;
 
        /* update source name */
        if (count > 0)
@@ -542,12 +506,13 @@ rb_play_queue_source_update_count (RBPlayQueueSource *source,
                g_free (name);
 
        /* make 'clear queue' and 'shuffle queue' actions sensitive when there are entries in the queue */
-       action = gtk_action_group_get_action (priv->action_group,
-                                             "ClearQueue");
-       g_object_set (G_OBJECT (action), "sensitive", (count > 0), NULL);
+       action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()),
+                                            "queue-clear");
+       g_simple_action_set_enabled (G_SIMPLE_ACTION (action), (count > 0));
 
-       action = gtk_action_group_get_action (priv->action_group, "ShuffleQueue");
-       g_object_set (G_OBJECT (action), "sensitive", (count > 0), NULL);
+       action = g_action_map_lookup_action (G_ACTION_MAP (g_application_get_default ()),
+                                            "queue-shuffle");
+       g_simple_action_set_enabled (G_SIMPLE_ACTION (action), (count > 0));
 }
 
 static void
@@ -559,29 +524,65 @@ impl_save_contents_to_xml (RBPlaylistSource *source,
 }
 
 static void
-rb_play_queue_source_cmd_clear (GtkAction *action,
-                               RBPlayQueueSource *source)
+queue_clear_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
 {
-       rb_play_queue_source_clear_queue (source);
+       rb_play_queue_source_clear_queue (RB_PLAY_QUEUE_SOURCE (data));
 }
 
 static void
-rb_play_queue_source_cmd_shuffle (GtkAction *action,
-                                 RBPlayQueueSource *source)
+queue_shuffle_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
 {
        RhythmDBQueryModel *model;
 
-       model = rb_playlist_source_get_query_model (RB_PLAYLIST_SOURCE (source));
+       model = rb_playlist_source_get_query_model (RB_PLAYLIST_SOURCE (data));
        rhythmdb_query_model_shuffle_entries (model);
 }
 
-static gboolean
-impl_show_popup (RBDisplayPage *page)
+static void
+queue_delete_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
+{
+       RBPlayQueueSource *source = RB_PLAY_QUEUE_SOURCE (data);
+       RBPlayQueueSourcePrivate *priv = RB_PLAY_QUEUE_SOURCE_GET_PRIVATE (source);
+       RBEntryView *sidebar = priv->sidebar;
+       GList *sel, *tem;
+
+       sel = rb_entry_view_get_selected_entries (sidebar);
+       for (tem = sel; tem != NULL; tem = tem->next)
+               rb_static_playlist_source_remove_entry (RB_STATIC_PLAYLIST_SOURCE (source),
+                                                       (RhythmDBEntry *) tem->data);
+       g_list_free (sel);
+}
+
+static void
+queue_properties_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
+{
+       RBPlayQueueSource *source = RB_PLAY_QUEUE_SOURCE (data);
+       RBPlayQueueSourcePrivate *priv = RB_PLAY_QUEUE_SOURCE_GET_PRIVATE (source);
+       GtkWidget *song_info = NULL;
+
+       g_return_if_fail (priv->sidebar != NULL);
+
+       song_info = rb_song_info_new (RB_SOURCE (source), priv->sidebar);
+       if (song_info)
+               gtk_widget_show_all (song_info);
+       else
+               rb_debug ("failed to create dialog, or no selection!");
+}
+
+static void
+queue_save_action_cb (GSimpleAction *action, GVariant *parameters, gpointer data)
 {
-       _rb_display_page_show_popup (page, PLAY_QUEUE_SOURCE_POPUP_PATH);
-       return TRUE;
+       RBShell *shell;
+       RBPlaylistManager *mgr;
+
+       g_object_get (data, "shell", &shell, NULL);
+       g_object_get (shell, "playlist-manager", &mgr, NULL);
+       rb_playlist_manager_save_playlist_file (mgr, RB_SOURCE (data));
+       g_object_unref (mgr);
+       g_object_unref (shell);
 }
 
+
 static void
 rb_play_queue_dbus_method_call (GDBusConnection *connection,
                                const char *sender,
diff --git a/sources/rb-play-queue-source.h b/sources/rb-play-queue-source.h
index 22af570..6116a91 100644
--- a/sources/rb-play-queue-source.h
+++ b/sources/rb-play-queue-source.h
@@ -61,8 +61,7 @@ GType         rb_play_queue_source_get_type           (void);
 
 RBSource *     rb_play_queue_source_new                (RBShell *shell);
 
-void           rb_play_queue_source_sidebar_song_info  (RBPlayQueueSource *source);
-void           rb_play_queue_source_sidebar_delete     (RBPlayQueueSource *source);
+/* XXX die */
 void           rb_play_queue_source_clear_queue        (RBPlayQueueSource *source);
 
 G_END_DECLS
diff --git a/sources/rb-playlist-source.c b/sources/rb-playlist-source.c
index b9ae1ff..3215805 100644
--- a/sources/rb-playlist-source.c
+++ b/sources/rb-playlist-source.c
@@ -45,12 +45,14 @@
 #include "rb-playlist-source.h"
 #include "rb-debug.h"
 #include "rb-song-info.h"
+#include "rb-builder-helpers.h"
 
 #include "rb-playlist-xml.h"
 #include "rb-static-playlist-source.h"
 #include "rb-auto-playlist-source.h"
 
 #include "rb-playlist-manager.h"
+#include "rb-application.h"
 
 /**
  * SECTION:rb-playlist-source
@@ -85,7 +87,6 @@ static void rb_playlist_source_get_property (GObject *object,
 /* source methods */
 static RBEntryView *impl_get_entry_view (RBSource *source);
 static void impl_song_properties (RBSource *source);
-static gboolean impl_show_popup (RBDisplayPage *page);
 
 static void rb_playlist_source_songs_show_popup_cb (RBEntryView *view,
                                                    gboolean over_entry,
@@ -115,24 +116,13 @@ static void rb_playlist_source_songs_sort_order_changed_cb (GObject *object,
                                                            GParamSpec *pspec,
                                                            RBPlaylistSource *source);
 
-static void remove_from_playlist_cmd (GtkAction *action, RBSource *source);
-static char *impl_get_delete_action (RBSource *source);
-
-#define PLAYLIST_SOURCE_SONGS_POPUP_PATH "/PlaylistViewPopup"
-#define PLAYLIST_SOURCE_POPUP_PATH "/PlaylistSourcePopup"
-
-static GtkActionEntry rb_playlist_source_actions [] =
-{
-       { "RemoveFromPlaylist", GTK_STOCK_REMOVE, N_("Remove From Playlist"), NULL,
-         N_("Remove each selected song from the playlist"),
-         G_CALLBACK (remove_from_playlist_cmd) },
-};
-
+static char *impl_get_delete_label (RBSource *source);
+static gboolean impl_can_remove (RBDisplayPage *page);
+static void impl_remove (RBDisplayPage *page);
 
 struct RBPlaylistSourcePrivate
 {
        RhythmDB *db;
-       GtkActionGroup *action_group;
 
        GHashTable *entries;
 
@@ -145,6 +135,8 @@ struct RBPlaylistSourcePrivate
        gboolean dispose_has_run;
 
        char *title;
+
+       GMenu *popup;
 };
 
 #define RB_PLAYLIST_SOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_PLAYLIST_SOURCE, 
RBPlaylistSourcePrivate))
@@ -165,8 +157,8 @@ 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);
+       RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
 
        object_class->dispose = rb_playlist_source_dispose;
        object_class->finalize = rb_playlist_source_finalize;
@@ -174,8 +166,6 @@ rb_playlist_source_class_init (RBPlaylistSourceClass *klass)
        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_entry_view = impl_get_entry_view;
        source_class->impl_can_rename = (RBSourceFeatureFunc) rb_true_function;
        source_class->impl_can_cut = (RBSourceFeatureFunc) rb_false_function;
@@ -184,7 +174,10 @@ 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_get_delete_action = impl_get_delete_action;
+       source_class->impl_get_delete_label = impl_get_delete_label;
+
+       page_class->can_remove = impl_can_remove;
+       page_class->remove = impl_remove;
 
        klass->impl_show_entry_view_popup = default_show_entry_view_popup;
        klass->impl_mark_dirty = default_mark_dirty;
@@ -268,6 +261,7 @@ rb_playlist_source_constructed (GObject *object)
        RBShell *shell;
        RhythmDB *db;
        RhythmDBQueryModel *query_model;
+       GtkBuilder *builder;
 
        RB_CHAIN_GOBJECT_METHOD (rb_playlist_source_parent_class, constructed, object);
        source = RB_PLAYLIST_SOURCE (object);
@@ -280,17 +274,13 @@ rb_playlist_source_constructed (GObject *object)
        rb_playlist_source_set_db (source, db);
        g_object_unref (db);
 
-       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);
 
+       builder = rb_builder_load ("playlist-popup.ui", NULL);
+       source->priv->popup = G_MENU (gtk_builder_get_object (builder, "playlist-popup"));
+       rb_application_link_shared_menus (RB_APPLICATION (g_application_get_default ()), source->priv->popup);
+       g_object_unref (builder);
+
        source->priv->entries = g_hash_table_new_full (rb_refstring_hash, rb_refstring_equal,
                                                       (GDestroyNotify)rb_refstring_unref, NULL);
 
@@ -458,9 +448,26 @@ default_show_entry_view_popup (RBPlaylistSource *source,
                               RBEntryView *view,
                               gboolean over_entry)
 {
-       if (over_entry) {
-               _rb_display_page_show_popup (RB_DISPLAY_PAGE (source), PLAYLIST_SOURCE_SONGS_POPUP_PATH);
-       }
+       GtkWidget *menu;
+       GMenuModel *playlist_menu;
+
+       if (over_entry == FALSE)
+               return;
+
+       /* update add to playlist menu links */
+       g_object_get (source, "playlist-menu", &playlist_menu, NULL);
+       rb_menu_update_link (source->priv->popup, "rb-playlist-menu-link", playlist_menu);
+       g_object_unref (playlist_menu);
+
+       menu = gtk_menu_new_from_model (G_MENU_MODEL (source->priv->popup));
+       gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (source), NULL);
+       gtk_menu_popup (GTK_MENU (menu),
+                       NULL,
+                       NULL,
+                       NULL,
+                       NULL,
+                       3,
+                       gtk_get_current_event_time ());
 }
 
 static void
@@ -496,13 +503,6 @@ impl_song_properties (RBSource *asource)
                rb_debug ("failed to create dialog, or no selection!");
 }
 
-static gboolean
-impl_show_popup (RBDisplayPage *page)
-{
-       _rb_display_page_show_popup (page, PLAYLIST_SOURCE_POPUP_PATH);
-       return TRUE;
-}
-
 static void
 rb_playlist_source_drop_cb (GtkWidget *widget,
                            GdkDragContext *context,
@@ -1090,14 +1090,21 @@ rb_playlist_source_songs_sort_order_changed_cb (GObject *object,
        rb_entry_view_resort_model (RB_ENTRY_VIEW (object));
 }
 
-static void
-remove_from_playlist_cmd (GtkAction *action, RBSource *source)
+static char *
+impl_get_delete_label (RBSource *source)
 {
-       rb_source_delete (source);
+       return g_strdup (_("Remove from Playlist"));
 }
 
-static char *
-impl_get_delete_action (RBSource *source)
+static gboolean
+impl_can_remove (RBDisplayPage *page)
+{
+       RBPlaylistSource *source = RB_PLAYLIST_SOURCE (page);
+       return (source->priv->is_local);
+}
+
+void
+impl_remove (RBDisplayPage *page)
 {
-       return g_strdup ("RemoveFromPlaylist");
+       rb_display_page_delete_thyself (page);
 }
diff --git a/sources/rb-source-search-basic.c b/sources/rb-source-search-basic.c
index 749893b..50150a0 100644
--- a/sources/rb-source-search-basic.c
+++ b/sources/rb-source-search-basic.c
@@ -37,7 +37,10 @@
 
 #include "config.h"
 
+#include "rb-shell.h"
+#include "rb-source.h"
 #include "rb-source-search-basic.h"
+#include "rb-debug.h"
 
 static void    rb_source_search_basic_class_init (RBSourceSearchBasicClass *klass);
 static void    rb_source_search_basic_init (RBSourceSearchBasic *search);
@@ -48,6 +51,7 @@ enum
 {
        PROP_0,
        PROP_SEARCH_PROP,
+       PROP_DESCRIPTION
 };
 
 static RhythmDBQuery *
@@ -58,6 +62,13 @@ impl_create_query (RBSourceSearch *bsearch, RhythmDB *db, const char *search_tex
        return _rb_source_search_create_simple_query (bsearch, db, search_text, search->search_prop);
 }
 
+static char *
+impl_get_description (RBSourceSearch *bsearch)
+{
+       RBSourceSearchBasic *search = RB_SOURCE_SEARCH_BASIC (bsearch);
+       return g_strdup (search->description);
+}
+
 static void
 impl_set_property (GObject *object,
                   guint prop_id,
@@ -70,6 +81,9 @@ impl_set_property (GObject *object,
        case PROP_SEARCH_PROP:
                search->search_prop = g_value_get_int (value);
                break;
+       case PROP_DESCRIPTION:
+               search->description = g_value_dup_string (value);
+               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
@@ -88,6 +102,9 @@ impl_get_property (GObject *object,
        case PROP_SEARCH_PROP:
                g_value_set_int (value, search->search_prop);
                break;
+       case PROP_DESCRIPTION:
+               g_value_set_string (value, search->description);
+               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
@@ -95,6 +112,16 @@ impl_get_property (GObject *object,
 }
 
 static void
+impl_finalize (GObject *object)
+{
+       RBSourceSearchBasic *search = RB_SOURCE_SEARCH_BASIC (object);
+
+       g_free (search->description);
+
+       G_OBJECT_CLASS (rb_source_search_basic_parent_class)->finalize (object);
+}
+
+static void
 rb_source_search_basic_class_init (RBSourceSearchBasicClass *klass)
 {
        GObjectClass *object_class = G_OBJECT_CLASS (klass);
@@ -102,8 +129,10 @@ rb_source_search_basic_class_init (RBSourceSearchBasicClass *klass)
 
        object_class->set_property = impl_set_property;
        object_class->get_property = impl_get_property;
+       object_class->finalize = impl_finalize;
 
        search_class->create_query = impl_create_query;
+       search_class->get_description = impl_get_description;
 
        g_object_class_install_property (object_class,
                                         PROP_SEARCH_PROP,
@@ -113,6 +142,13 @@ rb_source_search_basic_class_init (RBSourceSearchBasicClass *klass)
                                                           0, RHYTHMDB_NUM_PROPERTIES,
                                                           RHYTHMDB_PROP_TYPE,
                                                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+       g_object_class_install_property (object_class,
+                                        PROP_DESCRIPTION,
+                                        g_param_spec_string ("description",
+                                                             "description",
+                                                             "description",
+                                                             NULL,
+                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 }
 
 static void
@@ -125,6 +161,7 @@ rb_source_search_basic_init (RBSourceSearchBasic *search)
 /**
  * rb_source_search_basic_new:
  * @prop:      the #RhythmDBPropType to search
+ * @description: description for the search
  *
  * Creates a new #RBSourceSearchBasic instance.
  * This performs simple string matching on a specified
@@ -133,40 +170,118 @@ rb_source_search_basic_init (RBSourceSearchBasic *search)
  * Return value: newly created #RBSourceSearchBasic
  */
 RBSourceSearch *
-rb_source_search_basic_new (RhythmDBPropType prop)
+rb_source_search_basic_new (RhythmDBPropType prop, const char *description)
 {
-       return g_object_new (RB_TYPE_SOURCE_SEARCH_BASIC, "prop", prop, NULL);
+       return g_object_new (RB_TYPE_SOURCE_SEARCH_BASIC,
+                            "prop", prop,
+                            "description", description,
+                            NULL);
 }
 
 /**
- * rb_source_search_basic_create_for_actions:
- * @action_group:      the GtkActionGroup containing the actions
- * @actions:           the GtkRadioActionEntries for the actions
- * @n_actions:         the number of actions
+ * rb_source_search_basic_register:
+ * @prop: the property to search on
+ * @name: short non-translated name for the search instance
+ * @description: user-visible description for the search
  *
- * Creates #RBSourceSearchBasic instances for a set of
- * search actions and associates them with the actions.
- * The property to match on is taken from the action
- * value in the GtkRadioActionEntry structure.
+ * Ensures that a search instance is registered with the specified name.
  */
 void
-rb_source_search_basic_create_for_actions (GtkActionGroup *action_group,
-                                          GtkRadioActionEntry *actions,
-                                          int n_actions)
+rb_source_search_basic_register (RhythmDBPropType prop, const char *name, const char *description)
 {
-       int i;
-       for (i = 0; i < n_actions; i++) {
-               GtkAction *action;
-               RBSourceSearch *search;
-
-               if (actions[i].value != RHYTHMDB_NUM_PROPERTIES) {
-                       action = gtk_action_group_get_action (action_group, actions[i].name);
-                       g_assert (action != NULL);
-
-                       search = rb_source_search_basic_new (actions[i].value);
-                       rb_source_search_action_attach (search, G_OBJECT (action));
-                       g_object_unref (search);
-               }
+       RBSourceSearch *search;
+       search = rb_source_search_get_by_name (name);
+       if (search == NULL) {
+               search = rb_source_search_basic_new (prop, description);
+               rb_source_search_register (search, name);
        }
 }
 
+static void
+action_activate_cb (GSimpleAction *action, GVariant *parameter, gpointer data)
+{
+       g_action_change_state (G_ACTION (action), parameter);
+}
+
+static void
+action_change_state_cb (GSimpleAction *action, GVariant *parameter, GSettings *settings)
+{
+       const char *search_name;
+       RBSourceSearch *search;
+
+       search_name = g_variant_get_string (parameter, NULL);
+       search = rb_source_search_get_by_name (search_name);
+       if (search == NULL) {
+               rb_debug ("tried to change search type to unknown value %s", search_name);
+               return;
+       }
+
+       g_simple_action_set_state (action, parameter);
+
+       if (settings != NULL) {
+               g_settings_set_string (settings, "search-type", search_name);
+       }
+}
+
+/**
+ * rb_source_create_search_action:
+ * @source: a #RBSource
+ *
+ * Creates a GAction representing the selected search type for @source.
+ * The action is stateful. Its state is a string containing the name of
+ * a registered search instance.  If the source has a settings instance,
+ * it will be updated when the state changes.  Changes coming from the
+ * settings instance are ignored.  If the source doesn't have a settings
+ * instance, it should set a default state on the action at some point.
+ *
+ * Return value: #GAction instance
+ */
+GAction *
+rb_source_create_search_action (RBSource *source)
+{
+       GAction *action;
+       GSettings *settings;
+       GVariant *state;
+       char *action_name;
+
+       g_object_get (source, "settings", &settings, NULL);
+
+       action_name = g_strdup_printf ("source-search-%p", source);
+       if (settings != NULL) {
+               state = g_settings_get_value (settings, "search-type");
+       } else {
+               state = g_variant_new_string ("");
+       }
+       action = G_ACTION (g_simple_action_new_stateful (action_name, G_VARIANT_TYPE_STRING, state));
+       g_free (action_name);
+
+       g_signal_connect (action, "activate", G_CALLBACK (action_activate_cb), NULL);
+       g_signal_connect (action, "change-state", G_CALLBACK (action_change_state_cb), settings);
+       /* don't bother updating action state on settings changes */
+
+       if (settings != NULL) {
+               g_object_unref (settings);
+       }
+       return action;
+}
+
+
+/**
+ * rb_source_search_basic_add_to_menu:
+ * @menu: the #GMenu to populate
+ * @action_namespace: action namespace to use for the action ("app" or "win")
+ * @search_action: the search action to associate the search with
+ * @prop: the property to search on
+ * @name: short untranslated name for the search
+ * @label: descriptive translatable label for the search
+ *
+ * Adds an item to @menu that will select a search based on the specified
+ * property.  If there isn't already a registered search instance for the
+ * property, one is created.
+ */
+void
+rb_source_search_basic_add_to_menu (GMenu *menu, const char *action_namespace, GAction *search_action, 
RhythmDBPropType prop, const char *name, const char *label)
+{
+       rb_source_search_basic_register (prop, name, label);
+       rb_source_search_add_to_menu (menu, action_namespace, search_action, name);
+}
diff --git a/sources/rb-source-search-basic.h b/sources/rb-source-search-basic.h
index c46eaa4..3e17b91 100644
--- a/sources/rb-source-search-basic.h
+++ b/sources/rb-source-search-basic.h
@@ -53,6 +53,7 @@ struct _RBSourceSearchBasic
 {
        RBSourceSearch parent;
        RhythmDBPropType search_prop;
+       char *description;
 };
 
 struct _RBSourceSearchBasicClass
@@ -62,11 +63,17 @@ struct _RBSourceSearchBasicClass
 
 GType          rb_source_search_basic_get_type (void);
 
-RBSourceSearch *rb_source_search_basic_new (RhythmDBPropType prop);
+RBSourceSearch *rb_source_search_basic_new (RhythmDBPropType prop, const char *description);
+void           rb_source_search_basic_register (RhythmDBPropType prop, const char *name, const char 
*description);
 
-void           rb_source_search_basic_create_for_actions (GtkActionGroup *action_group,
-                                                          GtkRadioActionEntry *actions,
-                                                          int n_actions);
+GAction *      rb_source_create_search_action (RBSource *source);
+
+void           rb_source_search_basic_add_to_menu (GMenu *menu,
+                                                   const char *action_namespace,
+                                                   GAction *search_action,
+                                                   RhythmDBPropType prop,
+                                                   const char *name,
+                                                   const char *label);
 
 G_END_DECLS
 
diff --git a/sources/rb-source-search.c b/sources/rb-source-search.c
index ce96192..c8fcd3c 100644
--- a/sources/rb-source-search.c
+++ b/sources/rb-source-search.c
@@ -44,6 +44,7 @@
 
 #include "config.h"
 
+#include "rb-source.h"
 #include "rb-source-search.h"
 
 static void    rb_source_search_class_init (RBSourceSearchClass *klass);
@@ -54,7 +55,7 @@ G_DEFINE_TYPE (RBSourceSearch, rb_source_search, G_TYPE_OBJECT)
 #define RB_SOURCE_SEARCH_DATA_ITEM     "rb-source-search"
 
 static gboolean
-default_is_subset (RBSourceSearch *source, const char *current, const char *next)
+default_is_subset (RBSourceSearch *search, const char *current, const char *next)
 {
        /* the most common searches will return a strict subset if the
         * next search is the current search with a suffix.
@@ -62,10 +63,19 @@ default_is_subset (RBSourceSearch *source, const char *current, const char *next
        return (current != NULL && g_str_has_prefix (next, current));
 }
 
+static char *
+default_get_description (RBSourceSearch *search)
+{
+       return g_strdup ("");
+}
+
 static void
 rb_source_search_class_init (RBSourceSearchClass *klass)
 {
+       klass->searches = g_hash_table_new (g_str_hash, g_str_equal);
+
        klass->is_subset = default_is_subset;
+       klass->get_description = default_get_description;
 }
 
 static void
@@ -75,6 +85,38 @@ rb_source_search_init (RBSourceSearch *search)
 }
 
 /**
+ * rb_source_search_get_by_name:
+ * @name: name to look up
+ *
+ * Finds the registered search instance with the specified name
+ *
+ * Returns: (transfer none): search instance, or NULL if not found
+ */
+RBSourceSearch *
+rb_source_search_get_by_name (const char *name)
+{
+       RBSourceSearchClass *klass;
+       klass = RB_SOURCE_SEARCH_CLASS (g_type_class_peek (RB_TYPE_SOURCE_SEARCH));
+       return g_hash_table_lookup (klass->searches, name);
+}
+
+/**
+ * rb_source_search_register:
+ * @search: search instance to register
+ * @name: name to register
+ *
+ * Registers a named search instance that can be used in menus and
+ * search action states.
+ */
+void
+rb_source_search_register (RBSourceSearch *search, const char *name)
+{
+       RBSourceSearchClass *klass;
+       klass = RB_SOURCE_SEARCH_CLASS (g_type_class_peek (RB_TYPE_SOURCE_SEARCH));
+       g_hash_table_insert (klass->searches, g_strdup (name), search);
+}
+
+/**
  * rb_source_search_is_subset:
  * @search: a #RBSourceSearch
  * @current: the current search text (or NULL if the current search was done with a different
@@ -95,6 +137,21 @@ rb_source_search_is_subset (RBSourceSearch *search, const char *current, const c
 }
 
 /**
+ * rb_source_search_get_description:
+ * @search: a #RBSourceSearch
+ *
+ * Returns a description of the search suitable for displaying in a menu
+ *
+ * Return value: description string
+ */
+char *
+rb_source_search_get_description (RBSourceSearch *search)
+{
+       RBSourceSearchClass *klass = RB_SOURCE_SEARCH_GET_CLASS (search);
+       return klass->get_description (search);
+}
+
+/**
  * rb_source_search_create_query:
  * @search: a #RBSourceSearch
  * @db: the #RhythmDB
@@ -171,3 +228,35 @@ rb_source_search_get_from_action (GObject *action)
        return RB_SOURCE_SEARCH (data);
 }
 
+
+/**
+ * rb_source_search_add_to_menu:
+ * @menu: #GMenu instance to populate
+ * @action_namespace: muxer namespace for the action ("app" or "win")
+ * @action: search action to attach the menu item to
+ * @name: name of the search instance to add
+ *
+ * Adds a registered search instance to a search menu.
+ */
+void
+rb_source_search_add_to_menu (GMenu *menu, const char *action_namespace, GAction *action, const char *name)
+{
+       GMenuItem *item;
+       RBSourceSearch *search;
+       char *action_name;
+       
+       search = rb_source_search_get_by_name (name);
+       g_assert (search != NULL);
+
+       if (action_namespace != NULL) {
+               action_name = g_strdup_printf ("%s.%s", action_namespace, g_action_get_name (action));
+       } else {
+               action_name = g_strdup (g_action_get_name (action));
+       }
+
+       item = g_menu_item_new (rb_source_search_get_description (search), NULL);
+       g_menu_item_set_action_and_target (item, action_name, "s", name);
+       g_menu_append_item (menu, item);
+
+       g_free (action_name);
+}
diff --git a/sources/rb-source-search.h b/sources/rb-source-search.h
index 05464f9..c58c023 100644
--- a/sources/rb-source-search.h
+++ b/sources/rb-source-search.h
@@ -51,6 +51,7 @@ struct _RBSourceSearch
 struct _RBSourceSearchClass
 {
        GObjectClass parent_class;
+       GHashTable *searches;
 
        /* virtual functions */
        gboolean       (*is_subset)     (RBSourceSearch *search,
@@ -59,11 +60,15 @@ struct _RBSourceSearchClass
        RhythmDBQuery *(*create_query)  (RBSourceSearch *search,
                                         RhythmDB *db,
                                         const char *search_text);
+       char *         (*get_description)(RBSourceSearch *search);
 
 };
 
 GType          rb_source_search_get_type       (void);
 
+RBSourceSearch *rb_source_search_get_by_name (const char *name);
+void            rb_source_search_register (RBSourceSearch *search, const char *name);
+
 gboolean       rb_source_search_is_subset (RBSourceSearch *search,
                                            const char *current,
                                            const char *next);
@@ -71,10 +76,17 @@ RhythmDBQuery *     rb_source_search_create_query (RBSourceSearch *search,
                                               RhythmDB *db,
                                               const char *search_text);
 
+char *         rb_source_search_get_description (RBSourceSearch *search);
+
 void           rb_source_search_action_attach (RBSourceSearch *search,
                                                GObject *action);
 RBSourceSearch *rb_source_search_get_from_action (GObject *action);
 
+void           rb_source_search_add_to_menu (GMenu *menu,
+                                             const char *action_namespace,
+                                             GAction *action,
+                                             const char *name);
+
 /* for search implementations */
 RhythmDBQuery *        _rb_source_search_create_simple_query (RBSourceSearch *search,
                                                       RhythmDB *db,
diff --git a/sources/rb-source.c b/sources/rb-source.c
index 7019aea..2ce6c21 100644
--- a/sources/rb-source.c
+++ b/sources/rb-source.c
@@ -69,7 +69,7 @@ static RBSourceEOFType default_handle_eos (RBSource *source);
 static RBEntryView *default_get_entry_view (RBSource *source);
 static void default_add_to_queue (RBSource *source, RBSource *queue);
 static void default_move_to_trash (RBSource *source);
-static char *default_get_delete_action (RBSource *source);
+static char *default_get_delete_label (RBSource *source);
 
 static void rb_source_post_entry_deleted_cb (GtkTreeModel *model,
                                             RhythmDBEntry *entry,
@@ -112,7 +112,8 @@ struct _RBSourcePrivate
 
        GSettings *settings;
 
-       char *toolbar_path;
+       GMenu *toolbar_menu;
+       GMenuModel *playlist_menu;
 };
 
 enum
@@ -126,7 +127,8 @@ enum
        PROP_SETTINGS,
        PROP_SHOW_BROWSER,
        PROP_LOAD_STATUS,
-       PROP_TOOLBAR_PATH
+       PROP_TOOLBAR_MENU,
+       PROP_PLAYLIST_MENU
 };
 
 enum
@@ -166,7 +168,7 @@ rb_source_class_init (RBSourceClass *klass)
        klass->impl_handle_eos = default_handle_eos;
        klass->impl_try_playlist = default_try_playlist;
        klass->impl_add_to_queue = default_add_to_queue;
-       klass->impl_get_delete_action = default_get_delete_action;
+       klass->impl_get_delete_label = default_get_delete_label;
        klass->impl_move_to_trash = default_move_to_trash;
 
        /**
@@ -278,22 +280,36 @@ rb_source_class_init (RBSourceClass *klass)
                                                               TRUE,
                                                               G_PARAM_READWRITE));
        /**
-        * RBSource:toolbar-path:
+        * RBSource:toolbar-menu:
         *
-        * UI manager path for a toolbar to display at the top of the source.
-        * The #RBSource class doesn't actually display the toolbar anywhere.
-        * Adding the toolbar to a container is the responsibility of a subclass
-        * such as #RBBrowserSource.
+        * A GMenu instance describing the contents of a toolbar to display at
+        * the top of the source.  The #RBSource class doesn't actually display
+        * the toolbar anywhere.  Adding the toolbar to a container is the
+        * responsibility of a subclass such as #RBBrowserSource.
         */
        g_object_class_install_property (object_class,
-                                        PROP_TOOLBAR_PATH,
-                                        g_param_spec_string ("toolbar-path",
-                                                             "toolbar path",
-                                                             "toolbar UI path",
-                                                             NULL,
+                                        PROP_TOOLBAR_MENU,
+                                        g_param_spec_object ("toolbar-menu",
+                                                             "toolbar menu",
+                                                             "toolbar menu",
+                                                             G_TYPE_MENU_MODEL,
                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
 
        /**
+        * RBSource:playlist-menu:
+        *
+        * A GMenu instance to attach to the 'add to playlist' item in the edit menu.
+        * If NULL, the item will be disabled.
+        */
+       g_object_class_install_property (object_class,
+                                        PROP_PLAYLIST_MENU,
+                                        g_param_spec_object ("playlist-menu",
+                                                             "playlist menu",
+                                                             "playlist menu",
+                                                             G_TYPE_MENU_MODEL,
+                                                             G_PARAM_READWRITE));
+
+       /**
         * RBSource::filter-changed:
         * @source: the #RBSource
         *
@@ -338,10 +354,10 @@ rb_source_dispose (GObject *object)
                g_source_remove (source->priv->update_status_id);
                source->priv->update_status_id = 0;
        }
-       if (source->priv->settings != NULL) {
-               g_object_unref (source->priv->settings);
-               source->priv->settings = NULL;
-       }
+
+       g_clear_object (&source->priv->settings);
+       g_clear_object (&source->priv->toolbar_menu);
+       g_clear_object (&source->priv->playlist_menu);
 
        G_OBJECT_CLASS (rb_source_parent_class)->dispose (object);
 }
@@ -363,8 +379,6 @@ rb_source_finalize (GObject *object)
                g_object_unref (source->priv->query_model);
        }
 
-       g_free (source->priv->toolbar_path);
-
        G_OBJECT_CLASS (rb_source_parent_class)->finalize (object);
 }
 
@@ -471,8 +485,11 @@ rb_source_set_property (GObject *object,
        case PROP_LOAD_STATUS:
                source->priv->load_status = g_value_get_enum (value);
                break;
-       case PROP_TOOLBAR_PATH:
-               source->priv->toolbar_path = g_value_dup_string (value);
+       case PROP_TOOLBAR_MENU:
+               source->priv->toolbar_menu = g_value_dup_object (value);
+               break;
+       case PROP_PLAYLIST_MENU:
+               source->priv->playlist_menu = g_value_dup_object (value);
                break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -513,8 +530,11 @@ rb_source_get_property (GObject *object,
        case PROP_LOAD_STATUS:
                g_value_set_enum (value, source->priv->load_status);
                break;
-       case PROP_TOOLBAR_PATH:
-               g_value_set_string (value, source->priv->toolbar_path);
+       case PROP_TOOLBAR_MENU:
+               g_value_set_object (value, source->priv->toolbar_menu);
+               break;
+       case PROP_PLAYLIST_MENU:
+               g_value_set_object (value, source->priv->playlist_menu);
                break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -1176,26 +1196,26 @@ default_get_entry_view (RBSource *source)
 }
 
 static char *
-default_get_delete_action (RBSource *source)
+default_get_delete_label (RBSource *source)
 {
-       return g_strdup ("EditRemove");
+       return g_strdup (_("Remove"));
 }
 
 /**
- * rb_source_get_delete_action:
+ * rb_source_get_delete_label:
  * @source: a #RBSource
  *
- * Returns the name of the UI action to use for 'delete'.
- * This allows the source to customise the visible action name
- * and description to better describe what deletion actually does.
+ * Returns a translated label for the 'delete' menu item, allowing
+ * sources to better describe what happens to deleted entries.
+ * Playlists, for example, return "Remove from Playlist" here.
  *
- * Return value: allocated string holding UI action name
+ * Return value: allocated string holding the label string
  */
 char *
-rb_source_get_delete_action (RBSource *source)
+rb_source_get_delete_label (RBSource *source)
 {
        RBSourceClass *klass = RB_SOURCE_GET_CLASS (source);
-       return klass->impl_get_delete_action (source);
+       return klass->impl_get_delete_label (source);
 }
 
 static gboolean
diff --git a/sources/rb-source.h b/sources/rb-source.h
index 6cd7599..fd9bb08 100644
--- a/sources/rb-source.h
+++ b/sources/rb-source.h
@@ -59,8 +59,6 @@ typedef struct _RBSource      RBSource;
 typedef struct _RBSourceClass  RBSourceClass;
 typedef struct _RBSourcePrivate        RBSourcePrivate;
 
-typedef void (*RBSourceActionCallback) (GtkAction *action, RBSource *source);
-
 GType rb_source_eof_type_get_type (void);
 #define RB_TYPE_SOURCE_EOF_TYPE        (rb_source_eof_type_get_type())
 
@@ -133,7 +131,7 @@ struct _RBSourceClass
        gboolean        (*impl_can_pause)       (RBSource *source);
        RBSourceEOFType (*impl_handle_eos)      (RBSource *source);
 
-       char *          (*impl_get_delete_action) (RBSource *source);
+       char *          (*impl_get_delete_label) (RBSource *source);
 };
 
 GType          rb_source_get_type              (void);
@@ -189,7 +187,7 @@ void                rb_source_add_uri               (RBSource *source,
 gboolean       rb_source_can_pause             (RBSource *source);
 RBSourceEOFType        rb_source_handle_eos            (RBSource *source);
 
-char *         rb_source_get_delete_action     (RBSource *source);
+char *         rb_source_get_delete_label      (RBSource *source);
 
 GList *                rb_source_gather_selected_properties (RBSource *source, RhythmDBPropType prop);
 
diff --git a/sources/rb-static-playlist-source.c b/sources/rb-static-playlist-source.c
index 395a438..304db81 100644
--- a/sources/rb-static-playlist-source.c
+++ b/sources/rb-static-playlist-source.c
@@ -59,6 +59,8 @@
 #include "rb-playlist-xml.h"
 #include "rb-source-search-basic.h"
 #include "rb-source-toolbar.h"
+#include "rb-builder-helpers.h"
+#include "rb-application.h"
 
 static void rb_static_playlist_source_constructed (GObject *object);
 static void rb_static_playlist_source_dispose (GObject *object);
@@ -118,14 +120,6 @@ static void rb_static_playlist_source_rows_reordered (GtkTreeModel *model,
                                                      gint *order_map,
                                                      RBStaticPlaylistSource *source);
 
-static GtkRadioActionEntry rb_static_playlist_source_radio_actions [] =
-{
-       { "StaticPlaylistSearchAll", NULL, N_("Search all fields"), NULL, NULL, RHYTHMDB_PROP_SEARCH_MATCH },
-       { "StaticPlaylistSearchArtists", NULL, N_("Search artists"), NULL, NULL, RHYTHMDB_PROP_ARTIST_FOLDED 
},
-       { "StaticPlaylistSearchAlbums", NULL, N_("Search albums"), NULL, NULL, RHYTHMDB_PROP_ALBUM_FOLDED },
-       { "StaticPlaylistSearchTitles", NULL, N_("Search titles"), NULL, NULL, RHYTHMDB_PROP_TITLE_FOLDED }
-};
-
 enum
 {
        PROP_0,
@@ -148,8 +142,9 @@ typedef struct
 
        RBSourceSearch *default_search;
        RhythmDBQuery *search_query;
+       GMenu *search_popup;
+       GAction *search_action;
 
-       gboolean dispose_has_run;
 } RBStaticPlaylistSourcePrivate;
 
 static gpointer playlist_pixbuf = NULL;
@@ -193,34 +188,6 @@ rb_static_playlist_source_class_init (RBStaticPlaylistSourceClass *klass)
        g_type_class_add_private (klass, sizeof (RBStaticPlaylistSourcePrivate));
 }
 
-void
-rb_static_playlist_source_create_actions (RBShell *shell)
-{
-       RBStaticPlaylistSourceClass *klass;
-       GtkUIManager *uimanager;
-
-       klass = RB_STATIC_PLAYLIST_SOURCE_CLASS (g_type_class_ref (RB_TYPE_STATIC_PLAYLIST_SOURCE));
-
-       klass->action_group = gtk_action_group_new ("StaticPlaylistActions");
-       gtk_action_group_set_translation_domain (klass->action_group, GETTEXT_PACKAGE);
-
-       g_object_get (shell, "ui-manager", &uimanager, NULL);
-       gtk_ui_manager_insert_action_group (uimanager, klass->action_group, 0);
-       g_object_unref (uimanager);
-
-       gtk_action_group_add_radio_actions (klass->action_group,
-                                           rb_static_playlist_source_radio_actions,
-                                           G_N_ELEMENTS (rb_static_playlist_source_radio_actions),
-                                           0,
-                                           NULL,
-                                           NULL);
-       rb_source_search_basic_create_for_actions (klass->action_group,
-                                                  rb_static_playlist_source_radio_actions,
-                                                  G_N_ELEMENTS (rb_static_playlist_source_radio_actions));
-
-       g_type_class_unref (klass);
-}
-
 static void
 set_playlist_pixbuf (RBStaticPlaylistSource *source)
 {
@@ -255,30 +222,13 @@ rb_static_playlist_source_dispose (GObject *object)
 {
        RBStaticPlaylistSourcePrivate *priv = RB_STATIC_PLAYLIST_SOURCE_GET_PRIVATE (object);
 
-       if (priv->dispose_has_run) {
-               /* If dispose did already run, return. */
-               rb_debug ("Dispose has already run for static playlist source %p", object);
-               return;
-       }
-       /* Make sure dispose does not run twice. */
-       priv->dispose_has_run = TRUE;
-
        rb_debug ("Disposing static playlist source %p", object);
 
-       if (priv->base_model != NULL) {
-               g_object_unref (priv->base_model);
-               priv->base_model = NULL;
-       }
-
-       if (priv->filter_model != NULL) {
-               g_object_unref (priv->filter_model);
-               priv->filter_model = NULL;
-       }
-
-       if (priv->default_search != NULL) {
-               g_object_unref (priv->default_search);
-               priv->default_search = NULL;
-       }
+       g_clear_object (&priv->base_model);
+       g_clear_object (&priv->filter_model);
+       g_clear_object (&priv->default_search);
+       g_clear_object (&priv->search_popup);
+       g_clear_object (&priv->search_action);
 
        G_OBJECT_CLASS (rb_static_playlist_source_parent_class)->dispose (object);
 }
@@ -307,9 +257,11 @@ rb_static_playlist_source_constructed (GObject *object)
        RBEntryView *songs;
        RBShell *shell;
        RhythmDBEntryType *entry_type;
-       GtkUIManager *ui_manager;
+       GtkAccelGroup *accel_group;
        GtkWidget *grid;
        GtkWidget *paned;
+       GMenu *section;
+       RBApplication *app = RB_APPLICATION (g_application_get_default ());
 
        RB_CHAIN_GOBJECT_METHOD (rb_static_playlist_source_parent_class, constructed, object);
 
@@ -331,10 +283,10 @@ rb_static_playlist_source_constructed (GObject *object)
        gtk_widget_set_hexpand (paned, TRUE);
        gtk_widget_set_vexpand (paned, TRUE);
 
-       priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH);
+       priv->default_search = rb_source_search_basic_new (RHYTHMDB_PROP_SEARCH_MATCH, NULL);
 
        g_object_get (source, "shell", &shell, NULL);
-       g_object_get (shell, "ui-manager", &ui_manager, NULL);
+       g_object_get (shell, "accel-group", &accel_group, NULL);
        g_object_unref (shell);
 
        g_object_get (source, "entry-type", &entry_type, NULL);
@@ -359,9 +311,27 @@ rb_static_playlist_source_constructed (GObject *object)
        gtk_paned_pack2 (GTK_PANED (paned), GTK_WIDGET (songs), TRUE, FALSE);
 
        /* set up search box / toolbar */
-       priv->toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (source), ui_manager);
-       rb_source_toolbar_add_search_entry (priv->toolbar, "/StaticPlaylistSourceSearchMenu", NULL);
-       g_object_unref (ui_manager);
+       priv->toolbar = rb_source_toolbar_new (RB_DISPLAY_PAGE (source), accel_group);
+       g_object_unref (accel_group);
+
+       priv->search_action = rb_source_create_search_action (RB_SOURCE (source));
+       g_action_change_state (priv->search_action, g_variant_new_string ("search-match"));
+       g_action_map_add_action (G_ACTION_MAP (app), priv->search_action);
+
+       rb_source_search_basic_register (RHYTHMDB_PROP_SEARCH_MATCH, "search-match", _("Search all fields"));
+       rb_source_search_basic_register (RHYTHMDB_PROP_ARTIST_FOLDED, "artist", _("Search artists"));
+       rb_source_search_basic_register (RHYTHMDB_PROP_ALBUM_FOLDED, "album", _("Search albums"));
+       rb_source_search_basic_register (RHYTHMDB_PROP_TITLE_FOLDED, "title", _("Search titles"));
+       
+       section = g_menu_new ();
+       rb_source_search_add_to_menu (section, "app", priv->search_action, "search-match");
+       rb_source_search_add_to_menu (section, "app", priv->search_action, "artist");
+       rb_source_search_add_to_menu (section, "app", priv->search_action, "album");
+       rb_source_search_add_to_menu (section, "app", priv->search_action, "title");
+
+       priv->search_popup = g_menu_new ();
+       g_menu_append_section (priv->search_popup, NULL, G_MENU_MODEL (section));
+       rb_source_toolbar_add_search_entry_menu (priv->toolbar, G_MENU_MODEL (priv->search_popup), 
priv->search_action);
 
        /* put it all together */
        grid = gtk_grid_new ();
@@ -375,6 +345,11 @@ rb_static_playlist_source_constructed (GObject *object)
        rb_source_bind_settings (RB_SOURCE (source), GTK_WIDGET (songs), paned, GTK_WIDGET (priv->browser));
        g_object_unref (songs);
 
+       /* set up playlist menu */
+       g_object_set (source,
+                     "playlist-menu", rb_application_get_shared_menu (app, "playlist-page-menu"),
+                     NULL);
+
        /* watch these to find out when things are dropped into the entry view */
        g_signal_connect_object (priv->base_model, "row-inserted",
                                 G_CALLBACK (rb_static_playlist_source_row_inserted),
@@ -404,7 +379,10 @@ rb_static_playlist_source_constructed (GObject *object)
 RBSource *
 rb_static_playlist_source_new (RBShell *shell, const char *name, const char *settings_name, gboolean local, 
RhythmDBEntryType *entry_type)
 {
+       RBSource *source;
        GSettings *settings;
+       GtkBuilder *builder;
+       GMenu *toolbar;
 
        if (name == NULL)
                name = "";
@@ -418,14 +396,20 @@ rb_static_playlist_source_new (RBShell *shell, const char *name, const char *set
                settings = NULL;
        }
 
-       return RB_SOURCE (g_object_new (RB_TYPE_STATIC_PLAYLIST_SOURCE,
-                                       "name", name,
-                                       "settings", settings,
-                                       "shell", shell,
-                                       "is-local", local,
-                                       "entry-type", entry_type,
-                                       "toolbar-path", "/StaticPlaylistSourceToolBar",
-                                       NULL));
+       builder = rb_builder_load ("playlist-toolbar.ui", NULL);
+       toolbar = G_MENU (gtk_builder_get_object (builder, "playlist-toolbar"));
+       rb_application_link_shared_menus (RB_APPLICATION (g_application_get_default ()), toolbar);
+
+       source = RB_SOURCE (g_object_new (RB_TYPE_STATIC_PLAYLIST_SOURCE,
+                                         "name", name,
+                                         "settings", settings,
+                                         "shell", shell,
+                                         "is-local", local,
+                                         "entry-type", entry_type,
+                                         "toolbar-menu", toolbar,
+                                         NULL));
+       g_object_unref (builder);
+       return source;
 }
 
 static void
diff --git a/sources/rb-static-playlist-source.h b/sources/rb-static-playlist-source.h
index 64f97c5..4a2c80f 100644
--- a/sources/rb-static-playlist-source.h
+++ b/sources/rb-static-playlist-source.h
@@ -56,14 +56,10 @@ struct _RBStaticPlaylistSource
 struct _RBStaticPlaylistSourceClass
 {
        RBPlaylistSourceClass parent;
-
-       GtkActionGroup *action_group;
 };
 
 GType          rb_static_playlist_source_get_type      (void);
 
-void           rb_static_playlist_source_create_actions (RBShell *shell);
-
 RBSource *     rb_static_playlist_source_new           (RBShell *shell,
                                                         const char *name,
                                                         const char *settings_name,
diff --git a/widgets/Makefile.am b/widgets/Makefile.am
index 07e8fbd..10456a3 100644
--- a/widgets/Makefile.am
+++ b/widgets/Makefile.am
@@ -16,7 +16,8 @@ widgetinclude_HEADERS =                                       \
        rb-uri-dialog.h                                 \
        rb-fading-image.h                               \
        rb-object-property-editor.h                     \
-       rb-import-dialog.h
+       rb-import-dialog.h                              \
+       rb-button-bar.h
 
 librbwidgets_la_SOURCES =                              \
        $(widgetinclude_HEADERS)                        \
@@ -50,7 +51,8 @@ librbwidgets_la_SOURCES =                             \
        rb-source-toolbar.c                             \
        rb-fading-image.c                               \
        rb-object-property-editor.c                     \
-       rb-import-dialog.c
+       rb-import-dialog.c                              \
+       rb-button-bar.c
 
 INCLUDES =                                             \
        -DGNOMELOCALEDIR=\""$(datadir)/locale"\"        \
diff --git a/widgets/rb-button-bar.c b/widgets/rb-button-bar.c
new file mode 100644
index 0000000..c9ffbef
--- /dev/null
+++ b/widgets/rb-button-bar.c
@@ -0,0 +1,376 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ *  Copyright (C) 2012 Jonathan Matthew <jonathan d14n org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The Rhythmbox authors hereby grant permission for non-GPL compatible
+ *  GStreamer plugins to be used and distributed together with GStreamer
+ *  and Rhythmbox. This permission is above and beyond the permissions granted
+ *  by the GPL license by which Rhythmbox is covered. If you modify this code
+ *  you may extend this exception to your version of the code, but you are not
+ *  obligated to do so. If you do not wish to do so, delete this exception
+ *  statement from your version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+ *
+ */
+
+#include <config.h>
+
+#include <widgets/rb-button-bar.h>
+#include <lib/rb-util.h>
+
+static void rb_button_bar_class_init (RBButtonBarClass *klass);
+static void rb_button_bar_init (RBButtonBar *bar);
+
+struct _RBButtonBarPrivate
+{
+       GObject *target;
+       GtkSizeGroup *size_group;
+       GMenuModel *model;
+       guint item_change_id;
+
+       int position;
+};
+
+G_DEFINE_TYPE (RBButtonBar, rb_button_bar, GTK_TYPE_GRID);
+
+enum {
+       PROP_0,
+       PROP_MODEL,
+       PROP_TARGET
+};
+
+static void
+clear_button_bar (RBButtonBar *bar)
+{
+       GList *c, *l;
+
+       c = gtk_container_get_children (GTK_CONTAINER (bar));
+       for (l = c; l != NULL; l = l->next) {
+               gtk_size_group_remove_widget (bar->priv->size_group, l->data);
+               gtk_container_remove (GTK_CONTAINER (bar), l->data);
+       }
+       g_list_free (c);
+
+       bar->priv->position = 0;
+}
+
+static gboolean
+append_menu (RBButtonBar *bar, GMenuModel *menu, gboolean need_separator)
+{
+       int i;
+
+       for (i = 0; i < g_menu_model_get_n_items (menu); i++) {
+               char *label_text;
+               char *accel;
+               GtkWidget *button;
+               GtkWidget *label;
+               GMenuModel *submenu;
+
+               /* recurse into sections */
+               submenu = g_menu_model_get_item_link (menu, i, G_MENU_LINK_SECTION);
+               if (submenu != NULL) {
+                       need_separator = append_menu (bar, submenu, TRUE);
+                       continue;
+               }
+
+               /* if this item and the previous item are in different sections, add
+                * a separator between them.  this may not be a good idea.
+                */
+               if (need_separator) {
+                       GtkWidget *sep;
+
+                       sep = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
+                       gtk_grid_attach (GTK_GRID (bar), sep, bar->priv->position++, 0, 1, 1);
+
+                       need_separator = FALSE;
+               }
+
+               button = NULL;
+
+               /* submenus become menu buttons, normal items become buttons */
+               submenu = g_menu_model_get_item_link (menu, i, G_MENU_LINK_SUBMENU);
+
+               if (submenu != NULL) {
+                       button = gtk_menu_button_new ();
+                       gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (button), submenu);
+               } else {
+                       GMenuAttributeIter *iter;
+                       const char *name;
+                       GVariant *value;
+                       char *str;
+               
+                       /* we can't do more than one of action and rb-property-bind,
+                        * so just do whichever turns up first in the iterator
+                        */
+                       iter = g_menu_model_iterate_item_attributes (menu, i);
+                       while (g_menu_attribute_iter_get_next (iter, &name, &value)) {
+                               if (g_str_equal (name, "action")) {
+                                       button = gtk_button_new ();
+                                       g_variant_get (value, "s", &str, NULL);
+                                       gtk_actionable_set_action_name (GTK_ACTIONABLE (button), str);
+                                       /* action target too somehow? */
+                                       g_free (str);
+                                       break;
+                               } else if (g_str_equal (name, "rb-property-bind")) {
+                                       /* property has to be a boolean, can't do inverts, etc. etc. */
+                                       button = gtk_toggle_button_new ();
+                                       g_variant_get (value, "s", &str, NULL);
+                                       g_object_bind_property (bar->priv->target, str,
+                                                               button, "active",
+                                                               G_BINDING_BIDIRECTIONAL | 
G_BINDING_SYNC_CREATE);
+                                       g_free (str);
+                                       break;
+                               }
+                       }
+
+                       g_object_unref (iter);
+               }
+
+               if (button == NULL) {
+                       g_warning ("no idea what's going on here");
+                       continue;
+               }
+
+               gtk_widget_set_hexpand (button, FALSE);
+               gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+
+               label_text = NULL;
+               g_menu_model_get_item_attribute (menu, i, "label", "s", &label_text);
+               label = gtk_label_new (g_dgettext (NULL, label_text));
+               g_object_set (label, "xpad", 6, NULL);
+               gtk_container_add (GTK_CONTAINER (button), label);
+
+               if (g_menu_model_get_item_attribute (menu, i, "accel", "s", &accel)) {
+                       g_object_set_data_full (G_OBJECT (button), "rb-accel", accel, (GDestroyNotify) 
g_free);
+               }
+
+               gtk_size_group_add_widget (bar->priv->size_group, button);
+               gtk_grid_attach (GTK_GRID (bar), button, bar->priv->position++, 0, 1, 1);
+
+               g_free (label_text);
+       }
+
+       return need_separator;
+}
+
+static void
+build_button_bar (RBButtonBar *bar)
+{
+       GtkWidget *waste;
+
+       append_menu (bar, bar->priv->model, FALSE);
+
+       waste = gtk_label_new ("");
+       gtk_widget_set_hexpand (waste, TRUE);
+       gtk_grid_attach (GTK_GRID (bar), waste, bar->priv->position++, 0, 1, 1);
+}
+
+static void
+items_changed_cb (GMenuModel *model, int position, int added, int removed, RBButtonBar *bar)
+{
+       clear_button_bar (bar);
+       build_button_bar (bar);
+}
+
+static void
+impl_constructed (GObject *object)
+{
+       RBButtonBar *bar;
+
+       RB_CHAIN_GOBJECT_METHOD (rb_button_bar_parent_class, constructed, object);
+
+       bar = RB_BUTTON_BAR (object);
+
+       gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (bar)),
+                                    GTK_STYLE_CLASS_TOOLBAR);
+
+       bar->priv->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+       bar->priv->item_change_id = g_signal_connect (bar->priv->model, "items-changed", G_CALLBACK 
(items_changed_cb), bar);
+       build_button_bar (bar);
+}
+
+static void
+impl_dispose (GObject *object)
+{
+       RBButtonBar *bar = RB_BUTTON_BAR (object);
+       
+       if (bar->priv->model != NULL) {
+               g_signal_handler_disconnect (bar->priv->model, bar->priv->item_change_id);
+               g_clear_object (&bar->priv->model);
+       }
+       G_OBJECT_CLASS (rb_button_bar_parent_class)->dispose (object);
+}
+
+static void
+impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+       RBButtonBar *bar = RB_BUTTON_BAR (object);
+       
+       switch (prop_id) {
+       case PROP_MODEL:
+               g_value_set_object (value, bar->priv->model);
+               break;
+       case PROP_TARGET:
+               g_value_set_object (value, bar->priv->target);
+               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)
+{
+       RBButtonBar *bar = RB_BUTTON_BAR (object);
+       
+       switch (prop_id) {
+       case PROP_MODEL:
+               bar->priv->model = g_value_dup_object (value);
+               break;
+       case PROP_TARGET:
+               /* we're inside the target object usually, so don't ref it */
+               bar->priv->target = g_value_get_object (value);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+
+
+static void
+rb_button_bar_init (RBButtonBar *bar)
+{
+       bar->priv = G_TYPE_INSTANCE_GET_PRIVATE (bar, RB_TYPE_BUTTON_BAR, RBButtonBarPrivate);
+}
+
+static void
+rb_button_bar_class_init (RBButtonBarClass *klass)
+{
+       GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+       g_type_class_add_private (klass, sizeof (RBButtonBarPrivate));
+
+       gobject_class->constructed = impl_constructed;
+       gobject_class->dispose = impl_dispose;
+       gobject_class->set_property = impl_set_property;
+       gobject_class->get_property = impl_get_property;
+
+       g_object_class_install_property (gobject_class,
+                                        PROP_MODEL,
+                                        g_param_spec_object ("model",
+                                                             "model",
+                                                             "model",
+                                                             G_TYPE_MENU_MODEL,
+                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+       g_object_class_install_property (gobject_class,
+                                        PROP_TARGET,
+                                        g_param_spec_object ("target",
+                                                             "target",
+                                                             "binding target",
+                                                             G_TYPE_OBJECT,
+                                                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+/**
+ * rb_button_bar_new:
+ * @model: a #GMenuModel
+ * @target: property and signal binding target
+ *
+ * Creates a toolbar-like widget (not actually a #GtkToolbar) containing
+ * a row of buttons representing the items in @model.  If an item in the
+ * model has an rb-property-bind attribute set, the state of the button
+ * is bound to the corresponding property of the source that the toolbar
+ * is associated with.  This only works for toggle buttons, so the property
+ * must be a boolean.
+ *
+ * Return value: the button bar
+ */
+GtkWidget *
+rb_button_bar_new (GMenuModel *model, GObject *target)
+{
+       return GTK_WIDGET (g_object_new (RB_TYPE_BUTTON_BAR,
+                                        "model", model,
+                                        "target", target,
+                                        "column-homogeneous", FALSE,
+                                        "hexpand", FALSE,
+                                        /* column-spacing? */
+                                        NULL));
+}
+
+/**
+ * rb_button_bar_add_accelerators:
+ * @bar: a #RBButtonBar
+ * @group: the #GtkAccelGroup to add accelerators to
+ *
+ * Adds accelerators for the buttons in @bar to the accelerator
+ * group @group.
+ */
+void
+rb_button_bar_add_accelerators (RBButtonBar *bar, GtkAccelGroup *group)
+{
+       GList *c, *l;
+
+       c = gtk_container_get_children (GTK_CONTAINER (bar));
+       for (l = c; l != NULL; l = l->next) {
+               GtkWidget *widget = l->data;
+               const char *accel_text;
+               guint accel_key;
+               GdkModifierType accel_mods;
+
+               accel_text = g_object_get_data (G_OBJECT (widget), "rb-accel");
+               if (accel_text != NULL) {
+                       gtk_accelerator_parse (accel_text, &accel_key, &accel_mods);
+                       if (accel_key != 0) {
+                               gtk_widget_add_accelerator (widget, "activate", group, accel_key, accel_mods, 
0);
+                       }
+               }
+       }
+       g_list_free (c);
+}
+
+/**
+ * rb_button_bar_remove_accelerators:
+ * @bar: a #RBButtonBar
+ * @group: the #GtkAccelGroup to remove accelerators from
+ *
+ * Reverses the effects of @rb_button_bar_add_accelerators.
+ */
+void
+rb_button_bar_remove_accelerators (RBButtonBar *bar, GtkAccelGroup *group)
+{
+       GList *c, *l;
+
+       c = gtk_container_get_children (GTK_CONTAINER (bar));
+       for (l = c; l != NULL; l = l->next) {
+               GtkWidget *widget = l->data;
+               const char *accel_text;
+               guint accel_key;
+               GdkModifierType accel_mods;
+
+               accel_text = g_object_get_data (G_OBJECT (widget), "rb-accel");
+               if (accel_text != NULL) {
+                       gtk_accelerator_parse (accel_text, &accel_key, &accel_mods);
+                       if (accel_key != 0) {
+                               gtk_widget_remove_accelerator (widget, group, accel_key, accel_mods);
+                       }
+               }
+       }
+       g_list_free (c);
+}
diff --git a/widgets/rb-button-bar.h b/widgets/rb-button-bar.h
new file mode 100644
index 0000000..98eef02
--- /dev/null
+++ b/widgets/rb-button-bar.h
@@ -0,0 +1,67 @@
+/*
+ *  Copyright (C) 2012 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_BUTTON_BAR_H
+#define RB_BUTTON_BAR_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_BUTTON_BAR         (rb_button_bar_get_type ())
+#define RB_BUTTON_BAR(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_BUTTON_BAR, RBButtonBar))
+#define RB_BUTTON_BAR_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_BUTTON_BAR, RBButtonBarClass))
+#define RB_IS_BUTTON_BAR(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_BUTTON_BAR))
+#define RB_IS_BUTTON_BAR_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_BUTTON_BAR))
+#define RB_BUTTON_BAR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_BUTTON_BAR, RBButtonBarClass))
+
+typedef struct _RBButtonBar RBButtonBar;
+typedef struct _RBButtonBarClass RBButtonBarClass;
+typedef struct _RBButtonBarPrivate RBButtonBarPrivate;
+
+struct _RBButtonBar
+{
+       GtkGrid parent;
+
+       RBButtonBarPrivate *priv;
+};
+
+struct _RBButtonBarClass
+{
+       GtkGridClass parent;
+};
+
+GType          rb_button_bar_get_type          (void);
+
+GtkWidget *    rb_button_bar_new               (GMenuModel *model, GObject *target);
+
+void           rb_button_bar_add_accelerators  (RBButtonBar *bar, GtkAccelGroup *group);
+void           rb_button_bar_remove_accelerators (RBButtonBar *bar, GtkAccelGroup *group);
+
+G_END_DECLS
+
+#endif /* RB_BUTTON_BAR_H */
diff --git a/widgets/rb-header.c b/widgets/rb-header.c
index 8567ee8..d5c4040 100644
--- a/widgets/rb-header.c
+++ b/widgets/rb-header.c
@@ -63,6 +63,7 @@
 
 static void rb_header_class_init (RBHeaderClass *klass);
 static void rb_header_init (RBHeader *header);
+static void rb_header_constructed (GObject *object);
 static void rb_header_dispose (GObject *object);
 static void rb_header_finalize (GObject *object);
 static void rb_header_set_property (GObject *object,
@@ -97,6 +98,8 @@ static void uri_dropped_cb (RBFadingImage *image, const char *uri, RBHeader *hea
 static void pixbuf_dropped_cb (RBFadingImage *image, GdkPixbuf *pixbuf, RBHeader *header);
 static void image_button_press_cb (GtkWidget *widget, GdkEvent *event, RBHeader *header);
 static void art_added_cb (RBExtDB *db, RBExtDBKey *key, const char *filename, GValue *data, RBHeader 
*header);
+static void volume_widget_changed_cb (GtkScaleButton *widget, gdouble volume, RBHeader *header);
+static void player_volume_changed_cb (RBShellPlayer *player, GParamSpec *pspec, RBHeader *header);
 
 struct RBHeaderPrivate
 {
@@ -110,6 +113,7 @@ struct RBHeaderPrivate
        GtkWidget *song;
        GtkWidget *details;
        GtkWidget *image;
+       GtkWidget *volume_button;
 
        GtkWidget *scale;
        GtkAdjustment *adjustment;
@@ -129,6 +133,8 @@ struct RBHeaderPrivate
        char *image_path;
        gboolean show_album_art;
        gboolean show_slider;
+       
+       gboolean syncing_volume;
 };
 
 enum
@@ -162,6 +168,7 @@ rb_header_class_init (RBHeaderClass *klass)
        GObjectClass *object_class = G_OBJECT_CLASS (klass);
        GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
 
+       object_class->constructed = rb_header_constructed;
        object_class->dispose = rb_header_dispose;
        object_class->finalize = rb_header_finalize;
 
@@ -269,6 +276,14 @@ static void
 rb_header_init (RBHeader *header)
 {
        header->priv = G_TYPE_INSTANCE_GET_PRIVATE (header, RB_TYPE_HEADER, RBHeaderPrivate);
+}
+
+static void
+rb_header_constructed (GObject *object)
+{
+       RBHeader *header = RB_HEADER (object);
+
+       RB_CHAIN_GOBJECT_METHOD (rb_header_parent_class, constructed, object);
 
        gtk_grid_set_column_spacing (GTK_GRID (header), 6);
        gtk_grid_set_column_homogeneous (GTK_GRID (header), TRUE);
@@ -360,10 +375,20 @@ rb_header_init (RBHeader *header)
                          G_CALLBACK (image_button_press_cb),
                          header);
 
+       /* volume button */
+       header->priv->volume_button = gtk_volume_button_new ();
+       g_signal_connect (header->priv->volume_button, "value-changed",
+                         G_CALLBACK (volume_widget_changed_cb),
+                         header);
+       g_signal_connect (header->priv->shell_player, "notify::volume",
+                         G_CALLBACK (player_volume_changed_cb),
+                         header);
+
        gtk_grid_attach (GTK_GRID (header), header->priv->image, 0, 0, 1, 1);
        gtk_grid_attach (GTK_GRID (header), header->priv->songbox, 2, 0, 1, 1);
        gtk_grid_attach (GTK_GRID (header), header->priv->timebutton, 3, 0, 1, 1);
        gtk_grid_attach (GTK_GRID (header), header->priv->scale, 4, 0, 1, 1);
+       gtk_grid_attach (GTK_GRID (header), header->priv->volume_button, 5, 0, 1, 1);
 
        /* currently, nothing sets this.  it should be set on track changes. */
        header->priv->seekable = TRUE;
@@ -498,6 +523,7 @@ rb_header_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
        int info_width;
        int time_width;
        int image_width;
+       int volume_width;
        GtkAllocation child_alloc;
        gboolean rtl;
 
@@ -524,6 +550,15 @@ rb_header_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
                image_width = 0;
        }
 
+       /* allocate space for the volume button at the end */
+       gtk_widget_get_preferred_width (RB_HEADER (widget)->priv->volume_button, &volume_width, NULL);
+       child_alloc.x = (allocation->x + allocation->width) - volume_width;
+       child_alloc.y = allocation->y;
+       child_alloc.width = volume_width;
+       child_alloc.height = allocation->height;
+       allocation->width -= volume_width + spacing;
+       gtk_widget_size_allocate (RB_HEADER (widget)->priv->volume_button, &child_alloc);
+
        /* figure out how much space to allocate to the scale.
         * it gets at least its minimum size, at most 1/3 of the
         * space we have.
@@ -1213,3 +1248,22 @@ time_button_clicked_cb (GtkWidget *widget, RBHeader *header)
 {
        g_object_set (header, "show-remaining", !header->priv->show_remaining, NULL);
 }
+
+static void
+volume_widget_changed_cb (GtkScaleButton *vol, gdouble value, RBHeader *header)
+{
+       if (!header->priv->syncing_volume) {
+               g_object_set (header->priv->shell_player, "volume", value, NULL);
+       }
+}
+
+static void
+player_volume_changed_cb (RBShellPlayer *player, GParamSpec *pspec, RBHeader *header)
+{
+       float volume;
+
+       g_object_get (player, "volume", &volume, NULL);
+       header->priv->syncing_volume = TRUE;
+       gtk_scale_button_set_value (GTK_SCALE_BUTTON (header->priv->volume_button), volume);
+       header->priv->syncing_volume = FALSE;
+}
diff --git a/widgets/rb-import-dialog.c b/widgets/rb-import-dialog.c
index 9fe7f98..78dc603 100644
--- a/widgets/rb-import-dialog.c
+++ b/widgets/rb-import-dialog.c
@@ -381,7 +381,7 @@ pulse_cb (RBImportDialog *dialog)
 static void
 start_scanning (RBImportDialog *dialog)
 {
-       rb_debug ("starting %s\n", dialog->priv->current_uri);
+       rb_debug ("starting %s", dialog->priv->current_uri);
        dialog->priv->import_job = rhythmdb_import_job_new (dialog->priv->db,
                                                            dialog->priv->entry_type,
                                                            dialog->priv->ignore_type,
diff --git a/widgets/rb-source-toolbar.c b/widgets/rb-source-toolbar.c
index 63d4ca6..fd4e5b1 100644
--- a/widgets/rb-source-toolbar.c
+++ b/widgets/rb-source-toolbar.c
@@ -29,23 +29,19 @@
 #include <config.h>
 
 #include <widgets/rb-source-toolbar.h>
+#include <widgets/rb-button-bar.h>
 #include <lib/rb-util.h>
 
 static void rb_source_toolbar_class_init (RBSourceToolbarClass *klass);
 static void rb_source_toolbar_init (RBSourceToolbar *toolbar);
 
-static void toolbar_add_widget_cb (GtkUIManager *ui_manager, GtkWidget *widget, RBSourceToolbar *toolbar);
-static void popup_add_widget_cb (GtkUIManager *ui_manager, GtkWidget *widget, RBSourceToolbar *toolbar);
-
 struct _RBSourceToolbarPrivate
 {
-       GtkUIManager *ui_manager;
-       RBSource *source;
+       GtkAccelGroup *accel_group;
+       RBDisplayPage *page;
        RBSearchEntry *search_entry;
        GtkWidget *search_popup;
-       GtkWidget *toolbar;
-       GBinding *browse_binding;
-       GtkAction *browse_action;
+       GtkWidget *button_bar;
        char *popup_path;
 
        /* search state */
@@ -53,7 +49,8 @@ struct _RBSourceToolbarPrivate
        gulong search_change_cb_id;
        RBSourceSearch *active_search;
        char *search_text;
-       GtkRadioAction *search_group;
+
+       GAction *search_action;
 };
 
 G_DEFINE_TYPE (RBSourceToolbar, rb_source_toolbar, GTK_TYPE_GRID)
@@ -72,48 +69,11 @@ G_DEFINE_TYPE (RBSourceToolbar, rb_source_toolbar, GTK_TYPE_GRID)
 enum
 {
        PROP_0,
-       PROP_SOURCE,
-       PROP_UI_MANAGER,
+       PROP_PAGE,
+       PROP_ACCEL_GROUP,
 };
 
 static void
-prepare_toolbar (GtkWidget *toolbar)
-{
-       static GtkCssProvider *provider = NULL;
-
-       if (provider == NULL) {
-               const char *style =
-                       "GtkToolbar {\n"
-                        "       -GtkToolbar-shadow-type: none;\n"
-                        "       border-style: none;\n"
-                        "}";
-
-               provider = gtk_css_provider_new ();
-               gtk_css_provider_load_from_data (provider, style, -1, NULL);
-       }
-
-       gtk_style_context_add_provider (gtk_widget_get_style_context (toolbar),
-                                       GTK_STYLE_PROVIDER (provider),
-                                       GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
-
-       gtk_widget_set_hexpand (toolbar, TRUE);
-
-       gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_TEXT);
-}
-
-static void
-search_change_cb (GtkRadioAction *group, GtkRadioAction *current, RBSourceToolbar *toolbar)
-{
-       toolbar->priv->active_search = rb_source_search_get_from_action (G_OBJECT (current));
-
-       if (toolbar->priv->search_text != NULL) {
-               rb_source_search (toolbar->priv->source, toolbar->priv->active_search, NULL, 
toolbar->priv->search_text);
-       }
-
-       rb_search_entry_set_placeholder (toolbar->priv->search_entry, gtk_action_get_label (GTK_ACTION 
(current)));
-}
-
-static void
 source_selected_cb (GObject *object, GParamSpec *pspec, RBSourceToolbar *toolbar)
 {
        gboolean selected;
@@ -121,87 +81,34 @@ source_selected_cb (GObject *object, GParamSpec *pspec, RBSourceToolbar *toolbar
        g_object_get (object, "selected", &selected, NULL);
 
        if (selected) {
-               char *toolbar_path;
-               char *browse_path;
-
-               if (toolbar->priv->toolbar != NULL) {
-                       gtk_grid_attach (GTK_GRID (toolbar), toolbar->priv->toolbar, 0, 0, 2, 1);
-                       gtk_widget_show_all (GTK_WIDGET (toolbar->priv->toolbar));
-               }
-
                if (toolbar->priv->search_entry != NULL) {
                        rb_search_entry_set_mnemonic (toolbar->priv->search_entry, TRUE);
 
                        gtk_widget_add_accelerator (GTK_WIDGET (toolbar->priv->search_entry),
                                                    "grab-focus",
-                                                   gtk_ui_manager_get_accel_group 
(toolbar->priv->ui_manager),
+                                                   toolbar->priv->accel_group,
                                                    gdk_unicode_to_keyval ('f'),
                                                    GDK_CONTROL_MASK,
                                                    0);
                }
 
-               if (toolbar->priv->search_group != NULL) {
-                       if (toolbar->priv->search_value != -1) {
-                               gtk_radio_action_set_current_value (toolbar->priv->search_group,
-                                                                   toolbar->priv->search_value);
-                       }
-
-                       toolbar->priv->search_change_cb_id = g_signal_connect (toolbar->priv->search_group,
-                                                                              "changed",
-                                                                              G_CALLBACK (search_change_cb),
-                                                                              toolbar);
-               }
-
-               g_object_get (toolbar->priv->source, "toolbar-path", &toolbar_path, NULL);
-               if (toolbar_path != NULL) {
-
-                       browse_path = g_strdup_printf ("%s/Browse", toolbar_path);
-                       toolbar->priv->browse_action = gtk_ui_manager_get_action (toolbar->priv->ui_manager,
-                                                                                 browse_path);
-                       g_free (browse_path);
-
-                       if (toolbar->priv->browse_action != NULL) {
-                               toolbar->priv->browse_binding =
-                                       g_object_bind_property (toolbar->priv->source, "show-browser",
-                                                               toolbar->priv->browse_action, "active",
-                                                               G_BINDING_BIDIRECTIONAL | 
G_BINDING_SYNC_CREATE);
-                               gtk_action_connect_accelerator (toolbar->priv->browse_action);
-                       }
-                       g_free (toolbar_path);
-               } else {
-                       toolbar->priv->browse_action = NULL;
+               if (toolbar->priv->button_bar != NULL) {
+                       rb_button_bar_add_accelerators (RB_BUTTON_BAR (toolbar->priv->button_bar),
+                                                       toolbar->priv->accel_group);
                }
        } else {
-               if (toolbar->priv->toolbar != NULL) {
-                       gtk_container_remove (GTK_CONTAINER (toolbar), toolbar->priv->toolbar);
-               }
-
                if (toolbar->priv->search_entry != NULL) {
                        rb_search_entry_set_mnemonic (toolbar->priv->search_entry, FALSE);
 
                        gtk_widget_remove_accelerator (GTK_WIDGET (toolbar->priv->search_entry),
-                                                      gtk_ui_manager_get_accel_group 
(toolbar->priv->ui_manager),
+                                                      toolbar->priv->accel_group,
                                                       gdk_unicode_to_keyval ('f'),
                                                       GDK_CONTROL_MASK);
                }
 
-               if (toolbar->priv->search_group != NULL) {
-                       if (toolbar->priv->search_change_cb_id != 0) {
-                               g_signal_handler_disconnect (toolbar->priv->search_group,
-                                                            toolbar->priv->search_change_cb_id);
-                       }
-
-                       toolbar->priv->search_value = gtk_radio_action_get_current_value 
(toolbar->priv->search_group);
-               }
-
-               if (toolbar->priv->browse_binding != NULL) {
-                       g_object_unref (toolbar->priv->browse_binding);
-                       toolbar->priv->browse_binding = NULL;
-               }
-
-               if (toolbar->priv->browse_action != NULL) {
-                       gtk_action_disconnect_accelerator (toolbar->priv->browse_action);
-                       toolbar->priv->browse_action = NULL;
+               if (toolbar->priv->button_bar != NULL) {
+                       rb_button_bar_remove_accelerators (RB_BUTTON_BAR (toolbar->priv->button_bar),
+                                                          toolbar->priv->accel_group);
                }
        }
 }
@@ -209,7 +116,9 @@ source_selected_cb (GObject *object, GParamSpec *pspec, RBSourceToolbar *toolbar
 static void
 search_cb (RBSearchEntry *search_entry, const char *text, RBSourceToolbar *toolbar)
 {
-       rb_source_search (toolbar->priv->source, toolbar->priv->active_search, toolbar->priv->search_text, 
text);
+       g_return_if_fail (RB_IS_SOURCE (toolbar->priv->page));
+
+       rb_source_search (RB_SOURCE (toolbar->priv->page), toolbar->priv->active_search, 
toolbar->priv->search_text, text);
 
        g_free (toolbar->priv->search_text);
        toolbar->priv->search_text = NULL;
@@ -243,82 +152,41 @@ impl_dispose (GObject *object)
 {
        RBSourceToolbar *toolbar = RB_SOURCE_TOOLBAR (object);
 
-       if (toolbar->priv->ui_manager != NULL) {
-               g_signal_handlers_disconnect_by_func (toolbar->priv->ui_manager, G_CALLBACK 
(popup_add_widget_cb), toolbar);
-               g_signal_handlers_disconnect_by_func (toolbar->priv->ui_manager, G_CALLBACK 
(toolbar_add_widget_cb), toolbar);
-
-               g_object_unref (toolbar->priv->ui_manager);
-               toolbar->priv->ui_manager = NULL;
-       }
-       if (toolbar->priv->search_popup != NULL) {
-               g_object_unref (toolbar->priv->search_popup);
-               toolbar->priv->search_popup = NULL;
-       }
-       if (toolbar->priv->toolbar != NULL) {
-               g_object_unref (toolbar->priv->toolbar);
-               toolbar->priv->toolbar = NULL;
-       }
-       if (toolbar->priv->browse_binding != NULL) {
-               g_object_unref (toolbar->priv->browse_binding);
-               toolbar->priv->browse_binding = NULL;
-       }
+       g_clear_object (&toolbar->priv->accel_group);
+       g_clear_object (&toolbar->priv->search_popup);
 
        G_OBJECT_CLASS (rb_source_toolbar_parent_class)->dispose (object);
 }
 
 static void
-toolbar_add_widget_cb (GtkUIManager *ui_manager, GtkWidget *widget, RBSourceToolbar *toolbar)
-{
-       char *toolbar_path;
-       gboolean selected;
-
-       g_object_get (toolbar->priv->source, "toolbar-path", &toolbar_path, "selected", &selected, NULL);
-       toolbar->priv->toolbar = gtk_ui_manager_get_widget (toolbar->priv->ui_manager, toolbar_path);
-       g_free (toolbar_path);
-
-       if (toolbar->priv->toolbar) {
-               g_object_ref (toolbar->priv->toolbar);
-               g_signal_handlers_disconnect_by_func (ui_manager, G_CALLBACK (toolbar_add_widget_cb), 
toolbar);
-
-               prepare_toolbar (toolbar->priv->toolbar);
-
-               if (selected) {
-                       gtk_grid_attach (GTK_GRID (toolbar), toolbar->priv->toolbar, 0, 0, 2, 1);
-                       gtk_widget_show_all (GTK_WIDGET (toolbar->priv->toolbar));
-               }
-       }
-}
-
-static void
 impl_constructed (GObject *object)
 {
        RBSourceToolbar *toolbar;
-       char *toolbar_path;
        GtkWidget *blank;
+       GMenuModel *toolbar_menu;
 
        RB_CHAIN_GOBJECT_METHOD (rb_source_toolbar_parent_class, constructed, object);
 
        toolbar = RB_SOURCE_TOOLBAR (object);
 
-       g_object_get (toolbar->priv->source, "toolbar-path", &toolbar_path, NULL);
-       if (toolbar_path) {
-               toolbar->priv->toolbar = gtk_ui_manager_get_widget (toolbar->priv->ui_manager, toolbar_path);
-               if (toolbar->priv->toolbar == NULL) {
-                       g_signal_connect (toolbar->priv->ui_manager, "add-widget", G_CALLBACK 
(toolbar_add_widget_cb), toolbar);
-               } else {
-                       g_object_ref (toolbar->priv->toolbar);
-                       prepare_toolbar (toolbar->priv->toolbar);
-               }
+       g_object_get (toolbar->priv->page,
+                     "toolbar-menu", &toolbar_menu,
+                     NULL);
+       if (toolbar_menu != NULL) {
+               toolbar->priv->button_bar = rb_button_bar_new (toolbar_menu, G_OBJECT (toolbar->priv->page));
+               gtk_widget_show_all (toolbar->priv->button_bar);
+               gtk_grid_attach (GTK_GRID (toolbar), toolbar->priv->button_bar, 0, 0, 2 ,1);
+               g_object_unref (toolbar_menu);
        } else {
                blank = gtk_toolbar_new ();
-               prepare_toolbar (blank);
+               gtk_widget_set_hexpand (blank, TRUE);
+               gtk_toolbar_set_style (GTK_TOOLBAR (blank), GTK_TOOLBAR_TEXT);
                gtk_grid_attach (GTK_GRID (toolbar), blank, 0, 0, 2 ,1);
        }
-       g_free (toolbar_path);
 
        /* search entry gets created later if required */
 
-       g_signal_connect (toolbar->priv->source, "notify::selected", G_CALLBACK (source_selected_cb), 
toolbar);
+       g_signal_connect (toolbar->priv->page, "notify::selected", G_CALLBACK (source_selected_cb), toolbar);
 }
 
 static void
@@ -327,11 +195,11 @@ impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *ps
        RBSourceToolbar *toolbar = RB_SOURCE_TOOLBAR (object);
 
        switch (prop_id) {
-       case PROP_SOURCE:
-               g_value_set_object (value, toolbar->priv->source);
+       case PROP_PAGE:
+               g_value_set_object (value, toolbar->priv->page);
                break;
-       case PROP_UI_MANAGER:
-               g_value_set_object (value, toolbar->priv->ui_manager);
+       case PROP_ACCEL_GROUP:
+               g_value_set_object (value, toolbar->priv->accel_group);
                break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -345,11 +213,11 @@ impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSp
        RBSourceToolbar *toolbar = RB_SOURCE_TOOLBAR (object);
 
        switch (prop_id) {
-       case PROP_SOURCE:
-               toolbar->priv->source = g_value_get_object (value);     /* don't take a ref */
+       case PROP_PAGE:
+               toolbar->priv->page = g_value_get_object (value);       /* don't take a ref */
                break;
-       case PROP_UI_MANAGER:
-               toolbar->priv->ui_manager = g_value_dup_object (value);
+       case PROP_ACCEL_GROUP:
+               toolbar->priv->accel_group = g_value_dup_object (value);
                break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -377,28 +245,28 @@ rb_source_toolbar_class_init (RBSourceToolbarClass *klass)
        object_class->get_property = impl_get_property;
 
        /**
-        * RBSourceToolbar:source:
+        * RBSourceToolbar:page:
         *
-        * The #RBSource the toolbar is associated with
+        * The #RBDisplayPage the toolbar is associated with
         */
        g_object_class_install_property (object_class,
-                                        PROP_SOURCE,
-                                        g_param_spec_object ("source",
-                                                             "source",
-                                                             "RBSource instance",
+                                        PROP_PAGE,
+                                        g_param_spec_object ("page",
+                                                             "page",
+                                                             "RBDisplayPage instance",
                                                              RB_TYPE_DISPLAY_PAGE,
                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
        /**
-        * RBSourceToolbar:ui-manager:
+        * RBSourceToolbar:accel-group:
         *
-        * The #GtkUIManager instance
+        * The #GtkAccelGroup to add accelerators to
         */
        g_object_class_install_property (object_class,
-                                        PROP_UI_MANAGER,
-                                        g_param_spec_object ("ui-manager",
-                                                             "ui manager",
-                                                             "GtkUIManager instance",
-                                                             GTK_TYPE_UI_MANAGER,
+                                        PROP_ACCEL_GROUP,
+                                        g_param_spec_object ("accel-group",
+                                                             "accel group",
+                                                             "GtkAccelGroup instance",
+                                                             GTK_TYPE_ACCEL_GROUP,
                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
        g_type_class_add_private (klass, sizeof (RBSourceToolbarPrivate));
 }
@@ -406,21 +274,21 @@ rb_source_toolbar_class_init (RBSourceToolbarClass *klass)
 /**
  * rb_source_toolbar_new:
  * @page: a #RBDisplayPage
- * @ui_manager: the #GtkUIManager
+ * @accel_group: a #GtkAccelGroup to add accelerators to
  *
  * Creates a new source toolbar for @page.  The toolbar does not
  * initially include a search entry.  Call #rb_source_toolbar_add_search_entry
- * to add one.  The toolbar content comes from the @RBSource:toolbar-path property.
+ * to add one.  The toolbar content comes from the @RBSource:toolbar-menu property.
  *
  * Return value: the #RBSourceToolbar
  */
 RBSourceToolbar *
-rb_source_toolbar_new (RBDisplayPage *page, GtkUIManager *ui_manager)
+rb_source_toolbar_new (RBDisplayPage *page, GtkAccelGroup *accel_group)
 {
        GObject *object;
        object = g_object_new (RB_TYPE_SOURCE_TOOLBAR,
-                              "source", page,
-                              "ui-manager", ui_manager,
+                              "page", page,
+                              "accel-group", accel_group,
                               "column-spacing", 6,
                               "column-homogeneous", TRUE,
                               "row-spacing", 6,
@@ -430,83 +298,76 @@ rb_source_toolbar_new (RBDisplayPage *page, GtkUIManager *ui_manager)
 }
 
 static void
-setup_search_popup (RBSourceToolbar *toolbar, GtkWidget *popup)
+search_state_notify_cb (GObject *action, GParamSpec *pspec, RBSourceToolbar *toolbar)
 {
-       GList *items;
-       GSList *l;
-       int active_value;
-
-       toolbar->priv->search_popup = g_object_ref (popup);
-
-       items = gtk_container_get_children (GTK_CONTAINER (toolbar->priv->search_popup));
-       toolbar->priv->search_group = GTK_RADIO_ACTION (gtk_activatable_get_related_action (GTK_ACTIVATABLE 
(items->data)));
-       g_list_free (items);
-
-       active_value = gtk_radio_action_get_current_value (toolbar->priv->search_group);
-       for (l = gtk_radio_action_get_group (toolbar->priv->search_group); l != NULL; l = l->next) {
-               int value;
-               g_object_get (G_OBJECT (l->data), "value", &value, NULL);
-               if (value == active_value) {
-                       rb_search_entry_set_placeholder (toolbar->priv->search_entry,
-                                                        gtk_action_get_label (GTK_ACTION (l->data)));
-               }
+       GVariant *state;
+
+       /* get search for current state */
+       state = g_action_get_state (G_ACTION (action));
+       toolbar->priv->active_search = rb_source_search_get_by_name (g_variant_get_string (state, NULL));
+       g_variant_unref (state);
+
+       if (toolbar->priv->search_text != NULL) {
+               rb_source_search (RB_SOURCE (toolbar->priv->page), toolbar->priv->active_search, NULL, 
toolbar->priv->search_text);
        }
 
-       g_signal_connect (toolbar->priv->search_entry, "show-popup", G_CALLBACK (show_popup_cb), toolbar);
+       if (toolbar->priv->active_search != NULL) {
+               rb_search_entry_set_placeholder (toolbar->priv->search_entry, 
rb_source_search_get_description (toolbar->priv->active_search));
+       } else {
+               rb_search_entry_set_placeholder (toolbar->priv->search_entry, NULL);    /* ? */
+       }
 }
 
 static void
-popup_add_widget_cb (GtkUIManager *ui_manager, GtkWidget *widget, RBSourceToolbar *toolbar)
+add_search_entry (RBSourceToolbar *toolbar, gboolean menu)
 {
-       GtkWidget *popup;
-       popup = gtk_ui_manager_get_widget (toolbar->priv->ui_manager, toolbar->priv->popup_path);
+       g_assert (toolbar->priv->search_entry == NULL);
 
-       if (popup) {
-               setup_search_popup (toolbar, popup);
-               g_signal_handlers_disconnect_by_func (ui_manager, G_CALLBACK (popup_add_widget_cb), toolbar);
-       }
-}
+       toolbar->priv->search_entry = rb_search_entry_new (menu);
+       gtk_widget_set_margin_right (GTK_WIDGET (toolbar->priv->search_entry), 6);
+       gtk_grid_attach (GTK_GRID (toolbar), GTK_WIDGET (toolbar->priv->search_entry), 2, 0, 1, 1);
 
+       g_signal_connect (toolbar->priv->search_entry, "search", G_CALLBACK (search_cb), toolbar);
+}
 
 /**
- * rb_source_toolbar_add_search_entry:
+ * rb_source_toolbar_add_search_entry_menu:
  * @toolbar: a #RBSourceToolbar
- * @popup_path: the UI path for the search popup (or NULL)
- * @placeholder: the placeholder text for the search entry (or NULL)
+ * @search_menu: a #GMenu containing search items
+ * @search_action: the #GAction for search state
  *
- * Adds a search entry to the toolbar.  If a popup path is specified,
- * clicking on the primary icon will show a menu allowing the user to
- * select a search type, and the placeholder text for the entry will
- * be the selected search description.  Otherwise, the specified placeholder
- * text will be displayed.
+ * Adds a search entry to the toolbar.
  */
 void
-rb_source_toolbar_add_search_entry (RBSourceToolbar *toolbar, const char *popup_path, const char 
*placeholder)
+rb_source_toolbar_add_search_entry_menu (RBSourceToolbar *toolbar, GMenuModel *search_menu, GAction 
*search_action)
 {
-       g_assert (toolbar->priv->search_entry == NULL);
+       g_return_if_fail (search_menu != NULL);
+       g_return_if_fail (search_action != NULL);
 
-       toolbar->priv->search_entry = rb_search_entry_new (popup_path != NULL);
-       gtk_widget_set_margin_right (GTK_WIDGET (toolbar->priv->search_entry), 6);
-       gtk_grid_attach (GTK_GRID (toolbar), GTK_WIDGET (toolbar->priv->search_entry), 2, 0, 1, 1);
+       add_search_entry (toolbar, TRUE);
 
-       if (placeholder) {
-               rb_search_entry_set_placeholder (toolbar->priv->search_entry, placeholder);
-       }
-
-       g_signal_connect (toolbar->priv->search_entry, "search", G_CALLBACK (search_cb), toolbar);
-       /* activate? */
+       toolbar->priv->search_popup = gtk_menu_new_from_model (search_menu);
+       gtk_menu_attach_to_widget (GTK_MENU (toolbar->priv->search_popup), GTK_WIDGET (toolbar), NULL);
+       g_object_ref_sink (toolbar->priv->search_popup);
+       toolbar->priv->search_action = g_object_ref (search_action);
 
-       if (popup_path != NULL) {
-               GtkWidget *popup;
-               toolbar->priv->popup_path = g_strdup (popup_path);
+       g_signal_connect (toolbar->priv->search_entry, "show-popup", G_CALLBACK (show_popup_cb), toolbar);
+       g_signal_connect (toolbar->priv->search_action, "notify::state", G_CALLBACK (search_state_notify_cb), 
toolbar);
+       search_state_notify_cb (G_OBJECT (toolbar->priv->search_action), NULL, toolbar);
+}
 
-               popup = gtk_ui_manager_get_widget (toolbar->priv->ui_manager, popup_path);
-               if (popup != NULL) {
-                       setup_search_popup (toolbar, popup);
-               } else {
-                       g_signal_connect (toolbar->priv->ui_manager, "add-widget", G_CALLBACK 
(popup_add_widget_cb), toolbar);
-               }
-       }
+/**
+ * rb_source_toolbar_add_search_entry:
+ * @toolbar: a #RBSourceToolbar
+ * @placeholder: the placeholder text for the search entry (or NULL)
+ *
+ * Adds a search entry with no search type menu.
+ */
+void
+rb_source_toolbar_add_search_entry (RBSourceToolbar *toolbar, const char *placeholder)
+{
+       add_search_entry (toolbar, FALSE);
+       rb_search_entry_set_placeholder (toolbar->priv->search_entry, placeholder);
 }
 
 /**
diff --git a/widgets/rb-source-toolbar.h b/widgets/rb-source-toolbar.h
index 0095a30..bc79f2e 100644
--- a/widgets/rb-source-toolbar.h
+++ b/widgets/rb-source-toolbar.h
@@ -60,10 +60,13 @@ struct _RBSourceToolbarClass
 GType          rb_source_toolbar_get_type              (void);
 
 RBSourceToolbar *rb_source_toolbar_new                         (RBDisplayPage *page,
-                                                        GtkUIManager *ui_manager);
+                                                        GtkAccelGroup *accel_group);
 
-void           rb_source_toolbar_add_search_entry      (RBSourceToolbar *toolbar,
-                                                        const char *popup_path,
+void           rb_source_toolbar_add_search_entry_menu (RBSourceToolbar *toolbar,
+                                                        GMenuModel *search_menu,
+                                                        GAction *search_action);
+
+void           rb_source_toolbar_add_search_entry      (RBSourceToolbar *toolbar,
                                                         const char *placeholder);
 
 void           rb_source_toolbar_clear_search_entry    (RBSourceToolbar *toolbar);


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