hythmbox-2.98/shell/rb-play-order.c

No issues found

  1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  2  *
  3  *  Copyright (C) 2003 Jeffrey Yasskin <jyasskin@mail.utexas.edu>
  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 <string.h>
 32 
 33 #include <glib/gi18n.h>
 34 
 35 #include "rb-play-order.h"
 36 
 37 #include "rb-shell-player.h"
 38 #include "rb-debug.h"
 39 #include "rb-marshal.h"
 40 
 41 /**
 42  * SECTION:rb-play-order
 43  * @short_description: base class for play order implementations
 44  *
 45  * A play order defines an ordering of the entries from a #RhythmDBQueryModel that
 46  * the #RBShellPlayer uses to get the next or previous entry to play.
 47  *
 48  * Play order methods are invoked when changes are made to the query model, when
 49  * the query model is replaced, the playing source is changed, or a new playing
 50  * entry is selected.
 51  *
 52  * The play order must implement methods to check for, retrieve, and move to the
 53  * next and previous entries in the play order.  Only the go_next and go_previous
 54  * methods actually change anything.
 55  *
 56  * The play order should also emit the have-next-previous-changed signal to hint that
 57  * the availability of either a next or previous entry in the order may have changed.
 58  * This information is used to update the sensitivity of the next and previous buttons.
 59  */
 60 
 61 static void rb_play_order_class_init (RBPlayOrderClass *klass);
 62 static void rb_play_order_init (RBPlayOrder *porder);
 63 static void rb_play_order_dispose (GObject *object);
 64 static void rb_play_order_set_property (GObject *object,
 65 					guint prop_id,
 66 					const GValue *value,
 67 					GParamSpec *pspec);
 68 static void rb_play_order_get_property (GObject *object,
 69 					guint prop_id,
 70 					GValue *value,
 71 					GParamSpec *pspec);
 72 
 73 static gboolean default_has_next (RBPlayOrder *porder);
 74 static gboolean default_has_previous (RBPlayOrder *porder);
 75 static void default_playing_entry_removed (RBPlayOrder *porder, RhythmDBEntry *entry);
 76 
 77 static void rb_play_order_entry_added_cb (GtkTreeModel *model,
 78 					  GtkTreePath *path,
 79 					  GtkTreeIter *iter,
 80 					  RBPlayOrder *porder);
 81 static void rb_play_order_row_deleted_cb (GtkTreeModel *model,
 82 					  GtkTreePath *path,
 83 					  RBPlayOrder *porder);
 84 static void rb_play_order_query_model_changed_cb (GObject *source,
 85 						  GParamSpec *arg,
 86 						  RBPlayOrder *porder);
 87 static void rb_play_order_update_have_next_previous (RBPlayOrder *porder);
 88 
 89 struct RBPlayOrderPrivate
 90 {
 91 	RBShellPlayer *player;
 92 	RBSource *source;
 93 	RhythmDB *db;
 94 	RhythmDBQueryModel *query_model;
 95 	RhythmDBEntry *playing_entry;
 96 	gulong query_model_change_id;
 97 	gulong sync_playing_entry_id;
 98 	gboolean have_next;
 99 	gboolean have_previous;
100 };
101 
102 #define RB_PLAY_ORDER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_PLAY_ORDER, RBPlayOrderPrivate))
103 
104 enum
105 {
106 	PROP_0,
107 	PROP_PLAYER,
108 	PROP_PLAYING_ENTRY
109 };
110 
111 enum
112 {
113 	HAVE_NEXT_PREVIOUS_CHANGED,
114 	LAST_SIGNAL
115 };
116 
117 static guint rb_play_order_signals[LAST_SIGNAL] = { 0 };
118 
119 G_DEFINE_TYPE (RBPlayOrder, rb_play_order, G_TYPE_OBJECT)
120 
121 static void
122 rb_play_order_class_init (RBPlayOrderClass *klass)
123 {
124 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
125 
126 	object_class->dispose = rb_play_order_dispose;
127 	object_class->set_property = rb_play_order_set_property;
128 	object_class->get_property = rb_play_order_get_property;
129 
130 	klass->has_next = default_has_next;
131 	klass->has_previous = default_has_previous;
132 	klass->playing_entry_removed = default_playing_entry_removed;
133 
134 	/**
135 	 * RBPlayOrder:player:
136 	 *
137 	 * The #RBShellPlayer instance
138 	 */
139 	g_object_class_install_property (object_class,
140 					 PROP_PLAYER,
141 					 g_param_spec_object ("player",
142 						 	      "RBShellPlayer",
143 						 	      "Rhythmbox Player",
144 						 	      RB_TYPE_SHELL_PLAYER,
145 						 	      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
146 
147 	/**
148 	 * RBPlayOrder:playing-entry:
149 	 *
150 	 * The current playing #RhythmDBEntry
151 	 */
152 	g_object_class_install_property (object_class,
153 					 PROP_PLAYING_ENTRY,
154 					 g_param_spec_boxed ("playing-entry",
155 						 	     "RhythmDBEntry",
156 						 	     "Playing entry",
157 							     RHYTHMDB_TYPE_ENTRY,
158 						 	     G_PARAM_READWRITE));
159 
160 	/**
161 	 * RBPlayOrder::have-next-previous-changed:
162 	 * @porder: the #RBPlayOrder
163 	 * @have_next: if %TRUE, the play order has at least one more entry
164 	 * @have_previous: if %TRUE, the play order has at least one entry
165 	 *    before the current entry
166 	 *
167 	 * Emitted as a hint to suggest that the sensitivity of next/previous
168 	 * buttons may need to be updated.
169 	 */
170 	rb_play_order_signals[HAVE_NEXT_PREVIOUS_CHANGED] =
171 		g_signal_new ("have_next_previous_changed",
172 			      G_OBJECT_CLASS_TYPE (object_class),
173 			      G_SIGNAL_RUN_LAST,
174 			      G_STRUCT_OFFSET (RBPlayOrderClass, have_next_previous_changed),
175 			      NULL, NULL,
176 			      rb_marshal_VOID__BOOLEAN_BOOLEAN,
177 			      G_TYPE_NONE,
178 			      2, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
179 
180 	g_type_class_add_private (klass, sizeof (RBPlayOrderPrivate));
181 }
182 
183 static void
184 rb_play_order_init (RBPlayOrder *porder)
185 {
186 	porder->priv = RB_PLAY_ORDER_GET_PRIVATE (porder);
187 }
188 
189 static void
190 rb_play_order_dispose (GObject *object)
191 {
192 	RBPlayOrder *porder;
193 
194 	g_return_if_fail (object != NULL);
195 	g_return_if_fail (RB_IS_PLAY_ORDER (object));
196 
197 	porder = RB_PLAY_ORDER (object);
198 
199 	if (porder->priv->query_model != NULL) {
200 		g_signal_handlers_disconnect_by_func (G_OBJECT (porder->priv->query_model),
201 						      G_CALLBACK (rb_play_order_entry_added_cb),
202 						      porder);
203 		g_signal_handlers_disconnect_by_func (G_OBJECT (porder->priv->query_model),
204 						      G_CALLBACK (rb_play_order_row_deleted_cb),
205 						      porder);
206 		g_object_unref (porder->priv->query_model);
207 		porder->priv->query_model = NULL;
208 	}
209 
210 	if (porder->priv->db != NULL) {
211 		g_object_unref (porder->priv->db);
212 		porder->priv->db = NULL;
213 	}
214 
215 	if (porder->priv->playing_entry != NULL) {
216 		rhythmdb_entry_unref (porder->priv->playing_entry);
217 		porder->priv->playing_entry = NULL;
218 	}
219 
220 	G_OBJECT_CLASS (rb_play_order_parent_class)->dispose (object);
221 }
222 
223 static void
224 rb_play_order_set_player (RBPlayOrder   *porder,
225 			  RBShellPlayer *player)
226 {
227 	porder->priv->player = player;
228 }
229 
230 static void
231 rb_play_order_set_playing_entry_internal (RBPlayOrder   *porder,
232 					  RhythmDBEntry *entry)
233 {
234 	RhythmDBEntry *old_entry;
235 
236 	old_entry = porder->priv->playing_entry;
237 	porder->priv->playing_entry = entry;
238 
239 	if (porder->priv->playing_entry != NULL) {
240 		rhythmdb_entry_ref (porder->priv->playing_entry);
241 	}
242 
243 	if (RB_PLAY_ORDER_GET_CLASS (porder)->playing_entry_changed)
244 		RB_PLAY_ORDER_GET_CLASS (porder)->playing_entry_changed (porder, old_entry, entry);
245 
246 	if (old_entry != NULL) {
247 		rhythmdb_entry_unref (old_entry);
248 	}
249 
250 	rb_play_order_update_have_next_previous (porder);
251 }
252 
253 static void
254 rb_play_order_set_property (GObject *object,
255 			    guint prop_id,
256 			    const GValue *value,
257 			    GParamSpec *pspec)
258 {
259 	RBPlayOrder *porder = RB_PLAY_ORDER (object);
260 
261 	switch (prop_id) {
262 	case PROP_PLAYER:
263 		rb_play_order_set_player (porder, g_value_get_object (value));
264 		break;
265 	case PROP_PLAYING_ENTRY:
266 		rb_play_order_set_playing_entry_internal (porder, g_value_get_boxed (value));
267 		break;
268 	default:
269 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
270 		break;
271 	}
272 }
273 
274 static void
275 rb_play_order_get_property (GObject *object,
276 			    guint prop_id,
277 			    GValue *value,
278 			    GParamSpec *pspec)
279 {
280 	RBPlayOrder *porder = RB_PLAY_ORDER (object);
281 
282 	switch (prop_id)
283 	{
284 	case PROP_PLAYER:
285 		g_value_set_object (value, porder->priv->player);
286 		break;
287 	case PROP_PLAYING_ENTRY:
288 		g_value_set_boxed (value, porder->priv->playing_entry);
289 		break;
290 	default:
291 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
292 		break;
293 	}
294 }
295 
296 /**
297  * rb_play_order_get_player:
298  * @porder: #RBPlayOrder instance
299  *
300  * Only for use by #RBPlayOrder subclasses.
301  *
302  * Returns: (transfer none): #RBShellPlayer instance
303  */
304 RBShellPlayer *
305 rb_play_order_get_player (RBPlayOrder *porder)
306 {
307 	g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), NULL);
308 
309 	return porder->priv->player;
310 }
311 
312 /**
313  * rb_play_order_get_source:
314  * @porder: #RBPlayOrder instance
315  *
316  * Only for use by #RBPlayOrder subclasses.
317  *
318  * Returns: (transfer none): the playing #RBSource instance.
319  **/
320 RBSource *
321 rb_play_order_get_source (RBPlayOrder *porder)
322 {
323 	g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), NULL);
324 
325 	return porder->priv->source;
326 }
327 
328 /**
329  * rb_play_order_get_db:
330  * @porder: #RBPlayOrder instance
331  *
332  * Only for use by #RBPlayOrder subclasses.
333  *
334  * Returns: (transfer none): the #RhythmDB instance.
335  **/
336 RhythmDB *
337 rb_play_order_get_db (RBPlayOrder *porder)
338 {
339 	g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), NULL);
340 
341 	return porder->priv->db;
342 }
343 
344 /**
345  * rb_play_order_get_query_model:
346  * @porder: #RBPlayOrder instance
347  *
348  * Only for use by #RBPlayOrder subclasses.
349  *
350  * Returns: (transfer none): the active #RhythmDBQueryModel for the playing source.
351  */
352 RhythmDBQueryModel *
353 rb_play_order_get_query_model (RBPlayOrder *porder)
354 {
355 	g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), NULL);
356 
357 	return porder->priv->query_model;
358 }
359 
360 /**
361  * rb_play_order_player_is_playing:
362  * @porder: #RBPlayOrder instance
363  *
364  * Returns %TRUE if there is a current playing entry in the play order.
365  *
366  * Return value: %TRUE if playing
367  **/
368 gboolean
369 rb_play_order_player_is_playing (RBPlayOrder *porder)
370 {
371 	g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), FALSE);
372 
373 	return (porder->priv->playing_entry != NULL);
374 }
375 
376 /**
377  * rb_play_order_set_playing_entry:
378  * @porder: #RBPlayOrder instance
379  * @entry: (transfer none) (allow-none): The new playing entry (or NULL for none)
380  *
381  * Sets the playing entry in the play order.
382  **/
383 void
384 rb_play_order_set_playing_entry (RBPlayOrder *porder,
385 				 RhythmDBEntry *entry)
386 {
387 	g_return_if_fail (RB_IS_PLAY_ORDER (porder));
388 
389 	rb_play_order_set_playing_entry_internal (porder, entry);
390 }
391 
392 /**
393  * rb_play_order_get_playing_entry:
394  * @porder: #RBPlayOrder instance
395  *
396  * Returns the current playing entry in the play order.
397  *
398  * Returns: (transfer full) playing entry
399  */
400 RhythmDBEntry *
401 rb_play_order_get_playing_entry (RBPlayOrder *porder)
402 {
403 	RhythmDBEntry *entry;
404 
405 	g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), NULL);
406 
407 	entry = porder->priv->playing_entry;
408 	if (entry != NULL) {
409 		rhythmdb_entry_ref (entry);
410 	}
411 
412 	return entry;
413 }
414 
415 /**
416  * rb_play_order_playing_source_changed:
417  * @porder: #RBPlayOrder instance
418  * @source: New playing #RBSource
419  *
420  * Sets the playing #RBSource for the play order.  Should be called
421  * by #RBShellPlayer when the active source changes.  Subclasses
422  * should implement playing_source_changed() to make any necessary
423  * changes.
424  */
425 void
426 rb_play_order_playing_source_changed (RBPlayOrder *porder,
427 				      RBSource *source)
428 {
429 	RhythmDB *db = NULL;
430 
431 	g_return_if_fail (RB_IS_PLAY_ORDER (porder));
432 
433 	g_object_get (porder->priv->player,
434 		      "db", &db,
435 		      NULL);
436 
437 	if (db != porder->priv->db) {
438 		if (RB_PLAY_ORDER_GET_CLASS (porder)->db_changed)
439 			RB_PLAY_ORDER_GET_CLASS (porder)->db_changed (porder, db);
440 
441 		if (porder->priv->db != NULL) {
442 			g_object_unref (porder->priv->db);
443 		}
444 
445 		porder->priv->db = g_object_ref (db);
446 	}
447 
448 	g_object_unref (db);
449 
450 	if (source != porder->priv->source) {
451 		if (porder->priv->source) {
452 			g_signal_handler_disconnect (G_OBJECT (porder->priv->source),
453 						     porder->priv->query_model_change_id);
454 		}
455 
456 		porder->priv->source = source;
457 		if (porder->priv->source) {
458 			porder->priv->query_model_change_id =
459 				g_signal_connect_object (G_OBJECT (porder->priv->source),
460 							 "notify::query-model",
461 							 G_CALLBACK (rb_play_order_query_model_changed_cb),
462 							 porder, 0);
463 		}
464 
465 		rb_play_order_query_model_changed (porder);
466 
467 		if (RB_PLAY_ORDER_GET_CLASS (porder)->playing_source_changed)
468 			RB_PLAY_ORDER_GET_CLASS (porder)->playing_source_changed (porder);
469 
470 		rb_play_order_update_have_next_previous (porder);
471 	}
472 }
473 
474 static void
475 rb_play_order_query_model_changed_cb (GObject *source,
476 				      GParamSpec *arg,
477 				      RBPlayOrder *porder)
478 {
479 	rb_play_order_query_model_changed (porder);
480 }
481 
482 /**
483  * rb_play_order_query_model_changed:
484  * @porder: RBPlayOrder instance
485  *
486  * Updates the #RhythmDBQueryModel instance for the play order.
487  * Called from the #RBSource notify signal handler, and also from
488  * #rb_play_order_source_changed.  Subclasses should implement
489  * query_model_changed() to make any necessary adjustments if they
490  * store any state based on the contents of the #RhythmDBQueryModel.
491  */
492 void
493 rb_play_order_query_model_changed (RBPlayOrder *porder)
494 {
495 	RhythmDBQueryModel *new_model = NULL;
496 
497 	g_return_if_fail (RB_IS_PLAY_ORDER (porder));
498 
499 	if (porder->priv->source)
500 		g_object_get (porder->priv->source, "query-model", &new_model, NULL);
501 
502 	if (porder->priv->query_model == new_model) {
503 		if (new_model != NULL)
504 			g_object_unref (new_model);
505 		return;
506 	}
507 
508 	if (porder->priv->query_model != NULL) {
509 		g_signal_handlers_disconnect_by_func (G_OBJECT (porder->priv->query_model),
510 						      rb_play_order_entry_added_cb,
511 						      porder);
512 		g_signal_handlers_disconnect_by_func (G_OBJECT (porder->priv->query_model),
513 						      rb_play_order_row_deleted_cb,
514 						      porder);
515 		g_object_unref (porder->priv->query_model);
516 		porder->priv->query_model = NULL;
517 	}
518 
519 	if (new_model != NULL) {
520 		porder->priv->query_model = new_model;
521 		g_signal_connect_object (G_OBJECT (porder->priv->query_model),
522 					 "row-inserted",
523 					 G_CALLBACK (rb_play_order_entry_added_cb),
524 					 porder, 0);
525 		g_signal_connect_object (G_OBJECT (porder->priv->query_model),
526 					 "row-deleted",
527 					 G_CALLBACK (rb_play_order_row_deleted_cb),
528 					 porder, 0);
529 	}
530 
531 	if (RB_PLAY_ORDER_GET_CLASS (porder)->query_model_changed)
532 		RB_PLAY_ORDER_GET_CLASS (porder)->query_model_changed (porder);
533 
534 	rb_play_order_update_have_next_previous (porder);
535 }
536 
537 /**
538  * rb_play_order_entry_added_cb:
539  * @model: #GtkTreeModel
540  * @path: #GtkTreePath for added entry
541  * @iter: #GtkTreeIter for added entry
542  * @porder: #RBPlayOrder instance
543  *
544  * Called when a new entry is added to the active #RhythmDBQueryModel.
545  * Subclasses should implement entry_added() to make any necessary
546  * changes if they store any state based on the contents of the
547  * #RhythmDBQueryModel.
548  */
549 static void
550 rb_play_order_entry_added_cb (GtkTreeModel *model,
551 			      GtkTreePath *path,
552 			      GtkTreeIter *iter,
553 			      RBPlayOrder *porder)
554 {
555 	RhythmDBEntry *entry;
556 
557 	entry = rhythmdb_query_model_iter_to_entry (RHYTHMDB_QUERY_MODEL (model),
558 						    iter);
559 	if (RB_PLAY_ORDER_GET_CLASS (porder)->entry_added)
560 		RB_PLAY_ORDER_GET_CLASS (porder)->entry_added (porder, entry);
561 
562 	if (!rhythmdb_query_model_has_pending_changes (RHYTHMDB_QUERY_MODEL (model)))
563 		rb_play_order_update_have_next_previous (porder);
564 
565 	rhythmdb_entry_unref (entry);
566 }
567 
568 /**
569  * rb_play_order_row_deleted_cb:
570  * @model: #GtkTreeModel
571  * @entry: the #RhythmDBEntry removed from the model
572  * @porder: #RBPlayOrder instance
573  *
574  * Called when an entry is removed from the active #RhythmDBQueryModel.
575  * Subclasses should implement entry_removed() to make any necessary
576  * changes if they store any state based on the contents of the
577  * #RhythmDBQueryModel.
578  *
579  * If the removed entry is the current playing entry, the playing-entry-deleted
580  * signal is emitted.
581  */
582 static void
583 rb_play_order_row_deleted_cb (GtkTreeModel *model,
584 			      GtkTreePath *row,
585 			      RBPlayOrder *porder)
586 {
587 	RhythmDBEntry *entry;
588 
589 	entry = rhythmdb_query_model_tree_path_to_entry (RHYTHMDB_QUERY_MODEL (model), row);
590 	if (entry == porder->priv->playing_entry) {
591 		RB_PLAY_ORDER_GET_CLASS (porder)->playing_entry_removed (porder, entry);
592 	}
593 
594 	if (RB_PLAY_ORDER_GET_CLASS (porder)->entry_removed)
595 		RB_PLAY_ORDER_GET_CLASS (porder)->entry_removed (porder, entry);
596 
597 	if (!rhythmdb_query_model_has_pending_changes (RHYTHMDB_QUERY_MODEL (model)))
598 		rb_play_order_update_have_next_previous (porder);
599 
600 	rhythmdb_entry_unref (entry);
601 }
602 
603 static gboolean
604 default_has_next (RBPlayOrder *porder)
605 {
606 	RhythmDBEntry *entry;
607 	gboolean res;
608 
609 	entry = rb_play_order_get_next (porder);
610 
611 	res = (entry != NULL);
612 	if (entry)
613 		rhythmdb_entry_unref (entry);
614 
615 	return res;
616 }
617 
618 static gboolean
619 default_has_previous (RBPlayOrder *porder)
620 {
621 	RhythmDBEntry *entry;
622 	gboolean res;
623 
624 	entry = rb_play_order_get_previous (porder);
625 
626 	res = (entry != NULL);
627 	if (entry)
628 		rhythmdb_entry_unref (entry);
629 
630 	return res;
631 }
632 
633 static gboolean
634 sync_playing_entry_cb (RBPlayOrder *porder)
635 {
636 	RBShellPlayer *player;
637 
638 	GDK_THREADS_ENTER ();
639 	
640 	player = rb_play_order_get_player (porder);
641 
642 	if (porder->priv->playing_entry) {
643 		rb_shell_player_play_entry (player,
644 					    porder->priv->playing_entry,
645 					    rb_play_order_get_source (porder));
646 	} else {
647 		/* Just try to play something.  This is mostly here to make
648 		 * the play queue work correctly, but it might be helpful otherwise.
649 		 */
650 		GError *error = NULL;
651 		if (!rb_shell_player_do_next (player, &error)) {
652 			if (error->domain != RB_SHELL_PLAYER_ERROR ||
653 			    error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST)
654 				g_warning ("sync_playing_entry_cb: Unhandled error: %s", error->message);
655 		}
656 	}
657 	porder->priv->sync_playing_entry_id = 0;
658 
659 	GDK_THREADS_LEAVE ();
660 	return FALSE;
661 }
662 
663 static void
664 default_playing_entry_removed (RBPlayOrder *porder,
665 			       RhythmDBEntry *entry)
666 {
667 	RBShellPlayer *player = rb_play_order_get_player (porder);
668 	RBSource *source = rb_shell_player_get_playing_source (player);
669 
670 	rb_debug ("playing entry removed");
671 
672 	/* only clear the playing source if the source this play order is using
673 	 * is currently playing.
674 	 */
675 	if (source == rb_play_order_get_source (porder)) {
676 		switch (rb_source_handle_eos (source)) {
677 		case RB_SOURCE_EOF_ERROR:
678 		case RB_SOURCE_EOF_STOP:
679 		case RB_SOURCE_EOF_RETRY:
680 			/* stop playing */
681 			rb_shell_player_stop (player);
682 			break;
683 		case RB_SOURCE_EOF_NEXT:
684 			{
685 				RhythmDBEntry *next_entry;
686 
687 				/* go to next song, in an idle function so that other handlers run first */
688 				next_entry = rb_play_order_get_next (porder);
689 
690 				if (next_entry == entry) {
691 					rhythmdb_entry_unref (next_entry);
692 					next_entry = NULL;
693 				}
694 
695 				rb_play_order_set_playing_entry_internal (porder, next_entry);
696 				if (porder->priv->sync_playing_entry_id == 0) {
697 					porder->priv->sync_playing_entry_id =
698 						g_idle_add_full (G_PRIORITY_HIGH_IDLE,
699 								 (GSourceFunc) sync_playing_entry_cb,
700 								 porder,
701 								 NULL);
702 				}
703 
704 				if (next_entry != NULL) {
705 					rhythmdb_entry_unref (next_entry);
706 				}
707 
708 				break;
709 			}
710 		}
711 	} else {
712 		rb_play_order_set_playing_entry (porder, NULL);
713 	}
714 }
715 
716 /**
717  * rb_play_order_has_next:
718  * @porder: RBPlayOrder instance.
719  *
720  * If there is no current playing entry, returns true if the play order is non-empty.
721  *
722  * Returns: true if there is an entry after the current playing entry in the play order.
723  */
724 gboolean
725 rb_play_order_has_next (RBPlayOrder *porder)
726 {
727 	g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), FALSE);
728 	g_return_val_if_fail (RB_PLAY_ORDER_GET_CLASS (porder)->has_next != NULL, FALSE);
729 
730 	return RB_PLAY_ORDER_GET_CLASS (porder)->has_next (porder);
731 }
732 
733 /**
734  * rb_play_order_get_next:
735  * @porder: RBPlayOrder instance
736  *
737  * Returns the next entry in the play order, or the first if not currently playing.
738  *
739  * Returns: (transfer full): next entry to play
740  */
741 RhythmDBEntry *
742 rb_play_order_get_next (RBPlayOrder *porder)
743 {
744 	g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), NULL);
745 	g_return_val_if_fail (RB_PLAY_ORDER_GET_CLASS (porder)->get_next != NULL, NULL);
746 
747 	return RB_PLAY_ORDER_GET_CLASS (porder)->get_next (porder);
748 }
749 
750 /**
751  * rb_play_order_go_next:
752  * @porder: RBPlayOrder instance
753  *
754  * Moves to the next entry in the play order.  If not currently playing, sets the
755  * first entry in the play order as the playing entry.
756  */
757 void
758 rb_play_order_go_next (RBPlayOrder *porder)
759 {
760 	g_return_if_fail (RB_IS_PLAY_ORDER (porder));
761 
762 	if (RB_PLAY_ORDER_GET_CLASS (porder)->go_next) {
763 		RB_PLAY_ORDER_GET_CLASS (porder)->go_next (porder);
764 	} else if (RB_PLAY_ORDER_GET_CLASS (porder)->get_next) {
765 		RhythmDBEntry *entry;
766 
767 		entry = RB_PLAY_ORDER_GET_CLASS (porder)->get_next (porder);
768 		rb_play_order_set_playing_entry (porder, entry);
769 		if (entry != NULL) {
770 			rhythmdb_entry_unref (entry);
771 		}
772 	}
773 }
774 
775 /**
776  * rb_play_order_has_previous:
777  * @porder: RBPlayOrder instance
778  *
779  * Returns %TRUE if there is an entry before the current entry in the play order.
780  * If not currently playing, returns %FALSE.
781  *
782  * Return value: %TRUE if previous entry exists
783  */
784 gboolean
785 rb_play_order_has_previous (RBPlayOrder *porder)
786 {
787 	g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), FALSE);
788 	g_return_val_if_fail (RB_PLAY_ORDER_GET_CLASS (porder)->has_previous != NULL, FALSE);
789 
790 	return RB_PLAY_ORDER_GET_CLASS (porder)->has_previous (porder);
791 }
792 
793 /**
794  * rb_play_order_get_previous:
795  * @porder: RBPlayOrder instance
796  *
797  * Returns the previous entry in the play order, or NULL if not currently playing.
798  *
799  * Return value: (transfer full): previous entry
800  */
801 RhythmDBEntry *
802 rb_play_order_get_previous (RBPlayOrder *porder)
803 {
804 	g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), NULL);
805 	g_return_val_if_fail (RB_PLAY_ORDER_GET_CLASS (porder)->get_previous != NULL, NULL);
806 
807 	return RB_PLAY_ORDER_GET_CLASS (porder)->get_previous (porder);
808 }
809 
810 /**
811  * rb_play_order_go_previous:
812  * @porder: RBPlayOrder instance
813  *
814  * Moves to the previous entry in the play order.  If not currently playing, does nothing.
815  */
816 void
817 rb_play_order_go_previous (RBPlayOrder *porder)
818 {
819 	g_return_if_fail (RB_IS_PLAY_ORDER (porder));
820 
821 	if (RB_PLAY_ORDER_GET_CLASS (porder)->go_previous) {
822 		RB_PLAY_ORDER_GET_CLASS (porder)->go_previous (porder);
823 	} else if (RB_PLAY_ORDER_GET_CLASS (porder)->get_previous) {
824 		RhythmDBEntry *entry;
825 
826 		entry = RB_PLAY_ORDER_GET_CLASS (porder)->get_previous (porder);
827 		rb_play_order_set_playing_entry (porder, entry);
828 		if (entry != NULL) {
829 			rhythmdb_entry_unref (entry);
830 		}
831 	}
832 }
833 
834 /**
835  * rb_play_order_model_not_empty:
836  * @porder: RBPlayOrder instance
837  *
838  * Returns %TRUE if the #RhythmDBQueryModel is not empty.
839  * Can be used to implement has_next and has_previous for play orders
840  * that have no beginning or end.
841  *
842  * Return value: %TRUE if not empty
843  */
844 gboolean
845 rb_play_order_model_not_empty (RBPlayOrder *porder)
846 {
847 	GtkTreeIter iter;
848 
849 	g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), FALSE);
850 
851 	if (!porder->priv->query_model)
852 		return FALSE;
853 	return gtk_tree_model_get_iter_first (GTK_TREE_MODEL (porder->priv->query_model), &iter);
854 }
855 
856 /**
857  * rb_play_order_update_have_next_previous:
858  * @porder: RBPlayOrder instance
859  *
860  * Updates the have_next and have_previous flags, and emits a signal if they
861  * change.  This is called whenever the play order changes in such a way that
862  * these flags might change.
863  */
864 void
865 rb_play_order_update_have_next_previous (RBPlayOrder *porder)
866 {
867 	gboolean have_next;
868 	gboolean have_previous;
869 
870 	g_return_if_fail (RB_IS_PLAY_ORDER (porder));
871 
872 	have_next = rb_play_order_has_next (porder);
873 	have_previous = rb_play_order_has_previous (porder);
874 
875 	if ((have_next != porder->priv->have_next) ||
876 	    (have_previous != porder->priv->have_previous)) {
877 		g_signal_emit (G_OBJECT (porder), rb_play_order_signals[HAVE_NEXT_PREVIOUS_CHANGED],
878 			       0, have_next, have_previous);
879 		porder->priv->have_next = have_next;
880 		porder->priv->have_previous = have_previous;
881 	}
882 }
883 
884 /* annotations for methods */
885 
886 /**
887  * get_next:
888  * @porder: the play order
889  *
890  * Return value: (transfer full): the next entry
891  */
892 
893 /**
894  * get_previous:
895  * @porder: the play order
896  *
897  * Return value: (transfer full): the previous entry
898  */