2006-12-14 Francisco Javier F. Serrador <serrador@openshine.com>
[rhythmbox.git] / shell / rb-play-order.c
blob8272c268b64983d6322603001915da60f7a80b06
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * arch-tag: Implementation of base class for play order classes
5 * Copyright (C) 2003 Jeffrey Yasskin <jyasskin@mail.utexas.edu>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "config.h"
25 #include <string.h>
27 #include <glib/gi18n.h>
29 #include "rb-play-order.h"
31 #include "rb-shell-player.h"
32 #include "rb-debug.h"
33 #include "rb-preferences.h"
34 #include "rb-marshal.h"
36 /* Play Orders */
37 #include "rb-play-order-linear.h"
38 #include "rb-play-order-linear-loop.h"
39 #include "rb-play-order-shuffle.h"
40 #include "rb-play-order-random-equal-weights.h"
41 #include "rb-play-order-random-by-age.h"
42 #include "rb-play-order-random-by-rating.h"
43 #include "rb-play-order-random-by-age-and-rating.h"
44 #include "rb-play-order-queue.h"
46 static void rb_play_order_class_init (RBPlayOrderClass *klass);
47 static void rb_play_order_init (RBPlayOrder *porder);
48 static GObject *rb_play_order_constructor (GType type, guint n_construct_properties,
49 GObjectConstructParam *construct_properties);
50 static void rb_play_order_finalize (GObject *object);
51 static void rb_play_order_set_property (GObject *object,
52 guint prop_id,
53 const GValue *value,
54 GParamSpec *pspec);
55 static void rb_play_order_get_property (GObject *object,
56 guint prop_id,
57 GValue *value,
58 GParamSpec *pspec);
60 static gboolean default_has_next (RBPlayOrder *porder);
61 static gboolean default_has_previous (RBPlayOrder *porder);
62 static void default_playing_entry_removed (RBPlayOrder *porder, RhythmDBEntry *entry);
64 static void rb_play_order_entry_added_cb (GtkTreeModel *model,
65 GtkTreePath *path,
66 GtkTreeIter *iter,
67 RBPlayOrder *porder);
68 static void rb_play_order_row_deleted_cb (GtkTreeModel *model,
69 GtkTreePath *path,
70 RBPlayOrder *porder);
71 static void rb_play_order_query_model_changed_cb (GObject *source,
72 GParamSpec *arg,
73 RBPlayOrder *porder);
74 static void rb_play_order_update_have_next_previous (RBPlayOrder *porder);
76 struct RBPlayOrderPrivate
78 RBShellPlayer *player;
79 RBSource *source;
80 RhythmDB *db;
81 RhythmDBQueryModel *query_model;
82 RhythmDBEntry *playing_entry;
83 gulong query_model_change_id;
84 gulong sync_playing_entry_id;
85 gboolean have_next;
86 gboolean have_previous;
89 #define RB_PLAY_ORDER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_PLAY_ORDER, RBPlayOrderPrivate))
91 enum
93 PROP_0,
94 PROP_PLAYER,
95 PROP_PLAYING_ENTRY
98 enum
100 HAVE_NEXT_PREVIOUS_CHANGED,
101 LAST_SIGNAL
104 static guint rb_play_order_signals[LAST_SIGNAL] = { 0 };
106 G_DEFINE_TYPE (RBPlayOrder, rb_play_order, G_TYPE_OBJECT)
108 static void
109 rb_play_order_class_init (RBPlayOrderClass *klass)
111 GObjectClass *object_class = G_OBJECT_CLASS (klass);
113 object_class->constructor = rb_play_order_constructor;
114 object_class->finalize = rb_play_order_finalize;
115 object_class->set_property = rb_play_order_set_property;
116 object_class->get_property = rb_play_order_get_property;
118 klass->has_next = default_has_next;
119 klass->has_previous = default_has_previous;
120 klass->playing_entry_removed = default_playing_entry_removed;
122 g_object_class_install_property (object_class,
123 PROP_PLAYER,
124 g_param_spec_object ("player",
125 "RBShellPlayer",
126 "Rhythmbox Player",
127 RB_TYPE_SHELL_PLAYER,
128 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
130 g_object_class_install_property (object_class,
131 PROP_PLAYING_ENTRY,
132 g_param_spec_boxed ("playing-entry",
133 "RhythmDBEntry",
134 "Playing entry",
135 RHYTHMDB_TYPE_ENTRY,
136 G_PARAM_READWRITE));
138 rb_play_order_signals[HAVE_NEXT_PREVIOUS_CHANGED] =
139 g_signal_new ("have_next_previous_changed",
140 G_OBJECT_CLASS_TYPE (object_class),
141 G_SIGNAL_RUN_LAST,
142 G_STRUCT_OFFSET (RBPlayOrderClass, have_next_previous_changed),
143 NULL, NULL,
144 rb_marshal_VOID__BOOLEAN_BOOLEAN,
145 G_TYPE_NONE,
146 2, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
148 g_type_class_add_private (klass, sizeof (RBPlayOrderPrivate));
151 static void
152 rb_play_order_init (RBPlayOrder *porder)
154 porder->priv = RB_PLAY_ORDER_GET_PRIVATE (porder);
157 static GObject *
158 rb_play_order_constructor (GType type,
159 guint n_construct_properties,
160 GObjectConstructParam *construct_properties)
162 RBPlayOrder *porder;
164 porder = RB_PLAY_ORDER (G_OBJECT_CLASS (rb_play_order_parent_class)
165 ->constructor (type, n_construct_properties, construct_properties));
167 return G_OBJECT (porder);
170 static void
171 rb_play_order_finalize (GObject *object)
173 RBPlayOrder *porder;
175 g_return_if_fail (object != NULL);
176 g_return_if_fail (RB_IS_PLAY_ORDER (object));
178 porder = RB_PLAY_ORDER (object);
180 if (porder->priv->playing_entry != NULL) {
181 rhythmdb_entry_unref (porder->priv->playing_entry);
184 if (porder->priv->query_model != NULL) {
185 g_signal_handlers_disconnect_by_func (G_OBJECT (porder->priv->query_model),
186 G_CALLBACK (rb_play_order_entry_added_cb),
187 porder);
188 g_signal_handlers_disconnect_by_func (G_OBJECT (porder->priv->query_model),
189 G_CALLBACK (rb_play_order_row_deleted_cb),
190 porder);
191 g_object_unref (porder->priv->query_model);
194 if (porder->priv->db != NULL) {
195 g_object_unref (porder->priv->db);
198 G_OBJECT_CLASS (rb_play_order_parent_class)->finalize (object);
201 static void
202 rb_play_order_set_player (RBPlayOrder *porder,
203 RBShellPlayer *player)
205 porder->priv->player = player;
208 static void
209 rb_play_order_set_playing_entry_internal (RBPlayOrder *porder,
210 RhythmDBEntry *entry)
212 RhythmDBEntry *old_entry;
214 old_entry = porder->priv->playing_entry;
215 porder->priv->playing_entry = entry;
217 if (porder->priv->playing_entry != NULL) {
218 rhythmdb_entry_ref (porder->priv->playing_entry);
221 if (RB_PLAY_ORDER_GET_CLASS (porder)->playing_entry_changed)
222 RB_PLAY_ORDER_GET_CLASS (porder)->playing_entry_changed (porder, old_entry, entry);
224 if (old_entry != NULL) {
225 rhythmdb_entry_unref (old_entry);
228 rb_play_order_update_have_next_previous (porder);
231 static void
232 rb_play_order_set_property (GObject *object,
233 guint prop_id,
234 const GValue *value,
235 GParamSpec *pspec)
237 RBPlayOrder *porder = RB_PLAY_ORDER (object);
239 switch (prop_id) {
240 case PROP_PLAYER:
241 rb_play_order_set_player (porder, g_value_get_object (value));
242 break;
243 case PROP_PLAYING_ENTRY:
244 rb_play_order_set_playing_entry_internal (porder, g_value_get_boxed (value));
245 break;
246 default:
247 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
248 break;
252 static void
253 rb_play_order_get_property (GObject *object,
254 guint prop_id,
255 GValue *value,
256 GParamSpec *pspec)
258 RBPlayOrder *porder = RB_PLAY_ORDER (object);
260 switch (prop_id)
262 case PROP_PLAYER:
263 g_value_set_object (value, porder->priv->player);
264 break;
265 case PROP_PLAYING_ENTRY:
266 g_value_set_boxed (value, porder->priv->playing_entry);
267 break;
268 default:
269 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
270 break;
275 * rb_play_order_get_orders:
277 * Defines the set of available play orders, their translatable descriptions, their
278 * constructor functions, whether they should appear in a drop-down list of
279 * play orders, and which one is the default.
281 * This should be the only function with full knowledge of what play orders are
282 * available.
284 const RBPlayOrderDescription *
285 rb_play_order_get_orders (void)
287 /* Exactly one entry must have is_default==TRUE. Otherwise you will
288 * cause a g_assert(). */
289 static const RBPlayOrderDescription orders[] = {
290 { "linear", N_("Linear"), rb_linear_play_order_new, TRUE, TRUE },
291 { "linear-loop", N_("Linear looping"), rb_linear_play_order_loop_new, TRUE, FALSE },
292 { "shuffle", N_("Shuffle"), rb_shuffle_play_order_new, TRUE, FALSE },
293 { "random-equal-weights", N_("Random with equal weights"), rb_random_play_order_equal_weights_new, TRUE, FALSE },
294 { "random-by-age", N_("Random by time since last play"), rb_random_play_order_by_age_new, TRUE, FALSE },
295 { "random-by-rating", N_("Random by rating"), rb_random_play_order_by_rating_new, TRUE, FALSE },
296 { "random-by-age-and-rating", N_("Random by time since last play and rating"), rb_random_play_order_by_age_and_rating_new, TRUE, FALSE },
297 { "queue", N_("Linear, removing entries once played"), rb_queue_play_order_new, FALSE, FALSE },
298 { NULL, NULL, NULL },
300 return orders;
304 * rb_play_order_new:
305 * @porder_name: Play order type name
306 * @player: #RBShellPlayer instance to attach to
308 * Creates a new #RBPlayOrder of the specified type.
310 * Returns: #RBPlayOrder instance
312 RBPlayOrder *
313 rb_play_order_new (const char* porder_name,
314 RBShellPlayer *player)
316 int default_index = -1;
317 const RBPlayOrderDescription *orders = rb_play_order_get_orders ();
318 int i;
320 g_return_val_if_fail (porder_name != NULL, NULL);
321 g_return_val_if_fail (player != NULL, NULL);
323 for (i=0; orders[i].name != NULL; ++i) {
324 if (strcmp (orders[i].name, porder_name) == 0)
325 return orders[i].constructor (player);
326 if (orders[i].is_default) {
327 /* There must not be two default play orders */
328 g_assert (default_index == -1);
329 default_index = i;
332 /* There must be a default play order */
333 g_assert (default_index != -1);
335 g_warning ("Unknown value \"%s\" in GConf key \"" CONF_STATE_PLAY_ORDER
336 "\". Using %s play order.", porder_name, orders[default_index].name);
337 return orders[default_index].constructor (player);
341 * rb_play_order_get_player:
342 * @porder: #RBPlayOrder instance
344 * Only for use by #RBPlayOrder subclasses.
346 * Returns: #RBShellPlayer instance
348 RBShellPlayer *
349 rb_play_order_get_player (RBPlayOrder *porder)
351 g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), NULL);
353 return porder->priv->player;
357 * rb_play_order_get_source:
358 * @porder: #RBPlayOrder instance
360 * Only for use by #RBPlayOrder subclasses.
362 * Returns: the playing #RBSource instance.
364 RBSource *
365 rb_play_order_get_source (RBPlayOrder *porder)
367 g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), NULL);
369 return porder->priv->source;
373 * rb_play_order_get_db:
374 * @porder: #RBPlayOrder instance
376 * Only for use by #RBPlayOrder subclasses.
378 * Returns: the #RhythmDB instance.
380 RhythmDB *
381 rb_play_order_get_db (RBPlayOrder *porder)
383 g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), NULL);
385 return porder->priv->db;
389 * rb_play_order_get_query_model:
390 * @porder: #RBPlayOrder instance
392 * Only for use by #RBPlayOrder subclasses.
394 * Returns: the active #RhythmDBQueryModel for the playing source.
396 RhythmDBQueryModel *
397 rb_play_order_get_query_model (RBPlayOrder *porder)
399 g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), NULL);
401 return porder->priv->query_model;
405 * rb_play_order_player_is_playing:
406 * @porder: #RBPlayOrder instance
408 * Returns: true if there is a current playing entry in the play order.
410 gboolean
411 rb_play_order_player_is_playing (RBPlayOrder *porder)
413 g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), FALSE);
415 return (porder->priv->playing_entry != NULL);
419 * rb_play_order_set_playing_entry:
420 * @porder: #RBPlayOrder instance
421 * @entry: The new playing entry (or NULL for none)
423 * Sets the playing entry in the play order.
425 void
426 rb_play_order_set_playing_entry (RBPlayOrder *porder,
427 RhythmDBEntry *entry)
429 g_return_if_fail (RB_IS_PLAY_ORDER (porder));
431 rb_play_order_set_playing_entry_internal (porder, entry);
435 * rb_play_order_get_playing_entry:
436 * @porder: #RBPlayOrder instance
438 * Returns: the current playing entry in the play order.
440 RhythmDBEntry *
441 rb_play_order_get_playing_entry (RBPlayOrder *porder)
443 RhythmDBEntry *entry;
445 g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), NULL);
447 entry = porder->priv->playing_entry;
448 if (entry != NULL) {
449 rhythmdb_entry_ref (entry);
452 return entry;
456 * rb_play_order_playing_source_changed:
457 * @porder: #RBPlayOrder instance
458 * @source: New playing #RBSource
460 * Sets the playing #RBSource for the play order. Should be called
461 * by #RBShellPlayer when the active source changes. Subclasses
462 * should implement playing_source_changed() to make any necessary
463 * changes.
465 void
466 rb_play_order_playing_source_changed (RBPlayOrder *porder,
467 RBSource *source)
469 RhythmDB *db = NULL;
471 g_return_if_fail (RB_IS_PLAY_ORDER (porder));
473 g_object_get (porder->priv->player,
474 "db", &db,
475 NULL);
477 if (db != porder->priv->db) {
478 if (RB_PLAY_ORDER_GET_CLASS (porder)->db_changed)
479 RB_PLAY_ORDER_GET_CLASS (porder)->db_changed (porder, db);
481 if (porder->priv->db != NULL) {
482 g_object_unref (porder->priv->db);
485 porder->priv->db = g_object_ref (db);
488 g_object_unref (db);
490 if (source != porder->priv->source) {
491 if (porder->priv->source) {
492 g_signal_handler_disconnect (G_OBJECT (porder->priv->source),
493 porder->priv->query_model_change_id);
496 porder->priv->source = source;
497 if (porder->priv->source) {
498 porder->priv->query_model_change_id =
499 g_signal_connect_object (G_OBJECT (porder->priv->source),
500 "notify::query-model",
501 G_CALLBACK (rb_play_order_query_model_changed_cb),
502 porder, 0);
505 rb_play_order_query_model_changed (porder);
507 if (RB_PLAY_ORDER_GET_CLASS (porder)->playing_source_changed)
508 RB_PLAY_ORDER_GET_CLASS (porder)->playing_source_changed (porder);
510 rb_play_order_update_have_next_previous (porder);
514 static void
515 rb_play_order_query_model_changed_cb (GObject *source,
516 GParamSpec *arg,
517 RBPlayOrder *porder)
519 rb_play_order_query_model_changed (porder);
523 * rb_play_order_query_model_changed:
524 * @porder: RBPlayOrder instance
526 * Updates the #RhythmDBQueryModel instance for the play order.
527 * Called from the #RBSource notify signal handler, and also from
528 * #rb_play_order_source_changed. Subclasses should implement
529 * query_model_changed() to make any necessary adjustments if they
530 * store any state based on the contents of the #RhythmDBQueryModel.
532 void
533 rb_play_order_query_model_changed (RBPlayOrder *porder)
535 RhythmDBQueryModel *new_model = NULL;
537 g_return_if_fail (RB_IS_PLAY_ORDER (porder));
539 if (porder->priv->source)
540 g_object_get (porder->priv->source, "query-model", &new_model, NULL);
542 if (porder->priv->query_model == new_model) {
543 g_object_unref (new_model);
544 return;
547 if (porder->priv->query_model != NULL) {
548 g_signal_handlers_disconnect_by_func (G_OBJECT (porder->priv->query_model),
549 rb_play_order_entry_added_cb,
550 porder);
551 g_signal_handlers_disconnect_by_func (G_OBJECT (porder->priv->query_model),
552 rb_play_order_row_deleted_cb,
553 porder);
554 g_object_unref (porder->priv->query_model);
555 porder->priv->query_model = NULL;
558 if (new_model != NULL) {
559 porder->priv->query_model = new_model;
560 g_signal_connect_object (G_OBJECT (porder->priv->query_model),
561 "row-inserted",
562 G_CALLBACK (rb_play_order_entry_added_cb),
563 porder, 0);
564 g_signal_connect_object (G_OBJECT (porder->priv->query_model),
565 "row-deleted",
566 G_CALLBACK (rb_play_order_row_deleted_cb),
567 porder, 0);
570 if (RB_PLAY_ORDER_GET_CLASS (porder)->query_model_changed)
571 RB_PLAY_ORDER_GET_CLASS (porder)->query_model_changed (porder);
573 rb_play_order_update_have_next_previous (porder);
577 * rb_play_order_entry_added_cb:
578 * @model: #GtkTreeModel
579 * @path: #GtkTreePath for added entry
580 * @iter: #GtkTreeIter for added entry
581 * @porder: #RBPlayOrder instance
583 * Called when a new entry is added to the active #RhythmDBQueryModel.
584 * Subclasses should implement entry_added() to make any necessary
585 * changes if they store any state based on the contents of the
586 * #RhythmDBQueryModel.
588 static void
589 rb_play_order_entry_added_cb (GtkTreeModel *model,
590 GtkTreePath *path,
591 GtkTreeIter *iter,
592 RBPlayOrder *porder)
594 RhythmDBEntry *entry;
596 entry = rhythmdb_query_model_iter_to_entry (RHYTHMDB_QUERY_MODEL (model),
597 iter);
598 if (RB_PLAY_ORDER_GET_CLASS (porder)->entry_added)
599 RB_PLAY_ORDER_GET_CLASS (porder)->entry_added (porder, entry);
601 if (!rhythmdb_query_model_has_pending_changes (RHYTHMDB_QUERY_MODEL (model)))
602 rb_play_order_update_have_next_previous (porder);
604 rhythmdb_entry_unref (entry);
608 * rb_play_order_row_deleted_cb:
609 * @model: #GtkTreeModel
610 * @entry: the #RhythmDBEntry removed from the model
611 * @porder: #RBPlayOrder instance
613 * Called when an entry is removed from the active #RhythmDBQueryModel.
614 * Subclasses should implement entry_removed() to make any necessary
615 * changes if they store any state based on the contents of the
616 * #RhythmDBQueryModel.
618 * If the removed entry is the current playing entry, the playing-entry-deleted
619 * signal is emitted.
621 static void
622 rb_play_order_row_deleted_cb (GtkTreeModel *model,
623 GtkTreePath *row,
624 RBPlayOrder *porder)
626 RhythmDBEntry *entry;
628 entry = rhythmdb_query_model_tree_path_to_entry (RHYTHMDB_QUERY_MODEL (model), row);
629 if (entry == porder->priv->playing_entry) {
630 RB_PLAY_ORDER_GET_CLASS (porder)->playing_entry_removed (porder, entry);
633 if (RB_PLAY_ORDER_GET_CLASS (porder)->entry_removed)
634 RB_PLAY_ORDER_GET_CLASS (porder)->entry_removed (porder, entry);
636 if (!rhythmdb_query_model_has_pending_changes (RHYTHMDB_QUERY_MODEL (model)))
637 rb_play_order_update_have_next_previous (porder);
639 rhythmdb_entry_unref (entry);
642 static gboolean
643 default_has_next (RBPlayOrder *porder)
645 RhythmDBEntry *entry;
646 gboolean res;
648 entry = rb_play_order_get_next (porder);
650 res = (entry != NULL);
651 if (entry)
652 rhythmdb_entry_unref (entry);
654 return res;
657 static gboolean
658 default_has_previous (RBPlayOrder *porder)
660 RhythmDBEntry *entry;
661 gboolean res;
663 entry = rb_play_order_get_previous (porder);
665 res = (entry != NULL);
666 if (entry)
667 rhythmdb_entry_unref (entry);
669 return res;
672 static gboolean
673 sync_playing_entry_cb (RBPlayOrder *porder)
675 RBShellPlayer *player;
677 GDK_THREADS_ENTER ();
679 player = rb_play_order_get_player (porder);
681 if (porder->priv->playing_entry) {
682 rb_shell_player_play_entry (player,
683 porder->priv->playing_entry,
684 rb_play_order_get_source (porder));
685 } else {
686 /* Just try to play something. This is mostly here to make
687 * the play queue work correctly, but it might be helpful otherwise.
689 GError *error = NULL;
690 if (!rb_shell_player_do_next (player, &error)) {
691 if (error->domain != RB_SHELL_PLAYER_ERROR ||
692 error->code != RB_SHELL_PLAYER_ERROR_END_OF_PLAYLIST)
693 g_warning ("sync_playing_entry_cb: Unhandled error: %s", error->message);
696 porder->priv->sync_playing_entry_id = 0;
698 GDK_THREADS_LEAVE ();
699 return FALSE;
702 static void
703 default_playing_entry_removed (RBPlayOrder *porder,
704 RhythmDBEntry *entry)
706 RBShellPlayer *player = rb_play_order_get_player (porder);
707 RBSource *source = rb_shell_player_get_playing_source (player);
709 rb_debug ("playing entry removed");
711 /* only clear the playing source if the source this play order is using
712 * is currently playing.
714 if (source == rb_play_order_get_source (porder)) {
715 switch (rb_source_handle_eos (source)) {
716 case RB_SOURCE_EOF_ERROR:
717 case RB_SOURCE_EOF_STOP:
718 case RB_SOURCE_EOF_RETRY:
719 /* stop playing */
720 rb_shell_player_set_playing_source (player, NULL);
721 break;
722 case RB_SOURCE_EOF_NEXT:
724 RhythmDBEntry *next_entry;
726 /* go to next song, in an idle function so that other handlers run first */
727 next_entry = rb_play_order_get_next (porder);
729 if (next_entry == entry) {
730 rhythmdb_entry_unref (next_entry);
731 next_entry = NULL;
734 rb_play_order_set_playing_entry_internal (porder, next_entry);
735 if (porder->priv->sync_playing_entry_id == 0) {
736 porder->priv->sync_playing_entry_id =
737 g_idle_add_full (G_PRIORITY_HIGH_IDLE,
738 (GSourceFunc) sync_playing_entry_cb,
739 porder,
740 NULL);
743 if (next_entry != NULL) {
744 rhythmdb_entry_unref (next_entry);
747 break;
750 } else {
751 rb_play_order_set_playing_entry (porder, NULL);
756 * rb_play_order_has_next:
757 * @porder: RBPlayOrder instance.
759 * If there is no current playing entry, returns true if the play order is non-empty.
761 * Returns: true if there is an entry after the current playing entry in the play order.
763 gboolean
764 rb_play_order_has_next (RBPlayOrder *porder)
766 g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), FALSE);
767 g_return_val_if_fail (RB_PLAY_ORDER_GET_CLASS (porder)->has_next != NULL, FALSE);
769 return RB_PLAY_ORDER_GET_CLASS (porder)->has_next (porder);
773 * rb_play_order_get_next:
774 * @porder: RBPlayOrder instance
776 * Returns: the next entry in the play order, or the first if not currently playing.
778 RhythmDBEntry *
779 rb_play_order_get_next (RBPlayOrder *porder)
781 g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), NULL);
782 g_return_val_if_fail (RB_PLAY_ORDER_GET_CLASS (porder)->get_next != NULL, NULL);
784 return RB_PLAY_ORDER_GET_CLASS (porder)->get_next (porder);
788 * rb_play_order_go_next:
789 * @porder: RBPlayOrder instance
791 * Moves to the next entry in the play order. If not currently playing, sets the
792 * first entry in the play order as the playing entry.
794 void
795 rb_play_order_go_next (RBPlayOrder *porder)
797 g_return_if_fail (RB_IS_PLAY_ORDER (porder));
799 if (RB_PLAY_ORDER_GET_CLASS (porder)->go_next) {
800 RB_PLAY_ORDER_GET_CLASS (porder)->go_next (porder);
801 } else if (RB_PLAY_ORDER_GET_CLASS (porder)->get_next) {
802 RhythmDBEntry *entry;
804 entry = RB_PLAY_ORDER_GET_CLASS (porder)->get_next (porder);
805 rb_play_order_set_playing_entry (porder, entry);
806 if (entry != NULL) {
807 rhythmdb_entry_unref (entry);
813 * rb_play_order_has_previous:
814 * @porder: RBPlayOrder instance
816 * Returns: true if there is an entry before the current entry in the play order.
817 * If not currently playing, returns false.
819 gboolean
820 rb_play_order_has_previous (RBPlayOrder *porder)
822 g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), FALSE);
823 g_return_val_if_fail (RB_PLAY_ORDER_GET_CLASS (porder)->has_previous != NULL, FALSE);
825 return RB_PLAY_ORDER_GET_CLASS (porder)->has_previous (porder);
829 * rb_play_order_get_previous:
830 * @porder: RBPlayOrder instance
832 * Returns: the previous entry in the play order, or NULL if not currently playing.
834 RhythmDBEntry *
835 rb_play_order_get_previous (RBPlayOrder *porder)
837 g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), NULL);
838 g_return_val_if_fail (RB_PLAY_ORDER_GET_CLASS (porder)->get_previous != NULL, NULL);
840 return RB_PLAY_ORDER_GET_CLASS (porder)->get_previous (porder);
844 * rb_play_order_go_previous:
845 * @porder: RBPlayOrder instance
847 * Moves to the previous entry in the play order. If not currently playing, does nothing.
849 void
850 rb_play_order_go_previous (RBPlayOrder *porder)
852 g_return_if_fail (RB_IS_PLAY_ORDER (porder));
854 if (RB_PLAY_ORDER_GET_CLASS (porder)->go_previous) {
855 RB_PLAY_ORDER_GET_CLASS (porder)->go_previous (porder);
856 } else if (RB_PLAY_ORDER_GET_CLASS (porder)->get_previous) {
857 RhythmDBEntry *entry;
859 entry = RB_PLAY_ORDER_GET_CLASS (porder)->get_previous (porder);
860 rb_play_order_set_playing_entry (porder, entry);
861 if (entry != NULL) {
862 rhythmdb_entry_unref (entry);
868 * rb_play_order_model_not_empty:
869 * @porder: RBPlayOrder instance
871 * Returns: true if the #RhythmDBQueryModel is not empty.
872 * Can be used to implement has_next and has_previous for play orders
873 * that have no beginning or end.
875 gboolean
876 rb_play_order_model_not_empty (RBPlayOrder *porder)
878 GtkTreeIter iter;
880 g_return_val_if_fail (RB_IS_PLAY_ORDER (porder), FALSE);
882 if (!porder->priv->query_model)
883 return FALSE;
884 return gtk_tree_model_get_iter_first (GTK_TREE_MODEL (porder->priv->query_model), &iter);
888 * rb_play_order_update_have_next_previous:
889 * @porder: RBPlayOrder instance
891 * Updates the have_next and have_previous flags, and emits a signal if they
892 * change. This is called whenever the play order changes in such a way that
893 * these flags might change.
895 void
896 rb_play_order_update_have_next_previous (RBPlayOrder *porder)
898 gboolean have_next;
899 gboolean have_previous;
901 g_return_if_fail (RB_IS_PLAY_ORDER (porder));
903 have_next = rb_play_order_has_next (porder);
904 have_previous = rb_play_order_has_previous (porder);
906 if ((have_next != porder->priv->have_next) ||
907 (have_previous != porder->priv->have_previous)) {
908 g_signal_emit (G_OBJECT (porder), rb_play_order_signals[HAVE_NEXT_PREVIOUS_CHANGED],
909 0, have_next, have_previous);
910 porder->priv->have_next = have_next;
911 porder->priv->have_previous = have_previous;