1/*
2 * Copyright © 2018 Benjamin Otte
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Benjamin Otte <otte@gnome.org>
18 */
19
20#include "config.h"
21
22#include "gtkfilterlistmodel.h"
23
24#include "gtkbitset.h"
25#include "gtkintl.h"
26#include "gtkprivate.h"
27
28/**
29 * GtkFilterListModel:
30 *
31 * `GtkFilterListModel` is a list model that filters the elements of
32 * the underlying model according to a `GtkFilter`.
33 *
34 * It hides some elements from the other model according to
35 * criteria given by a `GtkFilter`.
36 *
37 * The model can be set up to do incremental searching, so that
38 * filtering long lists doesn't block the UI. See
39 * [method@Gtk.FilterListModel.set_incremental] for details.
40 */
41
42enum {
43 PROP_0,
44 PROP_FILTER,
45 PROP_INCREMENTAL,
46 PROP_MODEL,
47 PROP_PENDING,
48 NUM_PROPERTIES
49};
50
51struct _GtkFilterListModel
52{
53 GObject parent_instance;
54
55 GListModel *model;
56 GtkFilter *filter;
57 GtkFilterMatch strictness;
58 gboolean incremental;
59
60 GtkBitset *matches; /* NULL if strictness != GTK_FILTER_MATCH_SOME */
61 GtkBitset *pending; /* not yet filtered items or NULL if all filtered */
62 guint pending_cb; /* idle callback handle */
63};
64
65struct _GtkFilterListModelClass
66{
67 GObjectClass parent_class;
68};
69
70static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
71
72static GType
73gtk_filter_list_model_get_item_type (GListModel *list)
74{
75 return G_TYPE_OBJECT;
76}
77
78static guint
79gtk_filter_list_model_get_n_items (GListModel *list)
80{
81 GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (ptr: list);
82
83 switch (self->strictness)
84 {
85 case GTK_FILTER_MATCH_NONE:
86 return 0;
87
88 case GTK_FILTER_MATCH_ALL:
89 return g_list_model_get_n_items (list: self->model);
90
91 case GTK_FILTER_MATCH_SOME:
92 return gtk_bitset_get_size (self: self->matches);
93
94 default:
95 g_assert_not_reached ();
96 return 0;
97 }
98}
99
100static gpointer
101gtk_filter_list_model_get_item (GListModel *list,
102 guint position)
103{
104 GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (ptr: list);
105 guint unfiltered;
106
107 switch (self->strictness)
108 {
109 case GTK_FILTER_MATCH_NONE:
110 return NULL;
111
112 case GTK_FILTER_MATCH_ALL:
113 unfiltered = position;
114 break;
115
116 case GTK_FILTER_MATCH_SOME:
117 unfiltered = gtk_bitset_get_nth (self: self->matches, nth: position);
118 if (unfiltered == 0 && position >= gtk_bitset_get_size (self: self->matches))
119 return NULL;
120 break;
121
122 default:
123 g_assert_not_reached ();
124 }
125
126 return g_list_model_get_item (list: self->model, position: unfiltered);
127}
128
129static void
130gtk_filter_list_model_model_init (GListModelInterface *iface)
131{
132 iface->get_item_type = gtk_filter_list_model_get_item_type;
133 iface->get_n_items = gtk_filter_list_model_get_n_items;
134 iface->get_item = gtk_filter_list_model_get_item;
135}
136
137G_DEFINE_TYPE_WITH_CODE (GtkFilterListModel, gtk_filter_list_model, G_TYPE_OBJECT,
138 G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_filter_list_model_model_init))
139
140static gboolean
141gtk_filter_list_model_run_filter_on_item (GtkFilterListModel *self,
142 guint position)
143{
144 gpointer item;
145 gboolean visible;
146
147 /* all other cases should have beeen optimized away */
148 g_assert (self->strictness == GTK_FILTER_MATCH_SOME);
149
150 item = g_list_model_get_item (list: self->model, position);
151 visible = gtk_filter_match (self: self->filter, item);
152 g_object_unref (object: item);
153
154 return visible;
155}
156
157static void
158gtk_filter_list_model_run_filter (GtkFilterListModel *self,
159 guint n_steps)
160{
161 GtkBitsetIter iter;
162 guint i, pos;
163 gboolean more;
164
165 g_return_if_fail (GTK_IS_FILTER_LIST_MODEL (self));
166
167 if (self->pending == NULL)
168 return;
169
170 for (i = 0, more = gtk_bitset_iter_init_first (iter: &iter, set: self->pending, value: &pos);
171 i < n_steps && more;
172 i++, more = gtk_bitset_iter_next (iter: &iter, value: &pos))
173 {
174 if (gtk_filter_list_model_run_filter_on_item (self, position: pos))
175 gtk_bitset_add (self: self->matches, value: pos);
176 }
177
178 if (more)
179 gtk_bitset_remove_range_closed (self: self->pending, first: 0, last: pos - 1);
180 else
181 g_clear_pointer (&self->pending, gtk_bitset_unref);
182 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_PENDING]);
183
184 return;
185}
186
187static void
188gtk_filter_list_model_stop_filtering (GtkFilterListModel *self)
189{
190 gboolean notify_pending = self->pending != NULL;
191
192 g_clear_pointer (&self->pending, gtk_bitset_unref);
193 g_clear_handle_id (&self->pending_cb, g_source_remove);
194
195 if (notify_pending)
196 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_PENDING]);
197}
198
199static void
200gtk_filter_list_model_emit_items_changed_for_changes (GtkFilterListModel *self,
201 GtkBitset *old)
202{
203 GtkBitset *changes;
204
205 changes = gtk_bitset_copy (self: self->matches);
206 gtk_bitset_difference (self: changes, other: old);
207 if (!gtk_bitset_is_empty (self: changes))
208 {
209 guint min, max;
210
211 min = gtk_bitset_get_minimum (self: changes);
212 max = gtk_bitset_get_maximum (self: changes);
213 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self),
214 position: min > 0 ? gtk_bitset_get_size_in_range (self: self->matches, first: 0, last: min - 1) : 0,
215 removed: gtk_bitset_get_size_in_range (self: old, first: min, last: max),
216 added: gtk_bitset_get_size_in_range (self: self->matches, first: min, last: max));
217 }
218 gtk_bitset_unref (self: changes);
219 gtk_bitset_unref (self: old);
220}
221
222static gboolean
223gtk_filter_list_model_run_filter_cb (gpointer data)
224{
225 GtkFilterListModel *self = data;
226 GtkBitset *old;
227
228 old = gtk_bitset_copy (self: self->matches);
229 gtk_filter_list_model_run_filter (self, n_steps: 512);
230
231 if (self->pending == NULL)
232 gtk_filter_list_model_stop_filtering (self);
233
234 gtk_filter_list_model_emit_items_changed_for_changes (self, old);
235
236 return G_SOURCE_CONTINUE;
237}
238
239/* NB: bitset is (transfer full) */
240static void
241gtk_filter_list_model_start_filtering (GtkFilterListModel *self,
242 GtkBitset *items)
243{
244 if (self->pending)
245 {
246 gtk_bitset_union (self: self->pending, other: items);
247 gtk_bitset_unref (self: items);
248 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_PENDING]);
249 return;
250 }
251
252 if (gtk_bitset_is_empty (self: items))
253 {
254 gtk_bitset_unref (self: items);
255 return;
256 }
257
258 self->pending = items;
259
260 if (!self->incremental)
261 {
262 gtk_filter_list_model_run_filter (self, G_MAXUINT);
263 g_assert (self->pending == NULL);
264 return;
265 }
266
267 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_PENDING]);
268 g_assert (self->pending_cb == 0);
269 self->pending_cb = g_idle_add (function: gtk_filter_list_model_run_filter_cb, data: self);
270 gdk_source_set_static_name_by_id (tag: self->pending_cb, name: "[gtk] gtk_filter_list_model_run_filter_cb");
271}
272
273static void
274gtk_filter_list_model_items_changed_cb (GListModel *model,
275 guint position,
276 guint removed,
277 guint added,
278 GtkFilterListModel *self)
279{
280 guint filter_removed, filter_added;
281
282 switch (self->strictness)
283 {
284 case GTK_FILTER_MATCH_NONE:
285 return;
286
287 case GTK_FILTER_MATCH_ALL:
288 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position, removed, added);
289 return;
290
291 case GTK_FILTER_MATCH_SOME:
292 break;
293
294 default:
295 g_assert_not_reached ();
296 }
297
298 if (removed > 0)
299 filter_removed = gtk_bitset_get_size_in_range (self: self->matches, first: position, last: position + removed - 1);
300 else
301 filter_removed = 0;
302
303 gtk_bitset_splice (self: self->matches, position, removed, added);
304 if (self->pending)
305 gtk_bitset_splice (self: self->pending, position, removed, added);
306
307 if (added > 0)
308 {
309 gtk_filter_list_model_start_filtering (self, items: gtk_bitset_new_range (start: position, n_items: added));
310 filter_added = gtk_bitset_get_size_in_range (self: self->matches, first: position, last: position + added - 1);
311 }
312 else
313 filter_added = 0;
314
315 if (filter_removed > 0 || filter_added > 0)
316 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self),
317 position: position > 0 ? gtk_bitset_get_size_in_range (self: self->matches, first: 0, last: position - 1) : 0,
318 removed: filter_removed, added: filter_added);
319}
320
321static void
322gtk_filter_list_model_set_property (GObject *object,
323 guint prop_id,
324 const GValue *value,
325 GParamSpec *pspec)
326{
327 GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (ptr: object);
328
329 switch (prop_id)
330 {
331 case PROP_FILTER:
332 gtk_filter_list_model_set_filter (self, filter: g_value_get_object (value));
333 break;
334
335 case PROP_INCREMENTAL:
336 gtk_filter_list_model_set_incremental (self, incremental: g_value_get_boolean (value));
337 break;
338
339 case PROP_MODEL:
340 gtk_filter_list_model_set_model (self, model: g_value_get_object (value));
341 break;
342
343 default:
344 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
345 break;
346 }
347}
348
349static void
350gtk_filter_list_model_get_property (GObject *object,
351 guint prop_id,
352 GValue *value,
353 GParamSpec *pspec)
354{
355 GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (ptr: object);
356
357 switch (prop_id)
358 {
359 case PROP_FILTER:
360 g_value_set_object (value, v_object: self->filter);
361 break;
362
363 case PROP_INCREMENTAL:
364 g_value_set_boolean (value, v_boolean: self->incremental);
365 break;
366
367 case PROP_MODEL:
368 g_value_set_object (value, v_object: self->model);
369 break;
370
371 case PROP_PENDING:
372 g_value_set_uint (value, v_uint: gtk_filter_list_model_get_pending (self));
373 break;
374
375 default:
376 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
377 break;
378 }
379}
380
381static void
382gtk_filter_list_model_clear_model (GtkFilterListModel *self)
383{
384 if (self->model == NULL)
385 return;
386
387 gtk_filter_list_model_stop_filtering (self);
388 g_signal_handlers_disconnect_by_func (self->model, gtk_filter_list_model_items_changed_cb, self);
389 g_clear_object (&self->model);
390 if (self->matches)
391 gtk_bitset_remove_all (self: self->matches);
392}
393
394static void
395gtk_filter_list_model_refilter (GtkFilterListModel *self,
396 GtkFilterChange change)
397{
398 GtkFilterMatch new_strictness;
399
400 if (self->model == NULL)
401 new_strictness = GTK_FILTER_MATCH_NONE;
402 else if (self->filter == NULL)
403 new_strictness = GTK_FILTER_MATCH_ALL;
404 else
405 new_strictness = gtk_filter_get_strictness (self: self->filter);
406
407 /* don't set self->strictness yet so get_n_items() and friends return old values */
408
409 switch (new_strictness)
410 {
411 case GTK_FILTER_MATCH_NONE:
412 {
413 guint n_before = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: self));
414 g_clear_pointer (&self->matches, gtk_bitset_unref);
415 self->strictness = new_strictness;
416 gtk_filter_list_model_stop_filtering (self);
417 if (n_before > 0)
418 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position: 0, removed: n_before, added: 0);
419 }
420 break;
421
422 case GTK_FILTER_MATCH_ALL:
423 switch (self->strictness)
424 {
425 case GTK_FILTER_MATCH_NONE:
426 self->strictness = new_strictness;
427 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position: 0, removed: 0, added: g_list_model_get_n_items (list: self->model));
428 break;
429 case GTK_FILTER_MATCH_ALL:
430 self->strictness = new_strictness;
431 break;
432 default:
433 case GTK_FILTER_MATCH_SOME:
434 {
435 guint n_before, n_after;
436
437 gtk_filter_list_model_stop_filtering (self);
438 self->strictness = new_strictness;
439 n_after = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: self));
440 n_before = gtk_bitset_get_size (self: self->matches);
441 if (n_before == n_after)
442 {
443 g_clear_pointer (&self->matches, gtk_bitset_unref);
444 }
445 else
446 {
447 GtkBitset *inverse;
448 guint start, end;
449
450 inverse = gtk_bitset_new_range (start: 0, n_items: n_after);
451 gtk_bitset_subtract (self: inverse, other: self->matches);
452 /* otherwise all items would be visible */
453 g_assert (!gtk_bitset_is_empty (inverse));
454
455 /* find first filtered */
456 start = gtk_bitset_get_minimum (self: inverse);
457 end = n_after - gtk_bitset_get_maximum (self: inverse) - 1;
458
459 gtk_bitset_unref (self: inverse);
460
461 g_clear_pointer (&self->matches, gtk_bitset_unref);
462 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position: start, removed: n_before - end - start, added: n_after - end - start);
463 }
464 }
465 break;
466 }
467 break;
468
469 default:
470 g_assert_not_reached ();
471 break;
472
473 case GTK_FILTER_MATCH_SOME:
474 {
475 GtkBitset *old, *pending;
476
477 if (self->matches == NULL)
478 {
479 if (self->strictness == GTK_FILTER_MATCH_ALL)
480 old = gtk_bitset_new_range (start: 0, n_items: g_list_model_get_n_items (list: self->model));
481 else
482 old = gtk_bitset_new_empty ();
483 }
484 else
485 {
486 old = self->matches;
487 }
488 self->strictness = new_strictness;
489 switch (change)
490 {
491 default:
492 g_assert_not_reached ();
493 /* fall thru */
494 case GTK_FILTER_CHANGE_DIFFERENT:
495 self->matches = gtk_bitset_new_empty ();
496 pending = gtk_bitset_new_range (start: 0, n_items: g_list_model_get_n_items (list: self->model));
497 break;
498 case GTK_FILTER_CHANGE_LESS_STRICT:
499 self->matches = gtk_bitset_copy (self: old);
500 pending = gtk_bitset_new_range (start: 0, n_items: g_list_model_get_n_items (list: self->model));
501 gtk_bitset_subtract (self: pending, other: self->matches);
502 break;
503 case GTK_FILTER_CHANGE_MORE_STRICT:
504 self->matches = gtk_bitset_new_empty ();
505 pending = gtk_bitset_copy (self: old);
506 break;
507 }
508 gtk_filter_list_model_start_filtering (self, items: pending);
509
510 gtk_filter_list_model_emit_items_changed_for_changes (self, old);
511 }
512 }
513}
514
515static void
516gtk_filter_list_model_filter_changed_cb (GtkFilter *filter,
517 GtkFilterChange change,
518 GtkFilterListModel *self)
519{
520 gtk_filter_list_model_refilter (self, change);
521}
522
523static void
524gtk_filter_list_model_clear_filter (GtkFilterListModel *self)
525{
526 if (self->filter == NULL)
527 return;
528
529 g_signal_handlers_disconnect_by_func (self->filter, gtk_filter_list_model_filter_changed_cb, self);
530 g_clear_object (&self->filter);
531}
532
533static void
534gtk_filter_list_model_dispose (GObject *object)
535{
536 GtkFilterListModel *self = GTK_FILTER_LIST_MODEL (ptr: object);
537
538 gtk_filter_list_model_clear_model (self);
539 gtk_filter_list_model_clear_filter (self);
540 g_clear_pointer (&self->matches, gtk_bitset_unref);
541
542 G_OBJECT_CLASS (gtk_filter_list_model_parent_class)->dispose (object);
543}
544
545static void
546gtk_filter_list_model_class_init (GtkFilterListModelClass *class)
547{
548 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
549
550 gobject_class->set_property = gtk_filter_list_model_set_property;
551 gobject_class->get_property = gtk_filter_list_model_get_property;
552 gobject_class->dispose = gtk_filter_list_model_dispose;
553
554 /**
555 * GtkFilterListModel:filter: (attributes org.gtk.Property.get=gtk_filter_list_model_get_filter org.gtk.Property.set=gtk_filter_list_model_set_filter)
556 *
557 * The filter for this model.
558 */
559 properties[PROP_FILTER] =
560 g_param_spec_object (name: "filter",
561 P_("Filter"),
562 P_("The filter set for this model"),
563 GTK_TYPE_FILTER,
564 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
565
566 /**
567 * GtkFilterListModel:incremental: (attributes org.gtk.Property.get=gtk_filter_list_model_get_incremental org.gtk.Property.set=gtk_filter_list_model_set_incremental)
568 *
569 * If the model should filter items incrementally.
570 */
571 properties[PROP_INCREMENTAL] =
572 g_param_spec_boolean (name: "incremental",
573 P_("Incremental"),
574 P_("Filter items incrementally"),
575 FALSE,
576 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
577
578 /**
579 * GtkFilterListModel:model: (attributes org.gtk.Property.get=gtk_filter_list_model_get_model org.gtk.Property.set=gtk_filter_list_model_set_model)
580 *
581 * The model being filtered.
582 */
583 properties[PROP_MODEL] =
584 g_param_spec_object (name: "model",
585 P_("Model"),
586 P_("The model being filtered"),
587 G_TYPE_LIST_MODEL,
588 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
589
590 /**
591 * GtkFilterListModel:pending: (attributes org.gtk.Property.get=gtk_filter_list_model_get_pending)
592 *
593 * Number of items not yet filtered.
594 */
595 properties[PROP_PENDING] =
596 g_param_spec_uint (name: "pending",
597 P_("Pending"),
598 P_("Number of items not yet filtered"),
599 minimum: 0, G_MAXUINT, default_value: 0,
600 GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
601
602 g_object_class_install_properties (oclass: gobject_class, n_pspecs: NUM_PROPERTIES, pspecs: properties);
603}
604
605static void
606gtk_filter_list_model_init (GtkFilterListModel *self)
607{
608 self->strictness = GTK_FILTER_MATCH_NONE;
609}
610
611/**
612 * gtk_filter_list_model_new:
613 * @model: (nullable) (transfer full): the model to sort
614 * @filter: (nullable) (transfer full): filter
615 *
616 * Creates a new `GtkFilterListModel` that will filter @model using the given
617 * @filter.
618 *
619 * Returns: a new `GtkFilterListModel`
620 **/
621GtkFilterListModel *
622gtk_filter_list_model_new (GListModel *model,
623 GtkFilter *filter)
624{
625 GtkFilterListModel *result;
626
627 g_return_val_if_fail (model == NULL || G_IS_LIST_MODEL (model), NULL);
628 g_return_val_if_fail (filter == NULL || GTK_IS_FILTER (filter), NULL);
629
630 result = g_object_new (GTK_TYPE_FILTER_LIST_MODEL,
631 first_property_name: "model", model,
632 "filter", filter,
633 NULL);
634
635 /* consume the references */
636 g_clear_object (&model);
637 g_clear_object (&filter);
638
639 return result;
640}
641
642/**
643 * gtk_filter_list_model_set_filter: (attributes org.gtk.Method.set_property=filter)
644 * @self: a `GtkFilterListModel`
645 * @filter: (nullable) (transfer none): filter to use
646 *
647 * Sets the filter used to filter items.
648 **/
649void
650gtk_filter_list_model_set_filter (GtkFilterListModel *self,
651 GtkFilter *filter)
652{
653 g_return_if_fail (GTK_IS_FILTER_LIST_MODEL (self));
654 g_return_if_fail (filter == NULL || GTK_IS_FILTER (filter));
655
656 if (self->filter == filter)
657 return;
658
659 gtk_filter_list_model_clear_filter (self);
660
661 if (filter)
662 {
663 self->filter = g_object_ref (filter);
664 g_signal_connect (filter, "changed", G_CALLBACK (gtk_filter_list_model_filter_changed_cb), self);
665 gtk_filter_list_model_filter_changed_cb (filter, change: GTK_FILTER_CHANGE_DIFFERENT, self);
666 }
667 else
668 {
669 gtk_filter_list_model_refilter (self, change: GTK_FILTER_CHANGE_LESS_STRICT);
670 }
671
672 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_FILTER]);
673}
674
675/**
676 * gtk_filter_list_model_get_filter: (attributes org.gtk.Method.get_property=filter)
677 * @self: a `GtkFilterListModel`
678 *
679 * Gets the `GtkFilter` currently set on @self.
680 *
681 * Returns: (nullable) (transfer none): The filter currently in use
682 */
683GtkFilter *
684gtk_filter_list_model_get_filter (GtkFilterListModel *self)
685{
686 g_return_val_if_fail (GTK_IS_FILTER_LIST_MODEL (self), FALSE);
687
688 return self->filter;
689}
690
691/**
692 * gtk_filter_list_model_set_model: (attributes org.gtk.Method.set_property=model)
693 * @self: a `GtkFilterListModel`
694 * @model: (nullable): The model to be filtered
695 *
696 * Sets the model to be filtered.
697 *
698 * Note that GTK makes no effort to ensure that @model conforms to
699 * the item type of @self. It assumes that the caller knows what they
700 * are doing and have set up an appropriate filter to ensure that item
701 * types match.
702 */
703void
704gtk_filter_list_model_set_model (GtkFilterListModel *self,
705 GListModel *model)
706{
707 guint removed, added;
708
709 g_return_if_fail (GTK_IS_FILTER_LIST_MODEL (self));
710 g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
711 /* Note: We don't check for matching item type here, we just assume the
712 * filter func takes care of filtering wrong items. */
713
714 if (self->model == model)
715 return;
716
717 removed = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: self));
718 gtk_filter_list_model_clear_model (self);
719
720 if (model)
721 {
722 self->model = g_object_ref (model);
723 g_signal_connect (model, "items-changed", G_CALLBACK (gtk_filter_list_model_items_changed_cb), self);
724 if (removed == 0)
725 {
726 self->strictness = GTK_FILTER_MATCH_NONE;
727 gtk_filter_list_model_refilter (self, change: GTK_FILTER_CHANGE_LESS_STRICT);
728 added = 0;
729 }
730 else if (self->matches)
731 {
732 gtk_filter_list_model_start_filtering (self, items: gtk_bitset_new_range (start: 0, n_items: g_list_model_get_n_items (list: model)));
733 added = gtk_bitset_get_size (self: self->matches);
734 }
735 else
736 {
737 added = g_list_model_get_n_items (list: model);
738 }
739 }
740 else
741 {
742 self->strictness = GTK_FILTER_MATCH_NONE;
743 added = 0;
744 }
745
746 if (removed > 0 || added > 0)
747 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position: 0, removed, added);
748
749 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_MODEL]);
750}
751
752/**
753 * gtk_filter_list_model_get_model: (attributes org.gtk.Method.get_property=model)
754 * @self: a `GtkFilterListModel`
755 *
756 * Gets the model currently filtered or %NULL if none.
757 *
758 * Returns: (nullable) (transfer none): The model that gets filtered
759 */
760GListModel *
761gtk_filter_list_model_get_model (GtkFilterListModel *self)
762{
763 g_return_val_if_fail (GTK_IS_FILTER_LIST_MODEL (self), NULL);
764
765 return self->model;
766}
767
768/**
769 * gtk_filter_list_model_set_incremental: (attributes org.gtk.Method.set_property=incremental)
770 * @self: a `GtkFilterListModel`
771 * @incremental: %TRUE to enable incremental filtering
772 *
773 * Sets the filter model to do an incremental sort.
774 *
775 * When incremental filtering is enabled, the `GtkFilterListModel` will not
776 * run filters immediately, but will instead queue an idle handler that
777 * incrementally filters the items and adds them to the list. This of course
778 * means that items are not instantly added to the list, but only appear
779 * incrementally.
780 *
781 * When your filter blocks the UI while filtering, you might consider
782 * turning this on. Depending on your model and filters, this may become
783 * interesting around 10,000 to 100,000 items.
784 *
785 * By default, incremental filtering is disabled.
786 *
787 * See [method@Gtk.FilterListModel.get_pending] for progress information
788 * about an ongoing incremental filtering operation.
789 **/
790void
791gtk_filter_list_model_set_incremental (GtkFilterListModel *self,
792 gboolean incremental)
793{
794 g_return_if_fail (GTK_IS_FILTER_LIST_MODEL (self));
795
796 if (self->incremental == incremental)
797 return;
798
799 self->incremental = incremental;
800
801 if (!incremental)
802 {
803 GtkBitset *old;
804 gtk_filter_list_model_run_filter (self, G_MAXUINT);
805
806 old = gtk_bitset_copy (self: self->matches);
807 gtk_filter_list_model_run_filter (self, n_steps: 512);
808
809 gtk_filter_list_model_stop_filtering (self);
810
811 gtk_filter_list_model_emit_items_changed_for_changes (self, old);
812 }
813
814 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_INCREMENTAL]);
815}
816
817/**
818 * gtk_filter_list_model_get_incremental: (attributes org.gtk.Method.get_property=incremental)
819 * @self: a `GtkFilterListModel`
820 *
821 * Returns whether incremental filtering is enabled.
822 *
823 * See [method@Gtk.FilterListModel.set_incremental].
824 *
825 * Returns: %TRUE if incremental filtering is enabled
826 */
827gboolean
828gtk_filter_list_model_get_incremental (GtkFilterListModel *self)
829{
830 g_return_val_if_fail (GTK_IS_FILTER_LIST_MODEL (self), FALSE);
831
832 return self->incremental;
833}
834
835/**
836 * gtk_filter_list_model_get_pending: (attributes org.gtk.Method.get_property=pending)
837 * @self: a `GtkFilterListModel`
838 *
839 * Returns the number of items that have not been filtered yet.
840 *
841 * You can use this value to check if @self is busy filtering by
842 * comparing the return value to 0 or you can compute the percentage
843 * of the filter remaining by dividing the return value by the total
844 * number of items in the underlying model:
845 *
846 * ```c
847 * pending = gtk_filter_list_model_get_pending (self);
848 * model = gtk_filter_list_model_get_model (self);
849 * percentage = pending / (double) g_list_model_get_n_items (model);
850 * ```
851 *
852 * If no filter operation is ongoing - in particular when
853 * [property@Gtk.FilterListModel:incremental] is %FALSE - this
854 * function returns 0.
855 *
856 * Returns: The number of items not yet filtered
857 */
858guint
859gtk_filter_list_model_get_pending (GtkFilterListModel *self)
860{
861 g_return_val_if_fail (GTK_IS_FILTER_LIST_MODEL (self), FALSE);
862
863 if (self->pending == NULL)
864 return 0;
865
866 return gtk_bitset_get_size (self: self->pending);
867}
868

source code of gtk/gtk/gtkfilterlistmodel.c