Add GListModel
[glib.git] / gio / gliststore.c
blob407dcbf67290d64d013da64a4c81e1d92642e3f2
1 /*
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 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/>.
18 * Authors:
19 * Lars Uebernickel <lars@uebernic.de>
20 * Ryan Lortie <desrt@desrt.ca>
23 #include "config.h"
25 #include "gliststore.h"
26 #include "glistmodel.h"
28 /**
29 * SECTION:glistmodel
30 * @title: GListStore
31 * @short_description: A simple implementation of #GListModel
32 * @include: gio/gio.h
34 * #GListStore is a simple implementation of #GListModel that stores all
35 * items in memory.
37 * It provides insertions, deletions, and lookups in logarithmic time
38 * with a fast path for the common case of iterating the list linearly.
41 struct _GListStore
43 GObject parent_instance;
45 GType item_type;
46 GSequence *items;
48 /* cache */
49 guint last_position;
50 GSequenceIter *last_iter;
53 enum
55 PROP_0,
56 PROP_ITEM_TYPE,
57 N_PROPERTIES
60 static void g_list_store_iface_init (GListModelInterface *iface);
62 G_DEFINE_TYPE_WITH_CODE (GListStore, g_list_store, G_TYPE_OBJECT,
63 G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, g_list_store_iface_init));
65 static void
66 g_list_store_items_changed (GListStore *store,
67 guint position,
68 guint removed,
69 guint added)
71 /* check if the iter cache may have been invalidated */
72 if (position <= store->last_position)
74 store->last_iter = NULL;
75 store->last_position = -1u;
78 g_list_model_items_changed (G_LIST_MODEL (store), position, removed, added);
81 static void
82 g_list_store_dispose (GObject *object)
84 GListStore *store = G_LIST_STORE (object);
86 g_clear_pointer (&store->items, g_sequence_free);
88 G_OBJECT_CLASS (g_list_store_parent_class)->dispose (object);
91 static void
92 g_list_store_get_property (GObject *object,
93 guint property_id,
94 GValue *value,
95 GParamSpec *pspec)
97 GListStore *store = G_LIST_STORE (object);
99 switch (property_id)
101 case PROP_ITEM_TYPE:
102 g_value_set_gtype (value, store->item_type);
103 break;
105 default:
106 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
110 static void
111 g_list_store_set_property (GObject *object,
112 guint property_id,
113 const GValue *value,
114 GParamSpec *pspec)
116 GListStore *store = G_LIST_STORE (object);
118 switch (property_id)
120 case PROP_ITEM_TYPE: /* construct-only */
121 store->item_type = g_value_get_gtype (value);
122 if (!g_type_is_a (store->item_type, G_TYPE_OBJECT))
123 g_critical ("GListStore cannot store items of type '%s'. Items must be GObjects.",
124 g_type_name (store->item_type));
125 break;
127 default:
128 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
132 static void
133 g_list_store_class_init (GListStoreClass *klass)
135 GObjectClass *object_class = G_OBJECT_CLASS (klass);
137 object_class->dispose = g_list_store_dispose;
138 object_class->get_property = g_list_store_get_property;
139 object_class->set_property = g_list_store_set_property;
142 * GListStore:item-type:
144 * The type of items contained in this list store. Items must be
145 * subclasses of #GObject.
147 * Since: 2.44
149 g_object_class_install_property (object_class, PROP_ITEM_TYPE,
150 g_param_spec_gtype ("item-type", "", "", G_TYPE_OBJECT,
151 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
154 static GType
155 g_list_store_get_item_type (GListModel *list)
157 GListStore *store = G_LIST_STORE (list);
159 return store->item_type;
162 static guint
163 g_list_store_get_n_items (GListModel *list)
165 GListStore *store = G_LIST_STORE (list);
167 return g_sequence_get_length (store->items);
170 static gpointer
171 g_list_store_get_item (GListModel *list,
172 guint position)
174 GListStore *store = G_LIST_STORE (list);
175 GSequenceIter *it = NULL;
177 if (store->last_position != -1u)
179 if (store->last_position == position + 1)
180 it = g_sequence_iter_prev (store->last_iter);
181 else if (store->last_position == position - 1)
182 it = g_sequence_iter_next (store->last_iter);
183 else if (store->last_position == position)
184 it = store->last_iter;
187 if (it == NULL)
188 it = g_sequence_get_iter_at_pos (store->items, position);
190 store->last_iter = it;
191 store->last_position = position;
193 if (g_sequence_iter_is_end (it))
194 return NULL;
195 else
196 return g_object_ref (g_sequence_get (it));
199 static void
200 g_list_store_iface_init (GListModelInterface *iface)
202 iface->get_item_type = g_list_store_get_item_type;
203 iface->get_n_items = g_list_store_get_n_items;
204 iface->get_item = g_list_store_get_item;
207 static void
208 g_list_store_init (GListStore *store)
210 store->items = g_sequence_new (g_object_unref);
211 store->last_position = -1u;
215 * g_list_store_new:
216 * @item_type: the #GType of items in the list
218 * Creates a new #GListStore with items of type @item_type. @item_type
219 * must be a subclass of #GObject.
221 * Returns: a new #GListStore
222 * Since: 2.44
224 GListStore *
225 g_list_store_new (GType item_type)
227 /* We only allow GObjects as item types right now. This might change
228 * in the future.
230 g_return_val_if_fail (g_type_is_a (item_type, G_TYPE_OBJECT), NULL);
232 return g_object_new (G_TYPE_LIST_STORE,
233 "item-type", item_type,
234 NULL);
238 * g_list_store_insert:
239 * @store: a #GListStore
240 * @position: the position at which to insert the new item
241 * @item: the new item
243 * Inserts @item into @store at @position. @item must be of type
244 * #GListStore:item-type or derived from it. @position must be smaller
245 * than the length of the list, or equal to it to append.
247 * This function takes a ref on @item.
249 * Use g_list_store_splice() to insert multiple items at the same time
250 * efficiently.
252 * Since: 2.44
254 void
255 g_list_store_insert (GListStore *store,
256 guint position,
257 gpointer item)
259 GSequenceIter *it;
261 g_return_if_fail (G_IS_LIST_STORE (store));
262 g_return_if_fail (g_type_is_a (G_OBJECT_TYPE (item), store->item_type));
263 g_return_if_fail (position <= g_sequence_get_length (store->items));
265 it = g_sequence_get_iter_at_pos (store->items, position);
266 g_sequence_insert_before (it, g_object_ref (item));
268 g_list_store_items_changed (store, position, 0, 1);
272 * g_list_store_append:
273 * @store: a #GListStore
274 * @item: the new item
276 * Appends @item to @store. @item must be of type #GListStore:item-type.
278 * This function takes a ref on @item.
280 * Use g_list_store_splice() to append multiple items at the same time
281 * efficiently.
283 * Since: 2.44
285 void
286 g_list_store_append (GListStore *store,
287 gpointer item)
289 guint n_items;
291 g_return_if_fail (G_IS_LIST_STORE (store));
292 g_return_if_fail (g_type_is_a (G_OBJECT_TYPE (item), store->item_type));
294 n_items = g_sequence_get_length (store->items);
295 g_sequence_append (store->items, g_object_ref (item));
297 g_list_store_items_changed (store, n_items, 0, 1);
301 * g_list_store_remove:
302 * @store: a #GListStore
303 * @position: the position of the item that is to be removed
305 * Removes the item from @store that is at @position. @position must be
306 * smaller than the current length of the list.
308 * Use g_list_store_splice() to remove multiple items at the same time
309 * efficiently.
311 * Since: 2.44
313 void
314 g_list_store_remove (GListStore *store,
315 guint position)
317 GSequenceIter *it;
319 g_return_if_fail (G_IS_LIST_STORE (store));
321 it = g_sequence_get_iter_at_pos (store->items, position);
322 g_return_if_fail (!g_sequence_iter_is_end (it));
324 g_sequence_remove (it);
325 g_list_store_items_changed (store, position, 1, 0);
329 * g_list_store_remove_all:
330 * @store: a #GListStore
332 * Removes all items from @store.
334 * Since: 2.44
336 void
337 g_list_store_remove_all (GListStore *store)
339 guint n_items;
341 g_return_if_fail (G_IS_LIST_STORE (store));
343 n_items = g_sequence_get_length (store->items);
344 g_sequence_remove_range (g_sequence_get_begin_iter (store->items),
345 g_sequence_get_end_iter (store->items));
347 g_list_store_items_changed (store, 0, n_items, 0);
351 * g_list_store_splice:
352 * @store: a #GListStore
353 * @position: the position at which to make the change
354 * @n_removals: the number of items to remove
355 * @additions: (array length=n_additions): the items to add
356 * @n_additions: the number of items to add
358 * Changes @store by removing @n_removals items and adding @n_additions
359 * items to it. @additions must contain @n_additions items of type
360 * #GListStore:item-type. %NULL is not permitted.
362 * This function is more efficient than g_list_store_insert() and
363 * g_list_store_remove(), because it only emits
364 * #GListModel::items-changed once for the change.
366 * This function takes a ref on each item in @additions.
368 * The parameters @position and @n_removals must be correct (ie:
369 * @position + @n_removals must be less than or equal to the length of
370 * the list at the time this function is called).
372 * Since: 2.44
374 void
375 g_list_store_splice (GListStore *store,
376 guint position,
377 guint n_removals,
378 gpointer *additions,
379 guint n_additions)
381 GSequenceIter *it;
382 guint n_items;
384 g_return_if_fail (G_IS_LIST_STORE (store));
385 g_return_if_fail (position + n_removals >= position); /* overflow */
387 n_items = g_sequence_get_length (store->items);
388 g_return_if_fail (position + n_removals <= n_items);
390 it = g_sequence_get_iter_at_pos (store->items, position);
392 if (n_removals)
394 GSequenceIter *end;
396 end = g_sequence_iter_move (it, n_removals);
397 g_sequence_remove_range (it, end);
399 it = end;
402 if (n_additions)
404 gint i;
406 it = g_sequence_iter_next (it);
407 for (i = 0; i < n_additions; i++)
409 if G_UNLIKELY (!g_type_is_a (G_OBJECT_TYPE (additions[i]), store->item_type))
411 g_critical ("%s: item %d is a %s instead of a %s. GListStore is now in an undefined state.",
412 G_STRFUNC, i, G_OBJECT_TYPE_NAME (additions[i]), g_type_name (store->item_type));
413 return;
416 it = g_sequence_insert_before (it, g_object_ref (additions[i]));
420 g_list_store_items_changed (store, position, n_removals, n_additions);