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 | |
35 | enum { |
36 | PROP_0, |
37 | PROP_MODEL, |
38 | NUM_PROPERTIES |
39 | }; |
40 | |
41 | struct _GtkSelectionFilterModel |
42 | { |
43 | GObject parent_instance; |
44 | |
45 | GtkSelectionModel *model; |
46 | GtkBitset *selection; |
47 | }; |
48 | |
49 | struct _GtkSelectionFilterModelClass |
50 | { |
51 | GObjectClass parent_class; |
52 | }; |
53 | |
54 | static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; |
55 | |
56 | static GType |
57 | gtk_selection_filter_model_get_item_type (GListModel *list) |
58 | { |
59 | return G_TYPE_OBJECT; |
60 | } |
61 | |
62 | static guint |
63 | gtk_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 | |
73 | static gpointer |
74 | gtk_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 | |
90 | static void |
91 | gtk_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 | |
98 | G_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 | |
101 | static void |
102 | selection_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 | |
132 | static void |
133 | gtk_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 | |
142 | static void |
143 | gtk_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 | |
151 | static void |
152 | gtk_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 | |
171 | static void |
172 | gtk_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 | |
191 | static void |
192 | gtk_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 | |
204 | static void |
205 | gtk_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 | |
214 | static void |
215 | gtk_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 | |
238 | static void |
239 | gtk_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 | */ |
252 | GtkSelectionFilterModel * |
253 | gtk_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 | **/ |
272 | void |
273 | gtk_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 | */ |
317 | GtkSelectionModel * |
318 | gtk_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 | |