1/*
2 * Copyright © 2020 Red Hat, Inc.
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: Matthias Clasen
18 */
19
20#include "config.h"
21
22#include "gtkselectionfiltermodel.h"
23#include "gtkbitset.h"
24
25#include "gtkintl.h"
26#include "gtkprivate.h"
27
28/**
29 * GtkSelectionFilterModel:
30 *
31 * `GtkSelectionFilterModel` is a list model that presents the selection from
32 * a `GtkSelectionModel`.
33 */
34
35enum {
36 PROP_0,
37 PROP_MODEL,
38 NUM_PROPERTIES
39};
40
41struct _GtkSelectionFilterModel
42{
43 GObject parent_instance;
44
45 GtkSelectionModel *model;
46 GtkBitset *selection;
47};
48
49struct _GtkSelectionFilterModelClass
50{
51 GObjectClass parent_class;
52};
53
54static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
55
56static GType
57gtk_selection_filter_model_get_item_type (GListModel *list)
58{
59 return G_TYPE_OBJECT;
60}
61
62static guint
63gtk_selection_filter_model_get_n_items (GListModel *list)
64{
65 GtkSelectionFilterModel *self = GTK_SELECTION_FILTER_MODEL (ptr: list);
66
67 if (!self->selection)
68 return 0;
69
70 return gtk_bitset_get_size (self: self->selection);
71}
72
73static gpointer
74gtk_selection_filter_model_get_item (GListModel *list,
75 guint position)
76{
77 GtkSelectionFilterModel *self = GTK_SELECTION_FILTER_MODEL (ptr: list);
78
79 if (!self->selection)
80 return NULL;
81
82 if (position >= gtk_bitset_get_size (self: self->selection))
83 return NULL;
84
85 position = gtk_bitset_get_nth (self: self->selection, nth: position);
86
87 return g_list_model_get_item (list: G_LIST_MODEL (ptr: self->model), position);
88}
89
90static void
91gtk_selection_filter_model_list_model_init (GListModelInterface *iface)
92{
93 iface->get_item_type = gtk_selection_filter_model_get_item_type;
94 iface->get_n_items = gtk_selection_filter_model_get_n_items;
95 iface->get_item = gtk_selection_filter_model_get_item;
96}
97
98G_DEFINE_TYPE_WITH_CODE (GtkSelectionFilterModel, gtk_selection_filter_model, G_TYPE_OBJECT,
99 G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_selection_filter_model_list_model_init))
100
101static void
102selection_filter_model_items_changed (GtkSelectionFilterModel *self,
103 guint position,
104 guint removed,
105 guint added)
106{
107 GtkBitset *selection;
108 guint sel_position = 0;
109 guint sel_removed = 0;
110 guint sel_added = 0;
111
112 selection = gtk_selection_model_get_selection (model: self->model);
113
114 if (position > 0)
115 sel_position = gtk_bitset_get_size_in_range (self: self->selection, first: 0, last: position - 1);
116
117 if (removed > 0)
118 sel_removed = gtk_bitset_get_size_in_range (self: self->selection, first: position, last: position + removed - 1);
119
120 if (added > 0)
121 sel_added = gtk_bitset_get_size_in_range (self: selection, first: position, last: position + added - 1);
122
123 gtk_bitset_unref (self: self->selection);
124 self->selection = gtk_bitset_copy (self: selection);
125
126 gtk_bitset_unref (self: selection);
127
128 if (sel_removed > 0 || sel_added > 0)
129 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position: sel_position, removed: sel_removed, added: sel_added);
130}
131
132static void
133gtk_selection_filter_model_items_changed_cb (GListModel *model,
134 guint position,
135 guint removed,
136 guint added,
137 GtkSelectionFilterModel *self)
138{
139 selection_filter_model_items_changed (self, position, removed, added);
140}
141
142static void
143gtk_selection_filter_model_selection_changed_cb (GListModel *model,
144 guint position,
145 guint n_items,
146 GtkSelectionFilterModel *self)
147{
148 selection_filter_model_items_changed (self, position, removed: n_items, added: n_items);
149}
150
151static void
152gtk_selection_filter_model_set_property (GObject *object,
153 guint prop_id,
154 const GValue *value,
155 GParamSpec *pspec)
156{
157 GtkSelectionFilterModel *self = GTK_SELECTION_FILTER_MODEL (ptr: object);
158
159 switch (prop_id)
160 {
161 case PROP_MODEL:
162 gtk_selection_filter_model_set_model (self, model: g_value_get_object (value));
163 break;
164
165 default:
166 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
167 break;
168 }
169}
170
171static void
172gtk_selection_filter_model_get_property (GObject *object,
173 guint prop_id,
174 GValue *value,
175 GParamSpec *pspec)
176{
177 GtkSelectionFilterModel *self = GTK_SELECTION_FILTER_MODEL (ptr: object);
178
179 switch (prop_id)
180 {
181 case PROP_MODEL:
182 g_value_set_object (value, v_object: self->model);
183 break;
184
185 default:
186 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
187 break;
188 }
189}
190
191static void
192gtk_selection_filter_model_clear_model (GtkSelectionFilterModel *self)
193{
194 if (self->model == NULL)
195 return;
196
197 g_signal_handlers_disconnect_by_func (self->model, gtk_selection_filter_model_items_changed_cb, self);
198 g_signal_handlers_disconnect_by_func (self->model, gtk_selection_filter_model_selection_changed_cb, self);
199
200 g_clear_object (&self->model);
201 g_clear_pointer (&self->selection, gtk_bitset_unref);
202}
203
204static void
205gtk_selection_filter_model_dispose (GObject *object)
206{
207 GtkSelectionFilterModel *self = GTK_SELECTION_FILTER_MODEL (ptr: object);
208
209 gtk_selection_filter_model_clear_model (self);
210
211 G_OBJECT_CLASS (gtk_selection_filter_model_parent_class)->dispose (object);
212}
213
214static void
215gtk_selection_filter_model_class_init (GtkSelectionFilterModelClass *class)
216{
217 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
218
219 gobject_class->set_property = gtk_selection_filter_model_set_property;
220 gobject_class->get_property = gtk_selection_filter_model_get_property;
221 gobject_class->dispose = gtk_selection_filter_model_dispose;
222
223 /**
224 * GtkSelectionFilterModel:model: (attributes org.gtk.Property.get=gtk_selection_filter_model_get_model org.gtk.Property.set=gtk_selection_filter_model_set_model)
225 *
226 * The model being filtered.
227 */
228 properties[PROP_MODEL] =
229 g_param_spec_object (name: "model",
230 P_("Model"),
231 P_("The model being filtered"),
232 GTK_TYPE_SELECTION_MODEL,
233 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
234
235 g_object_class_install_properties (oclass: gobject_class, n_pspecs: NUM_PROPERTIES, pspecs: properties);
236}
237
238static void
239gtk_selection_filter_model_init (GtkSelectionFilterModel *self)
240{
241}
242
243/**
244 * gtk_selection_filter_model_new:
245 * @model: (nullable) (transfer none): the selection model to filter
246 *
247 * Creates a new `GtkSelectionFilterModel` that will include the
248 * selected items from the underlying selection model.
249 *
250 * Returns: a new `GtkSelectionFilterModel`
251 */
252GtkSelectionFilterModel *
253gtk_selection_filter_model_new (GtkSelectionModel *model)
254{
255 return g_object_new (GTK_TYPE_SELECTION_FILTER_MODEL,
256 first_property_name: "model", model,
257 NULL);
258}
259
260/**
261 * gtk_selection_filter_model_set_model: (attributes org.gtk.Method.set_property=model)
262 * @self: a `GtkSelectionFilterModel`
263 * @model: (nullable): The model to be filtered
264 *
265 * Sets the model to be filtered.
266 *
267 * Note that GTK makes no effort to ensure that @model conforms to
268 * the item type of @self. It assumes that the caller knows what they
269 * are doing and have set up an appropriate filter to ensure that item
270 * types match.
271 **/
272void
273gtk_selection_filter_model_set_model (GtkSelectionFilterModel *self,
274 GtkSelectionModel *model)
275{
276 guint removed, added;
277
278 g_return_if_fail (GTK_IS_SELECTION_FILTER_MODEL (self));
279 g_return_if_fail (model == NULL || GTK_IS_SELECTION_MODEL (model));
280
281 if (self->model == model)
282 return;
283
284 removed = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: self));
285 gtk_selection_filter_model_clear_model (self);
286
287 if (model)
288 {
289 GtkBitset *selection;
290
291 self->model = g_object_ref (model);
292
293 selection = gtk_selection_model_get_selection (model: self->model);
294 self->selection = gtk_bitset_copy (self: selection);
295 gtk_bitset_unref (self: selection);
296
297 g_signal_connect (model, "items-changed", G_CALLBACK (gtk_selection_filter_model_items_changed_cb), self);
298 g_signal_connect (model, "selection-changed", G_CALLBACK (gtk_selection_filter_model_selection_changed_cb), self);
299 }
300
301 added = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: self));
302
303 if (removed > 0 || added > 0)
304 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position: 0, removed, added);
305
306 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_MODEL]);
307}
308
309/**
310 * gtk_selection_filter_model_get_model: (attributes org.gtk.Method.get_property=model)
311 * @self: a `GtkSelectionFilterModel`
312 *
313 * Gets the model currently filtered or %NULL if none.
314 *
315 * Returns: (nullable) (transfer none): The model that gets filtered
316 */
317GtkSelectionModel *
318gtk_selection_filter_model_get_model (GtkSelectionFilterModel *self)
319{
320 g_return_val_if_fail (GTK_IS_SELECTION_FILTER_MODEL (self), NULL);
321
322 return self->model;
323}
324

source code of gtk/gtk/gtkselectionfiltermodel.c