From ce7fa7ff226b67d37094d1167d3cd0cc883d3103 Mon Sep 17 00:00:00 2001 From: Jonathan Matthew Date: Tue, 26 Feb 2013 08:05:49 +1000 Subject: [PATCH] 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-profile-ui.xml | 5 - .../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 +- .../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 +- .../generic-player/rb-generic-player-plugin.c | 97 - .../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 +- .../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(-) create mode 100644 data/ui/app-menu.ui create mode 100644 data/ui/browser-popup.ui create mode 100644 data/ui/display-page-add-menu.ui create mode 100644 data/ui/edit-menu.ui create mode 100644 data/ui/import-errors-popup.ui create mode 100644 data/ui/library-toolbar.ui create mode 100644 data/ui/main-toolbar.ui create mode 100644 data/ui/missing-files-popup.ui create mode 100644 data/ui/playlist-menu.ui create mode 100644 data/ui/playlist-popup.ui create mode 100644 data/ui/playlist-toolbar.ui create mode 100644 data/ui/podcast-popups.ui create mode 100644 data/ui/podcast-toolbar.ui create mode 100644 data/ui/queue-popups.ui create mode 100644 data/ui/queue-toolbar.ui delete mode 100644 data/ui/rhythmbox-ui.xml delete mode 100644 lib/eggsmclient-private.h delete mode 100644 lib/eggsmclient-xsmp.c delete mode 100644 lib/eggsmclient.c delete mode 100644 lib/eggsmclient.h create mode 100644 plugins/audiocd/audiocd-toolbar.ui delete mode 100644 plugins/audiocd/audiocd-ui.xml delete mode 100644 plugins/audioscrobbler/audioscrobbler-profile-ui.xml delete mode 100644 plugins/audioscrobbler/audioscrobbler-radio-ui.xml create mode 100644 plugins/daap/daap-toolbar.ui delete mode 100644 plugins/daap/daap-ui.xml create mode 100644 plugins/fmradio/fmradio-popup.ui create mode 100644 plugins/fmradio/fmradio-toolbar.ui delete mode 100644 plugins/fmradio/fmradio-ui.xml create mode 100644 plugins/generic-player/generic-player-toolbar.ui delete mode 100644 plugins/generic-player/generic-player-ui.xml create mode 100644 plugins/ipod/ipod-toolbar.ui delete mode 100644 plugins/ipod/ipod-ui.xml create mode 100644 plugins/iradio/iradio-popup.ui create mode 100644 plugins/iradio/iradio-toolbar.ui delete mode 100644 plugins/iradio/iradio-ui.xml create mode 100644 plugins/magnatune/magnatune-popup.ui create mode 100644 plugins/magnatune/magnatune-toolbar.ui create mode 100644 plugins/mtpdevice/mtp-toolbar.ui delete mode 100644 plugins/mtpdevice/mtp-ui.xml delete mode 100644 plugins/visualizer/visualizer-ui.xml create mode 100644 shell/rb-application.c create mode 100644 shell/rb-application.h create mode 100644 sources/rb-display-page-menu.c create mode 100644 sources/rb-display-page-menu.h create mode 100644 widgets/rb-button-bar.c create mode 100644 widgets/rb-button-bar.h diff --git a/bindings/gi/Makefile.am b/bindings/gi/Makefile.am index 065907ac1..7194c6190 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 9e27d1de0..f739a19cb 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 9c1e4bfd2..649ffc6a7 100644 --- a/data/org.gnome.rhythmbox.gschema.xml +++ b/data/org.gnome.rhythmbox.gschema.xml @@ -16,6 +16,11 @@ Position of browser pane (if it exists) Position of browser pane. + + 'search-match' + Selected search type + The currently selected search type for the source. + @@ -70,10 +75,10 @@ Position of the right pane Position of the right pane - - false + + true Statusbar visibility - If true, the statusbar is hidden. + If true, the statusbar is visible. false @@ -174,6 +179,7 @@ ('Feed',true) 180 true + 'search-match' @@ -409,7 +415,7 @@ GStreamer element to use for visual effects The name of the GStreamer element to use for visual effects. - + 'medium' The frame rate and size to use for visual effects The frame rate and size to use for visual effects diff --git a/data/ui/Makefile.am b/data/ui/Makefile.am index 75bfa00fd..c4727de8f 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 000000000..cdbfecac8 --- /dev/null +++ b/data/ui/app-menu.ui @@ -0,0 +1,80 @@ + + + +
+ + _Add Music + app.library-import + +
+
+ + _View +
+ + P_arty Mode + win.party-mode + F11 + + + Side Pane + win.display-page-tree-visible + F9 + + + Play Queue in Side Pane + win.queue-as-sidebar + <Primary>k + + + Status Bar + win.statusbar-visible + + + Song Position Slider + win.show-song-position-slider + + + Album Art + win.show-album-art + +
+
+ view +
+
+ + _Tools + tools + +
+
+ + P_lugins + app.plugins + + + _Preferences + app.preferences + +
+
+ + _Help + app.help + + + _About + app.about + +
+
+ + _Quit + app.quit + <Primary>q + +
+
+ +
diff --git a/data/ui/browser-popup.ui b/data/ui/browser-popup.ui new file mode 100644 index 000000000..c3079eeb9 --- /dev/null +++ b/data/ui/browser-popup.ui @@ -0,0 +1,57 @@ + + + +
+ + Add to Queue + app.clipboard-add-to-queue + + + Add to Playlist + + +
+
+ + Copy + app.clipboard-copy + + + Cut + app.clipboard-cut + +
+
+
+ delete-menu +
+ + _Move to Trash + app.clipboard-trash + +
+
+ + Browse this Genre + app.browser-select-genre + + + Browse this Artist + app.browser-select-artist + + + Browse this Album + app.browser-select-album + +
+
+ browser-popup +
+
+ + Pr_operties + app.clipboard-properties + +
+
+
diff --git a/data/ui/display-page-add-menu.ui b/data/ui/display-page-add-menu.ui new file mode 100644 index 000000000..ff5c61313 --- /dev/null +++ b/data/ui/display-page-add-menu.ui @@ -0,0 +1,31 @@ + + + +
+ + _New Playlist + app.playlist-new + + + New _Automatic Playlist + app.playlist-new-auto + + + _Load from File + app.playlist-load + +
+
+ display-page-add-playlist +
+
+ + _Check for New Devices + app.check-devices + +
+
+ display-page-add +
+
+
diff --git a/data/ui/edit-menu.ui b/data/ui/edit-menu.ui new file mode 100644 index 000000000..575295ff2 --- /dev/null +++ b/data/ui/edit-menu.ui @@ -0,0 +1,57 @@ + + + +
+ + Cu_t + app.clipboard-cut + + + _Copy + app.clipboard-copy + + + _Paste + app.clipboard-paste + +
+
+ + Select _All + app.clipboard-select-all + + + D_eselect All + app.clipboard-select-none + +
+
+ + Add to Play Queue + app.clipboard-add-to-queue + + + Add to Playlist + + +
+
+ + Pr_operties + app.clipboard-properties + +
+
+
+ delete-menu +
+ + _Move to Trash + app.clipboard-move-to-trash + +
+
+ edit +
+
+
diff --git a/data/ui/import-errors-popup.ui b/data/ui/import-errors-popup.ui new file mode 100644 index 000000000..40875b3db --- /dev/null +++ b/data/ui/import-errors-popup.ui @@ -0,0 +1,15 @@ + + + +
+ + _Remove + app.clipboard-delete + + + _Move to Trash + app.clipboard-move-to-trash + +
+
+
diff --git a/data/ui/library-toolbar.ui b/data/ui/library-toolbar.ui new file mode 100644 index 000000000..5f4a4b15f --- /dev/null +++ b/data/ui/library-toolbar.ui @@ -0,0 +1,28 @@ + + + +
+ + Edit + edit-menu + + + Browse + show-browser + <Primary>b + + + View All + app.source-view-all + + + Import + app.library-import + +
+
+ library-toolbar +
+
+
+ diff --git a/data/ui/main-toolbar.ui b/data/ui/main-toolbar.ui new file mode 100644 index 000000000..ac2afbebe --- /dev/null +++ b/data/ui/main-toolbar.ui @@ -0,0 +1,114 @@ + + + + + True + False + icons + 6 + + + False + True + False + Start playing the previous song + False + app.play-previous + Previous + media-skip-backward + + + False + True + + + + + False + True + False + Pause playback + False + app.play + Play + media-playback-start + + + False + True + + + + + False + True + False + Start playing the next song + False + app.play-next + Next + media-skip-forward + + + False + True + + + + + False + True + False + False + + + False + True + + + + + False + True + False + Play first song again after all songs are played + False + app.play-repeat + Repeat + media-playlist-repeat + + + False + True + + + + + False + True + False + Play songs in a random order + False + app.play-shuffle + Shuffle + media-playlist-shuffle + + + False + True + + + + + False + True + False + False + + + False + True + + + + diff --git a/data/ui/missing-files-popup.ui b/data/ui/missing-files-popup.ui new file mode 100644 index 000000000..d1607b210 --- /dev/null +++ b/data/ui/missing-files-popup.ui @@ -0,0 +1,15 @@ + + + +
+ + _Remove + app.clipboard-delete + + + _Properties + app.clipboard-properties + +
+
+
diff --git a/data/ui/playlist-menu.ui b/data/ui/playlist-menu.ui new file mode 100644 index 000000000..b6f4eeb98 --- /dev/null +++ b/data/ui/playlist-menu.ui @@ -0,0 +1,34 @@ + + + +
+ + _Edit... + app.playlist-edit + + + _Rename + app.playlist-rename + +
+
+ + _Queue All Tracks + app.playlist-queue + + + _Shuffle Playlist + app.playlist-shuffle + +
+
+ + _Save to File... + app.playlist-save + +
+
+ playlist-menu +
+
+
diff --git a/data/ui/playlist-popup.ui b/data/ui/playlist-popup.ui new file mode 100644 index 000000000..ecbcc733f --- /dev/null +++ b/data/ui/playlist-popup.ui @@ -0,0 +1,57 @@ + + + +
+ + Add to Queue + app.clipboard-add-to-queue + + + Add to Playlist + + +
+
+ + Copy + app.clipboard-copy + + + Cut + app.clipboard-cut + +
+
+
+ delete-menu +
+ + _Move to Trash + app.clipboard-trash + +
+
+ + Browse this Genre + app.browser-select-genre + + + Browse this Artist + app.browser-select-artist + + + Browse this Album + app.browser-select-album + +
+
+ playlist-popup +
+
+ + Pr_operties + app.clipboard-properties + +
+
+
diff --git a/data/ui/playlist-toolbar.ui b/data/ui/playlist-toolbar.ui new file mode 100644 index 000000000..ca42efde1 --- /dev/null +++ b/data/ui/playlist-toolbar.ui @@ -0,0 +1,27 @@ + + + +
+ + Edit + edit-menu + + + Browse + show-browser + <Primary>b + + + View All + app.source-view-all + + + Playlist + playlist-menu + +
+
+ playlist-toolbar +
+
+
diff --git a/data/ui/podcast-add-dialog.ui b/data/ui/podcast-add-dialog.ui index 6462b1e52..c1574de84 100644 --- a/data/ui/podcast-add-dialog.ui +++ b/data/ui/podcast-add-dialog.ui @@ -1,9 +1,6 @@ - - Subscribe - True False diff --git a/data/ui/podcast-popups.ui b/data/ui/podcast-popups.ui new file mode 100644 index 000000000..9c600612d --- /dev/null +++ b/data/ui/podcast-popups.ui @@ -0,0 +1,65 @@ + + + +
+ + New Podcast Feed... + app.podcast-add + + + Update All Feeds + app.podcast-feed-update-all + +
+
+ + Update Podcast Feed + app.podcast-feed-update + + + Delete Podcast Feed + app.clipboard-delete + +
+
+ podcast-feed-popup +
+
+ + Properties + app.clipboard-properties + +
+
+ +
+ + Download Episode + app.podcast-download + + + Cancel Download + app.podcast-download-cancel + + + Delete + app.clipboard-delete + +
+
+ + Add to Queue + app.clipboard-add-to-queue + +
+
+ podcast-episode-popup +
+
+ + Properties + app.clipboard-properties + +
+
+
diff --git a/data/ui/podcast-toolbar.ui b/data/ui/podcast-toolbar.ui new file mode 100644 index 000000000..76cd43d79 --- /dev/null +++ b/data/ui/podcast-toolbar.ui @@ -0,0 +1,31 @@ + + + +
+ + Edit + edit-menu + + + Browse + show-browser + <Primary>b + + + View All + app.source-view-all + + + Add + app.podcast-add + + + Update + app.podcast-feed-update + +
+
+ podcast-toolbar +
+
+
diff --git a/data/ui/queue-popups.ui b/data/ui/queue-popups.ui new file mode 100644 index 000000000..6090f197d --- /dev/null +++ b/data/ui/queue-popups.ui @@ -0,0 +1,57 @@ + + + +
+ + Remove from Play Queue + app.clipboard-delete + + + Shuffle Play Queue + app.queue-shuffle + + + _Save to File... + app.save-playlist + +
+
+ queue-popup +
+
+ + Pr_operties + app.clipboard-properties + +
+
+ +
+ + Remove from Play Queue + app.clipboard-delete + + + Clear Play Queue + app.queue-clear + + + Shuffle Play Queue + app.queue-shuffle + + + _Save to File... + app.save-playlist + +
+
+ queue-popup +
+
+ + Pr_operties + app.queue-properties + +
+
+
diff --git a/data/ui/queue-toolbar.ui b/data/ui/queue-toolbar.ui new file mode 100644 index 000000000..9aadefe10 --- /dev/null +++ b/data/ui/queue-toolbar.ui @@ -0,0 +1,26 @@ + + + +
+ + Edit + edit-menu + + + Shuffle + app.queue-shuffle + + + Clear + app.queue-clear + + + Playlist + playlist-menu + +
+
+ queue-toolbar +
+
+
diff --git a/data/ui/rhythmbox-ui.xml b/data/ui/rhythmbox-ui.xml deleted file mode 100644 index b2d73ce03..000000000 --- a/data/ui/rhythmbox-ui.xml +++ /dev/null @@ -1,326 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/reference/Makefile.am b/doc/reference/Makefile.am index 665d3c25b..d9f3aa489 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 9da74377f..e7767710a 100644 --- a/doc/reference/rhythmbox-docs.sgml +++ b/doc/reference/rhythmbox-docs.sgml @@ -45,6 +45,7 @@ Shell + diff --git a/doc/reference/rhythmbox.types b/doc/reference/rhythmbox.types index 6e9620488..7b76aeb00 100644 --- a/doc/reference/rhythmbox.types +++ b/doc/reference/rhythmbox.types @@ -2,6 +2,7 @@ #include #include +#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 88e524548..a8fd65a27 100644 --- a/help/C/index.docbook +++ b/help/C/index.docbook @@ -1563,17 +1563,6 @@ Deselect All - - - - Ctrl - E - - - - Extract CD (launch Sound-Juicer) - - @@ -1607,28 +1596,6 @@ Create a New playlist - - - - Ctrl - I - - - - Add a new Internet Radio Station - - - - - - Ctrl - P - - - - Add a New Podcast Feed - - diff --git a/lib/Makefile.am b/lib/Makefile.am index fd20835ac..4ed8e5fa8 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/eggsmclient-private.h b/lib/eggsmclient-private.h deleted file mode 100644 index e39121891..000000000 --- a/lib/eggsmclient-private.h +++ /dev/null @@ -1,53 +0,0 @@ -/* eggsmclient-private.h - * Copyright (C) 2007 Novell, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#ifndef __EGG_SM_CLIENT_PRIVATE_H__ -#define __EGG_SM_CLIENT_PRIVATE_H__ - -#include -#include "eggsmclient.h" - -G_BEGIN_DECLS - -GKeyFile *egg_sm_client_save_state (EggSMClient *client); -void egg_sm_client_quit_requested (EggSMClient *client); -void egg_sm_client_quit_cancelled (EggSMClient *client); -void egg_sm_client_quit (EggSMClient *client); - -#if defined (GDK_WINDOWING_X11) -# ifdef EGG_SM_CLIENT_BACKEND_XSMP -GType egg_sm_client_xsmp_get_type (void); -EggSMClient *egg_sm_client_xsmp_new (void); -# endif -# ifdef EGG_SM_CLIENT_BACKEND_DBUS -GType egg_sm_client_dbus_get_type (void); -EggSMClient *egg_sm_client_dbus_new (void); -# endif -#elif defined (GDK_WINDOWING_WIN32) -GType egg_sm_client_win32_get_type (void); -EggSMClient *egg_sm_client_win32_new (void); -#elif defined (GDK_WINDOWING_QUARTZ) -GType egg_sm_client_osx_get_type (void); -EggSMClient *egg_sm_client_osx_new (void); -#endif - -G_END_DECLS - - -#endif /* __EGG_SM_CLIENT_PRIVATE_H__ */ diff --git a/lib/eggsmclient-xsmp.c b/lib/eggsmclient-xsmp.c deleted file mode 100644 index 645da382b..000000000 --- a/lib/eggsmclient-xsmp.c +++ /dev/null @@ -1,1371 +0,0 @@ -/* - * Copyright (C) 2007 Novell, Inc. - * - * Inspired by various other pieces of code including GsmClient (C) - * 2001 Havoc Pennington, GnomeClient (C) 1998 Carsten Schaar, and twm - * session code (C) 1998 The Open Group. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#include "config.h" - -#include "eggsmclient.h" -#include "eggsmclient-private.h" - -#include "eggdesktopfile.h" - -#include -#include -#include -#include -#include -#include - -#include -#include - -#define EGG_TYPE_SM_CLIENT_XSMP (egg_sm_client_xsmp_get_type ()) -#define EGG_SM_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMP)) -#define EGG_SM_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMPClass)) -#define EGG_IS_SM_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_XSMP)) -#define EGG_IS_SM_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_XSMP)) -#define EGG_SM_CLIENT_XSMP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMPClass)) - -typedef struct _EggSMClientXSMP EggSMClientXSMP; -typedef struct _EggSMClientXSMPClass EggSMClientXSMPClass; - -/* These mostly correspond to the similarly-named states in section - * 9.1 of the XSMP spec. Some of the states there aren't represented - * here, because we don't need them. SHUTDOWN_CANCELLED is slightly - * different from the spec; we use it when the client is IDLE after a - * ShutdownCancelled message, but the application is still interacting - * and doesn't know the shutdown has been cancelled yet. - */ -typedef enum -{ - XSMP_STATE_IDLE, - XSMP_STATE_SAVE_YOURSELF, - XSMP_STATE_INTERACT_REQUEST, - XSMP_STATE_INTERACT, - XSMP_STATE_SAVE_YOURSELF_DONE, - XSMP_STATE_SHUTDOWN_CANCELLED, - XSMP_STATE_CONNECTION_CLOSED -} EggSMClientXSMPState; - -static const char *state_names[] = { - "idle", - "save-yourself", - "interact-request", - "interact", - "save-yourself-done", - "shutdown-cancelled", - "connection-closed" -}; - -#define EGG_SM_CLIENT_XSMP_STATE(xsmp) (state_names[(xsmp)->state]) - -struct _EggSMClientXSMP -{ - EggSMClient parent; - - SmcConn connection; - char *client_id; - - EggSMClientXSMPState state; - char **restart_command; - gboolean set_restart_command; - int restart_style; - - guint idle; - - /* Current SaveYourself state */ - guint expecting_initial_save_yourself : 1; - guint need_save_state : 1; - guint need_quit_requested : 1; - guint interact_errors : 1; - guint shutting_down : 1; - - /* Todo list */ - guint waiting_to_set_initial_properties : 1; - guint waiting_to_emit_quit : 1; - guint waiting_to_emit_quit_cancelled : 1; - guint waiting_to_save_myself : 1; - -}; - -struct _EggSMClientXSMPClass -{ - EggSMClientClass parent_class; - -}; - -static void sm_client_xsmp_startup (EggSMClient *client, - const char *client_id); -static void sm_client_xsmp_set_restart_command (EggSMClient *client, - int argc, - const char **argv); -static void sm_client_xsmp_will_quit (EggSMClient *client, - gboolean will_quit); -static gboolean sm_client_xsmp_end_session (EggSMClient *client, - EggSMClientEndStyle style, - gboolean request_confirmation); - -static void xsmp_save_yourself (SmcConn smc_conn, - SmPointer client_data, - int save_style, - Bool shutdown, - int interact_style, - Bool fast); -static void xsmp_die (SmcConn smc_conn, - SmPointer client_data); -static void xsmp_save_complete (SmcConn smc_conn, - SmPointer client_data); -static void xsmp_shutdown_cancelled (SmcConn smc_conn, - SmPointer client_data); -static void xsmp_interact (SmcConn smc_conn, - SmPointer client_data); - -static SmProp *array_prop (const char *name, - ...); -static SmProp *ptrarray_prop (const char *name, - GPtrArray *values); -static SmProp *string_prop (const char *name, - const char *value); -static SmProp *card8_prop (const char *name, - unsigned char value); - -static void set_properties (EggSMClientXSMP *xsmp, ...); -static void delete_properties (EggSMClientXSMP *xsmp, ...); - -static GPtrArray *generate_command (char **restart_command, - const char *client_id, - const char *state_file); - -static void save_state (EggSMClientXSMP *xsmp); -static void do_save_yourself (EggSMClientXSMP *xsmp); -static void update_pending_events (EggSMClientXSMP *xsmp); - -static void ice_init (void); -static gboolean process_ice_messages (IceConn ice_conn); -static void smc_error_handler (SmcConn smc_conn, - Bool swap, - int offending_minor_opcode, - unsigned long offending_sequence, - int error_class, - int severity, - SmPointer values); - -G_DEFINE_TYPE (EggSMClientXSMP, egg_sm_client_xsmp, EGG_TYPE_SM_CLIENT) - -static void -egg_sm_client_xsmp_init (EggSMClientXSMP *xsmp) -{ - xsmp->state = XSMP_STATE_CONNECTION_CLOSED; - xsmp->connection = NULL; - xsmp->restart_style = SmRestartIfRunning; -} - -static void -egg_sm_client_xsmp_class_init (EggSMClientXSMPClass *klass) -{ - EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass); - - sm_client_class->startup = sm_client_xsmp_startup; - sm_client_class->set_restart_command = sm_client_xsmp_set_restart_command; - sm_client_class->will_quit = sm_client_xsmp_will_quit; - sm_client_class->end_session = sm_client_xsmp_end_session; -} - -EggSMClient * -egg_sm_client_xsmp_new (void) -{ - if (!g_getenv ("SESSION_MANAGER")) - return NULL; - - return g_object_new (EGG_TYPE_SM_CLIENT_XSMP, NULL); -} - -static gboolean -sm_client_xsmp_set_initial_properties (gpointer user_data) -{ - EggSMClientXSMP *xsmp = user_data; - EggDesktopFile *desktop_file; - GPtrArray *clone, *restart; - char pid_str[64]; - - if (xsmp->idle) - { - g_source_remove (xsmp->idle); - xsmp->idle = 0; - } - xsmp->waiting_to_set_initial_properties = FALSE; - - if (egg_sm_client_get_mode () == EGG_SM_CLIENT_MODE_NO_RESTART) - xsmp->restart_style = SmRestartNever; - - /* Parse info out of desktop file */ - desktop_file = egg_get_desktop_file (); - if (desktop_file) - { - GError *err = NULL; - char *cmdline, **argv; - int argc; - - if (xsmp->restart_style == SmRestartIfRunning) - { - if (egg_desktop_file_get_boolean (desktop_file, - "X-GNOME-AutoRestart", NULL)) - xsmp->restart_style = SmRestartImmediately; - } - - if (!xsmp->set_restart_command) - { - cmdline = egg_desktop_file_parse_exec (desktop_file, NULL, &err); - if (cmdline && g_shell_parse_argv (cmdline, &argc, &argv, &err)) - { - egg_sm_client_set_restart_command (EGG_SM_CLIENT (xsmp), - argc, (const char **)argv); - g_strfreev (argv); - } - else - { - g_warning ("Could not parse Exec line in desktop file: %s", - err->message); - g_error_free (err); - } - g_free (cmdline); - } - } - - if (!xsmp->set_restart_command) - xsmp->restart_command = g_strsplit (g_get_prgname (), " ", -1); - - clone = generate_command (xsmp->restart_command, NULL, NULL); - restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL); - - g_debug ("Setting initial properties"); - - /* Program, CloneCommand, RestartCommand, and UserID are required. - * ProcessID isn't required, but the SM may be able to do something - * useful with it. - */ - g_snprintf (pid_str, sizeof (pid_str), "%lu", (gulong) getpid ()); - set_properties (xsmp, - string_prop (SmProgram, g_get_prgname ()), - ptrarray_prop (SmCloneCommand, clone), - ptrarray_prop (SmRestartCommand, restart), - string_prop (SmUserID, g_get_user_name ()), - string_prop (SmProcessID, pid_str), - card8_prop (SmRestartStyleHint, xsmp->restart_style), - NULL); - g_ptr_array_free (clone, TRUE); - g_ptr_array_free (restart, TRUE); - - if (desktop_file) - { - set_properties (xsmp, - string_prop ("_GSM_DesktopFile", egg_desktop_file_get_source (desktop_file)), - NULL); - } - - update_pending_events (xsmp); - return FALSE; -} - -/* This gets called from two different places: xsmp_die() (when the - * server asks us to disconnect) and process_ice_messages() (when the - * server disconnects unexpectedly). - */ -static void -sm_client_xsmp_disconnect (EggSMClientXSMP *xsmp) -{ - SmcConn connection; - - if (!xsmp->connection) - return; - - g_debug ("Disconnecting"); - - connection = xsmp->connection; - xsmp->connection = NULL; - SmcCloseConnection (connection, 0, NULL); - xsmp->state = XSMP_STATE_CONNECTION_CLOSED; - - xsmp->waiting_to_save_myself = FALSE; - update_pending_events (xsmp); -} - -static void -sm_client_xsmp_startup (EggSMClient *client, - const char *client_id) -{ - EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client; - SmcCallbacks callbacks; - char *ret_client_id; - char error_string_ret[256]; - - xsmp->client_id = g_strdup (client_id); - - ice_init (); - SmcSetErrorHandler (smc_error_handler); - - callbacks.save_yourself.callback = xsmp_save_yourself; - callbacks.die.callback = xsmp_die; - callbacks.save_complete.callback = xsmp_save_complete; - callbacks.shutdown_cancelled.callback = xsmp_shutdown_cancelled; - - callbacks.save_yourself.client_data = xsmp; - callbacks.die.client_data = xsmp; - callbacks.save_complete.client_data = xsmp; - callbacks.shutdown_cancelled.client_data = xsmp; - - client_id = NULL; - error_string_ret[0] = '\0'; - xsmp->connection = - SmcOpenConnection (NULL, xsmp, SmProtoMajor, SmProtoMinor, - SmcSaveYourselfProcMask | SmcDieProcMask | - SmcSaveCompleteProcMask | - SmcShutdownCancelledProcMask, - &callbacks, - xsmp->client_id, &ret_client_id, - sizeof (error_string_ret), error_string_ret); - - if (!xsmp->connection) - { - g_warning ("Failed to connect to the session manager: %s\n", - error_string_ret[0] ? - error_string_ret : "no error message given"); - xsmp->state = XSMP_STATE_CONNECTION_CLOSED; - return; - } - - /* We expect a pointless initial SaveYourself if either (a) we - * didn't have an initial client ID, or (b) we DID have an initial - * client ID, but the server rejected it and gave us a new one. - */ - if (!xsmp->client_id || - (ret_client_id && strcmp (xsmp->client_id, ret_client_id) != 0)) - xsmp->expecting_initial_save_yourself = TRUE; - - if (ret_client_id) - { - g_free (xsmp->client_id); - xsmp->client_id = g_strdup (ret_client_id); - free (ret_client_id); - - gdk_threads_enter (); - gdk_x11_set_sm_client_id (xsmp->client_id); - gdk_threads_leave (); - - g_debug ("Got client ID \"%s\"", xsmp->client_id); - } - - xsmp->state = XSMP_STATE_IDLE; - - /* Do not set the initial properties until we reach the main loop, - * so that the application has a chance to call - * egg_set_desktop_file(). (This may also help the session manager - * have a better idea of when the application is fully up and - * running.) - */ - xsmp->waiting_to_set_initial_properties = TRUE; - xsmp->idle = g_idle_add (sm_client_xsmp_set_initial_properties, client); -} - -static void -sm_client_xsmp_set_restart_command (EggSMClient *client, - int argc, - const char **argv) -{ - EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client; - int i; - - g_strfreev (xsmp->restart_command); - - xsmp->restart_command = g_new (char *, argc + 1); - for (i = 0; i < argc; i++) - xsmp->restart_command[i] = g_strdup (argv[i]); - xsmp->restart_command[i] = NULL; - - xsmp->set_restart_command = TRUE; -} - -static void -sm_client_xsmp_will_quit (EggSMClient *client, - gboolean will_quit) -{ - EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client; - - if (xsmp->state == XSMP_STATE_CONNECTION_CLOSED) - { - /* The session manager has already exited! Schedule a quit - * signal. - */ - xsmp->waiting_to_emit_quit = TRUE; - update_pending_events (xsmp); - return; - } - else if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED) - { - /* We received a ShutdownCancelled message while the application - * was interacting; Schedule a quit_cancelled signal. - */ - xsmp->waiting_to_emit_quit_cancelled = TRUE; - update_pending_events (xsmp); - return; - } - - g_return_if_fail (xsmp->state == XSMP_STATE_INTERACT); - - g_debug ("Sending InteractDone(%s)", will_quit ? "False" : "True"); - SmcInteractDone (xsmp->connection, !will_quit); - - if (will_quit && xsmp->need_save_state) - save_state (xsmp); - - g_debug ("Sending SaveYourselfDone(%s)", will_quit ? "True" : "False"); - SmcSaveYourselfDone (xsmp->connection, will_quit); - xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE; -} - -static gboolean -sm_client_xsmp_end_session (EggSMClient *client, - EggSMClientEndStyle style, - gboolean request_confirmation) -{ - EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client; - int save_type; - - /* To end the session via XSMP, we have to send a - * SaveYourselfRequest. We aren't allowed to do that if anything - * else is going on, but we don't want to expose this fact to the - * application. So we do our best to patch things up here... - * - * In the worst case, this method might block for some length of - * time in process_ice_messages, but the only time that code path is - * honestly likely to get hit is if the application tries to end the - * session as the very first thing it does, in which case it - * probably won't actually block anyway. It's not worth gunking up - * the API to try to deal nicely with the other 0.01% of cases where - * this happens. - */ - - while (xsmp->state != XSMP_STATE_IDLE || - xsmp->expecting_initial_save_yourself) - { - /* If we're already shutting down, we don't need to do anything. */ - if (xsmp->shutting_down) - return TRUE; - - switch (xsmp->state) - { - case XSMP_STATE_CONNECTION_CLOSED: - return FALSE; - - case XSMP_STATE_SAVE_YOURSELF: - /* Trying to log out from the save_state callback? Whatever. - * Abort the save_state. - */ - SmcSaveYourselfDone (xsmp->connection, FALSE); - xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE; - break; - - case XSMP_STATE_INTERACT_REQUEST: - case XSMP_STATE_INTERACT: - case XSMP_STATE_SHUTDOWN_CANCELLED: - /* Already in a shutdown-related state, just ignore - * the new shutdown request... - */ - return TRUE; - - case XSMP_STATE_IDLE: - if (xsmp->waiting_to_set_initial_properties) - sm_client_xsmp_set_initial_properties (xsmp); - - if (!xsmp->expecting_initial_save_yourself) - break; - /* else fall through */ - - case XSMP_STATE_SAVE_YOURSELF_DONE: - /* We need to wait for some response from the server.*/ - process_ice_messages (SmcGetIceConnection (xsmp->connection)); - break; - - default: - /* Hm... shouldn't happen */ - return FALSE; - } - } - - /* xfce4-session will do the wrong thing if we pass SmSaveGlobal and - * the user chooses to save the session. But gnome-session will do - * the wrong thing if we pass SmSaveBoth and the user chooses NOT to - * save the session... Sigh. - */ - if (!strcmp (SmcVendor (xsmp->connection), "xfce4-session")) - save_type = SmSaveBoth; - else - save_type = SmSaveGlobal; - - g_debug ("Sending SaveYourselfRequest(SmSaveGlobal, Shutdown, SmInteractStyleAny, %sFast)", request_confirmation ? "!" : ""); - SmcRequestSaveYourself (xsmp->connection, - save_type, - True, /* shutdown */ - SmInteractStyleAny, - !request_confirmation, /* fast */ - True /* global */); - return TRUE; -} - -static gboolean -idle_do_pending_events (gpointer data) -{ - EggSMClientXSMP *xsmp = data; - EggSMClient *client = data; - - gdk_threads_enter (); - - xsmp->idle = 0; - - if (xsmp->waiting_to_emit_quit) - { - xsmp->waiting_to_emit_quit = FALSE; - egg_sm_client_quit (client); - goto out; - } - - if (xsmp->waiting_to_emit_quit_cancelled) - { - xsmp->waiting_to_emit_quit_cancelled = FALSE; - egg_sm_client_quit_cancelled (client); - xsmp->state = XSMP_STATE_IDLE; - } - - if (xsmp->waiting_to_save_myself) - { - xsmp->waiting_to_save_myself = FALSE; - do_save_yourself (xsmp); - } - - out: - gdk_threads_leave (); - return FALSE; -} - -static void -update_pending_events (EggSMClientXSMP *xsmp) -{ - gboolean want_idle = - xsmp->waiting_to_emit_quit || - xsmp->waiting_to_emit_quit_cancelled || - xsmp->waiting_to_save_myself; - - if (want_idle) - { - if (xsmp->idle == 0) - xsmp->idle = g_idle_add (idle_do_pending_events, xsmp); - } - else - { - if (xsmp->idle != 0) - g_source_remove (xsmp->idle); - xsmp->idle = 0; - } -} - -static void -fix_broken_state (EggSMClientXSMP *xsmp, const char *message, - gboolean send_interact_done, - gboolean send_save_yourself_done) -{ - g_warning ("Received XSMP %s message in state %s: client or server error", - message, EGG_SM_CLIENT_XSMP_STATE (xsmp)); - - /* Forget any pending SaveYourself plans we had */ - xsmp->waiting_to_save_myself = FALSE; - update_pending_events (xsmp); - - if (send_interact_done) - SmcInteractDone (xsmp->connection, False); - if (send_save_yourself_done) - SmcSaveYourselfDone (xsmp->connection, True); - - xsmp->state = send_save_yourself_done ? XSMP_STATE_SAVE_YOURSELF_DONE : XSMP_STATE_IDLE; -} - -/* SM callbacks */ - -static void -xsmp_save_yourself (SmcConn smc_conn, - SmPointer client_data, - int save_type, - Bool shutdown, - int interact_style, - Bool fast) -{ - EggSMClientXSMP *xsmp = client_data; - gboolean wants_quit_requested; - - g_debug ("Received SaveYourself(%s, %s, %s, %s) in state %s", - save_type == SmSaveLocal ? "SmSaveLocal" : - save_type == SmSaveGlobal ? "SmSaveGlobal" : "SmSaveBoth", - shutdown ? "Shutdown" : "!Shutdown", - interact_style == SmInteractStyleAny ? "SmInteractStyleAny" : - interact_style == SmInteractStyleErrors ? "SmInteractStyleErrors" : - "SmInteractStyleNone", fast ? "Fast" : "!Fast", - EGG_SM_CLIENT_XSMP_STATE (xsmp)); - - if (xsmp->state != XSMP_STATE_IDLE && - xsmp->state != XSMP_STATE_SHUTDOWN_CANCELLED) - { - fix_broken_state (xsmp, "SaveYourself", FALSE, TRUE); - return; - } - - if (xsmp->waiting_to_set_initial_properties) - sm_client_xsmp_set_initial_properties (xsmp); - - /* If this is the initial SaveYourself, ignore it; we've already set - * properties and there's no reason to actually save state too. - */ - if (xsmp->expecting_initial_save_yourself) - { - xsmp->expecting_initial_save_yourself = FALSE; - - if (save_type == SmSaveLocal && - interact_style == SmInteractStyleNone && - !shutdown && !fast) - { - g_debug ("Sending SaveYourselfDone(True) for initial SaveYourself"); - SmcSaveYourselfDone (xsmp->connection, True); - /* As explained in the comment at the end of - * do_save_yourself(), SAVE_YOURSELF_DONE is the correct - * state here, not IDLE. - */ - xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE; - return; - } - else - g_warning ("First SaveYourself was not the expected one!"); - } - - /* Even ignoring the "fast" flag completely, there are still 18 - * different combinations of save_type, shutdown and interact_style. - * We interpret them as follows: - * - * Type Shutdown Interact Interpretation - * G F A/E/N do nothing (1) - * G T N do nothing (1)* - * G T A/E quit_requested (2) - * L/B F A/E/N save_state (3) - * L/B T N save_state (3)* - * L/B T A/E quit_requested, then save_state (4) - * - * 1. Do nothing, because the SM asked us to do something - * uninteresting (save open files, but then don't quit - * afterward) or rude (save open files without asking the user - * for confirmation). - * - * 2. Request interaction and then emit ::quit_requested. This - * perhaps isn't quite correct for the SmInteractStyleErrors - * case, but we don't care. - * - * 3. Emit ::save_state. The SmSaveBoth SaveYourselfs in these - * rows essentially get demoted to SmSaveLocal, because their - * Global halves correspond to "do nothing". - * - * 4. Request interaction, emit ::quit_requested, and then emit - * ::save_state after interacting. This is the SmSaveBoth - * equivalent of #2, but we also promote SmSaveLocal shutdown - * SaveYourselfs to SmSaveBoth here, because we want to give - * the user a chance to save open files before quitting. - * - * (* It would be nice if we could do something useful when the - * session manager sends a SaveYourself with shutdown True and - * SmInteractStyleNone. But we can't, so we just pretend it didn't - * even tell us it was shutting down. The docs for ::quit mention - * that it might not always be preceded by ::quit_requested.) - */ - - /* As an optimization, we don't actually request interaction and - * emit ::quit_requested if the application isn't listening to the - * signal. - */ - wants_quit_requested = g_signal_has_handler_pending (xsmp, g_signal_lookup ("quit_requested", EGG_TYPE_SM_CLIENT), 0, FALSE); - - xsmp->need_save_state = (save_type != SmSaveGlobal); - xsmp->need_quit_requested = (shutdown && wants_quit_requested && - interact_style != SmInteractStyleNone); - xsmp->interact_errors = (interact_style == SmInteractStyleErrors); - - xsmp->shutting_down = shutdown; - - do_save_yourself (xsmp); -} - -static void -do_save_yourself (EggSMClientXSMP *xsmp) -{ - if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED) - { - /* The SM cancelled a previous SaveYourself, but we haven't yet - * had a chance to tell the application, so we can't start - * processing this SaveYourself yet. - */ - xsmp->waiting_to_save_myself = TRUE; - update_pending_events (xsmp); - return; - } - - if (xsmp->need_quit_requested) - { - xsmp->state = XSMP_STATE_INTERACT_REQUEST; - - g_debug ("Sending InteractRequest(%s)", - xsmp->interact_errors ? "Error" : "Normal"); - SmcInteractRequest (xsmp->connection, - xsmp->interact_errors ? SmDialogError : SmDialogNormal, - xsmp_interact, - xsmp); - return; - } - - if (xsmp->need_save_state) - { - save_state (xsmp); - - /* Though unlikely, the client could have been disconnected - * while the application was saving its state. - */ - if (!xsmp->connection) - return; - } - - g_debug ("Sending SaveYourselfDone(True)"); - SmcSaveYourselfDone (xsmp->connection, True); - - /* The client state diagram in the XSMP spec says that after a - * non-shutdown SaveYourself, we go directly back to "idle". But - * everything else in both the XSMP spec and the libSM docs - * disagrees. - */ - xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE; -} - -static void -save_state (EggSMClientXSMP *xsmp) -{ - GKeyFile *state_file; - char *state_file_path, *data; - EggDesktopFile *desktop_file; - GPtrArray *restart; - int offset, fd; - - /* We set xsmp->state before emitting save_state, but our caller is - * responsible for setting it back afterward. - */ - xsmp->state = XSMP_STATE_SAVE_YOURSELF; - - state_file = egg_sm_client_save_state ((EggSMClient *)xsmp); - if (!state_file) - { - restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL); - set_properties (xsmp, - ptrarray_prop (SmRestartCommand, restart), - NULL); - g_ptr_array_free (restart, TRUE); - delete_properties (xsmp, SmDiscardCommand, NULL); - return; - } - - desktop_file = egg_get_desktop_file (); - if (desktop_file) - { - GKeyFile *merged_file; - char *desktop_file_path; - - merged_file = g_key_file_new (); - desktop_file_path = - g_filename_from_uri (egg_desktop_file_get_source (desktop_file), - NULL, NULL); - if (desktop_file_path && - g_key_file_load_from_file (merged_file, desktop_file_path, - G_KEY_FILE_KEEP_COMMENTS | - G_KEY_FILE_KEEP_TRANSLATIONS, NULL)) - { - guint g, k, i; - char **groups, **keys, *value, *exec; - - groups = g_key_file_get_groups (state_file, NULL); - for (g = 0; groups[g]; g++) - { - keys = g_key_file_get_keys (state_file, groups[g], NULL, NULL); - for (k = 0; keys[k]; k++) - { - value = g_key_file_get_value (state_file, groups[g], - keys[k], NULL); - if (value) - { - g_key_file_set_value (merged_file, groups[g], - keys[k], value); - g_free (value); - } - } - g_strfreev (keys); - } - g_strfreev (groups); - - g_key_file_free (state_file); - state_file = merged_file; - - /* Update Exec key using "--sm-client-state-file %k" */ - restart = generate_command (xsmp->restart_command, - NULL, "%k"); - for (i = 0; i < restart->len; i++) - restart->pdata[i] = g_shell_quote (restart->pdata[i]); - g_ptr_array_add (restart, NULL); - exec = g_strjoinv (" ", (char **)restart->pdata); - g_strfreev ((char **)restart->pdata); - g_ptr_array_free (restart, FALSE); - - g_key_file_set_string (state_file, EGG_DESKTOP_FILE_GROUP, - EGG_DESKTOP_FILE_KEY_EXEC, - exec); - g_free (exec); - } - else - desktop_file = NULL; - - g_free (desktop_file_path); - } - - /* Now write state_file to disk. (We can't use mktemp(), because - * that requires the filename to end with "XXXXXX", and we want - * it to end with ".desktop".) - */ - - data = g_key_file_to_data (state_file, NULL, NULL); - g_key_file_free (state_file); - - offset = 0; - while (1) - { - state_file_path = g_strdup_printf ("%s%csession-state%c%s-%ld.%s", - g_get_user_config_dir (), - G_DIR_SEPARATOR, G_DIR_SEPARATOR, - g_get_prgname (), - (long)time (NULL) + offset, - desktop_file ? "desktop" : "state"); - - fd = open (state_file_path, O_WRONLY | O_CREAT | O_EXCL, 0644); - if (fd == -1) - { - if (errno == EEXIST) - { - offset++; - g_free (state_file_path); - continue; - } - else if (errno == ENOTDIR || errno == ENOENT) - { - char *sep = strrchr (state_file_path, G_DIR_SEPARATOR); - - *sep = '\0'; - if (g_mkdir_with_parents (state_file_path, 0755) != 0) - { - g_warning ("Could not create directory '%s'", - state_file_path); - g_free (state_file_path); - state_file_path = NULL; - break; - } - - continue; - } - - g_warning ("Could not create file '%s': %s", - state_file_path, g_strerror (errno)); - g_free (state_file_path); - state_file_path = NULL; - break; - } - - close (fd); - g_file_set_contents (state_file_path, data, -1, NULL); - break; - } - g_free (data); - - restart = generate_command (xsmp->restart_command, xsmp->client_id, - state_file_path); - set_properties (xsmp, - ptrarray_prop (SmRestartCommand, restart), - NULL); - g_ptr_array_free (restart, TRUE); - - if (state_file_path) - { - set_properties (xsmp, - array_prop (SmDiscardCommand, - "/bin/rm", "-rf", state_file_path, - NULL), - NULL); - g_free (state_file_path); - } -} - -static void -xsmp_interact (SmcConn smc_conn, - SmPointer client_data) -{ - EggSMClientXSMP *xsmp = client_data; - EggSMClient *client = client_data; - - g_debug ("Received Interact message in state %s", - EGG_SM_CLIENT_XSMP_STATE (xsmp)); - - if (xsmp->state != XSMP_STATE_INTERACT_REQUEST) - { - fix_broken_state (xsmp, "Interact", TRUE, TRUE); - return; - } - - xsmp->state = XSMP_STATE_INTERACT; - egg_sm_client_quit_requested (client); -} - -static void -xsmp_die (SmcConn smc_conn, - SmPointer client_data) -{ - EggSMClientXSMP *xsmp = client_data; - EggSMClient *client = client_data; - - g_debug ("Received Die message in state %s", - EGG_SM_CLIENT_XSMP_STATE (xsmp)); - - sm_client_xsmp_disconnect (xsmp); - egg_sm_client_quit (client); -} - -static void -xsmp_save_complete (SmcConn smc_conn, - SmPointer client_data) -{ - EggSMClientXSMP *xsmp = client_data; - - g_debug ("Received SaveComplete message in state %s", - EGG_SM_CLIENT_XSMP_STATE (xsmp)); - - if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE) - xsmp->state = XSMP_STATE_IDLE; - else - fix_broken_state (xsmp, "SaveComplete", FALSE, FALSE); -} - -static void -xsmp_shutdown_cancelled (SmcConn smc_conn, - SmPointer client_data) -{ - EggSMClientXSMP *xsmp = client_data; - EggSMClient *client = client_data; - - g_debug ("Received ShutdownCancelled message in state %s", - EGG_SM_CLIENT_XSMP_STATE (xsmp)); - - xsmp->shutting_down = FALSE; - - if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE) - { - /* We've finished interacting and now the SM has agreed to - * cancel the shutdown. - */ - xsmp->state = XSMP_STATE_IDLE; - egg_sm_client_quit_cancelled (client); - } - else if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED) - { - /* Hm... ok, so we got a shutdown SaveYourself, which got - * cancelled, but the application was still interacting, so we - * didn't tell it yet, and then *another* SaveYourself arrived, - * which we must still be waiting to tell the app about, except - * that now that SaveYourself has been cancelled too! Dizzy yet? - */ - xsmp->waiting_to_save_myself = FALSE; - update_pending_events (xsmp); - } - else - { - g_debug ("Sending SaveYourselfDone(False)"); - SmcSaveYourselfDone (xsmp->connection, False); - - if (xsmp->state == XSMP_STATE_INTERACT) - { - /* The application is currently interacting, so we can't - * tell it about the cancellation yet; we will wait until - * after it calls egg_sm_client_will_quit(). - */ - xsmp->state = XSMP_STATE_SHUTDOWN_CANCELLED; - } - else - { - /* The shutdown was cancelled before the application got a - * chance to interact. - */ - xsmp->state = XSMP_STATE_IDLE; - } - } -} - -/* Utilities */ - -/* Create a restart/clone/Exec command based on @restart_command. - * If @client_id is non-%NULL, add "--sm-client-id @client_id". - * If @state_file is non-%NULL, add "--sm-client-state-file @state_file". - * - * None of the input strings are g_strdup()ed; the caller must keep - * them around until it is done with the returned GPtrArray, and must - * then free the array, but not its contents. - */ -static GPtrArray * -generate_command (char **restart_command, const char *client_id, - const char *state_file) -{ - GPtrArray *cmd; - int i; - - cmd = g_ptr_array_new (); - g_ptr_array_add (cmd, restart_command[0]); - - if (client_id) - { - g_ptr_array_add (cmd, (char *)"--sm-client-id"); - g_ptr_array_add (cmd, (char *)client_id); - } - - if (state_file) - { - g_ptr_array_add (cmd, (char *)"--sm-client-state-file"); - g_ptr_array_add (cmd, (char *)state_file); - } - - for (i = 1; restart_command[i]; i++) - g_ptr_array_add (cmd, restart_command[i]); - - return cmd; -} - -/* Takes a NULL-terminated list of SmProp * values, created by - * array_prop, ptrarray_prop, string_prop, card8_prop, sets them, and - * frees them. - */ -static void -set_properties (EggSMClientXSMP *xsmp, ...) -{ - GPtrArray *props; - SmProp *prop; - va_list ap; - guint i; - - props = g_ptr_array_new (); - - va_start (ap, xsmp); - while ((prop = va_arg (ap, SmProp *))) - g_ptr_array_add (props, prop); - va_end (ap); - - if (xsmp->connection) - { - SmcSetProperties (xsmp->connection, props->len, - (SmProp **)props->pdata); - } - - for (i = 0; i < props->len; i++) - { - prop = props->pdata[i]; - g_free (prop->vals); - g_free (prop); - } - g_ptr_array_free (props, TRUE); -} - -/* Takes a NULL-terminated list of property names and deletes them. */ -static void -delete_properties (EggSMClientXSMP *xsmp, ...) -{ - GPtrArray *props; - char *prop; - va_list ap; - - if (!xsmp->connection) - return; - - props = g_ptr_array_new (); - - va_start (ap, xsmp); - while ((prop = va_arg (ap, char *))) - g_ptr_array_add (props, prop); - va_end (ap); - - SmcDeleteProperties (xsmp->connection, props->len, - (char **)props->pdata); - - g_ptr_array_free (props, TRUE); -} - -/* Takes an array of strings and creates a LISTofARRAY8 property. The - * strings are neither dupped nor freed; they need to remain valid - * until you're done with the SmProp. - */ -static SmProp * -array_prop (const char *name, ...) -{ - SmProp *prop; - SmPropValue pv; - GArray *vals; - char *value; - va_list ap; - - prop = g_new (SmProp, 1); - prop->name = (char *)name; - prop->type = (char *)SmLISTofARRAY8; - - vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue)); - - va_start (ap, name); - while ((value = va_arg (ap, char *))) - { - pv.length = strlen (value); - pv.value = value; - g_array_append_val (vals, pv); - } - - prop->num_vals = vals->len; - prop->vals = (SmPropValue *)vals->data; - - g_array_free (vals, FALSE); - - return prop; -} - -/* Takes a GPtrArray of strings and creates a LISTofARRAY8 property. - * The array contents are neither dupped nor freed; they need to - * remain valid until you're done with the SmProp. - */ -static SmProp * -ptrarray_prop (const char *name, GPtrArray *values) -{ - SmProp *prop; - SmPropValue pv; - GArray *vals; - guint i; - - prop = g_new (SmProp, 1); - prop->name = (char *)name; - prop->type = (char *)SmLISTofARRAY8; - - vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue)); - - for (i = 0; i < values->len; i++) - { - pv.length = strlen (values->pdata[i]); - pv.value = values->pdata[i]; - g_array_append_val (vals, pv); - } - - prop->num_vals = vals->len; - prop->vals = (SmPropValue *)vals->data; - - g_array_free (vals, FALSE); - - return prop; -} - -/* Takes a string and creates an ARRAY8 property. The string is - * neither dupped nor freed; it needs to remain valid until you're - * done with the SmProp. - */ -static SmProp * -string_prop (const char *name, const char *value) -{ - SmProp *prop; - - prop = g_new (SmProp, 1); - prop->name = (char *)name; - prop->type = (char *)SmARRAY8; - - prop->num_vals = 1; - prop->vals = g_new (SmPropValue, 1); - - prop->vals[0].length = strlen (value); - prop->vals[0].value = (char *)value; - - return prop; -} - -/* Takes a char and creates a CARD8 property. */ -static SmProp * -card8_prop (const char *name, unsigned char value) -{ - SmProp *prop; - char *card8val; - - /* To avoid having to allocate and free prop->vals[0], we cheat and - * make vals a 2-element-long array and then use the second element - * to store value. - */ - - prop = g_new (SmProp, 1); - prop->name = (char *)name; - prop->type = (char *)SmCARD8; - - prop->num_vals = 1; - prop->vals = g_new (SmPropValue, 2); - card8val = (char *)(&prop->vals[1]); - card8val[0] = value; - - prop->vals[0].length = 1; - prop->vals[0].value = card8val; - - return prop; -} - -/* ICE code. This makes no effort to play nice with anyone else trying - * to use libICE. Fortunately, no one uses libICE for anything other - * than SM. (DCOP uses ICE, but it has its own private copy of - * libICE.) - * - * When this moves to gtk, it will need to be cleverer, to avoid - * tripping over old apps that use GnomeClient or that use libSM - * directly. - */ - -#include -#include - -static void ice_error_handler (IceConn ice_conn, - Bool swap, - int offending_minor_opcode, - unsigned long offending_sequence, - int error_class, - int severity, - IcePointer values); -static void ice_io_error_handler (IceConn ice_conn); -static void ice_connection_watch (IceConn ice_conn, - IcePointer client_data, - Bool opening, - IcePointer *watch_data); - -static void -ice_init (void) -{ - IceSetIOErrorHandler (ice_io_error_handler); - IceSetErrorHandler (ice_error_handler); - IceAddConnectionWatch (ice_connection_watch, NULL); -} - -static gboolean -process_ice_messages (IceConn ice_conn) -{ - IceProcessMessagesStatus status; - - gdk_threads_enter (); - status = IceProcessMessages (ice_conn, NULL, NULL); - gdk_threads_leave (); - - switch (status) - { - case IceProcessMessagesSuccess: - return TRUE; - - case IceProcessMessagesIOError: - sm_client_xsmp_disconnect (IceGetConnectionContext (ice_conn)); - return FALSE; - - case IceProcessMessagesConnectionClosed: - return FALSE; - - default: - g_assert_not_reached (); - } -} - -static gboolean -ice_iochannel_watch (GIOChannel *channel, - GIOCondition condition, - gpointer client_data) -{ - return process_ice_messages (client_data); -} - -static void -ice_connection_watch (IceConn ice_conn, - IcePointer client_data, - Bool opening, - IcePointer *watch_data) -{ - guint watch_id; - - if (opening) - { - GIOChannel *channel; - int fd = IceConnectionNumber (ice_conn); - - fcntl (fd, F_SETFD, fcntl (fd, F_GETFD, 0) | FD_CLOEXEC); - channel = g_io_channel_unix_new (fd); - watch_id = g_io_add_watch (channel, G_IO_IN | G_IO_ERR, - ice_iochannel_watch, ice_conn); - g_io_channel_unref (channel); - - *watch_data = GUINT_TO_POINTER (watch_id); - } - else - { - watch_id = GPOINTER_TO_UINT (*watch_data); - g_source_remove (watch_id); - } -} - -static void -ice_error_handler (IceConn ice_conn, - Bool swap, - int offending_minor_opcode, - unsigned long offending_sequence, - int error_class, - int severity, - IcePointer values) -{ - /* Do nothing */ -} - -static void -ice_io_error_handler (IceConn ice_conn) -{ - /* Do nothing */ -} - -static void -smc_error_handler (SmcConn smc_conn, - Bool swap, - int offending_minor_opcode, - unsigned long offending_sequence, - int error_class, - int severity, - SmPointer values) -{ - /* Do nothing */ -} diff --git a/lib/eggsmclient.c b/lib/eggsmclient.c deleted file mode 100644 index 92be8a7ef..000000000 --- a/lib/eggsmclient.c +++ /dev/null @@ -1,604 +0,0 @@ -/* - * Copyright (C) 2007 Novell, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#include "config.h" - -#include -#include - -#include "eggsmclient.h" -#include "eggsmclient-private.h" - -static void egg_sm_client_debug_handler (const char *log_domain, - GLogLevelFlags log_level, - const char *message, - gpointer user_data); - -enum { - SAVE_STATE, - QUIT_REQUESTED, - QUIT_CANCELLED, - QUIT, - LAST_SIGNAL -}; - -static guint signals[LAST_SIGNAL]; - -struct _EggSMClientPrivate { - GKeyFile *state_file; -}; - -#define EGG_SM_CLIENT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), EGG_TYPE_SM_CLIENT, EggSMClientPrivate)) - -G_DEFINE_TYPE (EggSMClient, egg_sm_client, G_TYPE_OBJECT) - -static EggSMClient *global_client; -static EggSMClientMode global_client_mode = EGG_SM_CLIENT_MODE_NORMAL; - -static void -egg_sm_client_init (EggSMClient *client) -{ - ; -} - -static void -egg_sm_client_class_init (EggSMClientClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - g_type_class_add_private (klass, sizeof (EggSMClientPrivate)); - - /** - * EggSMClient::save_state: - * @client: the client - * @state_file: a #GKeyFile to save state information into - * - * Emitted when the session manager has requested that the - * application save information about its current state. The - * application should save its state into @state_file, and then the - * session manager may then restart the application in a future - * session and tell it to initialize itself from that state. - * - * You should not save any data into @state_file's "start group" - * (ie, the %NULL group). Instead, applications should save their - * data into groups with names that start with the application name, - * and libraries that connect to this signal should save their data - * into groups with names that start with the library name. - * - * Alternatively, rather than (or in addition to) using @state_file, - * the application can save its state by calling - * egg_sm_client_set_restart_command() during the processing of this - * signal (eg, to include a list of files to open). - **/ - signals[SAVE_STATE] = - g_signal_new ("save_state", - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (EggSMClientClass, save_state), - NULL, NULL, - g_cclosure_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, G_TYPE_POINTER); - - /** - * EggSMClient::quit_requested: - * @client: the client - * - * Emitted when the session manager requests that the application - * exit (generally because the user is logging out). The application - * should decide whether or not it is willing to quit (perhaps after - * asking the user what to do with documents that have unsaved - * changes) and then call egg_sm_client_will_quit(), passing %TRUE - * or %FALSE to give its answer to the session manager. (It does not - * need to give an answer before returning from the signal handler; - * it can interact with the user asynchronously and then give its - * answer later on.) If the application does not connect to this - * signal, then #EggSMClient will automatically return %TRUE on its - * behalf. - * - * The application should not save its session state as part of - * handling this signal; if the user has requested that the session - * be saved when logging out, then ::save_state will be emitted - * separately. - * - * If the application agrees to quit, it should then wait for either - * the ::quit_cancelled or ::quit signals to be emitted. - **/ - signals[QUIT_REQUESTED] = - g_signal_new ("quit_requested", - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (EggSMClientClass, quit_requested), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, - 0); - - /** - * EggSMClient::quit_cancelled: - * @client: the client - * - * Emitted when the session manager decides to cancel a logout after - * the application has already agreed to quit. After receiving this - * signal, the application can go back to what it was doing before - * receiving the ::quit_requested signal. - **/ - signals[QUIT_CANCELLED] = - g_signal_new ("quit_cancelled", - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (EggSMClientClass, quit_cancelled), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, - 0); - - /** - * EggSMClient::quit: - * @client: the client - * - * Emitted when the session manager wants the application to quit - * (generally because the user is logging out). The application - * should exit as soon as possible after receiving this signal; if - * it does not, the session manager may choose to forcibly kill it. - * - * Normally a GUI application would only be sent a ::quit if it - * agreed to quit in response to a ::quit_requested signal. However, - * this is not guaranteed; in some situations the session manager - * may decide to end the session without giving applications a - * chance to object. - **/ - signals[QUIT] = - g_signal_new ("quit", - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (EggSMClientClass, quit), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, - 0); -} - -static gboolean sm_client_disable = FALSE; -static char *sm_client_state_file = NULL; -static char *sm_client_id = NULL; -static char *sm_config_prefix = NULL; - -static gboolean -sm_client_post_parse_func (GOptionContext *context, - GOptionGroup *group, - gpointer data, - GError **error) -{ - EggSMClient *client = egg_sm_client_get (); - - if (sm_client_id == NULL) - { - const gchar *desktop_autostart_id; - - desktop_autostart_id = g_getenv ("DESKTOP_AUTOSTART_ID"); - - if (desktop_autostart_id != NULL) - sm_client_id = g_strdup (desktop_autostart_id); - } - - /* Unset DESKTOP_AUTOSTART_ID in order to avoid child processes to - * use the same client id. */ - g_unsetenv ("DESKTOP_AUTOSTART_ID"); - - if (global_client_mode != EGG_SM_CLIENT_MODE_DISABLED && - EGG_SM_CLIENT_GET_CLASS (client)->startup) - EGG_SM_CLIENT_GET_CLASS (client)->startup (client, sm_client_id); - return TRUE; -} - -/** - * egg_sm_client_get_option_group: - * - * Creates a %GOptionGroup containing the session-management-related - * options. You should add this group to the application's - * %GOptionContext if you want to use #EggSMClient. - * - * Return value: the %GOptionGroup - **/ -GOptionGroup * -egg_sm_client_get_option_group (void) -{ - const GOptionEntry entries[] = { - { "sm-client-disable", 0, 0, - G_OPTION_ARG_NONE, &sm_client_disable, - N_("Disable connection to session manager"), NULL }, - { "sm-client-state-file", 0, 0, - G_OPTION_ARG_FILENAME, &sm_client_state_file, - N_("Specify file containing saved configuration"), N_("FILE") }, - { "sm-client-id", 0, 0, - G_OPTION_ARG_STRING, &sm_client_id, - N_("Specify session management ID"), N_("ID") }, - /* GnomeClient compatibility option */ - { "sm-disable", 0, G_OPTION_FLAG_HIDDEN, - G_OPTION_ARG_NONE, &sm_client_disable, - NULL, NULL }, - /* GnomeClient compatibility option. This is a dummy option that only - * exists so that sessions saved by apps with GnomeClient can be restored - * later when they've switched to EggSMClient. See bug #575308. - */ - { "sm-config-prefix", 0, G_OPTION_FLAG_HIDDEN, - G_OPTION_ARG_STRING, &sm_config_prefix, - NULL, NULL }, - { NULL } - }; - GOptionGroup *group; - - /* Use our own debug handler for the "EggSMClient" domain. */ - g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, - egg_sm_client_debug_handler, NULL); - - group = g_option_group_new ("sm-client", - _("Session management options:"), - _("Show session management options"), - NULL, NULL); - g_option_group_add_entries (group, entries); - g_option_group_set_parse_hooks (group, NULL, sm_client_post_parse_func); - - return group; -} - -/** - * egg_sm_client_set_mode: - * @mode: an #EggSMClient mode - * - * Sets the "mode" of #EggSMClient as follows: - * - * %EGG_SM_CLIENT_MODE_DISABLED: Session management is completely - * disabled, until the mode is changed again. The application will - * not even connect to the session manager. (egg_sm_client_get() - * will still return an #EggSMClient object.) - * - * %EGG_SM_CLIENT_MODE_NO_RESTART: The application will connect to - * the session manager (and thus will receive notification when the - * user is logging out, etc), but will request to not be - * automatically restarted with saved state in future sessions. - * - * %EGG_SM_CLIENT_MODE_NORMAL: The default. #EggSMCLient will - * function normally. - * - * This must be called before the application's main loop begins and - * before any call to egg_sm_client_get(), unless the mode was set - * earlier to %EGG_SM_CLIENT_MODE_DISABLED and this call enables - * session management. Note that option parsing will call - * egg_sm_client_get(). - **/ -void -egg_sm_client_set_mode (EggSMClientMode mode) -{ - EggSMClientMode old_mode = global_client_mode; - - g_return_if_fail (global_client == NULL || global_client_mode == EGG_SM_CLIENT_MODE_DISABLED); - g_return_if_fail (!(global_client != NULL && mode == EGG_SM_CLIENT_MODE_DISABLED)); - - global_client_mode = mode; - - if (global_client != NULL && old_mode == EGG_SM_CLIENT_MODE_DISABLED) - { - if (EGG_SM_CLIENT_GET_CLASS (global_client)->startup) - EGG_SM_CLIENT_GET_CLASS (global_client)->startup (global_client, sm_client_id); - } -} - -/** - * egg_sm_client_get_mode: - * - * Gets the global #EggSMClientMode. See egg_sm_client_set_mode() - * for details. - * - * Return value: the global #EggSMClientMode - **/ -EggSMClientMode -egg_sm_client_get_mode (void) -{ - return global_client_mode; -} - -/** - * egg_sm_client_get: - * - * Returns the master #EggSMClient for the application. - * - * On platforms that support saved sessions (ie, POSIX/X11), the - * application will only request to be restarted by the session - * manager if you call egg_set_desktop_file() to set an application - * desktop file. In particular, if the desktop file contains the key - * "X - * - * Return value: the master #EggSMClient. - **/ -EggSMClient * -egg_sm_client_get (void) -{ - if (!global_client) - { - if (!sm_client_disable) - { -#if defined (GDK_WINDOWING_WIN32) - global_client = egg_sm_client_win32_new (); -#elif defined (GDK_WINDOWING_QUARTZ) - global_client = egg_sm_client_osx_new (); -#else - /* If both D-Bus and XSMP are compiled in, try XSMP first - * (since it supports state saving) and fall back to D-Bus - * if XSMP isn't available. - */ -# ifdef EGG_SM_CLIENT_BACKEND_XSMP - global_client = egg_sm_client_xsmp_new (); -# endif -# ifdef EGG_SM_CLIENT_BACKEND_DBUS - if (!global_client) - global_client = egg_sm_client_dbus_new (); -# endif -#endif - } - - /* Fallback: create a dummy client, so that callers don't have - * to worry about a %NULL return value. - */ - if (!global_client) - global_client = g_object_new (EGG_TYPE_SM_CLIENT, NULL); - } - - return global_client; -} - -/** - * egg_sm_client_is_resumed: - * @client: the client - * - * Checks whether or not the current session has been resumed from - * a previous saved session. If so, the application should call - * egg_sm_client_get_state_file() and restore its state from the - * returned #GKeyFile. - * - * Return value: %TRUE if the session has been resumed - **/ -gboolean -egg_sm_client_is_resumed (EggSMClient *client) -{ - g_return_val_if_fail (client == global_client, FALSE); - - return sm_client_state_file != NULL; -} - -/** - * egg_sm_client_get_state_file: - * @client: the client - * - * If the application was resumed by the session manager, this will - * return the #GKeyFile containing its state from the previous - * session. - * - * Note that other libraries and #EggSMClient itself may also store - * state in the key file, so if you call egg_sm_client_get_groups(), - * on it, the return value will likely include groups that you did not - * put there yourself. (It is also not guaranteed that the first - * group created by the application will still be the "start group" - * when it is resumed.) - * - * Return value: the #GKeyFile containing the application's earlier - * state, or %NULL on error. You should not free this key file; it - * is owned by @client. - **/ -GKeyFile * -egg_sm_client_get_state_file (EggSMClient *client) -{ - EggSMClientPrivate *priv = EGG_SM_CLIENT_GET_PRIVATE (client); - char *state_file_path; - GError *err = NULL; - - g_return_val_if_fail (client == global_client, NULL); - - if (!sm_client_state_file) - return NULL; - if (priv->state_file) - return priv->state_file; - - if (!strncmp (sm_client_state_file, "file://", 7)) - state_file_path = g_filename_from_uri (sm_client_state_file, NULL, NULL); - else - state_file_path = g_strdup (sm_client_state_file); - - priv->state_file = g_key_file_new (); - if (!g_key_file_load_from_file (priv->state_file, state_file_path, 0, &err)) - { - g_warning ("Could not load SM state file '%s': %s", - sm_client_state_file, err->message); - g_clear_error (&err); - g_key_file_free (priv->state_file); - priv->state_file = NULL; - } - - g_free (state_file_path); - return priv->state_file; -} - -/** - * egg_sm_client_set_restart_command: - * @client: the client - * @argc: the length of @argv - * @argv: argument vector - * - * Sets the command used to restart @client if it does not have a - * .desktop file that can be used to find its restart command. - * - * This can also be used when handling the ::save_state signal, to - * save the current state via an updated command line. (Eg, providing - * a list of filenames to open when the application is resumed.) - **/ -void -egg_sm_client_set_restart_command (EggSMClient *client, - int argc, - const char **argv) -{ - g_return_if_fail (EGG_IS_SM_CLIENT (client)); - - if (EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command) - EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command (client, argc, argv); -} - -/** - * egg_sm_client_will_quit: - * @client: the client - * @will_quit: whether or not the application is willing to quit - * - * This MUST be called in response to the ::quit_requested signal, to - * indicate whether or not the application is willing to quit. The - * application may call it either directly from the signal handler, or - * at some later point (eg, after asynchronously interacting with the - * user). - * - * If the application does not connect to ::quit_requested, - * #EggSMClient will call this method on its behalf (passing %TRUE - * for @will_quit). - * - * After calling this method, the application should wait to receive - * either ::quit_cancelled or ::quit. - **/ -void -egg_sm_client_will_quit (EggSMClient *client, - gboolean will_quit) -{ - g_return_if_fail (EGG_IS_SM_CLIENT (client)); - - if (EGG_SM_CLIENT_GET_CLASS (client)->will_quit) - EGG_SM_CLIENT_GET_CLASS (client)->will_quit (client, will_quit); -} - -/** - * egg_sm_client_end_session: - * @style: a hint at how to end the session - * @request_confirmation: whether or not the user should get a chance - * to confirm the action - * - * Requests that the session manager end the current session. @style - * indicates how the session should be ended, and - * @request_confirmation indicates whether or not the user should be - * given a chance to confirm the logout/reboot/shutdown. Both of these - * flags are merely hints though; the session manager may choose to - * ignore them. - * - * Return value: %TRUE if the request was sent; %FALSE if it could not - * be (eg, because it could not connect to the session manager). - **/ -gboolean -egg_sm_client_end_session (EggSMClientEndStyle style, - gboolean request_confirmation) -{ - EggSMClient *client = egg_sm_client_get (); - - g_return_val_if_fail (EGG_IS_SM_CLIENT (client), FALSE); - - if (EGG_SM_CLIENT_GET_CLASS (client)->end_session) - { - return EGG_SM_CLIENT_GET_CLASS (client)->end_session (client, style, - request_confirmation); - } - else - return FALSE; -} - -/* Signal-emitting callbacks from platform-specific code */ - -GKeyFile * -egg_sm_client_save_state (EggSMClient *client) -{ - GKeyFile *state_file; - char *group; - - g_return_val_if_fail (client == global_client, NULL); - - state_file = g_key_file_new (); - - g_debug ("Emitting save_state"); - g_signal_emit (client, signals[SAVE_STATE], 0, state_file); - g_debug ("Done emitting save_state"); - - group = g_key_file_get_start_group (state_file); - if (group) - { - g_free (group); - return state_file; - } - else - { - g_key_file_free (state_file); - return NULL; - } -} - -void -egg_sm_client_quit_requested (EggSMClient *client) -{ - g_return_if_fail (client == global_client); - - if (!g_signal_has_handler_pending (client, signals[QUIT_REQUESTED], 0, FALSE)) - { - g_debug ("Not emitting quit_requested because no one is listening"); - egg_sm_client_will_quit (client, TRUE); - return; - } - - g_debug ("Emitting quit_requested"); - g_signal_emit (client, signals[QUIT_REQUESTED], 0); - g_debug ("Done emitting quit_requested"); -} - -void -egg_sm_client_quit_cancelled (EggSMClient *client) -{ - g_return_if_fail (client == global_client); - - g_debug ("Emitting quit_cancelled"); - g_signal_emit (client, signals[QUIT_CANCELLED], 0); - g_debug ("Done emitting quit_cancelled"); -} - -void -egg_sm_client_quit (EggSMClient *client) -{ - g_return_if_fail (client == global_client); - - g_debug ("Emitting quit"); - g_signal_emit (client, signals[QUIT], 0); - g_debug ("Done emitting quit"); - - /* FIXME: should we just call gtk_main_quit() here? */ -} - -static void -egg_sm_client_debug_handler (const char *log_domain, - GLogLevelFlags log_level, - const char *message, - gpointer user_data) -{ - static int debug = -1; - - if (debug < 0) - debug = (g_getenv ("EGG_SM_CLIENT_DEBUG") != NULL); - - if (debug) - g_log_default_handler (log_domain, log_level, message, NULL); -} diff --git a/lib/eggsmclient.h b/lib/eggsmclient.h deleted file mode 100644 index e620b754a..000000000 --- a/lib/eggsmclient.h +++ /dev/null @@ -1,117 +0,0 @@ -/* eggsmclient.h - * Copyright (C) 2007 Novell, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#ifndef __EGG_SM_CLIENT_H__ -#define __EGG_SM_CLIENT_H__ - -#include - -G_BEGIN_DECLS - -#define EGG_TYPE_SM_CLIENT (egg_sm_client_get_type ()) -#define EGG_SM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT, EggSMClient)) -#define EGG_SM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT, EggSMClientClass)) -#define EGG_IS_SM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT)) -#define EGG_IS_SM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT)) -#define EGG_SM_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT, EggSMClientClass)) - -typedef struct _EggSMClient EggSMClient; -typedef struct _EggSMClientClass EggSMClientClass; -typedef struct _EggSMClientPrivate EggSMClientPrivate; - -typedef enum { - EGG_SM_CLIENT_END_SESSION_DEFAULT, - EGG_SM_CLIENT_LOGOUT, - EGG_SM_CLIENT_REBOOT, - EGG_SM_CLIENT_SHUTDOWN -} EggSMClientEndStyle; - -typedef enum { - EGG_SM_CLIENT_MODE_DISABLED, - EGG_SM_CLIENT_MODE_NO_RESTART, - EGG_SM_CLIENT_MODE_NORMAL -} EggSMClientMode; - -struct _EggSMClient -{ - GObject parent; - -}; - -struct _EggSMClientClass -{ - GObjectClass parent_class; - - /* signals */ - void (*save_state) (EggSMClient *client, - GKeyFile *state_file); - - void (*quit_requested) (EggSMClient *client); - void (*quit_cancelled) (EggSMClient *client); - void (*quit) (EggSMClient *client); - - /* virtual methods */ - void (*startup) (EggSMClient *client, - const char *client_id); - void (*set_restart_command) (EggSMClient *client, - int argc, - const char **argv); - void (*will_quit) (EggSMClient *client, - gboolean will_quit); - gboolean (*end_session) (EggSMClient *client, - EggSMClientEndStyle style, - gboolean request_confirmation); - - /* Padding for future expansion */ - void (*_egg_reserved1) (void); - void (*_egg_reserved2) (void); - void (*_egg_reserved3) (void); - void (*_egg_reserved4) (void); -}; - -GType egg_sm_client_get_type (void) G_GNUC_CONST; - -GOptionGroup *egg_sm_client_get_option_group (void); - -/* Initialization */ -void egg_sm_client_set_mode (EggSMClientMode mode); -EggSMClientMode egg_sm_client_get_mode (void); -EggSMClient *egg_sm_client_get (void); - -/* Resuming a saved session */ -gboolean egg_sm_client_is_resumed (EggSMClient *client); -GKeyFile *egg_sm_client_get_state_file (EggSMClient *client); - -/* Alternate means of saving state */ -void egg_sm_client_set_restart_command (EggSMClient *client, - int argc, - const char **argv); - -/* Handling "quit_requested" signal */ -void egg_sm_client_will_quit (EggSMClient *client, - gboolean will_quit); - -/* Initiate a logout/reboot/shutdown */ -gboolean egg_sm_client_end_session (EggSMClientEndStyle style, - gboolean request_confirmation); - -G_END_DECLS - - -#endif /* __EGG_SM_CLIENT_H__ */ diff --git a/lib/rb-builder-helpers.c b/lib/rb-builder-helpers.c index bc8ed3362..f9750892f 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 86efc21a3..5e7b064da 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 5445cfffb..ab86a898e 100644 --- a/lib/rb-util.c +++ b/lib/rb-util.c @@ -376,28 +376,6 @@ rb_image_new_from_stock (const gchar *stock_id, GtkIconSize size) return NULL; } -/** - * 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: * @@ -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 2225e6424..0c767f332 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 b132b4d9a..d6d35ceba 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 000000000..5f8d8e32c --- /dev/null +++ b/plugins/audiocd/audiocd-toolbar.ui @@ -0,0 +1,26 @@ + + + +
+ + Edit + edit-menu + + + Extract + app.audiocd-copy-tracks + + + Eject + app.removable-media-eject + + + Reload + app.audiocd-reload + +
+
+ audiocd-toolbar +
+
+
diff --git a/plugins/audiocd/audiocd-ui.xml b/plugins/audiocd/audiocd-ui.xml deleted file mode 100644 index 86fb0164d..000000000 --- a/plugins/audiocd/audiocd-ui.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/plugins/audiocd/rb-audiocd-plugin.c b/plugins/audiocd/rb-audiocd-plugin.c index b22eafb43..77c6ad4d1 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 2adba5cb2..d8def59cc 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/audioscrobbler-profile-ui.xml b/plugins/audioscrobbler/audioscrobbler-profile-ui.xml deleted file mode 100644 index fdda9a150..000000000 --- a/plugins/audioscrobbler/audioscrobbler-profile-ui.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/plugins/audioscrobbler/audioscrobbler-radio-ui.xml b/plugins/audioscrobbler/audioscrobbler-radio-ui.xml deleted file mode 100644 index 34604e56b..000000000 --- a/plugins/audioscrobbler/audioscrobbler-radio-ui.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/plugins/audioscrobbler/rb-audioscrobbler-profile-page.c b/plugins/audioscrobbler/rb-audioscrobbler-profile-page.c index e7634ceba..c3a112f2b 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 7287607aa..6f5f3cff5 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 160501c14..6dbe8d8f5 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 fa92bc554..f7515ce4f 100644 --- a/plugins/context/ContextView.py +++ b/plugins/context/ContextView.py @@ -32,23 +32,13 @@ 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 = """ - - - - - - - -""" - class ContextView (GObject.GObject): def __init__ (self, shell, plugin): @@ -91,16 +81,16 @@ def __init__ (self, shell, plugin): 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 @@ def deactivate (self, shell): 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 @@ def disconnect_signals (self): 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 e82870af7..b950befb5 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 000000000..ba8cece37 --- /dev/null +++ b/plugins/daap/daap-toolbar.ui @@ -0,0 +1,24 @@ + + + +
+ + Edit + edit-menu + + + Browse + show-browser + <Primary>b + + + View All + app.source-view-all + + + Disconnect + app.daap-disconnect + +
+
+
diff --git a/plugins/daap/daap-ui.xml b/plugins/daap/daap-ui.xml deleted file mode 100644 index 8720f62ea..000000000 --- a/plugins/daap/daap-ui.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/plugins/daap/rb-daap-plugin.c b/plugins/daap/rb-daap-plugin.c index 9f9b88920..b2f115f74 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 82cb91ebb..8140ebad5 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 06a886cad..c028953cd 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 000000000..bc30cf229 --- /dev/null +++ b/plugins/fmradio/fmradio-popup.ui @@ -0,0 +1,17 @@ + + + +
+ + Delete + app.clipboard-delete + +
+
+ + Properties + app.clipboard-properties + +
+
+
diff --git a/plugins/fmradio/fmradio-toolbar.ui b/plugins/fmradio/fmradio-toolbar.ui new file mode 100644 index 000000000..b138c6033 --- /dev/null +++ b/plugins/fmradio/fmradio-toolbar.ui @@ -0,0 +1,15 @@ + + + +
+ + Edit + edit-menu + + + New + app.fmradio-new-station + +
+
+
diff --git a/plugins/fmradio/fmradio-ui.xml b/plugins/fmradio/fmradio-ui.xml deleted file mode 100644 index 3d993fae7..000000000 --- a/plugins/fmradio/fmradio-ui.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/plugins/fmradio/rb-fm-radio-plugin.c b/plugins/fmradio/rb-fm-radio-plugin.c index 5407296fb..46a9e58d6 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 6b2298269..14bdba0b2 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 03046079e..343b74e54 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 f6db0dcb0..02dbad357 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 000000000..c5fb49f77 --- /dev/null +++ b/plugins/generic-player/generic-player-toolbar.ui @@ -0,0 +1,32 @@ + + + +
+ + Edit + edit-menu + + + Browse + show-browser + <Primary>b + + + View All + app.source-view-all + + + Properties + app.media-player-properties + + + Eject + app.removable-media-eject + + + Sync + app.media-player-sync + +
+
+
diff --git a/plugins/generic-player/generic-player-ui.xml b/plugins/generic-player/generic-player-ui.xml deleted file mode 100644 index eb80d36a8..000000000 --- a/plugins/generic-player/generic-player-ui.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/plugins/generic-player/rb-generic-player-playlist-source.c b/plugins/generic-player/rb-generic-player-playlist-source.c index 9ad6cdb66..4af8b86b1 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 57b3c540a..9b1134844 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 d0abb4618..c0b87d93c 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 4c92bcb94..f3f5326e4 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 ff10ab914..cc0eef3a9 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 000000000..8a3b7cad4 --- /dev/null +++ b/plugins/ipod/ipod-toolbar.ui @@ -0,0 +1,32 @@ + + + +
+ + Edit + edit-menu + + + Browse + show-browser + <Primary>b + + + View All + app.source-view-all + + + Properties + app.media-player-properties + + + Eject + app.removable-media-eject + + + Sync + app.media-player-sync + +
+
+
diff --git a/plugins/ipod/ipod-ui.xml b/plugins/ipod/ipod-ui.xml deleted file mode 100644 index 25ab19006..000000000 --- a/plugins/ipod/ipod-ui.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/plugins/ipod/rb-ipod-plugin.c b/plugins/ipod/rb-ipod-plugin.c index e0907bc41..7143ea939 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 4d32ed875..13de14fb3 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; @@ -298,6 +303,54 @@ rb_ipod_source_set_ipod_name (RBiPodSource *source, const char *name) rb_ipod_db_set_ipod_name (priv->ipod_db, 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 @@ -462,16 +539,28 @@ 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 9af5fa3c5..953b86d01 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 d389baa04..653fb8556 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 383b44728..2e3caa367 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 5185c9c0f..8c6713000 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 000000000..e4e8f3a86 --- /dev/null +++ b/plugins/iradio/iradio-popup.ui @@ -0,0 +1,17 @@ + + + +
+ + Remove + app.clipboard-delete + +
+
+ + Properties + app.clipboard-properties + +
+
+
diff --git a/plugins/iradio/iradio-toolbar.ui b/plugins/iradio/iradio-toolbar.ui new file mode 100644 index 000000000..325144f85 --- /dev/null +++ b/plugins/iradio/iradio-toolbar.ui @@ -0,0 +1,28 @@ + + + +
+ + Edit + edit-menu + + + Browse + show-browser + <Primary>b + + + View All + app.source-view-all + + + Radio + Add + app.iradio-new-station + +
+
+ iradio-toolbar +
+
+
diff --git a/plugins/iradio/iradio-ui.xml b/plugins/iradio/iradio-ui.xml deleted file mode 100644 index 5a669a653..000000000 --- a/plugins/iradio/iradio-ui.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/plugins/iradio/rb-iradio-plugin.c b/plugins/iradio/rb-iradio-plugin.c index a085642b1..d691d7836 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 7326c03c1..e4d95ed07 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..."), "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 7fd4a51fb..50e86e745 100644 --- a/plugins/lyrics/lyrics.py +++ b/plugins/lyrics/lyrics.py @@ -39,15 +39,6 @@ import gettext gettext.install('rhythmbox', RB.locale_dir()) -ui_str = """ - - - - - - - -""" LYRIC_TITLE_STRIP=["\(live[^\)]*\)", "\(acoustic[^\)]*\)", "\([^\)]*mix\)", "\([^\)]*version\)", "\([^\)]*edit\)", "\(feat[^\)]*\)"] LYRIC_TITLE_REPLACE=[("/", "-"), (" & ", " and ")] @@ -344,18 +335,16 @@ def __init__ (self): 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, "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 @@ def do_activate (self): 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 @@ def do_deactivate (self): 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 @@ def show_song_lyrics (self, action, shell): 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 b52322ed7..435264f71 100644 --- a/plugins/magnatune/MagnatuneSource.py +++ b/plugins/magnatune/MagnatuneSource.py @@ -35,7 +35,7 @@ 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 @@ def __init__(self): 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 @@ def __init__(self): # 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 @@ def do_selected(self): 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 @@ def change_idle_cb(): 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 @@ def download_finished(copy, success, self): 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 @@ def remove_download_files(): 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 @@ def cancel_downloads(self): 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 098e9f2be..50b348b43 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 000000000..482b31756 --- /dev/null +++ b/plugins/magnatune/magnatune-popup.ui @@ -0,0 +1,43 @@ + + + +
+ + Add to Queue + app.clipboard-add-to-queue + + + Download Album + app.magnatune-album-download + + + Artist Info + app.magnatune-artist-info + + + Cancel Download + app.magnatune-download-cancel + +
+
+ + Browse this Genre + app.browser-select-genre + + + Browse this Artist + app.browser-select-artist + + + Browse this Album + app.browser-select-album + +
+
+ + Pr_operties + app.clipboard-properties + +
+
+
diff --git a/plugins/magnatune/magnatune-toolbar.ui b/plugins/magnatune/magnatune-toolbar.ui new file mode 100644 index 000000000..98f3937b0 --- /dev/null +++ b/plugins/magnatune/magnatune-toolbar.ui @@ -0,0 +1,28 @@ + + + +
+ + Edit + edit-menu + + + Browse + show-browser + <Primary>b + + + Download + app.magnatune-album-download + + + Artist + app.magnatune-artist-info + + + Cancel + app.magnatune-download-cancel + +
+
+
diff --git a/plugins/magnatune/magnatune.py b/plugins/magnatune/magnatune.py index e88f0e26c..ef4330e39 100644 --- a/plugins/magnatune/magnatune.py +++ b/plugins/magnatune/magnatune.py @@ -43,31 +43,6 @@ import gettext gettext.install('rhythmbox', RB.locale_dir()) -popup_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 @@ def do_activate(self): 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 @@ def do_activate(self): 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 ac193bc05..3afc99557 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 000000000..b9a4d958a --- /dev/null +++ b/plugins/mtpdevice/mtp-toolbar.ui @@ -0,0 +1,32 @@ + + + +
+ + Edit + edit-menu + + + Browse + show-browser + <Primary>b + + + View All + app.source-view-all + + + Properties + app.media-player-properties + + + Eject + app.removable-media-eject + + + Sync + app.media-player-sync + +
+
+
diff --git a/plugins/mtpdevice/mtp-ui.xml b/plugins/mtpdevice/mtp-ui.xml deleted file mode 100644 index eec2c5bc6..000000000 --- a/plugins/mtpdevice/mtp-ui.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/plugins/mtpdevice/rb-mtp-plugin.c b/plugins/mtpdevice/rb-mtp-plugin.c index 70c867872..56b0668d8 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 8134d094b..ca0bd36a7 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 36934fc3e..70b48d472 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; @@ -73,141 +72,41 @@ rb_gpm_plugin_init (RBGPMPlugin *plugin) rb_debug ("RBGPMPlugin initialising"); } -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 b78251c6e..6a889d203 100644 --- a/plugins/pythonconsole/pythonconsole.py +++ b/plugins/pythonconsole/pythonconsole.py @@ -37,7 +37,7 @@ 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 @@ except: have_rpdb2 = False -import gettext -gettext.install('rhythmbox', RB.locale_dir()) - -ui_str = """ - - - - - - - - - - -""" - 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 @@ def show_console(self, action, shell): 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 dd33531f1..036482aed 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 = """ - - - - - - - - - - - - -""" - class SendToPlugin (GObject.Object, Peas.Activatable): __gtype_name__ = 'SendToPlugin' @@ -55,30 +40,32 @@ def __init__(self): 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 b8dce3a94..be7018b0f 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 d49cfaeb5..5a1729649 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 6741b88ee..dffd5bc44 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 f021d6a0d..044253b4c 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 c07c34c5a..6ae98b8f6 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/plugins/visualizer/visualizer-ui.xml b/plugins/visualizer/visualizer-ui.xml deleted file mode 100644 index da6292af8..000000000 --- a/plugins/visualizer/visualizer-ui.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/po/POTFILES.in b/po/POTFILES.in index f47a2746a..66b410df5 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 bac625fd1..ec573a435 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 4dc89b22d..f5e7341a4 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 e4c1d6943..abff0e475 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 d71a6d1c2..1abe00c7b 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 f2203ef0a..ef73402a6 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 a5dd0aad6..be308bc4d 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 696f3e95a..cdc09d827 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 000000000..3f8eff6dc --- /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 + * + * 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 + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/** + * 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 000000000..50f3e289b --- /dev/null +++ b/shell/rb-application.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2012 Jonathan Matthew + * + * 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 + +#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 20b7de4d8..2f4297c33 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..."), "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; } @@ -1719,48 +1732,21 @@ static const GDBusInterfaceVTable playlist_manager_vtable = { NULL }; -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 f9e8fa0c4..1225e4e77 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 95d93e375..06b5c2bdf 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,39 +137,17 @@ 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: * @@ -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); @@ -371,6 +341,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) { @@ -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 afe5e4eb2..8c6bc24e4 100644 --- a/shell/rb-shell-clipboard.c +++ b/shell/rb-shell-clipboard.c @@ -50,17 +50,21 @@ #include #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"), "A", - N_("Select all songs"), - G_CALLBACK (rb_shell_clipboard_cmd_select_all) }, - { "EditSelectNone", NULL, N_("D_eselect All"), "A", - N_("Deselect all songs"), - G_CALLBACK (rb_shell_clipboard_cmd_select_none) }, - { "EditCut", GTK_STOCK_CUT, N_("Cu_t"), "X", - N_("Cut selection"), - G_CALLBACK (rb_shell_clipboard_cmd_cut) }, - { "EditCopy", GTK_STOCK_COPY, N_("_Copy"), "C", - N_("Copy selection"), - G_CALLBACK (rb_shell_clipboard_cmd_copy) }, - { "EditPaste", GTK_STOCK_PASTE, N_("_Paste"), "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"), "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; @@ -225,13 +150,6 @@ rb_shell_clipboard_class_init (RBShellClipboardClass *klass) "RBSource object", 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", @@ -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); @@ -344,6 +248,91 @@ rb_shell_clipboard_finalize (GObject *object) G_OBJECT_CLASS (rb_shell_clipboard_parent_class)->finalize (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 0366470f8..b4637a643 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 88b6c9301..671a7f663 100644 --- a/shell/rb-shell-player.c +++ b/shell/rb-shell-player.c @@ -62,6 +62,7 @@ #include #include +#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"), "Left", - N_("Start playing the previous song"), - G_CALLBACK (rb_shell_player_cmd_previous) }, - { "ControlNext", GTK_STOCK_MEDIA_NEXT, N_("_Next"), "Right", - N_("Start playing the next song"), - G_CALLBACK (rb_shell_player_cmd_next) }, - { "ControlVolumeUp", NULL, N_("_Increase Volume"), "Up", - N_("Increase playback volume"), - G_CALLBACK (rb_shell_player_cmd_volume_up) }, - { "ControlVolumeDown", NULL, N_("_Decrease Volume"), "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"), "space", - N_("Start playback"), - G_CALLBACK (rb_shell_player_cmd_play) }, - { "ControlShuffle", GNOME_MEDIA_SHUFFLE, N_("Sh_uffle"), "U", - N_("Play songs in a random order"), - G_CALLBACK (rb_shell_player_shuffle_changed_cb) }, - { "ControlRepeat", GNOME_MEDIA_REPEAT, N_("_Repeat"), "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 (¤t); + 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 (¤t); - 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), "p", "app.play", g_variant_new_boolean (TRUE)); + gtk_application_add_accelerator (GTK_APPLICATION (app), "Left", "app.play-previous", NULL); + gtk_application_add_accelerator (GTK_APPLICATION (app), "Right", "app.play-next", NULL); + gtk_application_add_accelerator (GTK_APPLICATION (app), "Up", "app.volume-up", NULL); + gtk_application_add_accelerator (GTK_APPLICATION (app), "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 217e7dfe2..3b8a5f728 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 57da123fe..6c34f37b3 100644 --- a/shell/rb-shell.c +++ b/shell/rb-shell.c @@ -58,6 +58,7 @@ #include #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..."), "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"), "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"), "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"), "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"), "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"), "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 @@ -2542,6 +2216,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) { @@ -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 ? */ @@ -3009,20 +2480,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) { @@ -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 e5c92f9be..e784cbc18 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 489ef9118..10e92a908 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 }; @@ -144,19 +141,6 @@ rb_statusbar_class_init (RBStatusbarClass *klass) "RBDisplayPage object", 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: * @@ -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 935f055a5..d5a5847b2 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 7020d482d..0405d5816 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 bd799ed65..47f930ba3 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 68954c62d..2e7e91c4b 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 253f89718..225021e68 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 000000000..62b032421 --- /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 + * + * 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 + +#include +#include +#include + +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 000000000..93ccac51c --- /dev/null +++ b/sources/rb-display-page-menu.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2013 Jonathan Matthew + * + * 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 + +#include + +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 071ab96f9..6556c9f4e 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); } @@ -481,77 +508,11 @@ model_row_inserted_cb (GtkTreeModel *model, gtk_tree_view_columns_autosize (GTK_TREE_VIEW (display_page_tree->priv->treeview)); } -static gboolean -emit_show_popup (GtkTreeView *treeview, - RBDisplayPageTree *display_page_tree) -{ - GtkTreeIter iter; - RBDisplayPage *page; - - 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, @@ -953,20 +938,11 @@ rb_display_page_tree_init (RBDisplayPageTree *display_page_tree) "row-expanded", G_CALLBACK (row_expanded_cb), display_page_tree, 0); - g_signal_connect_object (display_page_tree->priv->treeview, - "button_press_event", - G_CALLBACK (button_press_cb), - display_page_tree, 0); g_signal_connect_object (display_page_tree->priv->treeview, "key_release_event", G_CALLBACK (key_release_cb), display_page_tree, 0); - g_signal_connect_object (display_page_tree->priv->treeview, - "popup_menu", - G_CALLBACK (popup_menu_cb), - display_page_tree, 0); - display_page_tree->priv->main_column = gtk_tree_view_column_new (); gtk_tree_view_column_set_clickable (display_page_tree->priv->main_column, FALSE); @@ -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 91fafa995..5d947e253 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 51babeb24..5669fad39 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: @@ -174,6 +174,40 @@ rb_display_page_delete_thyself (RBDisplayPage *page) g_signal_emit (G_OBJECT (page), signals[DELETED], 0); } +/** + * 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; @@ -636,18 +609,6 @@ rb_display_page_class_init (RBDisplayPageClass *klass) "RBShell object", RB_TYPE_SHELL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); - /** - * RBDisplayPage:ui-manager: - * - * The Gtk UIManager object - */ - g_object_class_install_property (object_class, - PROP_UI_MANAGER, - g_param_spec_object ("ui-manager", - "GtkUIManager", - "GtkUIManager object", - GTK_TYPE_UI_MANAGER, - G_PARAM_READABLE)); /** * RBDisplayPage:name: * @@ -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 3c072eef3..32e425bd7 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 2eae5e6ae..ac70a7617 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 061b4bd3c..a4f3ccbec 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 20ac9839c..2e0273b04 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 eef883596..5b6e846d5 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 70efbeca3..86b3a1fd3 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 3bcdb537b..c26029dae 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 22af57092..6116a9151 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 b9ae1ffeb..321580525 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 749893be2..50150a016 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,12 +102,25 @@ 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; } } +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) { @@ -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 c46eaa4a1..3e17b91d1 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 ce96192c4..c8fcd3c03 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 @@ -74,6 +84,38 @@ rb_source_search_init (RBSourceSearch *search) /* nothing */ } +/** + * 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 @@ -94,6 +136,21 @@ rb_source_search_is_subset (RBSourceSearch *search, const char *current, const c return klass->is_subset (search, current, next); } +/** + * 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 @@ -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 05464f94b..c58c02304 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 7019aea74..2ce6c218d 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,21 +280,35 @@ 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 6cd759961..fd9bb080e 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 395a438e9..304db8199 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 64f97c58b..4a2c80ff4 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 07e8fbd71..10456a3e0 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 000000000..c9ffbeff7 --- /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 + * + * 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 + +#include +#include + +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 000000000..98eef029a --- /dev/null +++ b/widgets/rb-button-bar.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2012 Jonathan Matthew + * + * 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 + +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 8567ee8c4..d5c40409b 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 9fe7f985f..78dc6034a 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 63d4ca6c5..fd4e5b141 100644 --- a/widgets/rb-source-toolbar.c +++ b/widgets/rb-source-toolbar.c @@ -29,23 +29,19 @@ #include #include +#include #include 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,47 +69,10 @@ 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) { @@ -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 0095a30ef..bc79f2ea6 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);