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
;
67 static void g_list_store_iface_init (GListModelInterface
*iface
);
69 G_DEFINE_TYPE_WITH_CODE (GListStore
, g_list_store
, G_TYPE_OBJECT
,
70 G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL
, g_list_store_iface_init
));
73 g_list_store_items_changed (GListStore
*store
,
78 /* check if the iter cache may have been invalidated */
79 if (position
<= store
->last_position
)
81 store
->last_iter
= NULL
;
82 store
->last_position
= -1u;
85 g_list_model_items_changed (G_LIST_MODEL (store
), position
, removed
, added
);
89 g_list_store_dispose (GObject
*object
)
91 GListStore
*store
= G_LIST_STORE (object
);
93 g_clear_pointer (&store
->items
, g_sequence_free
);
95 G_OBJECT_CLASS (g_list_store_parent_class
)->dispose (object
);
99 g_list_store_get_property (GObject
*object
,
104 GListStore
*store
= G_LIST_STORE (object
);
109 g_value_set_gtype (value
, store
->item_type
);
113 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
118 g_list_store_set_property (GObject
*object
,
123 GListStore
*store
= G_LIST_STORE (object
);
127 case PROP_ITEM_TYPE
: /* construct-only */
128 store
->item_type
= g_value_get_gtype (value
);
129 if (!g_type_is_a (store
->item_type
, G_TYPE_OBJECT
))
130 g_critical ("GListStore cannot store items of type '%s'. Items must be GObjects.",
131 g_type_name (store
->item_type
));
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
!= -1u)
186 if (store
->last_position
== position
+ 1)
187 it
= g_sequence_iter_prev (store
->last_iter
);
188 else if (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
;
200 if (g_sequence_iter_is_end (it
))
203 return g_object_ref (g_sequence_get (it
));
207 g_list_store_iface_init (GListModelInterface
*iface
)
209 iface
->get_item_type
= g_list_store_get_item_type
;
210 iface
->get_n_items
= g_list_store_get_n_items
;
211 iface
->get_item
= g_list_store_get_item
;
215 g_list_store_init (GListStore
*store
)
217 store
->items
= g_sequence_new (g_object_unref
);
218 store
->last_position
= -1u;
223 * @item_type: the #GType of items in the list
225 * Creates a new #GListStore with items of type @item_type. @item_type
226 * must be a subclass of #GObject.
228 * Returns: a new #GListStore
232 g_list_store_new (GType item_type
)
234 /* We only allow GObjects as item types right now. This might change
237 g_return_val_if_fail (g_type_is_a (item_type
, G_TYPE_OBJECT
), NULL
);
239 return g_object_new (G_TYPE_LIST_STORE
,
240 "item-type", item_type
,
245 * g_list_store_insert:
246 * @store: a #GListStore
247 * @position: the position at which to insert the new item
248 * @item: (type GObject): the new item
250 * Inserts @item into @store at @position. @item must be of type
251 * #GListStore:item-type or derived from it. @position must be smaller
252 * than the length of the list, or equal to it to append.
254 * This function takes a ref on @item.
256 * Use g_list_store_splice() to insert multiple items at the same time
262 g_list_store_insert (GListStore
*store
,
268 g_return_if_fail (G_IS_LIST_STORE (store
));
269 g_return_if_fail (g_type_is_a (G_OBJECT_TYPE (item
), store
->item_type
));
270 g_return_if_fail (position
<= g_sequence_get_length (store
->items
));
272 it
= g_sequence_get_iter_at_pos (store
->items
, position
);
273 g_sequence_insert_before (it
, g_object_ref (item
));
275 g_list_store_items_changed (store
, position
, 0, 1);
279 * g_list_store_insert_sorted:
280 * @store: a #GListStore
281 * @item: (type GObject): the new item
282 * @compare_func: (scope call): pairwise comparison function for sorting
283 * @user_data: (closure): user data for @compare_func
285 * Inserts @item into @store at a position to be determined by the
288 * The list must already be sorted before calling this function or the
289 * result is undefined. Usually you would approach this by only ever
290 * inserting items by way of this function.
292 * This function takes a ref on @item.
294 * Returns: the position at which @item was inserted
299 g_list_store_insert_sorted (GListStore
*store
,
301 GCompareDataFunc compare_func
,
307 g_return_val_if_fail (G_IS_LIST_STORE (store
), 0);
308 g_return_val_if_fail (g_type_is_a (G_OBJECT_TYPE (item
), store
->item_type
), 0);
309 g_return_val_if_fail (compare_func
!= NULL
, 0);
311 it
= g_sequence_insert_sorted (store
->items
, g_object_ref (item
), compare_func
, user_data
);
312 position
= g_sequence_iter_get_position (it
);
314 g_list_store_items_changed (store
, position
, 0, 1);
321 * @store: a #GListStore
322 * @compare_func: (scope call): pairwise comparison function for sorting
323 * @user_data: (closure): user data for @compare_func
325 * Sort the items in @store according to @compare_func.
330 g_list_store_sort (GListStore
*store
,
331 GCompareDataFunc compare_func
,
336 g_return_if_fail (G_IS_LIST_STORE (store
));
337 g_return_if_fail (compare_func
!= NULL
);
339 g_sequence_sort (store
->items
, compare_func
, user_data
);
341 n_items
= g_sequence_get_length (store
->items
);
342 g_list_store_items_changed (store
, 0, n_items
, n_items
);
346 * g_list_store_append:
347 * @store: a #GListStore
348 * @item: (type GObject): the new item
350 * Appends @item to @store. @item must be of type #GListStore:item-type.
352 * This function takes a ref on @item.
354 * Use g_list_store_splice() to append multiple items at the same time
360 g_list_store_append (GListStore
*store
,
365 g_return_if_fail (G_IS_LIST_STORE (store
));
366 g_return_if_fail (g_type_is_a (G_OBJECT_TYPE (item
), store
->item_type
));
368 n_items
= g_sequence_get_length (store
->items
);
369 g_sequence_append (store
->items
, g_object_ref (item
));
371 g_list_store_items_changed (store
, n_items
, 0, 1);
375 * g_list_store_remove:
376 * @store: a #GListStore
377 * @position: the position of the item that is to be removed
379 * Removes the item from @store that is at @position. @position must be
380 * smaller than the current length of the list.
382 * Use g_list_store_splice() to remove multiple items at the same time
388 g_list_store_remove (GListStore
*store
,
393 g_return_if_fail (G_IS_LIST_STORE (store
));
395 it
= g_sequence_get_iter_at_pos (store
->items
, position
);
396 g_return_if_fail (!g_sequence_iter_is_end (it
));
398 g_sequence_remove (it
);
399 g_list_store_items_changed (store
, position
, 1, 0);
403 * g_list_store_remove_all:
404 * @store: a #GListStore
406 * Removes all items from @store.
411 g_list_store_remove_all (GListStore
*store
)
415 g_return_if_fail (G_IS_LIST_STORE (store
));
417 n_items
= g_sequence_get_length (store
->items
);
418 g_sequence_remove_range (g_sequence_get_begin_iter (store
->items
),
419 g_sequence_get_end_iter (store
->items
));
421 g_list_store_items_changed (store
, 0, n_items
, 0);
425 * g_list_store_splice:
426 * @store: a #GListStore
427 * @position: the position at which to make the change
428 * @n_removals: the number of items to remove
429 * @additions: (array length=n_additions) (element-type GObject): the items to add
430 * @n_additions: the number of items to add
432 * Changes @store by removing @n_removals items and adding @n_additions
433 * items to it. @additions must contain @n_additions items of type
434 * #GListStore:item-type. %NULL is not permitted.
436 * This function is more efficient than g_list_store_insert() and
437 * g_list_store_remove(), because it only emits
438 * #GListModel::items-changed once for the change.
440 * This function takes a ref on each item in @additions.
442 * The parameters @position and @n_removals must be correct (ie:
443 * @position + @n_removals must be less than or equal to the length of
444 * the list at the time this function is called).
449 g_list_store_splice (GListStore
*store
,
458 g_return_if_fail (G_IS_LIST_STORE (store
));
459 g_return_if_fail (position
+ n_removals
>= position
); /* overflow */
461 n_items
= g_sequence_get_length (store
->items
);
462 g_return_if_fail (position
+ n_removals
<= n_items
);
464 it
= g_sequence_get_iter_at_pos (store
->items
, position
);
470 end
= g_sequence_iter_move (it
, n_removals
);
471 g_sequence_remove_range (it
, end
);
480 it
= g_sequence_iter_next (it
);
481 for (i
= 0; i
< n_additions
; i
++)
483 if G_UNLIKELY (!g_type_is_a (G_OBJECT_TYPE (additions
[i
]), store
->item_type
))
485 g_critical ("%s: item %d is a %s instead of a %s. GListStore is now in an undefined state.",
486 G_STRFUNC
, i
, G_OBJECT_TYPE_NAME (additions
[i
]), g_type_name (store
->item_type
));
490 it
= g_sequence_insert_before (it
, g_object_ref (additions
[i
]));
494 g_list_store_items_changed (store
, position
, n_removals
, n_additions
);