1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
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.
25 #include "rb-history.h"
28 #include "gsequence.h"
30 struct RBHistoryPrivate
33 /* If seq is empty, current == g_sequence_get_end_ptr (seq) */
36 GHashTable
*entry_to_seqptr
;
38 gboolean truncate_on_play
;
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
,
57 static void rb_history_get_property (GObject
*object
,
62 static void rb_history_limit_size (RBHistory
*hist
, gboolean cut_from_beginning
);
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
);
72 PROP_TRUNCATE_ON_PLAY
,
76 G_DEFINE_TYPE (RBHistory
, rb_history
, G_TYPE_OBJECT
)
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",
92 "Whether rb_history_set_playing() truncates the rest of the list",
94 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT
));
96 g_object_class_install_property (object_class
,
98 g_param_spec_uint ("maximum-size",
100 "Maximum length of the history. Infinite if 0",
105 g_type_class_add_private (klass
, sizeof (RBHistoryPrivate
));
109 rb_history_new (gboolean truncate_on_play
, GFunc destroyer
, gpointer destroy_userdata
)
113 history
= g_object_new (RB_TYPE_HISTORY
,
114 "truncate-on-play", truncate_on_play
,
117 g_return_val_if_fail (history
->priv
!= NULL
, NULL
);
119 history
->priv
->destroyer
= destroyer
;
120 history
->priv
->destroy_userdata
= destroy_userdata
;
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
,
132 hist
->priv
->seq
= g_sequence_new (NULL
);
133 hist
->priv
->current
= g_sequence_get_begin_ptr (hist
->priv
->seq
);
137 rb_history_finalize (GObject
*object
)
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
);
155 rb_history_set_property (GObject
*object
,
160 RBHistory
*hist
= RB_HISTORY (object
);
164 case PROP_TRUNCATE_ON_PLAY
: {
165 hist
->priv
->truncate_on_play
= g_value_get_boolean (value
);
167 case PROP_MAX_SIZE
: {
168 hist
->priv
->maximum_size
= g_value_get_uint (value
);
169 rb_history_limit_size (hist
, TRUE
);
172 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
178 rb_history_get_property (GObject
*object
,
183 RBHistory
*hist
= RB_HISTORY (object
);
187 case PROP_TRUNCATE_ON_PLAY
: {
188 g_value_set_boolean (value
, hist
->priv
->truncate_on_play
);
190 case PROP_MAX_SIZE
: {
191 g_value_set_uint (value
, hist
->priv
->maximum_size
);
194 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
201 rb_history_clone (RBHistory
*orig
, GFunc callback
, gpointer userdata
)
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
)) {
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
);
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
;
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");
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");
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
);
259 rb_history_first (RBHistory
*hist
)
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
);
269 rb_history_previous (RBHistory
*hist
)
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
);
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
);
288 rb_history_next (RBHistory
*hist
)
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
);
298 rb_history_last (RBHistory
*hist
)
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
);
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
);
317 rb_history_go_previous (RBHistory
*hist
)
320 g_return_if_fail (RB_IS_HISTORY (hist
));
322 prev
= g_sequence_ptr_prev (hist
->priv
->current
);
324 hist
->priv
->current
= prev
;
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
);
336 rb_history_go_last (RBHistory
*hist
)
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
);
347 rb_history_set_playing (RBHistory
*hist
, RhythmDBEntry
*entry
)
349 g_return_if_fail (RB_IS_HISTORY (hist
));
352 hist
->priv
->current
= g_sequence_get_end_ptr (hist
->priv
->seq
);
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
);
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
);
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
);
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
);
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
)
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.
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
));
440 rb_history_remove_entry (hist
, rb_history_last (hist
));
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
);
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
);
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
);
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);
484 rb_history_dump (RBHistory
*hist
)
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
));
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
;