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 */