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.
27 #include <glib/gi18n.h>
29 #include "rb-play-order.h"
31 #include "rb-shell-player.h"
33 #include "rb-preferences.h"
34 #include "rb-marshal.h"
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
,
55 static void rb_play_order_get_property (GObject
*object
,
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
,
68 static void rb_play_order_row_deleted_cb (GtkTreeModel
*model
,
71 static void rb_play_order_query_model_changed_cb (GObject
*source
,
74 static void rb_play_order_update_have_next_previous (RBPlayOrder
*porder
);
76 struct RBPlayOrderPrivate
78 RBShellPlayer
*player
;
81 RhythmDBQueryModel
*query_model
;
82 RhythmDBEntry
*playing_entry
;
83 gulong query_model_change_id
;
84 gulong sync_playing_entry_id
;
86 gboolean have_previous
;
89 #define RB_PLAY_ORDER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_PLAY_ORDER, RBPlayOrderPrivate))
100 HAVE_NEXT_PREVIOUS_CHANGED
,
104 static guint rb_play_order_signals
[LAST_SIGNAL
] = { 0 };
106 G_DEFINE_TYPE (RBPlayOrder
, rb_play_order
, G_TYPE_OBJECT
)
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
,
124 g_param_spec_object ("player",
127 RB_TYPE_SHELL_PLAYER
,
128 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
130 g_object_class_install_property (object_class
,
132 g_param_spec_boxed ("playing-entry",
138 rb_play_order_signals
[HAVE_NEXT_PREVIOUS_CHANGED
] =
139 g_signal_new ("have_next_previous_changed",
140 G_OBJECT_CLASS_TYPE (object_class
),
142 G_STRUCT_OFFSET (RBPlayOrderClass
, have_next_previous_changed
),
144 rb_marshal_VOID__BOOLEAN_BOOLEAN
,
146 2, G_TYPE_BOOLEAN
, G_TYPE_BOOLEAN
);
148 g_type_class_add_private (klass
, sizeof (RBPlayOrderPrivate
));
152 rb_play_order_init (RBPlayOrder
*porder
)
154 porder
->priv
= RB_PLAY_ORDER_GET_PRIVATE (porder
);
158 rb_play_order_constructor (GType type
,
159 guint n_construct_properties
,
160 GObjectConstructParam
*construct_properties
)
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
);
171 rb_play_order_finalize (GObject
*object
)
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
),
188 g_signal_handlers_disconnect_by_func (G_OBJECT (porder
->priv
->query_model
),
189 G_CALLBACK (rb_play_order_row_deleted_cb
),
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
);
202 rb_play_order_set_player (RBPlayOrder
*porder
,
203 RBShellPlayer
*player
)
205 porder
->priv
->player
= player
;
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
);
232 rb_play_order_set_property (GObject
*object
,
237 RBPlayOrder
*porder
= RB_PLAY_ORDER (object
);
241 rb_play_order_set_player (porder
, g_value_get_object (value
));
243 case PROP_PLAYING_ENTRY
:
244 rb_play_order_set_playing_entry_internal (porder
, g_value_get_boxed (value
));
247 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
253 rb_play_order_get_property (GObject
*object
,
258 RBPlayOrder
*porder
= RB_PLAY_ORDER (object
);
263 g_value_set_object (value
, porder
->priv
->player
);
265 case PROP_PLAYING_ENTRY
:
266 g_value_set_boxed (value
, porder
->priv
->playing_entry
);
269 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
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
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
},
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
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 ();
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);
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
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.
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.
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.
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.
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.
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.
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
;
449 rhythmdb_entry_ref (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
466 rb_play_order_playing_source_changed (RBPlayOrder
*porder
,
471 g_return_if_fail (RB_IS_PLAY_ORDER (porder
));
473 g_object_get (porder
->priv
->player
,
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
);
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
),
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
);
515 rb_play_order_query_model_changed_cb (GObject
*source
,
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.
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
);
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
,
551 g_signal_handlers_disconnect_by_func (G_OBJECT (porder
->priv
->query_model
),
552 rb_play_order_row_deleted_cb
,
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
),
562 G_CALLBACK (rb_play_order_entry_added_cb
),
564 g_signal_connect_object (G_OBJECT (porder
->priv
->query_model
),
566 G_CALLBACK (rb_play_order_row_deleted_cb
),
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.
589 rb_play_order_entry_added_cb (GtkTreeModel
*model
,
594 RhythmDBEntry
*entry
;
596 entry
= rhythmdb_query_model_iter_to_entry (RHYTHMDB_QUERY_MODEL (model
),
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
622 rb_play_order_row_deleted_cb (GtkTreeModel
*model
,
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
);
643 default_has_next (RBPlayOrder
*porder
)
645 RhythmDBEntry
*entry
;
648 entry
= rb_play_order_get_next (porder
);
650 res
= (entry
!= NULL
);
652 rhythmdb_entry_unref (entry
);
658 default_has_previous (RBPlayOrder
*porder
)
660 RhythmDBEntry
*entry
;
663 entry
= rb_play_order_get_previous (porder
);
665 res
= (entry
!= NULL
);
667 rhythmdb_entry_unref (entry
);
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
));
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 ();
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
:
720 rb_shell_player_set_playing_source (player
, NULL
);
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
);
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
,
743 if (next_entry
!= NULL
) {
744 rhythmdb_entry_unref (next_entry
);
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.
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.
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.
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
);
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.
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.
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.
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
);
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.
876 rb_play_order_model_not_empty (RBPlayOrder
*porder
)
880 g_return_val_if_fail (RB_IS_PLAY_ORDER (porder
), FALSE
);
882 if (!porder
->priv
->query_model
)
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.
896 rb_play_order_update_have_next_previous (RBPlayOrder
*porder
)
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
;