hythmbox-2.98/sources/rb-play-queue-source.c

No issues found

  1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
  2 /*
  3  *  Copyright (C) 2005 Jonathan Matthew <jonathan@kaolin.hn.org>
  4  *
  5  *  This program is free software; you can redistribute it and/or modify
  6  *  it under the terms of the GNU General Public License as published by
  7  *  the Free Software Foundation; either version 2 of the License, or
  8  *  (at your option) any later version.
  9  *
 10  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
 11  *  GStreamer plugins to be used and distributed together with GStreamer
 12  *  and Rhythmbox. This permission is above and beyond the permissions granted
 13  *  by the GPL license by which Rhythmbox is covered. If you modify this code
 14  *  you may extend this exception to your version of the code, but you are not
 15  *  obligated to do so. If you do not wish to do so, delete this exception
 16  *  statement from your version.
 17  *
 18  *  This program is distributed in the hope that it will be useful,
 19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 21  *  GNU General Public License for more details.
 22  *
 23  *  You should have received a copy of the GNU General Public License
 24  *  along with this program; if not, write to the Free Software
 25  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 26  *
 27  */
 28 
 29 #include "config.h"
 30 
 31 #include <libxml/tree.h>
 32 #include <glib/gi18n.h>
 33 #include <gio/gio.h>
 34 #include <gtk/gtk.h>
 35 
 36 #include "rb-play-queue-source.h"
 37 #include "rb-playlist-xml.h"
 38 #include "rb-song-info.h"
 39 #include "rb-stock-icons.h"
 40 #include "rb-util.h"
 41 #include "rb-debug.h"
 42 #include "rb-play-order-queue.h"
 43 
 44 /**
 45  * SECTION:rb-play-queue-source
 46  * @short_description: source object for the play queue
 47  *
 48  * The main interesting thing about this source is that is
 49  * contains a second #RBEntryView to be displayed in the side
 50  * pane (beneath the source list).  This entry view displays
 51  * the track title, artist, and album in a single column,
 52  * split across three lines so the information mostly fits in
 53  * the usual horizontal space allowed for the side bar.
 54  */
 55 
 56 static const char *RB_PLAY_QUEUE_DBUS_PATH = "/org/gnome/Rhythmbox3/PlayQueue";
 57 static const char *RB_PLAY_QUEUE_IFACE_NAME = "org.gnome.Rhythmbox3.PlayQueue";
 58 
 59 static const char *rb_play_queue_dbus_spec =
 60 "<node>"
 61 "  <interface name='org.gnome.Rhythmbox3.PlayQueue'>"
 62 "    <method name='AddToQueue'>"
 63 "      <arg type='s' name='uri'/>"
 64 "    </method>"
 65 "    <method name='RemoveFromQueue'>"
 66 "      <arg type='s' name='uri'/>"
 67 "    </method>"
 68 "    <method name='ClearQueue'/>"
 69 "  </interface>"
 70 "</node>";
 71 
 72 static void rb_play_queue_source_constructed (GObject *object);
 73 static void rb_play_queue_source_get_property (GObject *object,
 74 					       guint prop_id,
 75 					       GValue *value,
 76 					       GParamSpec *pspec);
 77 static void rb_play_queue_source_track_info_cell_data_func (GtkTreeViewColumn *column,
 78 							    GtkCellRenderer *renderer,
 79 							    GtkTreeModel *tree_model,
 80 							    GtkTreeIter *iter,
 81 							    RBPlaylistSource *source);
 82 static void rb_play_queue_source_row_inserted_cb (GtkTreeModel *model,
 83 						  GtkTreePath *path,
 84 						  GtkTreeIter *iter,
 85 						  RBPlayQueueSource *source);
 86 static void rb_play_queue_source_row_deleted_cb (GtkTreeModel *model,
 87 						 GtkTreePath *path,
 88 						 RBPlayQueueSource *source);
 89 static void rb_play_queue_source_update_count (RBPlayQueueSource *source,
 90 					       GtkTreeModel *model,
 91 					       gint offset);
 92 static void impl_show_entry_view_popup (RBPlaylistSource *source,
 93 					RBEntryView *view,
 94 					gboolean over_entry);
 95 static void impl_save_contents_to_xml (RBPlaylistSource *source,
 96 				       xmlNodePtr node);
 97 static void rb_play_queue_source_cmd_clear (GtkAction *action,
 98 					    RBPlayQueueSource *source);
 99 static void rb_play_queue_source_cmd_shuffle (GtkAction *action,
100 					      RBPlayQueueSource *source);
101 static gboolean impl_show_popup (RBDisplayPage *page);
102 
103 static void rb_play_queue_dbus_method_call (GDBusConnection *connection,
104 					    const char *sender,
105 					    const char *object_path,
106 					    const char *interface_name,
107 					    const char *method_name,
108 					    GVariant *parameters,
109 					    GDBusMethodInvocation *invocation,
110 					    RBPlayQueueSource *source);
111 
112 #define PLAY_QUEUE_SOURCE_SONGS_POPUP_PATH "/QueuePlaylistViewPopup"
113 #define PLAY_QUEUE_SOURCE_SIDEBAR_POPUP_PATH "/QueueSidebarViewPopup"
114 #define PLAY_QUEUE_SOURCE_POPUP_PATH "/QueueSourcePopup"
115 
116 typedef struct _RBPlayQueueSourcePrivate RBPlayQueueSourcePrivate;
117 
118 struct _RBPlayQueueSourcePrivate
119 {
120 	RBEntryView *sidebar;
121 	GtkTreeViewColumn *sidebar_column;
122 	GtkActionGroup *action_group;
123 	RBPlayOrder *queue_play_order;
124 
125 	guint dbus_object_id;
126 	GDBusConnection *bus;
127 };
128 
129 enum
130 {
131 	PROP_0,
132 	PROP_SIDEBAR,
133 	PROP_PLAY_ORDER
134 };
135 
136 G_DEFINE_TYPE (RBPlayQueueSource, rb_play_queue_source, RB_TYPE_STATIC_PLAYLIST_SOURCE)
137 #define RB_PLAY_QUEUE_SOURCE_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), RB_TYPE_PLAY_QUEUE_SOURCE, RBPlayQueueSourcePrivate))
138 
139 static GtkActionEntry rb_play_queue_source_actions [] =
140 {
141 	{ "ClearQueue", GTK_STOCK_CLEAR, N_("Clear _Queue"), NULL,
142 	  N_("Remove all songs from the play queue"),
143 	  G_CALLBACK (rb_play_queue_source_cmd_clear) },
144 	{ "ShuffleQueue", GNOME_MEDIA_SHUFFLE, N_("Shuffle Queue"), NULL,
145 	  N_("Shuffle the tracks in the play queue"),
146 	  G_CALLBACK (rb_play_queue_source_cmd_shuffle) }
147 };
148 
149 static const GDBusInterfaceVTable play_queue_vtable = {
150 	(GDBusInterfaceMethodCallFunc) rb_play_queue_dbus_method_call,
151 	NULL,
152 	NULL
153 };
154 
155 static void
156 rb_play_queue_sync_playing_state (GObject *entry_view,
157 				  GParamSpec *pspec,
158 				  RBPlayQueueSource *source)
159 {
160 	int state;
161 	RBPlayQueueSourcePrivate *priv = RB_PLAY_QUEUE_SOURCE_GET_PRIVATE (source);
162 	g_object_get (entry_view, "playing-state", &state, NULL);
163 	rb_entry_view_set_state (priv->sidebar, state);
164 }
165 
166 static void
167 rb_play_queue_source_dispose (GObject *object)
168 {
169 	RBPlayQueueSourcePrivate *priv = RB_PLAY_QUEUE_SOURCE_GET_PRIVATE (object);
170 
171 	if (priv->action_group != NULL) {
172 		g_object_unref (priv->action_group);
173 		priv->action_group = NULL;
174 	}
175 
176 	if (priv->queue_play_order != NULL) {
177 		g_object_unref (priv->queue_play_order);
178 		priv->queue_play_order = NULL;
179 	}
180 
181 	if (priv->bus != NULL) {
182 		if (priv->dbus_object_id) {
183 			g_dbus_connection_unregister_object (priv->bus, priv->dbus_object_id);
184 			priv->dbus_object_id = 0;
185 		}
186 		g_object_unref (priv->bus);
187 	}
188 
189 	G_OBJECT_CLASS (rb_play_queue_source_parent_class)->dispose (object);
190 }
191 
192 static void
193 rb_play_queue_source_finalize (GObject *object)
194 {
195 	/* do nothing */
196 
197 	G_OBJECT_CLASS (rb_play_queue_source_parent_class)->finalize (object);
198 }
199 
200 static void
201 rb_play_queue_source_class_init (RBPlayQueueSourceClass *klass)
202 {
203 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
204 	RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
205 	RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
206 	RBPlaylistSourceClass *playlist_class = RB_PLAYLIST_SOURCE_CLASS (klass);
207 
208 	object_class->constructed = rb_play_queue_source_constructed;
209 	object_class->get_property = rb_play_queue_source_get_property;
210 	object_class->finalize = rb_play_queue_source_finalize;
211 	object_class->dispose  = rb_play_queue_source_dispose;
212 
213 	page_class->show_popup = impl_show_popup;
214 
215 	source_class->impl_can_add_to_queue = (RBSourceFeatureFunc) rb_false_function;
216 	source_class->impl_can_rename = (RBSourceFeatureFunc) rb_false_function;
217 
218 	playlist_class->impl_show_entry_view_popup = impl_show_entry_view_popup;
219 	playlist_class->impl_save_contents_to_xml = impl_save_contents_to_xml;
220 
221 	/**
222 	 * RBPlayQueueSource:sidebar:
223 	 *
224 	 * The #RBEntryView for the play queue side pane.
225 	 */
226 	g_object_class_install_property (object_class,
227 					 PROP_SIDEBAR,
228 					 g_param_spec_object ("sidebar",
229 							      "sidebar",
230 							      "queue sidebar entry view",
231 							      RB_TYPE_ENTRY_VIEW,
232 							      G_PARAM_READABLE));
233 
234 	/**
235 	 * RBPlayQueueSource:play-order:
236 	 *
237 	 * Overrides the play-order property from #RBSource
238 	 */
239 	g_object_class_override_property (object_class,
240 					  PROP_PLAY_ORDER,
241 					  "play-order");
242 
243 	g_type_class_add_private (klass, sizeof (RBPlayQueueSourcePrivate));
244 }
245 
246 static void
247 rb_play_queue_source_init (RBPlayQueueSource *source)
248 {
249 }
250 
251 static void
252 rb_play_queue_source_constructed (GObject *object)
253 {
254 	RBPlayQueueSource *source;
255 	RBPlayQueueSourcePrivate *priv;
256 	GObject *shell_player;
257 	RBShell *shell;
258 	RhythmDB *db;
259 	GtkCellRenderer *renderer;
260 	RhythmDBQueryModel *model;
261 	GtkAction *action;
262 
263 	RB_CHAIN_GOBJECT_METHOD (rb_play_queue_source_parent_class, constructed, object);
264 
265 	source = RB_PLAY_QUEUE_SOURCE (object);
266 	priv = RB_PLAY_QUEUE_SOURCE_GET_PRIVATE (source);
267 	db = rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (source));
268 
269 	g_object_get (source, "shell", &shell, NULL);
270 	g_object_get (shell, "shell-player", &shell_player, NULL);
271 	g_object_unref (shell);
272 
273 	priv->queue_play_order = rb_queue_play_order_new (RB_SHELL_PLAYER (shell_player));
274 
275 	priv->action_group = _rb_display_page_register_action_group (RB_DISPLAY_PAGE (source),
276 								     "PlayQueueActions",
277 								     rb_play_queue_source_actions,
278 								     G_N_ELEMENTS (rb_play_queue_source_actions),
279 								     source);
280 	action = gtk_action_group_get_action (priv->action_group,
281 					      "ClearQueue");
282 	/* Translators: this is the toolbutton label for Clear Queue action */
283 	g_object_set (G_OBJECT (action), "short-label", _("Clear"), NULL);
284 
285 	/* Translators: this is the toolbutton label for the 'shuffle queue' action */
286 	gtk_action_set_short_label (gtk_action_group_get_action (priv->action_group, "ShuffleQueue"), C_("Queue", "Shuffle"));
287 
288 	priv->sidebar = rb_entry_view_new (db, shell_player, TRUE, TRUE);
289 	g_object_unref (shell_player);
290 
291 	g_object_set (G_OBJECT (priv->sidebar), "vscrollbar-policy", GTK_POLICY_AUTOMATIC, NULL);
292 
293 	priv->sidebar_column = gtk_tree_view_column_new ();
294 	renderer = gtk_cell_renderer_text_new ();
295 	gtk_tree_view_column_pack_start (priv->sidebar_column, renderer, TRUE);
296 	gtk_tree_view_column_set_sizing (priv->sidebar_column, GTK_TREE_VIEW_COLUMN_FIXED);
297 	gtk_tree_view_column_set_expand (priv->sidebar_column, TRUE);
298 	gtk_tree_view_column_set_clickable (priv->sidebar_column, FALSE);
299 	gtk_tree_view_column_set_cell_data_func (priv->sidebar_column, renderer,
300 						 (GtkTreeCellDataFunc)
301 						 rb_play_queue_source_track_info_cell_data_func,
302 						 source, NULL);
303 	rb_entry_view_append_column_custom (priv->sidebar, priv->sidebar_column,
304 					    _("Play Queue"), "Title", NULL, 0, NULL);
305 	rb_entry_view_set_columns_clickable (priv->sidebar, FALSE);
306 	rb_playlist_source_setup_entry_view (RB_PLAYLIST_SOURCE (source), priv->sidebar);
307 
308 	model = rb_playlist_source_get_query_model (RB_PLAYLIST_SOURCE (source));
309 	rb_entry_view_set_model (priv->sidebar, model);
310 
311 	/* sync the state of the main entry view and the sidebar */
312 	g_signal_connect_object (G_OBJECT (rb_source_get_entry_view (RB_SOURCE (source))),
313 				 "notify::playing-state",
314 				 G_CALLBACK (rb_play_queue_sync_playing_state),
315 				 source, 0);
316 
317 	/* update the queued song count when the query model changes */
318 	g_signal_connect_object (G_OBJECT (model), "row-inserted",
319 				 G_CALLBACK (rb_play_queue_source_row_inserted_cb),
320 				 source, 0);
321 	g_signal_connect_object (G_OBJECT (model), "row-deleted",
322 				 G_CALLBACK (rb_play_queue_source_row_deleted_cb),
323 				 source, 0);
324 
325 	rb_play_queue_source_update_count (source, GTK_TREE_MODEL (model), 0);
326 
327 	/* register dbus interface */
328 	priv->bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
329 	if (priv->bus) {
330 		GDBusNodeInfo *node_info;
331 		GError *error = NULL;
332 
333 		node_info = g_dbus_node_info_new_for_xml (rb_play_queue_dbus_spec, &error);
334 		if (error != NULL) {
335 			g_warning ("Unable to parse playlist manager dbus spec: %s", error->message);
336 			g_clear_error (&error);
337 			return;
338 		}
339 
340 		priv->dbus_object_id = g_dbus_connection_register_object (priv->bus,
341 									  RB_PLAY_QUEUE_DBUS_PATH,
342 									  g_dbus_node_info_lookup_interface (node_info, RB_PLAY_QUEUE_IFACE_NAME),
343 									  &play_queue_vtable,
344 									  source,
345 									  NULL,
346 									  &error);
347 		if (error != NULL) {
348 			g_warning ("Unable to register play queue dbus object: %s", error->message);
349 			g_clear_error (&error);
350 		}
351 	}
352 }
353 
354 static void
355 rb_play_queue_source_get_property (GObject *object,
356 				   guint prop_id,
357 				   GValue *value,
358 				   GParamSpec *pspec)
359 {
360 	RBPlayQueueSourcePrivate *priv = RB_PLAY_QUEUE_SOURCE_GET_PRIVATE (object);
361 
362 	switch (prop_id)
363 	{
364 	case PROP_SIDEBAR:
365 		g_value_set_object (value, priv->sidebar);
366 		break;
367 	case PROP_PLAY_ORDER:
368 		g_value_set_object (value, priv->queue_play_order);
369 		break;
370 	default:
371 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
372 		break;
373 	}
374 }
375 
376 /**
377  * rb_play_queue_source_new:
378  * @shell: the #RBShell instance
379  *
380  * Creates the play queue source object.
381  * 
382  * Return value: the play queue source
383  */
384 RBSource *
385 rb_play_queue_source_new (RBShell *shell)
386 {
387 	return RB_SOURCE (g_object_new (RB_TYPE_PLAY_QUEUE_SOURCE,
388 					"name", _("Play Queue"),
389 					"shell", shell,
390 					"is-local", TRUE,
391 					"entry-type", NULL,
392 					"toolbar-path", "/QueueSourceToolBar",
393 					"show-browser", FALSE,
394 					NULL));
395 }
396 
397 /**
398  * rb_play_queue_source_sidebar_song_info:
399  * @source: the #RBPlayQueueSource
400  *
401  * Creates and displays a #RBSongInfo for the currently selected
402  * entry in the side pane play queue view
403  */
404 void
405 rb_play_queue_source_sidebar_song_info (RBPlayQueueSource *source)
406 {
407 	RBPlayQueueSourcePrivate *priv = RB_PLAY_QUEUE_SOURCE_GET_PRIVATE (source);
408 	GtkWidget *song_info = NULL;
409 
410 	g_return_if_fail (priv->sidebar != NULL);
411 
412 	song_info = rb_song_info_new (RB_SOURCE (source), priv->sidebar);
413 	if (song_info)
414 		gtk_widget_show_all (song_info);
415 	else
416 		rb_debug ("failed to create dialog, or no selection!");
417 }
418 
419 /**
420  * rb_play_queue_source_sidebar_delete:
421  * @source: the #RBPlayQueueSource
422  *
423  * Deletes the selected entries from the play queue side pane.
424  * This is called by the #RBShellClipboard.
425  */
426 void
427 rb_play_queue_source_sidebar_delete (RBPlayQueueSource *source)
428 {
429 	RBPlayQueueSourcePrivate *priv = RB_PLAY_QUEUE_SOURCE_GET_PRIVATE (source);
430 	RBEntryView *sidebar = priv->sidebar;
431 	GList *sel, *tem;
432 
433 	sel = rb_entry_view_get_selected_entries (sidebar);
434 	for (tem = sel; tem != NULL; tem = tem->next)
435 		rb_static_playlist_source_remove_entry (RB_STATIC_PLAYLIST_SOURCE (source),
436 							(RhythmDBEntry *) tem->data);
437 	g_list_free (sel);
438 }
439 
440 /**
441  * rb_play_queue_source_clear_queue:
442  * @source: the #RBPlayQueueSource
443  *
444  * Clears the play queue.
445  */
446 void
447 rb_play_queue_source_clear_queue (RBPlayQueueSource *source)
448 {
449 	GtkTreeIter iter;
450 	RhythmDBEntry *entry;
451 	RhythmDBQueryModel *model;
452 
453 	model = rb_playlist_source_get_query_model (RB_PLAYLIST_SOURCE (source));
454 	while (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter)) {
455 		entry = rhythmdb_query_model_iter_to_entry (model, &iter);
456 
457 		if (entry != NULL) {
458 			rhythmdb_query_model_remove_entry (model, entry);
459 			rhythmdb_entry_unref (entry);
460 		}
461 	}
462 }
463 
464 static void
465 impl_show_entry_view_popup (RBPlaylistSource *source,
466 			    RBEntryView *view,
467 			    gboolean over_entry)
468 {
469 	RBPlayQueueSourcePrivate *priv = RB_PLAY_QUEUE_SOURCE_GET_PRIVATE (source);
470 	const char *popup = PLAY_QUEUE_SOURCE_SONGS_POPUP_PATH;
471 	if (view == priv->sidebar)
472 		popup = PLAY_QUEUE_SOURCE_SIDEBAR_POPUP_PATH;
473 	else if (!over_entry)
474 		return;
475 	_rb_display_page_show_popup (RB_DISPLAY_PAGE (source), popup);
476 }
477 
478 static void
479 rb_play_queue_source_track_info_cell_data_func (GtkTreeViewColumn *column,
480 						GtkCellRenderer *renderer,
481 						GtkTreeModel *tree_model,
482 						GtkTreeIter *iter,
483 						RBPlaylistSource *source)
484 {
485 	RhythmDBEntry *entry;
486 	const char *title;
487 	const char *artist;
488 	const char *album;
489 	char *markup;
490 
491 	gtk_tree_model_get (tree_model, iter, 0, &entry, -1);
492 
493 	title = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE);
494 	artist = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST);
495 	album = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM);
496 
497 	/* Translators: format is "<title> from <album> by <artist>" */
498 	markup = g_markup_printf_escaped ("%s\n<span size=\"smaller\">%s <i>%s</i>\n%s <i>%s</i></span>",
499 					  title, _("from"), album, _("by"), artist);
500 
501 	g_object_set (G_OBJECT (renderer), "markup", markup, NULL);
502 
503 	g_free (markup);
504 	rhythmdb_entry_unref (entry);
505 }
506 
507 static void
508 rb_play_queue_source_row_inserted_cb (GtkTreeModel *model,
509 				      GtkTreePath *path,
510 				      GtkTreeIter *iter,
511 				      RBPlayQueueSource *source)
512 {
513 	rb_play_queue_source_update_count (source, model, 0);
514 }
515 
516 static void
517 rb_play_queue_source_row_deleted_cb (GtkTreeModel *model,
518 				     GtkTreePath *path,
519 				     RBPlayQueueSource *source)
520 {
521 	rb_play_queue_source_update_count (source, model, -1);
522 }
523 
524 static void
525 rb_play_queue_source_update_count (RBPlayQueueSource *source,
526 				   GtkTreeModel *model,
527 				   gint offset)
528 {
529 	gint count = gtk_tree_model_iter_n_children (model, NULL) + offset;
530 	RBPlayQueueSourcePrivate *priv = RB_PLAY_QUEUE_SOURCE_GET_PRIVATE (source);
531 	char *name = _("Play Queue");
532 	GtkAction *action;
533 
534 	/* update source name */
535 	if (count > 0)
536 		name = g_strdup_printf ("%s (%d)", name, count);
537 
538 	g_object_set (G_OBJECT (source), "name", name, NULL);
539 	gtk_tree_view_column_set_title (priv->sidebar_column, name);
540 
541 	if (count > 0)
542 		g_free (name);
543 
544 	/* make 'clear queue' and 'shuffle queue' actions sensitive when there are entries in the queue */
545 	action = gtk_action_group_get_action (priv->action_group,
546 					      "ClearQueue");
547 	g_object_set (G_OBJECT (action), "sensitive", (count > 0), NULL);
548 
549 	action = gtk_action_group_get_action (priv->action_group, "ShuffleQueue");
550 	g_object_set (G_OBJECT (action), "sensitive", (count > 0), NULL);
551 }
552 
553 static void
554 impl_save_contents_to_xml (RBPlaylistSource *source,
555 			   xmlNodePtr node)
556 {
557 	((RBPlaylistSourceClass*)rb_play_queue_source_parent_class)->impl_save_contents_to_xml (source, node);
558 	xmlSetProp (node, RB_PLAYLIST_TYPE, RB_PLAYLIST_QUEUE);
559 }
560 
561 static void
562 rb_play_queue_source_cmd_clear (GtkAction *action,
563 				RBPlayQueueSource *source)
564 {
565 	rb_play_queue_source_clear_queue (source);
566 }
567 
568 static void
569 rb_play_queue_source_cmd_shuffle (GtkAction *action,
570 				  RBPlayQueueSource *source)
571 {
572 	RhythmDBQueryModel *model;
573 
574 	model = rb_playlist_source_get_query_model (RB_PLAYLIST_SOURCE (source));
575 	rhythmdb_query_model_shuffle_entries (model);
576 }
577 
578 static gboolean
579 impl_show_popup (RBDisplayPage *page)
580 {
581 	_rb_display_page_show_popup (page, PLAY_QUEUE_SOURCE_POPUP_PATH);
582 	return TRUE;
583 }
584 
585 static void
586 rb_play_queue_dbus_method_call (GDBusConnection *connection,
587 				const char *sender,
588 				const char *object_path,
589 				const char *interface_name,
590 				const char *method_name,
591 				GVariant *parameters,
592 				GDBusMethodInvocation *invocation,
593 				RBPlayQueueSource *source)
594 {
595 	RhythmDBEntry *entry;
596 	RhythmDB *db;
597 	const char *uri;
598 
599 	if (g_strcmp0 (interface_name, RB_PLAY_QUEUE_IFACE_NAME) != 0) {
600 		rb_debug ("method call on unexpected interface %s", interface_name);
601 		g_dbus_method_invocation_return_error (invocation,
602 						       G_DBUS_ERROR,
603 						       G_DBUS_ERROR_NOT_SUPPORTED,
604 						       "Method %s.%s not supported",
605 						       interface_name,
606 						       method_name);
607 		return;
608 	}
609 
610 	if (g_strcmp0 (method_name, "AddToQueue") == 0) {
611 		g_variant_get (parameters, "(&s)", &uri);
612 
613 		db = rb_playlist_source_get_db (RB_PLAYLIST_SOURCE (source));
614 		entry = rhythmdb_entry_lookup_by_location (db, uri);
615 		if (entry == NULL) {
616 			RBSource *urisource;
617 			RBShell *shell;
618 
619 			g_object_get (source, "shell", &shell, NULL);
620 			urisource = rb_shell_guess_source_for_uri (shell, uri);
621 			g_object_unref (shell);
622 
623 			if (urisource != NULL) {
624 				rb_source_add_uri (urisource, uri, NULL, NULL, NULL, NULL, NULL);
625 			} else {
626 				g_dbus_method_invocation_return_error (invocation,
627 								       RB_SHELL_ERROR,
628 								       RB_SHELL_ERROR_NO_SOURCE_FOR_URI,
629 								       _("No registered source can handle URI %s"),
630 								       uri);
631 				return;
632 			}
633 		}
634 		rb_static_playlist_source_add_location (RB_STATIC_PLAYLIST_SOURCE (source),
635 							uri, -1);
636 
637 		g_dbus_method_invocation_return_value (invocation, NULL);
638 	} else if (g_strcmp0 (method_name, "RemoveFromQueue") == 0) {
639 		g_variant_get (parameters, "(&s)", &uri);
640 
641 		if (rb_playlist_source_location_in_map (RB_PLAYLIST_SOURCE (source), uri)) {
642 			rb_static_playlist_source_remove_location (RB_STATIC_PLAYLIST_SOURCE (source), uri);
643 		}
644 
645 		g_dbus_method_invocation_return_value (invocation, NULL);
646 	} else if (g_strcmp0 (method_name, "ClearQueue") == 0) {
647 		rb_play_queue_source_clear_queue (RB_PLAY_QUEUE_SOURCE (source));
648 		g_dbus_method_invocation_return_value (invocation, NULL);
649 	} else {
650 		g_dbus_method_invocation_return_error (invocation,
651 						       G_DBUS_ERROR,
652 						       G_DBUS_ERROR_NOT_SUPPORTED,
653 						       "Method %s.%s not supported",
654 						       interface_name,
655 						       method_name);
656 	}
657 }