ScpTreeStore - an array-based GtkTreeStore/GtkListStore like object.
ScpTreeStore is a GtkTreeStore/GtkListStore object, based entitely on arrays. In particular:
Using arrays for the column headers and row data would probably be beneficial for GtkTreeStore and GtkListStore too, leading to faster and smaller code.
ScpTreeStore implements GtkTreeModel, GtkTreeDragSource, GtkTreeDragDest, GtkTreeSortable and GtkBuildable. The functions that implement these interfaces are public, so you can invoke them directly, without type-casting the store to an interface (except for the GtkBuildable functions, which are never invoked directly).
The functions identical to their GtkTreeStore or interface counterparts, as well as the obvious funtions and macros, are listed without descriptions.
The GtkTreeStore behviour described below applies to GtkListStore too (where applicable).
#include <gtk/gtk.h>
#include "scptreestore.h"
/* Store */
ScpTreeStore *scp_tree_store_new(gboolean sublevels, gint
n_columns, ...);
ScpTreeStore *scp_tree_store_newv(gboolean sublevels, gint
n_columns, GType *types);
gboolean scp_tree_store_set_column_types(ScpTreeStore *store, gint n_columns, GType *types);
void scp_tree_store_set_utf8_collate(ScpTreeStore
*store, gint column, gboolean collate);
void scp_tree_store_set_valuesv(ScpTreeStore *store, GtkTreeIter *iter, gint *columns, GValue
*values, gint n_values);
void scp_tree_store_set_value(ScpTreeStore *store, GtkTreeIter *iter, gint column, GValue
*value);
void scp_tree_store_set_valist(ScpTreeStore *store, GtkTreeIter *iter, va_list ap);
void scp_tree_store_set(ScpTreeStore *store, GtkTreeIter *iter, ...);
gboolean scp_tree_store_remove(ScpTreeStore *store,
GtkTreeIter *iter);
void scp_tree_store_insert(ScpTreeStore *store, GtkTreeIter
*iter, GtkTreeIter *parent, gint position);
#define scp_tree_store_prepend(store, iter, parent)
#define scp_tree_store_append(store, iter, parent)
void scp_tree_store_insert_with_valuesv(ScpTreeStore *store, GtkTreeIter *iter, GtkTreeIter
*parent, gint position, gint *columns, GValue *values, gint n_values);
#define scp_tree_store_prepend_with_valuesv(store, iter, parent, columns, values, n_values)
#define scp_tree_store_append_with_valuesv(store, iter, parent, columns, values, n_values)
void scp_tree_store_insert_with_valist(ScpTreeStore *store, GtkTreeIter *iter, GtkTreeIter
*parent, gint position, va_list ap);
#define scp_tree_store_prepend_with_valist(store, iter, parent, ap)
#define scp_tree_store_append_with_valist(store, iter, parent, ap)
void scp_tree_store_insert_with_values(ScpTreeStore *store, GtkTreeIter *iter, GtkTreeIter
*parent, gint position, ...);
#define scp_tree_store_prepend_with_values(store, iter, parent, ...)
#define scp_tree_store_append_with_values(store, iter, parent, ...)
void scp_tree_store_get_valist(ScpTreeStore *store,
GtkTreeIter *iter, va_list var_args);
void scp_tree_store_get(ScpTreeStore *store, GtkTreeIter
*iter, ...);
gboolean scp_tree_store_is_ancestor(ScpTreeStore *store, GtkTreeIter *iter, GtkTreeIter
*descendant);
gint scp_tree_store_iter_depth(ScpTreeStore *store, GtkTreeIter *iter);
void scp_tree_store_clear_children(ScpTreeStore
*store, GtkTreeIter *parent, gboolean emit_subsignals);
#define scp_tree_store_clear(store)
gboolean scp_tree_store_iter_is_valid(ScpTreeStore *store, GtkTreeIter *iter);
void scp_tree_store_reorder(ScpTreeStore *store, GtkTreeIter *parent, gint *new_order);
void scp_tree_store_swap(ScpTreeStore *store, GtkTreeIter *a, GtkTreeIter *b);
void scp_tree_store_move(ScpTreeStore *store, GtkTreeIter
*iter, gint position);
/* Model */
GtkTreeModelFlags scp_tree_store_get_flags(ScpTreeStore *store);
gint scp_tree_store_get_n_columns(ScpTreeStore *store);
GType scp_tree_store_get_column_type(ScpTreeStore *store, gint index);
gboolean scp_tree_store_get_iter(ScpTreeStore *store, GtkTreeIter *iter, GtkTreePath *path);
GtkTreePath *scp_tree_store_get_path(ScpTreeStore *store, GtkTreeIter *iter);
void scp_tree_store_get_value(ScpTreeStore *model, GtkTreeIter *iter, gint column, GValue
*value);
gboolean scp_tree_store_iter_next(ScpTreeStore *store, GtkTreeIter *iter);
gboolean scp_tree_store_iter_previous(ScpTreeStore
*store, GtkTreeIter *iter);
gboolean scp_tree_store_iter_children(ScpTreeStore *store, GtkTreeIter *iter, GtkTreeIter
*parent);
gboolean scp_tree_store_iter_has_child(ScpTreeStore *store, GtkTreeIter *iter);
gint scp_tree_store_iter_n_children(ScpTreeStore *store, GtkTreeIter *iter);
gboolean scp_tree_store_iter_nth_child(ScpTreeStore *store, GtkTreeIter *iter, GtkTreeIter
*parent, gint n);
#define scp_tree_store_get_iter_first(store, iter)
gboolean scp_tree_store_iter_parent(ScpTreeStore *store, GtkTreeIter *iter, GtkTreeIter
*child);
void scp_tree_store_foreach(ScpTreeStore *store, GtkTreeModelForeachFunc func, gpointer
gdata);
/* DND */
gboolean scp_tree_store_row_draggable(ScpTreeStore *store, GtkTreePath *path);
gboolean scp_tree_store_drag_data_delete(ScpTreeStore *store, GtkTreePath *path);
gboolean scp_tree_store_drag_data_get(ScpTreeStore *store, GtkTreePath *path, GtkSelectionData
*selection_data);
gboolean scp_tree_store_drag_data_received(ScpTreeStore *store, GtkTreePath *dest,
GtkSelectionData *selection_data);
gboolean scp_tree_store_row_drop_possible(ScpTreeStore *store, GtkTreePath *dest_path,
GtkSelectionData *selection_data);
/* Sortable */
gboolean scp_tree_store_get_sort_column_id(ScpTreeStore *store, gint *sort_column_id,
GtkSortType *order);
void scp_tree_store_set_sort_column_id(ScpTreeStore *store, gint sort_column_id, GtkSortType
order);
void scp_tree_store_set_sort_func(ScpTreeStore *store, gint sort_column_id,
GtkTreeIterCompareFunc func, gpointer data, GDestroyNotify destroy);
void scp_tree_store_set_default_sort_func(ScpTreeStore *store, GtkTreeIterCompareFunc func,
gpointer data, GDestroyNotify destroy);
gboolean scp_tree_store_has_default_sort_func(ScpTreeStore *store);
/* Extra */
void scp_tree_store_set_allocation(ScpTreeStore
*store, guint toplevel_reserved, guint sublevel_reserved, gboolean sublevel_discard);
gint scp_tree_store_compare_func(ScpTreeStore *store,
GtkTreeIter *a, GtkTreeIter *b, gpointer data);
gboolean scp_tree_store_iter_seek(ScpTreeStore *store,
GtkTreeIter *iter, gint position);
gint scp_tree_store_iter_tell(ScpTreeStore *store, GtkTreeIter *iter);
gboolean scp_tree_store_search(ScpTreeStore *store,
gboolean sublevels, gboolean linear_order, GtkTreeIter *iter, GtkTreeIter *parent, gint column,
...);
typedef gint (*ScpTreeStoreTraverseFunc)(ScpTreeStore
*store, GtkTreeIter *iter, gpointer gdata);
gboolean scp_tree_store_traverse(ScpTreeStore *store,
gboolean sublevels, GtkTreeIter *iter, GtkTreeIter *parent, ScpTreeStoreTraverseFunc func,
gpointer gdata);
ScpTreeStore *scp_tree_store_new(gboolean sublevels, gint n_columns, ...);
ScpTreeStore *scp_tree_store_newv(gboolean sublevels, gint n_columns, GType *types);
void scp_tree_store_insert(ScpTreeStore *store, GtkTreeIter *iter, GtkTreeIter *parent, gint position);
position must be between -1 and N, where N is the number of rows at that level. GtkTreeStore allows position > N and treats it as N (i.e. appends). To be precise, it simply walks it's row-list either until position is reached or there are no more rows.
If you insert a row in a sorted GtkTreeStore, and do not set a value for the sort column (or for any column, if a non-standard sort function is being used), the row will remain where you inserted it, possibly breaking the sort order. ScpTreeStore will sort such rows if needed.
When a row is being sorted as a result of an insert_with or set operation, GtkTreeStore uses linear search, so this row always end up before the first row equal to it, if any. ScpTreeStore uses binary search, and "If two iters compare as equal, their order in the sorted model is undefined", as stated in the GtkTreeSortable documentation.
void scp_tree_store_remove(ScpTreeStore *store, GtkTreeIter *iter);
GtkTreeStore removes the children of iter (if any) from first to last, while ScpTreeStore removes them from last to first. The GTK+ documentation does not specify any particular order.
void scp_tree_store_get_valist(ScpTreeStore *store, GtkTreeIter *iter, va_list
var_args);
void scp_tree_store_get(ScpTreeStore *store, GtkTreeIter *iter, ...);
Unlike the similar GtkTreeModel functions, these do not copy the strings or reference the objects/boxeds, so the return values for these types (a) need not be freed/unreferenced, and (b) may become invalid if the row is removed.
void scp_tree_store_clear_children(ScpTreeStore *store, GtkTreeIter *parent, gboolean emit_subsignals);
#define scp_tree_store_clear(store)
gtk_tree_store_clear() emits "row-removed" signals not only for the top-level rows, but for their all their children as well. ScpTreeStore follows that behaviour. Clearing the store with scp_tree_store_clear_children(store, NULL, FALSE) is faster, though not significantly.
GtkTreeStore removes the rows from first to last, while ScpTreeStore removes them from last to first. The GTK+ documentation does not specify any particular order.
void scp_tree_store_move(ScpTreeStore *store, GtkTreeIter *iter, gint position);
gboolean scp_tree_store_iter_previous(ScpTreeStore *store, GtkTreeIter *iter);
gtk_tree_model_iter_previous() requires gtk+ 3.0.0 or later; scp_tree_model_iter_previous() does not.
void scp_tree_store_set_reserved(ScpTreeStore *store, guint toplevel_reserved, guint sublevel_reserved, gboolean sublevel_discard);
void scp_tree_store_set_utf8_collate(ScpTreeStore *store, gint column, gboolean collate);
gint scp_tree_store_compare_func(ScpTreeStore *store, GtkTreeIter *a, GtkTreeIter *b, gpointer data);
May NOT be used with GtkTreeStore and GtkListStore.
Compatible with and type-castable to GtkTreeIterCompareFunc.
gboolean scp_tree_store_iter_seek(ScpTreeStore *store, GtkTreeIter *iter, gint position);
gboolean scp_tree_store_search(ScpTreeStore *store, gboolean sublevels, gboolean linear_order, GtkTreeIter *iter, GtkTreeIter *parent, gint column, ...);
If column is the current sort column, it's compare function is the default one, and linear_order is FALSE, binary search will be used. Aside from that, the column compare function is ignored, because it requires an iterator, not a value. For string columns, utf8_collate is taken into account.
gint ScpTreeStoreTraverseFunc(ScpTreeStore *store, GtkTreeIter *iter, gpointer gdata);
gboolean scp_tree_store_traverse(ScpTreeStore *store, gboolean sublevels, GtkTreeIter *iter, GtkTreeIter *parent, ScpTreeStoreTraverseFunc func, gpointer gdata);
scp_tree_store_register_dynamic(void);
ScpTreeStore is written in a way that allows unloading the module which defines it, provided that:
"sublevels" (gboolean, read/construct) - whether the store supports sublevels.
"toplevel-reserved" (gint, read/write) - number of pointers to preallocate for the top-level array. Default = 0, GPtrArray allocates minimum 16.
"sublevel-reserved" (gint, read/write) - number of pointers to preallocate the sublevel arrays. Default = 0, GPtrArray allocates minimum 16.
"sublevel-discard" (gboolean, read/write) - whether to discard a sublevel array when the last row from that level is removed. Default = FALSE.
The arrays are allocated when the first row at that level is being inserted. Setting "-reserved" has no effect on any currently allocated arrays. The top-level array is discarded only when the store is finalized. Thus, setting "toplevel-reserved" after at least one row was inserted in the store has no effect.
ScpTreeStore's iterators are non-persistent - that is, each operation which changes the rows order may affect the iterators at that level. More specifically, the iterators work like array indexes: for you have an iter_a for the 5th row, and remove the 3rd row, iter_a will now point to the new 5th row (former 6th), or will become invalid, if there were 5 rows. Using invalid iterators may generate warning messages, or simply crash the application (that applies to GtkTreeStore as well).
Obviously, appending to an unsorted store will not affect the existing iterators, and you can predict the effects of all other operations - even for sorted stores, using scp_tree_store_tell(). But it's easy to make mistakes, so if you need a persistent reference to a row, it's better to try GtkTreeRowReference.
GtkTreeModelFilter and GtkTreeModelSort use persistent iterators (if available) to cache their GtkTreeRowReference-s. Although ScpTreeStore has a very fast path to iter, and is generally quite fast, these models may still be slower.
void user_function(GtkTreeModel *tree_model, GtkTreePath *path, GtkTreeIter *iter, gpointer new_order, gpointer user_data);
From the GtkTreeModel documentation (at least gtk+ <= 3.6.4):
"iter : a valid GtkTreeIter pointing to the node whose" [children have been reordered?]
When the top-level rows are being reordered, such iter may not be valid, because it must point above the top. GtkTreeStore pass a pseudo-valid iter which is suitable for a few operations, and so does ScpTreeStore, but you should not rely on that and check for path depth = 0 instead.
An unsorted ScpTreeStore is faster than GtkTreeStore, but the difference is not big (20% on average, depending on the operation and number of elements), and not significant for stores with less than 10000 elements (except foreach or lots of searches). Normally, inserting into and removing from a list is faster than using an array, but all store operations must emit a signal, containing the row path (element indexes), which negates this advantage.
The sorted gtk+ stores are slow, since they are based on lists, and sorting by a string with utf-8 collation makes them even slower.
Store sorted by double | Row count | Times | GtkTreeStore | ScpTreeStore |
---|---|---|---|---|
pre/apppend + set | 250 | 0.015 | 0.003 | |
pre/append with values | 250 | 0.014 | 0.001 | |
binary search | 250 | 250 | n/a | 0.000 |
unsorted linear search | 250 | 250 | 0.035 | 0.001 |
pre/apppend + set | 2500 | 1.321 | 0.041 | |
pre/append with values | 2500 | 1.285 | 0.016 | |
binary search | 2500 | 2500 | n/a | 0.001 |
unsorted linear search | 2500 | 770 | 1.031 | 0.041 |
Store sorted by string | Row count | Times | GtkTreeStore | ScpTreeStore |
pre/apppend + set | 250 | 0.088 | 0.012 | |
pre/append with values | 250 | 0.084 | 0.008 | |
binary search | 250 | 250 | n/a | 0.005 |
unsorted linear search | 250 | 250 | 0.220 | 0.102 |
pre/apppend + set | 2500 | 8.740 | 0.193 | |
pre/append with values | 2500 | 8.913 | 0.130 | |
binary search | 2500 | 2500 | n/a | 0.076 |
unsorted linear search | 2500 | 770 | 6.807 | 3.186 |
The full speed test is here. In general, a large sorted ScpTreeStore can be used as a normal data structure, unlike a GtkTree/ListStore. And, since ScpTreeStore is not part of gtk+, you can easily recompile it with -DG_DISABLE_CHECKS, but don't expect any significant difference.
Incomplete Glade support. ScpTreeStore-s can be included in .glade files with a text editor, but saving such files with Glade will discard the stores. I don't plan to fix this.
The linguistically correct string comparision is not well tested.
Report bugs to <dimitar.zhekov@gmail.com>.
Parts of DND, most defensive programming checks and most of the buildable implementation are from GtkTreeStore.
GtkTreeStore, Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford
ScpTreeStore was written for the Scope plugin of Geany light IDE, which relies heavily on stores.
ScpTreeStore 0.86.1, Copyright (C) 2014 Dimitar Toshkov Zhekov
ScpTreeStore is distributed under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.