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);