2006-12-14 Francisco Javier F. Serrador <serrador@openshine.com>
[rhythmbox.git] / shell / rb-play-order-shuffle.c
blob8e7933f17eadc99c52ee4d2b2428854657bb39e2
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.
23 #include <string.h>
25 #include "rb-play-order-shuffle.h"
27 #include "rb-history.h"
28 #include "rb-debug.h"
29 #include "rb-util.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
56 RBHistory *history;
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))
68 RBPlayOrder *
69 rb_shuffle_play_order_new (RBShellPlayer *player)
71 RBShufflePlayOrder *sorder;
73 sorder = g_object_new (RB_TYPE_SHUFFLE_PLAY_ORDER,
74 "player", player,
75 NULL);
77 return RB_PLAY_ORDER (sorder);
80 static void
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));
107 static void
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,
114 NULL);
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);
123 static void
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);
155 entry = NULL;
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);
161 if (entry)
162 rhythmdb_entry_ref (entry);
164 } else {
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);
170 if (entry == NULL)
171 entry = rb_history_first ( sorder->priv->history);
173 if (entry != NULL)
174 rhythmdb_entry_ref (entry);
177 if (current)
178 rhythmdb_entry_unref (current);
179 return entry;
182 static void
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));
207 if (entry)
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);
228 if (entry)
229 rhythmdb_entry_ref (entry);
231 return entry;
234 static void
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));
252 static void
253 handle_query_model_changed (RBShufflePlayOrder *sorder)
255 GPtrArray *history;
256 RhythmDBQueryModel *model;
257 GtkTreeIter iter;
258 int i;
260 if (!sorder->priv->query_model_changed)
261 return;
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)) {
275 do {
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;
286 static gboolean
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);
292 return TRUE;
295 static gboolean
296 add_randomly_to_history (RhythmDBEntry *entry, gpointer *unused, RBShufflePlayOrder *sorder)
298 gint history_size;
299 gint current_index;
301 if (rb_history_contains_entry (sorder->priv->history, entry))
302 return TRUE;
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,
311 history_size + 1));
312 return TRUE;
315 static void
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);
329 /* postconditions */
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 */
336 static GPtrArray *
337 get_query_model_contents (RhythmDBQueryModel *model)
339 guint num_entries;
340 guint i = 0;
341 GtkTreeIter iter;
343 GPtrArray *result = g_ptr_array_new ();
344 if (model == NULL)
345 return result;
347 num_entries = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model), NULL);
348 if (num_entries == 0)
349 return result;
351 g_ptr_array_set_size (result, num_entries);
353 if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter))
354 return result;
355 do {
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));
362 return result;
365 static void
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);
373 static void
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);
383 if (new_entry) {
384 if (new_entry == rb_history_current (sorder->priv->history)) {
385 /* Do nothing */
386 } else {
387 rhythmdb_entry_ref (new_entry);
389 rb_history_set_playing (sorder->priv->history, new_entry);
391 } else {
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);
398 static void
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);
406 static void
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);
414 static void
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;
421 static void
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 */
434 static gint
435 ptr_compare (gconstpointer a, gconstpointer b)
437 if (*(gconstpointer*)a < *(gconstpointer*)b)
438 return -1;
439 if (*(gconstpointer*)b < *(gconstpointer*)a)
440 return 1;
441 return 0;
444 static gboolean
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)
455 result = FALSE;
456 else {
457 int i;
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)) {
462 result = FALSE;
463 break;
467 g_ptr_array_free (history_contents, TRUE);
468 g_ptr_array_free (query_model_contents, TRUE);
469 return result;