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