2 * Copyright 2015 Lars Uebernickel
3 * Copyright 2015 Ryan Lortie
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General
16 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
19 * Lars Uebernickel <lars@uebernic.de>
20 * Ryan Lortie <desrt@desrt.ca>
25 #include "gliststore.h"
26 #include "glistmodel.h"
31 * @short_description: A simple implementation of #GListModel
34 * #GListStore is a simple implementation of #GListModel that stores all
37 * It provides insertions, deletions, and lookups in logarithmic time
38 * with a fast path for the common case of iterating the list linearly.
44 * #GListStore is an opaque data structure and can only be accessed
45 * using the following functions.
50 GObject parent_instance;
57 GSequenceIter *last_iter;
58 gboolean last_position_valid;
68 static void g_list_store_iface_init (GListModelInterface *iface);
70 G_DEFINE_TYPE_WITH_CODE (GListStore, g_list_store, G_TYPE_OBJECT,
71 G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, g_list_store_iface_init));
74 g_list_store_items_changed (GListStore *store,
79 /* check if the iter cache may have been invalidated */
80 if (position <= store->last_position)
82 store->last_iter = NULL;
83 store->last_position = 0;
84 store->last_position_valid = FALSE;
87 g_list_model_items_changed (G_LIST_MODEL (store), position, removed, added);
91 g_list_store_dispose (GObject *object)
93 GListStore *store = G_LIST_STORE (object);
95 g_clear_pointer (&store->items, g_sequence_free);
97 G_OBJECT_CLASS (g_list_store_parent_class)->dispose (object);
101 g_list_store_get_property (GObject *object,
106 GListStore *store = G_LIST_STORE (object);
111 g_value_set_gtype (value, store->item_type);
115 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
120 g_list_store_set_property (GObject *object,
125 GListStore *store = G_LIST_STORE (object);
129 case PROP_ITEM_TYPE: /* construct-only */
130 g_assert (g_type_is_a (g_value_get_gtype (value), G_TYPE_OBJECT));
131 store->item_type = g_value_get_gtype (value);
135 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
140 g_list_store_class_init (GListStoreClass *klass)
142 GObjectClass *object_class = G_OBJECT_CLASS (klass);
144 object_class->dispose = g_list_store_dispose;
145 object_class->get_property = g_list_store_get_property;
146 object_class->set_property = g_list_store_set_property;
149 * GListStore:item-type:
151 * The type of items contained in this list store. Items must be
152 * subclasses of #GObject.
156 g_object_class_install_property (object_class, PROP_ITEM_TYPE,
157 g_param_spec_gtype ("item-type", "", "", G_TYPE_OBJECT,
158 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
162 g_list_store_get_item_type (GListModel *list)
164 GListStore *store = G_LIST_STORE (list);
166 return store->item_type;
170 g_list_store_get_n_items (GListModel *list)
172 GListStore *store = G_LIST_STORE (list);
174 return g_sequence_get_length (store->items);
178 g_list_store_get_item (GListModel *list,
181 GListStore *store = G_LIST_STORE (list);
182 GSequenceIter *it = NULL;
184 if (store->last_position_valid)
186 if (position < G_MAXUINT && store->last_position == position + 1)
187 it = g_sequence_iter_prev (store->last_iter);
188 else if (position > 0 && store->last_position == position - 1)
189 it = g_sequence_iter_next (store->last_iter);
190 else if (store->last_position == position)
191 it = store->last_iter;
195 it = g_sequence_get_iter_at_pos (store->items, position);
197 store->last_iter = it;
198 store->last_position = position;
199 store->last_position_valid = TRUE;
201 if (g_sequence_iter_is_end (it))
204 return g_object_ref (g_sequence_get (it));
208 g_list_store_iface_init (GListModelInterface *iface)
210 iface->get_item_type = g_list_store_get_item_type;
211 iface->get_n_items = g_list_store_get_n_items;
212 iface->get_item = g_list_store_get_item;
216 g_list_store_init (GListStore *store)
218 store->items = g_sequence_new (g_object_unref);
219 store->last_position = 0;
220 store->last_position_valid = FALSE;
225 * @item_type: the #GType of items in the list
227 * Creates a new #GListStore with items of type @item_type. @item_type
228 * must be a subclass of #GObject.
230 * Returns: a new #GListStore
234 g_list_store_new (GType item_type)
236 /* We only allow GObjects as item types right now. This might change
239 g_return_val_if_fail (g_type_is_a (item_type, G_TYPE_OBJECT), NULL);
241 return g_object_new (G_TYPE_LIST_STORE,
242 "item-type", item_type,
247 * g_list_store_insert:
248 * @store: a #GListStore
249 * @position: the position at which to insert the new item
250 * @item: (type GObject): the new item
252 * Inserts @item into @store at @position. @item must be of type
253 * #GListStore:item-type or derived from it. @position must be smaller
254 * than the length of the list, or equal to it to append.
256 * This function takes a ref on @item.
258 * Use g_list_store_splice() to insert multiple items at the same time
264 g_list_store_insert (GListStore *store,
270 g_return_if_fail (G_IS_LIST_STORE (store));
271 g_return_if_fail (g_type_is_a (G_OBJECT_TYPE (item), store->item_type));
272 g_return_if_fail (position <= g_sequence_get_length (store->items));
274 it = g_sequence_get_iter_at_pos (store->items, position);
275 g_sequence_insert_before (it, g_object_ref (item));
277 g_list_store_items_changed (store, position, 0, 1);
281 * g_list_store_insert_sorted:
282 * @store: a #GListStore
283 * @item: (type GObject): the new item
284 * @compare_func: (scope call): pairwise comparison function for sorting
285 * @user_data: (closure): user data for @compare_func
287 * Inserts @item into @store at a position to be determined by the
290 * The list must already be sorted before calling this function or the
291 * result is undefined. Usually you would approach this by only ever
292 * inserting items by way of this function.
294 * This function takes a ref on @item.
296 * Returns: the position at which @item was inserted
301 g_list_store_insert_sorted (GListStore *store,
303 GCompareDataFunc compare_func,
309 g_return_val_if_fail (G_IS_LIST_STORE (store), 0);
310 g_return_val_if_fail (g_type_is_a (G_OBJECT_TYPE (item), store->item_type), 0);
311 g_return_val_if_fail (compare_func != NULL, 0);
313 it = g_sequence_insert_sorted (store->items, g_object_ref (item), compare_func, user_data);
314 position = g_sequence_iter_get_position (it);
316 g_list_store_items_changed (store, position, 0, 1);
323 * @store: a #GListStore
324 * @compare_func: (scope call): pairwise comparison function for sorting
325 * @user_data: (closure): user data for @compare_func
327 * Sort the items in @store according to @compare_func.
332 g_list_store_sort (GListStore *store,
333 GCompareDataFunc compare_func,
338 g_return_if_fail (G_IS_LIST_STORE (store));
339 g_return_if_fail (compare_func != NULL);
341 g_sequence_sort (store->items, compare_func, user_data);
343 n_items = g_sequence_get_length (store->items);
344 g_list_store_items_changed (store, 0, n_items, n_items);
348 * g_list_store_append:
349 * @store: a #GListStore
350 * @item: (type GObject): the new item
352 * Appends @item to @store. @item must be of type #GListStore:item-type.
354 * This function takes a ref on @item.
356 * Use g_list_store_splice() to append multiple items at the same time
362 g_list_store_append (GListStore *store,
367 g_return_if_fail (G_IS_LIST_STORE (store));
368 g_return_if_fail (g_type_is_a (G_OBJECT_TYPE (item), store->item_type));
370 n_items = g_sequence_get_length (store->items);
371 g_sequence_append (store->items, g_object_ref (item));
373 g_list_store_items_changed (store, n_items, 0, 1);
377 * g_list_store_remove:
378 * @store: a #GListStore
379 * @position: the position of the item that is to be removed
381 * Removes the item from @store that is at @position. @position must be
382 * smaller than the current length of the list.
384 * Use g_list_store_splice() to remove multiple items at the same time
390 g_list_store_remove (GListStore *store,
395 g_return_if_fail (G_IS_LIST_STORE (store));
397 it = g_sequence_get_iter_at_pos (store->items, position);
398 g_return_if_fail (!g_sequence_iter_is_end (it));
400 g_sequence_remove (it);
401 g_list_store_items_changed (store, position, 1, 0);
405 * g_list_store_remove_all:
406 * @store: a #GListStore
408 * Removes all items from @store.
413 g_list_store_remove_all (GListStore *store)
417 g_return_if_fail (G_IS_LIST_STORE (store));
419 n_items = g_sequence_get_length (store->items);
420 g_sequence_remove_range (g_sequence_get_begin_iter (store->items),
421 g_sequence_get_end_iter (store->items));
423 g_list_store_items_changed (store, 0, n_items, 0);
427 * g_list_store_splice:
428 * @store: a #GListStore
429 * @position: the position at which to make the change
430 * @n_removals: the number of items to remove
431 * @additions: (array length=n_additions) (element-type GObject): the items to add
432 * @n_additions: the number of items to add
434 * Changes @store by removing @n_removals items and adding @n_additions
435 * items to it. @additions must contain @n_additions items of type
436 * #GListStore:item-type. %NULL is not permitted.
438 * This function is more efficient than g_list_store_insert() and
439 * g_list_store_remove(), because it only emits
440 * #GListModel::items-changed once for the change.
442 * This function takes a ref on each item in @additions.
444 * The parameters @position and @n_removals must be correct (ie:
445 * @position + @n_removals must be less than or equal to the length of
446 * the list at the time this function is called).
451 g_list_store_splice (GListStore *store,
460 g_return_if_fail (G_IS_LIST_STORE (store));
461 g_return_if_fail (position + n_removals >= position); /* overflow */
463 n_items = g_sequence_get_length (store->items);
464 g_return_if_fail (position + n_removals <= n_items);
466 it = g_sequence_get_iter_at_pos (store->items, position);
472 end = g_sequence_iter_move (it, n_removals);
473 g_sequence_remove_range (it, end);
482 for (i = 0; i < n_additions; i++)
484 if G_UNLIKELY (!g_type_is_a (G_OBJECT_TYPE (additions[i]), store->item_type))
486 g_critical ("%s: item %d is a %s instead of a %s. GListStore is now in an undefined state.",
487 G_STRFUNC, i, G_OBJECT_TYPE_NAME (additions[i]), g_type_name (store->item_type));
491 g_sequence_insert_before (it, g_object_ref (additions[i]));
495 g_list_store_items_changed (store, position, n_removals, n_additions);
499 * g_list_store_find_with_equal_func:
500 * @store: a #GListStore
501 * @item: (type GObject): an item
502 * @equal_func: (scope call): A custom equality check function
503 * @position: (out) (optional): the first position of @item, if it was found.
505 * Looks up the given @item in the list store by looping over the items and
506 * comparing them with @compare_func until the first occurrence of @item which
507 * matches. If @item was not found, then @position will not be set, and this
508 * method will return %FALSE.
510 * Returns: Whether @store contains @item. If it was found, @position will be
511 * set to the position where @item occurred for the first time.
516 g_list_store_find_with_equal_func (GListStore *store,
518 GEqualFunc equal_func,
521 GSequenceIter *iter, *begin, *end;
523 g_return_val_if_fail (G_IS_LIST_STORE (store), FALSE);
524 g_return_val_if_fail (g_type_is_a (G_OBJECT_TYPE (item), store->item_type),
526 g_return_val_if_fail (equal_func != NULL, FALSE);
528 /* NOTE: We can't use g_sequence_lookup() or g_sequence_search(), because we
529 * can't assume the sequence is sorted. */
530 begin = g_sequence_get_begin_iter (store->items);
531 end = g_sequence_get_end_iter (store->items);
538 iter_item = g_sequence_get (iter);
539 if (equal_func (iter_item, item))
542 *position = g_sequence_iter_get_position (iter);
546 iter = g_sequence_iter_next (iter);
554 * @store: a #GListStore
555 * @item: (type GObject): an item
556 * @position: (out) (optional): the first position of @item, if it was found.
558 * Looks up the given @item in the list store by looping over the items until
559 * the first occurrence of @item. If @item was not found, then @position will
560 * not be set, and this method will return %FALSE.
562 * If you need to compare the two items with a custom comparison function, use
563 * g_list_store_find_with_equal_func() with a custom #GEqualFunc instead.
565 * Returns: Whether @store contains @item. If it was found, @position will be
566 * set to the position where @item occurred for the first time.
571 g_list_store_find (GListStore *store,
575 return g_list_store_find_with_equal_func (store,