forked from AuroraMiddleware/gtk
filterlistmodel: Add incremental filtering
This commit is contained in:
parent
d4b868d9bc
commit
2a90bc1a9b
@ -1547,6 +1547,8 @@ gtk_filter_list_model_set_model
|
|||||||
gtk_filter_list_model_get_model
|
gtk_filter_list_model_get_model
|
||||||
gtk_filter_list_model_set_filter
|
gtk_filter_list_model_set_filter
|
||||||
gtk_filter_list_model_get_filter
|
gtk_filter_list_model_get_filter
|
||||||
|
gtk_filter_list_model_set_incremental
|
||||||
|
gtk_filter_list_model_get_incremental
|
||||||
<SUBSECTION Standard>
|
<SUBSECTION Standard>
|
||||||
GTK_FILTER_LIST_MODEL
|
GTK_FILTER_LIST_MODEL
|
||||||
GTK_IS_FILTER_LIST_MODEL
|
GTK_IS_FILTER_LIST_MODEL
|
||||||
|
@ -35,11 +35,16 @@
|
|||||||
* listmodel.
|
* listmodel.
|
||||||
* It hides some elements from the other model according to
|
* It hides some elements from the other model according to
|
||||||
* criteria given by a #GtkFilter.
|
* criteria given by a #GtkFilter.
|
||||||
|
*
|
||||||
|
* The model can be set up to do incremental searching, so that
|
||||||
|
* filtering long lists doesn't block the UI. See
|
||||||
|
* gtk_filter_list_model_set_incremental() for details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
PROP_0,
|
PROP_0,
|
||||||
PROP_FILTER,
|
PROP_FILTER,
|
||||||
|
PROP_INCREMENTAL,
|
||||||
PROP_MODEL,
|
PROP_MODEL,
|
||||||
NUM_PROPERTIES
|
NUM_PROPERTIES
|
||||||
};
|
};
|
||||||
@ -51,8 +56,11 @@ struct _GtkFilterListModel
|
|||||||
GListModel *model;
|
GListModel *model;
|
||||||
GtkFilter *filter;
|
GtkFilter *filter;
|
||||||
GtkFilterMatch strictness;
|
GtkFilterMatch strictness;
|
||||||
|
gboolean incremental;
|
||||||
|
|
||||||
GtkBitset *matches; /* NULL if strictness != GTK_FILTER_MATCH_SOME */
|
GtkBitset *matches; /* NULL if strictness != GTK_FILTER_MATCH_SOME */
|
||||||
|
GtkBitset *pending; /* not yet filtered items or NULL if all filtered */
|
||||||
|
guint pending_cb; /* idle callback handle */
|
||||||
};
|
};
|
||||||
|
|
||||||
struct _GtkFilterListModelClass
|
struct _GtkFilterListModelClass
|
||||||
@ -148,64 +156,111 @@ gtk_filter_list_model_run_filter_on_item (GtkFilterListModel *self,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
gtk_filter_list_model_run_filter (GtkFilterListModel *self)
|
gtk_filter_list_model_run_filter (GtkFilterListModel *self,
|
||||||
|
guint n_steps)
|
||||||
{
|
{
|
||||||
guint i, n_items;
|
GtkBitsetIter iter;
|
||||||
GtkBitset *other;
|
guint i, pos;
|
||||||
|
gboolean more;
|
||||||
|
|
||||||
g_return_if_fail (GTK_IS_FILTER_LIST_MODEL (self));
|
g_return_if_fail (GTK_IS_FILTER_LIST_MODEL (self));
|
||||||
|
|
||||||
if (self->matches == NULL || self->model == NULL)
|
if (self->pending == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
n_items = g_list_model_get_n_items (self->model);
|
for (i = 0, more = gtk_bitset_iter_init_first (&iter, self->pending, &pos);
|
||||||
other = self->matches;
|
i < n_steps && more;
|
||||||
self->matches = gtk_bitset_new_empty ();
|
i++, more = gtk_bitset_iter_next (&iter, &pos))
|
||||||
|
|
||||||
for (i = 0; i < n_items; i++)
|
|
||||||
{
|
{
|
||||||
if (gtk_filter_list_model_run_filter_on_item (self, i))
|
if (gtk_filter_list_model_run_filter_on_item (self, pos))
|
||||||
gtk_bitset_add (self->matches, i);
|
gtk_bitset_add (self->matches, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
gtk_bitset_difference (other, self->matches);
|
if (more)
|
||||||
if (!gtk_bitset_is_empty (other))
|
gtk_bitset_remove_range_closed (self->pending, 0, pos);
|
||||||
{
|
else
|
||||||
guint min, max, changes, additions, existing;
|
g_clear_pointer (&self->pending, gtk_bitset_unref);
|
||||||
|
|
||||||
min = gtk_bitset_get_minimum (other);
|
return;
|
||||||
max = gtk_bitset_get_maximum (other);
|
|
||||||
existing = gtk_bitset_get_size_in_range (self->matches, min, max);
|
|
||||||
changes = gtk_bitset_get_size (other);
|
|
||||||
gtk_bitset_intersect (other, self->matches);
|
|
||||||
additions = gtk_bitset_get_size (other);
|
|
||||||
g_list_model_items_changed (G_LIST_MODEL (self),
|
|
||||||
min > 0 ? gtk_bitset_get_size_in_range (self->matches, 0, min - 1) : 0,
|
|
||||||
existing - additions + (changes - additions),
|
|
||||||
existing);
|
|
||||||
}
|
|
||||||
gtk_bitset_unref (other);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static guint
|
static void
|
||||||
gtk_filter_list_model_add_items (GtkFilterListModel *self,
|
gtk_filter_list_model_stop_filtering (GtkFilterListModel *self)
|
||||||
guint position,
|
|
||||||
guint n_items)
|
|
||||||
{
|
{
|
||||||
guint i, n_visible;
|
g_clear_pointer (&self->pending, gtk_bitset_unref);
|
||||||
|
g_clear_handle_id (&self->pending_cb, g_source_remove);
|
||||||
|
}
|
||||||
|
|
||||||
n_visible = 0;
|
static void
|
||||||
|
gtk_filter_list_model_emit_items_changed_for_changes (GtkFilterListModel *self,
|
||||||
|
GtkBitset *old)
|
||||||
|
{
|
||||||
|
GtkBitset *changes;
|
||||||
|
|
||||||
for (i = 0; i < n_items; i++)
|
changes = gtk_bitset_copy (self->matches);
|
||||||
|
gtk_bitset_difference (changes, old);
|
||||||
|
if (!gtk_bitset_is_empty (changes))
|
||||||
{
|
{
|
||||||
if (gtk_filter_list_model_run_filter_on_item (self, position + i))
|
guint min, max;
|
||||||
{
|
|
||||||
gtk_bitset_add (self->matches, position + i);
|
min = gtk_bitset_get_minimum (changes);
|
||||||
n_visible++;
|
max = gtk_bitset_get_maximum (changes);
|
||||||
|
g_list_model_items_changed (G_LIST_MODEL (self),
|
||||||
|
min > 0 ? gtk_bitset_get_size_in_range (self->matches, 0, min - 1) : 0,
|
||||||
|
gtk_bitset_get_size_in_range (old, min, max),
|
||||||
|
gtk_bitset_get_size_in_range (self->matches, min, max));
|
||||||
}
|
}
|
||||||
|
gtk_bitset_unref (changes);
|
||||||
|
gtk_bitset_unref (old);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
gtk_filter_list_model_run_filter_cb (gpointer data)
|
||||||
|
{
|
||||||
|
GtkFilterListModel *self = data;
|
||||||
|
GtkBitset *old;
|
||||||
|
|
||||||
|
old = gtk_bitset_copy (self->matches);
|
||||||
|
gtk_filter_list_model_run_filter (self, 512);
|
||||||
|
|
||||||
|
if (self->pending == NULL)
|
||||||
|
gtk_filter_list_model_stop_filtering (self);
|
||||||
|
|
||||||
|
gtk_filter_list_model_emit_items_changed_for_changes (self, old);
|
||||||
|
|
||||||
|
return G_SOURCE_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NB: bitset is (transfer full) */
|
||||||
|
static void
|
||||||
|
gtk_filter_list_model_start_filtering (GtkFilterListModel *self,
|
||||||
|
GtkBitset *items)
|
||||||
|
{
|
||||||
|
if (self->pending)
|
||||||
|
{
|
||||||
|
gtk_bitset_union (self->pending, items);
|
||||||
|
gtk_bitset_unref (items);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return n_visible;
|
if (gtk_bitset_is_empty (items))
|
||||||
|
{
|
||||||
|
gtk_bitset_unref (items);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->pending = items;
|
||||||
|
|
||||||
|
if (!self->incremental)
|
||||||
|
{
|
||||||
|
gtk_filter_list_model_run_filter (self, G_MAXUINT);
|
||||||
|
g_assert (self->pending == NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_assert (self->pending_cb == 0);
|
||||||
|
self->pending_cb = g_idle_add (gtk_filter_list_model_run_filter_cb, self);
|
||||||
|
g_source_set_name_by_id (self->pending_cb, "[gtk] gtk_filter_list_model_run_filter_cb");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -215,7 +270,7 @@ gtk_filter_list_model_items_changed_cb (GListModel *model,
|
|||||||
guint added,
|
guint added,
|
||||||
GtkFilterListModel *self)
|
GtkFilterListModel *self)
|
||||||
{
|
{
|
||||||
guint filter_position, filter_removed, filter_added;
|
guint filter_removed, filter_added;
|
||||||
|
|
||||||
switch (self->strictness)
|
switch (self->strictness)
|
||||||
{
|
{
|
||||||
@ -239,12 +294,21 @@ gtk_filter_list_model_items_changed_cb (GListModel *model,
|
|||||||
filter_removed = 0;
|
filter_removed = 0;
|
||||||
|
|
||||||
gtk_bitset_slice (self->matches, position, removed, added);
|
gtk_bitset_slice (self->matches, position, removed, added);
|
||||||
|
if (self->pending)
|
||||||
|
gtk_bitset_slice (self->pending, position, removed, added);
|
||||||
|
|
||||||
filter_position = gtk_bitset_get_size_in_range (self->matches, 0, position);
|
if (added > 0)
|
||||||
filter_added = gtk_filter_list_model_add_items (self, position, added);
|
{
|
||||||
|
gtk_filter_list_model_start_filtering (self, gtk_bitset_new_range (position, added));
|
||||||
|
filter_added = gtk_bitset_get_size_in_range (self->matches, position, position + added - 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
filter_added = 0;
|
||||||
|
|
||||||
if (filter_removed > 0 || filter_added > 0)
|
if (filter_removed > 0 || filter_added > 0)
|
||||||
g_list_model_items_changed (G_LIST_MODEL (self), filter_position, filter_removed, filter_added);
|
g_list_model_items_changed (G_LIST_MODEL (self),
|
||||||
|
position > 0 ? gtk_bitset_get_size_in_range (self->matches, 0, position - 1) : 0,
|
||||||
|
filter_removed, filter_added);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -261,6 +325,10 @@ gtk_filter_list_model_set_property (GObject *object,
|
|||||||
gtk_filter_list_model_set_filter (self, g_value_get_object (value));
|
gtk_filter_list_model_set_filter (self, g_value_get_object (value));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PROP_INCREMENTAL:
|
||||||
|
gtk_filter_list_model_set_incremental (self, g_value_get_boolean (value));
|
||||||
|
break;
|
||||||
|
|
||||||
case PROP_MODEL:
|
case PROP_MODEL:
|
||||||
gtk_filter_list_model_set_model (self, g_value_get_object (value));
|
gtk_filter_list_model_set_model (self, g_value_get_object (value));
|
||||||
break;
|
break;
|
||||||
@ -285,6 +353,10 @@ gtk_filter_list_model_get_property (GObject *object,
|
|||||||
g_value_set_object (value, self->filter);
|
g_value_set_object (value, self->filter);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PROP_INCREMENTAL:
|
||||||
|
g_value_set_boolean (value, self->incremental);
|
||||||
|
break;
|
||||||
|
|
||||||
case PROP_MODEL:
|
case PROP_MODEL:
|
||||||
g_value_set_object (value, self->model);
|
g_value_set_object (value, self->model);
|
||||||
break;
|
break;
|
||||||
@ -301,6 +373,7 @@ gtk_filter_list_model_clear_model (GtkFilterListModel *self)
|
|||||||
if (self->model == NULL)
|
if (self->model == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
gtk_filter_list_model_stop_filtering (self);
|
||||||
g_signal_handlers_disconnect_by_func (self->model, gtk_filter_list_model_items_changed_cb, self);
|
g_signal_handlers_disconnect_by_func (self->model, gtk_filter_list_model_items_changed_cb, self);
|
||||||
g_clear_object (&self->model);
|
g_clear_object (&self->model);
|
||||||
if (self->matches)
|
if (self->matches)
|
||||||
@ -329,6 +402,7 @@ gtk_filter_list_model_refilter (GtkFilterListModel *self,
|
|||||||
guint n_before = g_list_model_get_n_items (G_LIST_MODEL (self));
|
guint n_before = g_list_model_get_n_items (G_LIST_MODEL (self));
|
||||||
g_clear_pointer (&self->matches, gtk_bitset_unref);
|
g_clear_pointer (&self->matches, gtk_bitset_unref);
|
||||||
self->strictness = new_strictness;
|
self->strictness = new_strictness;
|
||||||
|
gtk_filter_list_model_stop_filtering (self);
|
||||||
if (n_before > 0)
|
if (n_before > 0)
|
||||||
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_before, 0);
|
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_before, 0);
|
||||||
}
|
}
|
||||||
@ -349,6 +423,7 @@ gtk_filter_list_model_refilter (GtkFilterListModel *self,
|
|||||||
{
|
{
|
||||||
guint start, end, n_before, n_after;
|
guint start, end, n_before, n_after;
|
||||||
|
|
||||||
|
gtk_filter_list_model_stop_filtering (self);
|
||||||
self->strictness = new_strictness;
|
self->strictness = new_strictness;
|
||||||
n_after = g_list_model_get_n_items (G_LIST_MODEL (self));
|
n_after = g_list_model_get_n_items (G_LIST_MODEL (self));
|
||||||
start = gtk_bitset_get_minimum (self->matches);
|
start = gtk_bitset_get_minimum (self->matches);
|
||||||
@ -361,9 +436,9 @@ gtk_filter_list_model_refilter (GtkFilterListModel *self,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
GtkBitset *inverse = gtk_bitset_new_empty ();
|
GtkBitset *inverse;
|
||||||
|
|
||||||
gtk_bitset_add_range (inverse, 0, n_after);
|
inverse = gtk_bitset_new_range (0, n_after);
|
||||||
gtk_bitset_subtract (inverse, self->matches);
|
gtk_bitset_subtract (inverse, self->matches);
|
||||||
/* otherwise all items would be visible */
|
/* otherwise all items would be visible */
|
||||||
g_assert (!gtk_bitset_is_empty (inverse));
|
g_assert (!gtk_bitset_is_empty (inverse));
|
||||||
@ -387,15 +462,26 @@ gtk_filter_list_model_refilter (GtkFilterListModel *self,
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case GTK_FILTER_MATCH_SOME:
|
case GTK_FILTER_MATCH_SOME:
|
||||||
|
{
|
||||||
|
GtkBitset *old;
|
||||||
|
|
||||||
if (self->matches == NULL)
|
if (self->matches == NULL)
|
||||||
{
|
{
|
||||||
self->matches = gtk_bitset_new_empty ();
|
|
||||||
if (self->strictness == GTK_FILTER_MATCH_ALL)
|
if (self->strictness == GTK_FILTER_MATCH_ALL)
|
||||||
gtk_bitset_add_range (self->matches, 0, g_list_model_get_n_items (self->model));
|
old = gtk_bitset_new_range (0, g_list_model_get_n_items (self->model));
|
||||||
|
else
|
||||||
|
old = gtk_bitset_new_empty ();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
old = self->matches;
|
||||||
|
}
|
||||||
|
self->matches = gtk_bitset_new_empty ();
|
||||||
self->strictness = new_strictness;
|
self->strictness = new_strictness;
|
||||||
gtk_filter_list_model_run_filter (self);
|
gtk_filter_list_model_start_filtering (self, gtk_bitset_new_range (0, g_list_model_get_n_items (self->model)));
|
||||||
|
|
||||||
|
gtk_filter_list_model_emit_items_changed_for_changes (self, old);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -450,6 +536,18 @@ gtk_filter_list_model_class_init (GtkFilterListModelClass *class)
|
|||||||
GTK_TYPE_FILTER,
|
GTK_TYPE_FILTER,
|
||||||
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GtkFilterListModel:incremental:
|
||||||
|
*
|
||||||
|
* If the model should filter items incrementally
|
||||||
|
*/
|
||||||
|
properties[PROP_INCREMENTAL] =
|
||||||
|
g_param_spec_boolean ("incremental",
|
||||||
|
P_("Incremental"),
|
||||||
|
P_("Filer items incrementally"),
|
||||||
|
FALSE,
|
||||||
|
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GtkFilterListModel:model:
|
* GtkFilterListModel:model:
|
||||||
*
|
*
|
||||||
@ -588,10 +686,15 @@ gtk_filter_list_model_set_model (GtkFilterListModel *self,
|
|||||||
added = 0;
|
added = 0;
|
||||||
}
|
}
|
||||||
else if (self->matches)
|
else if (self->matches)
|
||||||
added = gtk_filter_list_model_add_items (self, 0, g_list_model_get_n_items (model));
|
{
|
||||||
|
gtk_filter_list_model_start_filtering (self, gtk_bitset_new_range (0, g_list_model_get_n_items (model)));
|
||||||
|
added = gtk_bitset_get_size (self->matches);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
added = g_list_model_get_n_items (model);
|
added = g_list_model_get_n_items (model);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
self->strictness = GTK_FILTER_MATCH_NONE;
|
self->strictness = GTK_FILTER_MATCH_NONE;
|
||||||
@ -620,3 +723,63 @@ gtk_filter_list_model_get_model (GtkFilterListModel *self)
|
|||||||
return self->model;
|
return self->model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gtk_filter_list_model_set_incremental:
|
||||||
|
* @self: a #GtkFilterListModel
|
||||||
|
* @incremental: %TRUE to enable incremental filtering
|
||||||
|
*
|
||||||
|
* When incremental filtering is enabled, the filterlistmodel will not run
|
||||||
|
* filters immediately, but will instead queue an idle handler that
|
||||||
|
* incrementally filters the items and adds them to the list. This of course
|
||||||
|
* means that items are not instantly added to the list, but only appear
|
||||||
|
* incrementally.
|
||||||
|
*
|
||||||
|
* When your filter blocks the UI while filtering, you might consider
|
||||||
|
* turning this on. Depending on your model and filters, this may become
|
||||||
|
* interesting around 10,000 to 100,000 items.
|
||||||
|
*
|
||||||
|
* By default, incremental filtering is disabled.
|
||||||
|
**/
|
||||||
|
void
|
||||||
|
gtk_filter_list_model_set_incremental (GtkFilterListModel *self,
|
||||||
|
gboolean incremental)
|
||||||
|
{
|
||||||
|
g_return_if_fail (GTK_IS_FILTER_LIST_MODEL (self));
|
||||||
|
|
||||||
|
if (self->incremental == incremental)
|
||||||
|
return;
|
||||||
|
|
||||||
|
self->incremental = incremental;
|
||||||
|
|
||||||
|
if (!incremental)
|
||||||
|
{
|
||||||
|
GtkBitset *old;
|
||||||
|
gtk_filter_list_model_run_filter (self, G_MAXUINT);
|
||||||
|
|
||||||
|
old = gtk_bitset_copy (self->matches);
|
||||||
|
gtk_filter_list_model_run_filter (self, 512);
|
||||||
|
|
||||||
|
gtk_filter_list_model_stop_filtering (self);
|
||||||
|
|
||||||
|
gtk_filter_list_model_emit_items_changed_for_changes (self, old);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INCREMENTAL]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gtk_filter_list_model_get_incremental:
|
||||||
|
* @self: a #GtkFilterListModel
|
||||||
|
*
|
||||||
|
* Returns whether incremental filtering was enabled via
|
||||||
|
* gtk_filter_list_model_set_incremental().
|
||||||
|
*
|
||||||
|
* Returns: %TRUE if incremental filtering is enabled
|
||||||
|
**/
|
||||||
|
gboolean
|
||||||
|
gtk_filter_list_model_get_incremental (GtkFilterListModel *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (GTK_IS_FILTER_LIST_MODEL (self), FALSE);
|
||||||
|
|
||||||
|
return self->incremental;
|
||||||
|
}
|
||||||
|
@ -50,6 +50,12 @@ void gtk_filter_list_model_set_model (GtkFilterListMo
|
|||||||
GListModel *model);
|
GListModel *model);
|
||||||
GDK_AVAILABLE_IN_ALL
|
GDK_AVAILABLE_IN_ALL
|
||||||
GListModel * gtk_filter_list_model_get_model (GtkFilterListModel *self);
|
GListModel * gtk_filter_list_model_get_model (GtkFilterListModel *self);
|
||||||
|
GDK_AVAILABLE_IN_ALL
|
||||||
|
void gtk_filter_list_model_set_incremental (GtkFilterListModel *self,
|
||||||
|
gboolean incremental);
|
||||||
|
GDK_AVAILABLE_IN_ALL
|
||||||
|
gboolean gtk_filter_list_model_get_incremental (GtkFilterListModel *self);
|
||||||
|
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user