Updated Macedonian Translation <arangela@cvs.gnome.org>
[rhythmbox.git] / shell / rb-history.c
blob8a745174d60d1132b14024a264ceb4d13844d164
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * arch-tag: Implementation of Song History List
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.
21 */
23 #include <string.h>
25 #include "rb-history.h"
27 #include "rhythmdb.h"
28 #include "gsequence.h"
30 struct RBHistoryPrivate
32 GSequence *seq;
33 /* If seq is empty, current == g_sequence_get_end_ptr (seq) */
34 GSequencePtr current;
36 GHashTable *entry_to_seqptr;
38 gboolean truncate_on_play;
39 guint maximum_size;
41 GFunc destroyer;
42 gpointer destroy_userdata;
45 #define RB_HISTORY_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_HISTORY, RBHistoryPrivate))
47 #define MAX_HISTORY_SIZE 50
49 static void rb_history_class_init (RBHistoryClass *klass);
50 static void rb_history_init (RBHistory *shell_player);
51 static void rb_history_finalize (GObject *object);
53 static void rb_history_set_property (GObject *object,
54 guint prop_id,
55 const GValue *value,
56 GParamSpec *pspec);
57 static void rb_history_get_property (GObject *object,
58 guint prop_id,
59 GValue *value,
60 GParamSpec *pspec);
62 static void rb_history_limit_size (RBHistory *hist, gboolean cut_from_beginning);
64 /**
65 * Removes a link from the history and updates all pointers. Doesn't destroy the entry, but does change the size of the list.
67 static void rb_history_delete_link (RBHistory *hist, GSequencePtr to_delete);
69 enum
71 PROP_0,
72 PROP_TRUNCATE_ON_PLAY,
73 PROP_MAX_SIZE,
76 G_DEFINE_TYPE (RBHistory, rb_history, G_TYPE_OBJECT)
78 static void
79 rb_history_class_init (RBHistoryClass *klass)
81 GObjectClass *object_class = G_OBJECT_CLASS (klass);
83 object_class->finalize = rb_history_finalize;
85 object_class->set_property = rb_history_set_property;
86 object_class->get_property = rb_history_get_property;
88 g_object_class_install_property (object_class,
89 PROP_TRUNCATE_ON_PLAY,
90 g_param_spec_boolean ("truncate-on-play",
91 "Truncate on Play",
92 "Whether rb_history_set_playing() truncates the rest of the list",
93 FALSE,
94 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
96 g_object_class_install_property (object_class,
97 PROP_MAX_SIZE,
98 g_param_spec_uint ("maximum-size",
99 "Maximum Size",
100 "Maximum length of the history. Infinite if 0",
101 0, G_MAXUINT,
103 G_PARAM_READWRITE));
105 g_type_class_add_private (klass, sizeof (RBHistoryPrivate));
108 RBHistory *
109 rb_history_new (gboolean truncate_on_play, GFunc destroyer, gpointer destroy_userdata)
111 RBHistory *history;
113 history = g_object_new (RB_TYPE_HISTORY,
114 "truncate-on-play", truncate_on_play,
115 NULL);
117 g_return_val_if_fail (history->priv != NULL, NULL);
119 history->priv->destroyer = destroyer;
120 history->priv->destroy_userdata = destroy_userdata;
122 return history;
125 static void
126 rb_history_init (RBHistory *hist)
128 hist->priv = RB_HISTORY_GET_PRIVATE (hist);
130 hist->priv->entry_to_seqptr = g_hash_table_new (g_direct_hash,
131 g_direct_equal);
132 hist->priv->seq = g_sequence_new (NULL);
133 hist->priv->current = g_sequence_get_begin_ptr (hist->priv->seq);
136 static void
137 rb_history_finalize (GObject *object)
139 RBHistory *hist;
140 g_return_if_fail (object != NULL);
141 g_return_if_fail (RB_IS_HISTORY (object));
143 hist = RB_HISTORY (object);
145 /* remove all of the stored entries */
146 rb_history_clear (hist);
148 g_hash_table_destroy (hist->priv->entry_to_seqptr);
149 g_sequence_free (hist->priv->seq);
151 G_OBJECT_CLASS (rb_history_parent_class)->finalize (object);
154 static void
155 rb_history_set_property (GObject *object,
156 guint prop_id,
157 const GValue *value,
158 GParamSpec *pspec)
160 RBHistory *hist = RB_HISTORY (object);
162 switch (prop_id)
164 case PROP_TRUNCATE_ON_PLAY: {
165 hist->priv->truncate_on_play = g_value_get_boolean (value);
166 } break;
167 case PROP_MAX_SIZE: {
168 hist->priv->maximum_size = g_value_get_uint (value);
169 rb_history_limit_size (hist, TRUE);
170 } break;
171 default:
172 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
173 break;
177 static void
178 rb_history_get_property (GObject *object,
179 guint prop_id,
180 GValue *value,
181 GParamSpec *pspec)
183 RBHistory *hist = RB_HISTORY (object);
185 switch (prop_id)
187 case PROP_TRUNCATE_ON_PLAY: {
188 g_value_set_boolean (value, hist->priv->truncate_on_play);
189 } break;
190 case PROP_MAX_SIZE: {
191 g_value_set_uint (value, hist->priv->maximum_size);
192 } break;
193 default:
194 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
195 break;
200 RBHistory *
201 rb_history_clone (RBHistory *orig, GFunc callback, gpointer userdata)
203 GSequencePtr ptr;
204 RBHistory *hist = rb_history_new (orig->priv->truncate_on_play,
205 orig->priv->destroyer,
206 orig->priv->destroy_userdata);
207 rb_history_set_maximum_size (hist, orig->priv->maximum_size);
209 for (ptr=g_sequence_get_begin_ptr (orig->priv->seq); !g_sequence_ptr_is_end (ptr); ptr = g_sequence_ptr_next (ptr)) {
210 if (callback)
211 callback (g_sequence_ptr_get_data (ptr), userdata);
212 rb_history_append (hist, g_sequence_ptr_get_data (ptr));
214 /* Set current to point to the same place in the history */
215 if (orig->priv->current == ptr) {
216 rb_history_go_last (hist);
220 return hist;
223 void
224 rb_history_set_destroy_notify (RBHistory *hist, GFunc destroyer, gpointer destroy_userdata)
226 g_return_if_fail (RB_IS_HISTORY (hist));
228 hist->priv->destroyer = destroyer;
229 hist->priv->destroy_userdata = destroy_userdata;
232 void
233 rb_history_set_truncate_on_play (RBHistory *hist, gboolean truncate_on_play)
235 g_return_if_fail (RB_IS_HISTORY (hist));
237 hist->priv->truncate_on_play = truncate_on_play;
238 g_object_notify (G_OBJECT (hist), "truncate-on-play");
241 void
242 rb_history_set_maximum_size (RBHistory *hist, guint maximum_size)
244 g_return_if_fail (RB_IS_HISTORY (hist));
246 hist->priv->maximum_size = maximum_size;
247 g_object_notify (G_OBJECT (hist), "maximum-size");
250 guint
251 rb_history_length (RBHistory *hist)
253 g_return_val_if_fail (RB_IS_HISTORY (hist), 0);
255 return g_sequence_get_length (hist->priv->seq);
258 RhythmDBEntry *
259 rb_history_first (RBHistory *hist)
261 GSequencePtr begin;
262 g_return_val_if_fail (RB_IS_HISTORY (hist), NULL);
264 begin = g_sequence_get_begin_ptr (hist->priv->seq);
265 return g_sequence_ptr_is_end (begin) ? NULL : g_sequence_ptr_get_data (begin);
268 RhythmDBEntry *
269 rb_history_previous (RBHistory *hist)
271 GSequencePtr prev;
273 g_return_val_if_fail (RB_IS_HISTORY (hist), NULL);
275 prev = g_sequence_ptr_prev (hist->priv->current);
276 return prev == hist->priv->current ? NULL : g_sequence_ptr_get_data (prev);
279 RhythmDBEntry *
280 rb_history_current (RBHistory *hist)
282 g_return_val_if_fail (RB_IS_HISTORY (hist), NULL);
284 return g_sequence_ptr_is_end (hist->priv->current) ? NULL : g_sequence_ptr_get_data (hist->priv->current);
287 RhythmDBEntry *
288 rb_history_next (RBHistory *hist)
290 GSequencePtr next;
291 g_return_val_if_fail (RB_IS_HISTORY (hist), NULL);
293 next = g_sequence_ptr_next (hist->priv->current);
294 return g_sequence_ptr_is_end (next) ? NULL : g_sequence_ptr_get_data (next);
297 RhythmDBEntry *
298 rb_history_last (RBHistory *hist)
300 GSequencePtr last;
302 g_return_val_if_fail (RB_IS_HISTORY (hist), NULL);
304 last = g_sequence_ptr_prev (g_sequence_get_end_ptr (hist->priv->seq));
305 return g_sequence_ptr_is_end (last) ? NULL : g_sequence_ptr_get_data (last);
308 void
309 rb_history_go_first (RBHistory *hist)
311 g_return_if_fail (RB_IS_HISTORY (hist));
313 hist->priv->current = g_sequence_get_begin_ptr (hist->priv->seq);
316 void
317 rb_history_go_previous (RBHistory *hist)
319 GSequencePtr prev;
320 g_return_if_fail (RB_IS_HISTORY (hist));
322 prev = g_sequence_ptr_prev (hist->priv->current);
323 if (prev)
324 hist->priv->current = prev;
327 void
328 rb_history_go_next (RBHistory *hist)
330 g_return_if_fail (RB_IS_HISTORY (hist));
332 hist->priv->current = g_sequence_ptr_next (hist->priv->current);
335 void
336 rb_history_go_last (RBHistory *hist)
338 GSequencePtr last;
339 g_return_if_fail (RB_IS_HISTORY (hist));
341 last = g_sequence_ptr_prev (g_sequence_get_end_ptr (hist->priv->seq));
342 hist->priv->current = last ? last : g_sequence_get_end_ptr (hist->priv->seq);
346 void
347 rb_history_set_playing (RBHistory *hist, RhythmDBEntry *entry)
349 g_return_if_fail (RB_IS_HISTORY (hist));
351 if (entry == NULL) {
352 hist->priv->current = g_sequence_get_end_ptr (hist->priv->seq);
353 return;
356 rb_history_remove_entry (hist, entry);
358 g_sequence_insert (g_sequence_ptr_next (hist->priv->current), entry);
359 /* make hist->priv->current point to the new entry */
360 if (g_sequence_ptr_is_end (hist->priv->current))
361 hist->priv->current = g_sequence_ptr_prev (hist->priv->current);
362 else
363 hist->priv->current = g_sequence_ptr_next (hist->priv->current);
364 g_hash_table_insert (hist->priv->entry_to_seqptr, entry, hist->priv->current);
366 if (hist->priv->truncate_on_play) {
367 while (!g_sequence_ptr_is_end (g_sequence_ptr_next (hist->priv->current)))
368 rb_history_remove_entry (hist, g_sequence_ptr_get_data (g_sequence_ptr_next (hist->priv->current)));
371 rb_history_limit_size (hist, TRUE);
374 void
375 rb_history_append (RBHistory *hist, RhythmDBEntry *entry)
377 GSequencePtr new_node;
379 g_return_if_fail (RB_IS_HISTORY (hist));
380 g_return_if_fail (entry != NULL);
382 rb_history_remove_entry (hist, entry);
384 g_sequence_append (hist->priv->seq, entry);
385 new_node = g_sequence_ptr_prev (g_sequence_get_end_ptr (hist->priv->seq));
386 g_hash_table_insert (hist->priv->entry_to_seqptr, entry, new_node);
388 rb_history_limit_size (hist, TRUE);
391 gint
392 rb_history_get_current_index (RBHistory *hist)
394 g_return_val_if_fail (RB_IS_HISTORY (hist), -1);
396 return g_sequence_ptr_get_position (hist->priv->current);
399 void
400 rb_history_insert_at_index (RBHistory *hist, RhythmDBEntry *entry, guint index)
402 GSequencePtr old_node;
403 GSequencePtr new_node;
405 g_return_if_fail (RB_IS_HISTORY (hist));
406 g_return_if_fail (entry != NULL);
407 g_return_if_fail (index <= g_sequence_get_length (hist->priv->seq));
409 /* Deal with case where the entry is moving forward */
410 old_node = g_hash_table_lookup (hist->priv->entry_to_seqptr, entry);
411 if (old_node && g_sequence_ptr_get_position (old_node) < index)
412 index--;
414 rb_history_remove_entry (hist, entry);
416 new_node = g_sequence_get_ptr_at_pos (hist->priv->seq, index);
417 g_sequence_insert (new_node, entry);
418 new_node = g_sequence_ptr_prev (new_node);
419 g_hash_table_insert (hist->priv->entry_to_seqptr, entry, new_node);
421 if (g_sequence_ptr_is_end (hist->priv->current) && index == g_sequence_get_length (hist->priv->seq)-1 /*length just increased*/)
422 hist->priv->current = new_node;
424 rb_history_limit_size (hist, TRUE);
428 * Cuts nodes off of the history from the desired end until it is smaller than max_size.
429 * Never cuts off the current node.
431 static void
432 rb_history_limit_size (RBHistory *hist, gboolean cut_from_beginning)
434 if (hist->priv->maximum_size != 0)
435 while (g_sequence_get_length (hist->priv->seq) > hist->priv->maximum_size) {
436 if (cut_from_beginning
437 || hist->priv->current == g_sequence_ptr_prev (g_sequence_get_end_ptr (hist->priv->seq))) {
438 rb_history_remove_entry (hist, rb_history_first (hist));
439 } else {
440 rb_history_remove_entry (hist, rb_history_last (hist));
445 void
446 rb_history_remove_entry (RBHistory *hist, RhythmDBEntry *entry)
448 GSequencePtr to_delete;
449 g_return_if_fail (RB_IS_HISTORY (hist));
451 to_delete = g_hash_table_lookup (hist->priv->entry_to_seqptr, entry);
452 if (to_delete) {
453 g_hash_table_remove (hist->priv->entry_to_seqptr, entry);
454 if (hist->priv->destroyer)
455 hist->priv->destroyer (entry, hist->priv->destroy_userdata);
456 rb_history_delete_link (hist, to_delete);
460 static void
461 rb_history_delete_link (RBHistory *hist, GSequencePtr to_delete)
463 if (to_delete == hist->priv->current) {
464 hist->priv->current = g_sequence_get_end_ptr (hist->priv->seq);
466 g_assert (to_delete != hist->priv->current);
468 g_sequence_remove (to_delete);
471 void
472 rb_history_clear (RBHistory *hist)
474 g_return_if_fail (RB_IS_HISTORY (hist));
476 while (!g_sequence_ptr_is_end (g_sequence_get_begin_ptr (hist->priv->seq)))
477 rb_history_remove_entry (hist, rb_history_first (hist));
479 /* When the sequence is empty, the hash table should also be empty. */
480 g_assert (g_hash_table_size (hist->priv->entry_to_seqptr) == 0);
483 GPtrArray *
484 rb_history_dump (RBHistory *hist)
486 GSequencePtr cur;
487 GPtrArray *result;
489 g_return_val_if_fail (RB_IS_HISTORY (hist), NULL);
491 result = g_ptr_array_sized_new (g_sequence_get_length (hist->priv->seq));
492 for (cur = g_sequence_get_begin_ptr (hist->priv->seq);
493 !g_sequence_ptr_is_end (cur);
494 cur = g_sequence_ptr_next (cur)) {
495 g_ptr_array_add (result, g_sequence_ptr_get_data (cur));
497 return result;
500 gboolean
501 rb_history_contains_entry (RBHistory *hist, RhythmDBEntry *entry)
503 g_return_val_if_fail (RB_IS_HISTORY (hist), FALSE);
505 return g_hash_table_lookup (hist->priv->entry_to_seqptr, entry) != NULL;