1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * arch-tag: Implementation of shuffle play order
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.
25 #include "rb-play-order-shuffle.h"
27 #include "rb-history.h"
30 #include "eel-gconf-extensions.h"
32 static void rb_shuffle_play_order_class_init (RBShufflePlayOrderClass
*klass
);
33 static void rb_shuffle_play_order_init (RBShufflePlayOrder
*sorder
);
34 static void rb_shuffle_play_order_finalize (GObject
*object
);
36 static RhythmDBEntry
* rb_shuffle_play_order_get_next (RBPlayOrder
* method
);
37 static void rb_shuffle_play_order_go_next (RBPlayOrder
* method
);
38 static RhythmDBEntry
* rb_shuffle_play_order_get_previous (RBPlayOrder
* method
);
39 static void rb_shuffle_play_order_go_previous (RBPlayOrder
* method
);
41 static void rb_shuffle_sync_history_with_query_model (RBShufflePlayOrder
*sorder
);
42 static GPtrArray
*get_query_model_contents (RhythmDBQueryModel
*model
);
44 static void rb_shuffle_db_changed (RBPlayOrder
*porder
, RhythmDB
*db
);
45 static void rb_shuffle_playing_entry_changed (RBPlayOrder
*porder
,
46 RhythmDBEntry
*old_entry
,
47 RhythmDBEntry
*new_entry
);
48 static void rb_shuffle_entry_added (RBPlayOrder
*porder
, RhythmDBEntry
*entry
);
49 static void rb_shuffle_entry_removed (RBPlayOrder
*porder
, RhythmDBEntry
*entry
);
50 static void rb_shuffle_query_model_changed (RBPlayOrder
*porder
);
51 static void rb_shuffle_db_entry_deleted (RBPlayOrder
*porder
, RhythmDBEntry
*entry
);
52 static gboolean
query_model_and_history_contents_match (RBShufflePlayOrder
*sorder
);
54 struct RBShufflePlayOrderPrivate
58 /** TRUE if the query model has been changed */
59 gboolean query_model_changed
;
61 GHashTable
*entries_removed
;
62 GHashTable
*entries_added
;
65 G_DEFINE_TYPE (RBShufflePlayOrder
, rb_shuffle_play_order
, RB_TYPE_PLAY_ORDER
)
66 #define RB_SHUFFLE_PLAY_ORDER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_SHUFFLE_PLAY_ORDER, RBShufflePlayOrderPrivate))
69 rb_shuffle_play_order_new (RBShellPlayer
*player
)
71 RBShufflePlayOrder
*sorder
;
73 sorder
= g_object_new (RB_TYPE_SHUFFLE_PLAY_ORDER
,
77 return RB_PLAY_ORDER (sorder
);
81 rb_shuffle_play_order_class_init (RBShufflePlayOrderClass
*klass
)
83 RBPlayOrderClass
*porder
;
84 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
86 object_class
->finalize
= rb_shuffle_play_order_finalize
;
88 porder
= RB_PLAY_ORDER_CLASS (klass
);
90 porder
->db_changed
= rb_shuffle_db_changed
;
91 porder
->playing_entry_changed
= rb_shuffle_playing_entry_changed
;
92 porder
->entry_added
= rb_shuffle_entry_added
;
93 porder
->entry_removed
= rb_shuffle_entry_removed
;
94 porder
->query_model_changed
= rb_shuffle_query_model_changed
;
95 porder
->db_entry_deleted
= rb_shuffle_db_entry_deleted
;
97 porder
->has_next
= rb_play_order_model_not_empty
;
98 porder
->has_previous
= rb_play_order_model_not_empty
;
99 porder
->get_next
= rb_shuffle_play_order_get_next
;
100 porder
->go_next
= rb_shuffle_play_order_go_next
;
101 porder
->get_previous
= rb_shuffle_play_order_get_previous
;
102 porder
->go_previous
= rb_shuffle_play_order_go_previous
;
104 g_type_class_add_private (klass
, sizeof (RBShufflePlayOrderPrivate
));
108 rb_shuffle_play_order_init (RBShufflePlayOrder
*sorder
)
110 sorder
->priv
= RB_SHUFFLE_PLAY_ORDER_GET_PRIVATE (sorder
);
112 sorder
->priv
->history
= rb_history_new (FALSE
,
113 (GFunc
) rhythmdb_entry_unref
,
116 sorder
->priv
->query_model_changed
= FALSE
;
117 sorder
->priv
->entries_added
= g_hash_table_new_full (g_direct_hash
, g_direct_equal
,
118 (GDestroyNotify
)rhythmdb_entry_unref
, NULL
);
119 sorder
->priv
->entries_removed
= g_hash_table_new_full (g_direct_hash
, g_direct_equal
,
120 (GDestroyNotify
)rhythmdb_entry_unref
, NULL
);
124 rb_shuffle_play_order_finalize (GObject
*object
)
126 RBShufflePlayOrder
*sorder
;
128 g_return_if_fail (object
!= NULL
);
129 g_return_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (object
));
131 sorder
= RB_SHUFFLE_PLAY_ORDER (object
);
133 g_object_unref (sorder
->priv
->history
);
134 g_hash_table_destroy (sorder
->priv
->entries_added
);
135 g_hash_table_destroy (sorder
->priv
->entries_removed
);
137 G_OBJECT_CLASS (rb_shuffle_play_order_parent_class
)->finalize (object
);
140 static RhythmDBEntry
*
141 rb_shuffle_play_order_get_next (RBPlayOrder
* porder
)
143 RBShufflePlayOrder
*sorder
;
144 RhythmDBEntry
*entry
;
145 RhythmDBEntry
*current
;
147 g_return_val_if_fail (porder
!= NULL
, NULL
);
148 g_return_val_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (porder
), NULL
);
150 sorder
= RB_SHUFFLE_PLAY_ORDER (porder
);
152 rb_shuffle_sync_history_with_query_model (sorder
);
154 current
= rb_play_order_get_playing_entry (porder
);
157 if (current
== rb_history_current ( sorder
->priv
->history
) && current
!= NULL
) {
158 if (rb_history_current ( sorder
->priv
->history
) != rb_history_last ( sorder
->priv
->history
)) {
159 rb_debug ("choosing next entry in shuffle");
160 entry
= rb_history_next ( sorder
->priv
->history
);
162 rhythmdb_entry_ref (entry
);
165 /* If the player is currently stopped, the "next" (first) song
166 * is the first in the shuffle. */
167 rb_debug ("choosing current entry in shuffle");
168 entry
= rb_history_current ( sorder
->priv
->history
);
171 entry
= rb_history_first ( sorder
->priv
->history
);
174 rhythmdb_entry_ref (entry
);
178 rhythmdb_entry_unref (current
);
183 rb_shuffle_play_order_go_next (RBPlayOrder
* porder
)
185 RBShufflePlayOrder
*sorder
;
186 RhythmDBEntry
*entry
;
188 g_return_if_fail (porder
!= NULL
);
189 g_return_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (porder
));
191 sorder
= RB_SHUFFLE_PLAY_ORDER (porder
);
193 entry
= rb_play_order_get_playing_entry (porder
);
194 g_assert (entry
== NULL
||
195 rb_history_current (sorder
->priv
->history
) == NULL
||
196 entry
== rb_history_current (sorder
->priv
->history
));
198 if (rb_history_current (sorder
->priv
->history
) == NULL
) {
199 rb_history_go_first (sorder
->priv
->history
);
200 } else if (entry
== rb_history_current (sorder
->priv
->history
)) {
201 if (rb_history_current (sorder
->priv
->history
) != rb_history_last (sorder
->priv
->history
))
202 rb_history_go_next (sorder
->priv
->history
);
205 rb_play_order_set_playing_entry (porder
, rb_history_current (sorder
->priv
->history
));
208 rhythmdb_entry_unref (entry
);
211 static RhythmDBEntry
*
212 rb_shuffle_play_order_get_previous (RBPlayOrder
* porder
)
214 RBShufflePlayOrder
*sorder
;
215 RhythmDBEntry
*entry
;
217 g_return_val_if_fail (porder
!= NULL
, NULL
);
218 g_return_val_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (porder
), NULL
);
219 /* It doesn't make sense to call get_previous when the player is stopped */
220 g_return_val_if_fail (rb_play_order_player_is_playing (porder
), NULL
);
222 sorder
= RB_SHUFFLE_PLAY_ORDER (porder
);
224 rb_shuffle_sync_history_with_query_model (sorder
);
226 rb_debug ("choosing previous history entry");
227 entry
= rb_history_previous (sorder
->priv
->history
);
229 rhythmdb_entry_ref (entry
);
235 rb_shuffle_play_order_go_previous (RBPlayOrder
* porder
)
237 RBShufflePlayOrder
*sorder
;
239 g_return_if_fail (porder
!= NULL
);
240 g_return_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (porder
));
241 /* It doesn't make sense to call go_previous when the player is stopped */
242 g_return_if_fail (rb_play_order_player_is_playing (porder
));
244 sorder
= RB_SHUFFLE_PLAY_ORDER (porder
);
246 if (rb_history_current (sorder
->priv
->history
) != rb_history_first (sorder
->priv
->history
)) {
247 rb_history_go_previous (sorder
->priv
->history
);
248 rb_play_order_set_playing_entry (porder
, rb_history_current (sorder
->priv
->history
));
253 handle_query_model_changed (RBShufflePlayOrder
*sorder
)
256 RhythmDBQueryModel
*model
;
260 if (!sorder
->priv
->query_model_changed
)
263 g_hash_table_foreach_remove (sorder
->priv
->entries_added
, (GHRFunc
) rb_true_function
, NULL
);
264 g_hash_table_foreach_remove (sorder
->priv
->entries_removed
, (GHRFunc
) rb_true_function
, NULL
);
266 /* This simulates removing every entry in the old query model
267 * and then adding every entry in the new one. */
268 history
= rb_history_dump (sorder
->priv
->history
);
269 for (i
=0; i
< history
->len
; ++i
)
270 rb_shuffle_entry_removed (RB_PLAY_ORDER (sorder
), g_ptr_array_index (history
, i
));
271 g_ptr_array_free (history
, TRUE
);
273 model
= rb_play_order_get_query_model (RB_PLAY_ORDER (sorder
));
274 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model
), &iter
)) {
276 RhythmDBEntry
*entry
;
277 entry
= rhythmdb_query_model_iter_to_entry (model
, &iter
);
278 rb_shuffle_entry_added (RB_PLAY_ORDER (sorder
), entry
);
279 rhythmdb_entry_unref (entry
);
280 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (model
), &iter
));
283 sorder
->priv
->query_model_changed
= FALSE
;
287 remove_from_history (RhythmDBEntry
*entry
, gpointer
*unused
, RBShufflePlayOrder
*sorder
)
289 if (rb_history_contains_entry (sorder
->priv
->history
, entry
)) {
290 rb_history_remove_entry (sorder
->priv
->history
, entry
);
296 add_randomly_to_history (RhythmDBEntry
*entry
, gpointer
*unused
, RBShufflePlayOrder
*sorder
)
301 if (rb_history_contains_entry (sorder
->priv
->history
, entry
))
304 history_size
= rb_history_length (sorder
->priv
->history
);
305 current_index
= rb_history_get_current_index (sorder
->priv
->history
);
306 /* Insert entry into the history at a random position between
307 * just after current and the very end. */
308 rb_history_insert_at_index (sorder
->priv
->history
,
309 rhythmdb_entry_ref (entry
),
310 g_random_int_range (MIN (current_index
, history_size
-1) + 1,
316 rb_shuffle_sync_history_with_query_model (RBShufflePlayOrder
*sorder
)
318 RhythmDBEntry
*current
= rb_history_current (sorder
->priv
->history
);
320 handle_query_model_changed (sorder
);
321 g_hash_table_foreach_remove (sorder
->priv
->entries_removed
, (GHRFunc
) remove_from_history
, sorder
);
322 g_hash_table_foreach_remove (sorder
->priv
->entries_added
, (GHRFunc
) add_randomly_to_history
, sorder
);
324 /* if the current entry no longer exists in the history, go back to the start */
325 if (!rb_history_contains_entry (sorder
->priv
->history
, current
)) {
326 rb_history_set_playing (sorder
->priv
->history
, NULL
);
330 g_assert (query_model_and_history_contents_match (sorder
));
331 g_assert (g_hash_table_size (sorder
->priv
->entries_added
) == 0);
332 g_assert (g_hash_table_size (sorder
->priv
->entries_removed
) == 0);
335 /* NOTE: returned GPtrArray does not hold references to the entries */
337 get_query_model_contents (RhythmDBQueryModel
*model
)
343 GPtrArray
*result
= g_ptr_array_new ();
347 num_entries
= gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model
), NULL
);
348 if (num_entries
== 0)
351 g_ptr_array_set_size (result
, num_entries
);
353 if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model
), &iter
))
356 RhythmDBEntry
*entry
;
357 entry
= rhythmdb_query_model_iter_to_entry (model
, &iter
);
358 g_ptr_array_index (result
, i
++) = entry
;
359 rhythmdb_entry_unref (entry
);
360 } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (model
), &iter
));
366 rb_shuffle_db_changed (RBPlayOrder
*porder
, RhythmDB
*db
)
368 g_return_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (porder
));
370 rb_history_clear (RB_SHUFFLE_PLAY_ORDER (porder
)->priv
->history
);
374 rb_shuffle_playing_entry_changed (RBPlayOrder
*porder
,
375 RhythmDBEntry
*old_entry
,
376 RhythmDBEntry
*new_entry
)
378 RBShufflePlayOrder
*sorder
;
380 g_return_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (porder
));
381 sorder
= RB_SHUFFLE_PLAY_ORDER (porder
);
384 if (new_entry
== rb_history_current (sorder
->priv
->history
)) {
387 rhythmdb_entry_ref (new_entry
);
389 rb_history_set_playing (sorder
->priv
->history
, new_entry
);
392 /* go back to the start if we just finished the play order */
393 if (old_entry
== rb_history_last (sorder
->priv
->history
))
394 rb_history_go_first (sorder
->priv
->history
);
399 rb_shuffle_entry_added (RBPlayOrder
*porder
, RhythmDBEntry
*entry
)
401 g_return_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (porder
));
402 g_hash_table_remove (RB_SHUFFLE_PLAY_ORDER (porder
)->priv
->entries_removed
, entry
);
403 g_hash_table_insert (RB_SHUFFLE_PLAY_ORDER (porder
)->priv
->entries_added
, rhythmdb_entry_ref (entry
), entry
);
407 rb_shuffle_entry_removed (RBPlayOrder
*porder
, RhythmDBEntry
*entry
)
409 g_return_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (porder
));
410 g_hash_table_remove (RB_SHUFFLE_PLAY_ORDER (porder
)->priv
->entries_added
, entry
);
411 g_hash_table_insert (RB_SHUFFLE_PLAY_ORDER (porder
)->priv
->entries_removed
, rhythmdb_entry_ref (entry
), entry
);
415 rb_shuffle_query_model_changed (RBPlayOrder
*porder
)
417 g_return_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (porder
));
418 RB_SHUFFLE_PLAY_ORDER (porder
)->priv
->query_model_changed
= TRUE
;
422 rb_shuffle_db_entry_deleted (RBPlayOrder
*porder
, RhythmDBEntry
*entry
)
424 RBShufflePlayOrder
*sorder
;
426 g_return_if_fail (RB_IS_SHUFFLE_PLAY_ORDER (porder
));
427 sorder
= RB_SHUFFLE_PLAY_ORDER (porder
);
429 rb_history_remove_entry (sorder
->priv
->history
, entry
);
432 /* For some reason g_ptr_array_sort() passes pointers to the array elements
433 * rather than the elements themselves */
435 ptr_compare (gconstpointer a
, gconstpointer b
)
437 if (*(gconstpointer
*)a
< *(gconstpointer
*)b
)
439 if (*(gconstpointer
*)b
< *(gconstpointer
*)a
)
445 query_model_and_history_contents_match (RBShufflePlayOrder
*sorder
)
447 gboolean result
= TRUE
;
448 GPtrArray
*history_contents
;
449 GPtrArray
*query_model_contents
;
451 history_contents
= rb_history_dump (sorder
->priv
->history
);
452 query_model_contents
= get_query_model_contents (rb_play_order_get_query_model (RB_PLAY_ORDER (sorder
)));
454 if (history_contents
->len
!= query_model_contents
->len
)
458 g_ptr_array_sort (history_contents
, ptr_compare
);
459 g_ptr_array_sort (query_model_contents
, ptr_compare
);
460 for (i
=0; i
<history_contents
->len
; ++i
) {
461 if (g_ptr_array_index (history_contents
, i
) != g_ptr_array_index (query_model_contents
, i
)) {
467 g_ptr_array_free (history_contents
, TRUE
);
468 g_ptr_array_free (query_model_contents
, TRUE
);