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 "gtkselectionmodel.h" |
23 | |
24 | #include "gtkbitset.h" |
25 | #include "gtkintl.h" |
26 | #include "gtkmarshalers.h" |
27 | |
28 | /** |
29 | * GtkSelectionModel: |
30 | * |
31 | * `GtkSelectionModel` is an interface that add support for selection to list models. |
32 | * |
33 | * This support is then used by widgets using list models to add the ability |
34 | * to select and unselect various items. |
35 | * |
36 | * GTK provides default implementations of the most common selection modes such |
37 | * as [class@Gtk.SingleSelection], so you will only need to implement this |
38 | * interface if you want detailed control about how selections should be handled. |
39 | * |
40 | * A `GtkSelectionModel` supports a single boolean per item indicating if an item is |
41 | * selected or not. This can be queried via [method@Gtk.SelectionModel.is_selected]. |
42 | * When the selected state of one or more items changes, the model will emit the |
43 | * [signal@Gtk.SelectionModel::selection-changed] signal by calling the |
44 | * [method@Gtk.SelectionModel.selection_changed] function. The positions given |
45 | * in that signal may have their selection state changed, though that is not a |
46 | * requirement. If new items added to the model via the |
47 | * [signal@Gio.ListModel::items-changed] signal are selected or not is up to the |
48 | * implementation. |
49 | * |
50 | * Note that items added via [signal@Gio.ListModel::items-changed] may already |
51 | * be selected and no [signal@Gtk.SelectionModel::selection-changed] will be |
52 | * emitted for them. So to track which items are selected, it is necessary to |
53 | * listen to both signals. |
54 | * |
55 | * Additionally, the interface can expose functionality to select and unselect |
56 | * items. If these functions are implemented, GTK's list widgets will allow users |
57 | * to select and unselect items. However, `GtkSelectionModel`s are free to only |
58 | * implement them partially or not at all. In that case the widgets will not |
59 | * support the unimplemented operations. |
60 | * |
61 | * When selecting or unselecting is supported by a model, the return values of |
62 | * the selection functions do *not* indicate if selection or unselection happened. |
63 | * They are only meant to indicate complete failure, like when this mode of |
64 | * selecting is not supported by the model. |
65 | * |
66 | * Selections may happen asynchronously, so the only reliable way to find out |
67 | * when an item was selected is to listen to the signals that indicate selection. |
68 | */ |
69 | |
70 | G_DEFINE_INTERFACE (GtkSelectionModel, gtk_selection_model, G_TYPE_LIST_MODEL) |
71 | |
72 | enum { |
73 | SELECTION_CHANGED, |
74 | LAST_SIGNAL |
75 | }; |
76 | |
77 | static guint signals[LAST_SIGNAL] = { 0 }; |
78 | |
79 | static gboolean |
80 | gtk_selection_model_default_is_selected (GtkSelectionModel *model, |
81 | guint position) |
82 | { |
83 | GtkBitset *bitset; |
84 | gboolean selected; |
85 | |
86 | bitset = gtk_selection_model_get_selection_in_range (model, position, n_items: 1); |
87 | selected = gtk_bitset_contains (self: bitset, value: position); |
88 | gtk_bitset_unref (self: bitset); |
89 | |
90 | return selected; |
91 | } |
92 | |
93 | static GtkBitset * |
94 | gtk_selection_model_default_get_selection_in_range (GtkSelectionModel *model, |
95 | guint position, |
96 | guint n_items) |
97 | { |
98 | GtkBitset *bitset; |
99 | guint i; |
100 | |
101 | bitset = gtk_bitset_new_empty (); |
102 | |
103 | for (i = position; i < position + n_items; i++) |
104 | { |
105 | if (gtk_selection_model_is_selected (model, position: i)) |
106 | gtk_bitset_add (self: bitset, value: i); |
107 | } |
108 | |
109 | return bitset; |
110 | } |
111 | |
112 | static gboolean |
113 | gtk_selection_model_default_select_item (GtkSelectionModel *model, |
114 | guint position, |
115 | gboolean unselect_rest) |
116 | { |
117 | GtkBitset *selected; |
118 | GtkBitset *mask; |
119 | gboolean result; |
120 | |
121 | selected = gtk_bitset_new_empty (); |
122 | gtk_bitset_add (self: selected, value: position); |
123 | if (unselect_rest) |
124 | { |
125 | mask = gtk_bitset_new_empty (); |
126 | gtk_bitset_add_range (self: mask, start: 0, n_items: g_list_model_get_n_items (list: G_LIST_MODEL (ptr: model))); |
127 | } |
128 | else |
129 | { |
130 | mask = gtk_bitset_ref (self: selected); |
131 | } |
132 | |
133 | result = gtk_selection_model_set_selection (model, selected, mask); |
134 | |
135 | gtk_bitset_unref (self: selected); |
136 | gtk_bitset_unref (self: mask); |
137 | |
138 | return result; |
139 | } |
140 | |
141 | static gboolean |
142 | gtk_selection_model_default_unselect_item (GtkSelectionModel *model, |
143 | guint position) |
144 | { |
145 | GtkBitset *selected; |
146 | GtkBitset *mask; |
147 | gboolean result; |
148 | |
149 | selected = gtk_bitset_new_empty (); |
150 | mask = gtk_bitset_new_empty (); |
151 | gtk_bitset_add (self: mask, value: position); |
152 | |
153 | result = gtk_selection_model_set_selection (model, selected, mask); |
154 | |
155 | gtk_bitset_unref (self: selected); |
156 | gtk_bitset_unref (self: mask); |
157 | |
158 | return result; |
159 | } |
160 | |
161 | static gboolean |
162 | gtk_selection_model_default_select_range (GtkSelectionModel *model, |
163 | guint position, |
164 | guint n_items, |
165 | gboolean unselect_rest) |
166 | { |
167 | GtkBitset *selected; |
168 | GtkBitset *mask; |
169 | gboolean result; |
170 | |
171 | selected = gtk_bitset_new_empty (); |
172 | gtk_bitset_add_range (self: selected, start: position, n_items); |
173 | if (unselect_rest) |
174 | { |
175 | mask = gtk_bitset_new_empty (); |
176 | gtk_bitset_add_range (self: mask, start: 0, n_items: g_list_model_get_n_items (list: G_LIST_MODEL (ptr: model))); |
177 | } |
178 | else |
179 | { |
180 | mask = gtk_bitset_ref (self: selected); |
181 | } |
182 | |
183 | result = gtk_selection_model_set_selection (model, selected, mask); |
184 | |
185 | gtk_bitset_unref (self: selected); |
186 | gtk_bitset_unref (self: mask); |
187 | |
188 | return result; |
189 | } |
190 | |
191 | static gboolean |
192 | gtk_selection_model_default_unselect_range (GtkSelectionModel *model, |
193 | guint position, |
194 | guint n_items) |
195 | { |
196 | GtkBitset *selected; |
197 | GtkBitset *mask; |
198 | gboolean result; |
199 | |
200 | selected = gtk_bitset_new_empty (); |
201 | mask = gtk_bitset_new_empty (); |
202 | gtk_bitset_add_range (self: mask, start: position, n_items); |
203 | |
204 | result = gtk_selection_model_set_selection (model, selected, mask); |
205 | |
206 | gtk_bitset_unref (self: selected); |
207 | gtk_bitset_unref (self: mask); |
208 | |
209 | return result; |
210 | } |
211 | |
212 | static gboolean |
213 | gtk_selection_model_default_select_all (GtkSelectionModel *model) |
214 | { |
215 | return gtk_selection_model_select_range (model, position: 0, n_items: g_list_model_get_n_items (list: G_LIST_MODEL (ptr: model)), FALSE); |
216 | } |
217 | |
218 | static gboolean |
219 | gtk_selection_model_default_unselect_all (GtkSelectionModel *model) |
220 | { |
221 | return gtk_selection_model_unselect_range (model, position: 0, n_items: g_list_model_get_n_items (list: G_LIST_MODEL (ptr: model))); |
222 | } |
223 | |
224 | static gboolean |
225 | gtk_selection_model_default_set_selection (GtkSelectionModel *model, |
226 | GtkBitset *selected, |
227 | GtkBitset *mask) |
228 | { |
229 | return FALSE; |
230 | } |
231 | |
232 | static void |
233 | gtk_selection_model_default_init (GtkSelectionModelInterface *iface) |
234 | { |
235 | iface->is_selected = gtk_selection_model_default_is_selected; |
236 | iface->get_selection_in_range = gtk_selection_model_default_get_selection_in_range; |
237 | iface->select_item = gtk_selection_model_default_select_item; |
238 | iface->unselect_item = gtk_selection_model_default_unselect_item; |
239 | iface->select_range = gtk_selection_model_default_select_range; |
240 | iface->unselect_range = gtk_selection_model_default_unselect_range; |
241 | iface->select_all = gtk_selection_model_default_select_all; |
242 | iface->unselect_all = gtk_selection_model_default_unselect_all; |
243 | iface->set_selection = gtk_selection_model_default_set_selection; |
244 | |
245 | /** |
246 | * GtkSelectionModel::selection-changed |
247 | * @model: a `GtkSelectionModel` |
248 | * @position: The first item that may have changed |
249 | * @n_items: number of items with changes |
250 | * |
251 | * Emitted when the selection state of some of the items in @model changes. |
252 | * |
253 | * Note that this signal does not specify the new selection state of the |
254 | * items, they need to be queried manually. It is also not necessary for |
255 | * a model to change the selection state of any of the items in the selection |
256 | * model, though it would be rather useless to emit such a signal. |
257 | */ |
258 | signals[SELECTION_CHANGED] = |
259 | g_signal_new (signal_name: "selection-changed" , |
260 | GTK_TYPE_SELECTION_MODEL, |
261 | signal_flags: G_SIGNAL_RUN_LAST, |
262 | class_offset: 0, |
263 | NULL, NULL, |
264 | c_marshaller: _gtk_marshal_VOID__UINT_UINT, |
265 | G_TYPE_NONE, n_params: 2, G_TYPE_UINT, G_TYPE_UINT); |
266 | g_signal_set_va_marshaller (signal_id: signals[SELECTION_CHANGED], |
267 | GTK_TYPE_SELECTION_MODEL, |
268 | va_marshaller: _gtk_marshal_VOID__UINT_UINTv); |
269 | } |
270 | |
271 | /** |
272 | * gtk_selection_model_is_selected: |
273 | * @model: a `GtkSelectionModel` |
274 | * @position: the position of the item to query |
275 | * |
276 | * Checks if the given item is selected. |
277 | * |
278 | * Returns: %TRUE if the item is selected |
279 | */ |
280 | gboolean |
281 | gtk_selection_model_is_selected (GtkSelectionModel *model, |
282 | guint position) |
283 | { |
284 | GtkSelectionModelInterface *iface; |
285 | |
286 | g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0); |
287 | |
288 | iface = GTK_SELECTION_MODEL_GET_IFACE (ptr: model); |
289 | return iface->is_selected (model, position); |
290 | } |
291 | |
292 | /** |
293 | * gtk_selection_model_get_selection: |
294 | * @model: a `GtkSelectionModel` |
295 | * |
296 | * Gets the set containing all currently selected items in the model. |
297 | * |
298 | * This function may be slow, so if you are only interested in single item, |
299 | * consider using [method@Gtk.SelectionModel.is_selected] or if you are only |
300 | * interested in a few, consider [method@Gtk.SelectionModel.get_selection_in_range]. |
301 | * |
302 | * Returns: (transfer full): a `GtkBitset` containing all the values currently |
303 | * selected in @model. If no items are selected, the bitset is empty. |
304 | * The bitset must not be modified. |
305 | */ |
306 | GtkBitset * |
307 | gtk_selection_model_get_selection (GtkSelectionModel *model) |
308 | { |
309 | g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), gtk_bitset_new_empty ()); |
310 | |
311 | return gtk_selection_model_get_selection_in_range (model, position: 0, n_items: g_list_model_get_n_items (list: G_LIST_MODEL (ptr: model))); |
312 | } |
313 | |
314 | /** |
315 | * gtk_selection_model_get_selection_in_range: |
316 | * @model: a `GtkSelectionModel` |
317 | * @position: start of the queired range |
318 | * @n_items: number of items in the queried range |
319 | * |
320 | * Gets the set of selected items in a range. |
321 | * |
322 | * This function is an optimization for |
323 | * [method@Gtk.SelectionModel.get_selection] when you are only |
324 | * interested in part of the model's selected state. A common use |
325 | * case is in response to the [signal@Gtk.SelectionModel::selection-changed] |
326 | * signal. |
327 | * |
328 | * Returns: A `GtkBitset` that matches the selection state |
329 | * for the given range with all other values being undefined. |
330 | * The bitset must not be modified. |
331 | */ |
332 | GtkBitset * |
333 | gtk_selection_model_get_selection_in_range (GtkSelectionModel *model, |
334 | guint position, |
335 | guint n_items) |
336 | { |
337 | GtkSelectionModelInterface *iface; |
338 | |
339 | g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), gtk_bitset_new_empty ()); |
340 | |
341 | if (n_items == 0) |
342 | return gtk_bitset_new_empty (); |
343 | |
344 | iface = GTK_SELECTION_MODEL_GET_IFACE (ptr: model); |
345 | return iface->get_selection_in_range (model, position, n_items); |
346 | } |
347 | |
348 | /** |
349 | * gtk_selection_model_select_item: |
350 | * @model: a `GtkSelectionModel` |
351 | * @position: the position of the item to select |
352 | * @unselect_rest: whether previously selected items should be unselected |
353 | * |
354 | * Requests to select an item in the model. |
355 | * |
356 | * Returns: %TRUE if this action was supported and no fallback should be |
357 | * tried. This does not mean the item was selected. |
358 | */ |
359 | gboolean |
360 | gtk_selection_model_select_item (GtkSelectionModel *model, |
361 | guint position, |
362 | gboolean unselect_rest) |
363 | { |
364 | GtkSelectionModelInterface *iface; |
365 | |
366 | g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE); |
367 | |
368 | iface = GTK_SELECTION_MODEL_GET_IFACE (ptr: model); |
369 | return iface->select_item (model, position, unselect_rest); |
370 | } |
371 | |
372 | /** |
373 | * gtk_selection_model_unselect_item: |
374 | * @model: a `GtkSelectionModel` |
375 | * @position: the position of the item to unselect |
376 | * |
377 | * Requests to unselect an item in the model. |
378 | * |
379 | * Returns: %TRUE if this action was supported and no fallback should be |
380 | * tried. This does not mean the item was unselected. |
381 | */ |
382 | gboolean |
383 | gtk_selection_model_unselect_item (GtkSelectionModel *model, |
384 | guint position) |
385 | { |
386 | GtkSelectionModelInterface *iface; |
387 | |
388 | g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE); |
389 | |
390 | iface = GTK_SELECTION_MODEL_GET_IFACE (ptr: model); |
391 | return iface->unselect_item (model, position); |
392 | } |
393 | |
394 | /** |
395 | * gtk_selection_model_select_range: |
396 | * @model: a `GtkSelectionModel` |
397 | * @position: the first item to select |
398 | * @n_items: the number of items to select |
399 | * @unselect_rest: whether previously selected items should be unselected |
400 | * |
401 | * Requests to select a range of items in the model. |
402 | * |
403 | * Returns: %TRUE if this action was supported and no fallback should be |
404 | * tried. This does not mean the range was selected. |
405 | */ |
406 | gboolean |
407 | gtk_selection_model_select_range (GtkSelectionModel *model, |
408 | guint position, |
409 | guint n_items, |
410 | gboolean unselect_rest) |
411 | { |
412 | GtkSelectionModelInterface *iface; |
413 | |
414 | g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE); |
415 | |
416 | iface = GTK_SELECTION_MODEL_GET_IFACE (ptr: model); |
417 | return iface->select_range (model, position, n_items, unselect_rest); |
418 | } |
419 | |
420 | /** |
421 | * gtk_selection_model_unselect_range: |
422 | * @model: a `GtkSelectionModel` |
423 | * @position: the first item to unselect |
424 | * @n_items: the number of items to unselect |
425 | * |
426 | * Requests to unselect a range of items in the model. |
427 | * |
428 | * Returns: %TRUE if this action was supported and no fallback should be |
429 | * tried. This does not mean the range was unselected. |
430 | */ |
431 | gboolean |
432 | gtk_selection_model_unselect_range (GtkSelectionModel *model, |
433 | guint position, |
434 | guint n_items) |
435 | { |
436 | GtkSelectionModelInterface *iface; |
437 | |
438 | g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE); |
439 | |
440 | iface = GTK_SELECTION_MODEL_GET_IFACE (ptr: model); |
441 | return iface->unselect_range (model, position, n_items); |
442 | } |
443 | |
444 | /** |
445 | * gtk_selection_model_select_all: |
446 | * @model: a `GtkSelectionModel` |
447 | * |
448 | * Requests to select all items in the model. |
449 | * |
450 | * Returns: %TRUE if this action was supported and no fallback should be |
451 | * tried. This does not mean that all items are now selected. |
452 | */ |
453 | gboolean |
454 | gtk_selection_model_select_all (GtkSelectionModel *model) |
455 | { |
456 | GtkSelectionModelInterface *iface; |
457 | |
458 | g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE); |
459 | |
460 | iface = GTK_SELECTION_MODEL_GET_IFACE (ptr: model); |
461 | return iface->select_all (model); |
462 | } |
463 | |
464 | /** |
465 | * gtk_selection_model_unselect_all: |
466 | * @model: a `GtkSelectionModel` |
467 | * |
468 | * Requests to unselect all items in the model. |
469 | * |
470 | * Returns: %TRUE if this action was supported and no fallback should be |
471 | * tried. This does not mean that all items are now unselected. |
472 | */ |
473 | gboolean |
474 | gtk_selection_model_unselect_all (GtkSelectionModel *model) |
475 | { |
476 | GtkSelectionModelInterface *iface; |
477 | |
478 | g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE); |
479 | |
480 | iface = GTK_SELECTION_MODEL_GET_IFACE (ptr: model); |
481 | return iface->unselect_all (model); |
482 | } |
483 | |
484 | /** |
485 | * gtk_selection_model_set_selection: |
486 | * @model: a `GtkSelectionModel` |
487 | * @selected: bitmask specifying if items should be selected or unselected |
488 | * @mask: bitmask specifying which items should be updated |
489 | * |
490 | * Make selection changes. |
491 | * |
492 | * This is the most advanced selection updating method that allows |
493 | * the most fine-grained control over selection changes. If you can, |
494 | * you should try the simpler versions, as implementations are more |
495 | * likely to implement support for those. |
496 | * |
497 | * Requests that the selection state of all positions set in @mask |
498 | * be updated to the respective value in the @selected bitmask. |
499 | * |
500 | * In pseudocode, it would look something like this: |
501 | * |
502 | * ```c |
503 | * for (i = 0; i < n_items; i++) |
504 | * { |
505 | * // don't change values not in the mask |
506 | * if (!gtk_bitset_contains (mask, i)) |
507 | * continue; |
508 | * |
509 | * if (gtk_bitset_contains (selected, i)) |
510 | * select_item (i); |
511 | * else |
512 | * unselect_item (i); |
513 | * } |
514 | * |
515 | * gtk_selection_model_selection_changed (model, |
516 | * first_changed_item, |
517 | * n_changed_items); |
518 | * ``` |
519 | * |
520 | * @mask and @selected must not be modified. They may refer to the |
521 | * same bitset, which would mean that every item in the set should |
522 | * be selected. |
523 | * |
524 | * Returns: %TRUE if this action was supported and no fallback should be |
525 | * tried. This does not mean that all items were updated according |
526 | * to the inputs. |
527 | */ |
528 | gboolean |
529 | gtk_selection_model_set_selection (GtkSelectionModel *model, |
530 | GtkBitset *selected, |
531 | GtkBitset *mask) |
532 | { |
533 | GtkSelectionModelInterface *iface; |
534 | |
535 | g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE); |
536 | g_return_val_if_fail (selected != NULL, FALSE); |
537 | g_return_val_if_fail (mask != NULL, FALSE); |
538 | |
539 | iface = GTK_SELECTION_MODEL_GET_IFACE (ptr: model); |
540 | return iface->set_selection (model, selected, mask); |
541 | } |
542 | |
543 | /** |
544 | * gtk_selection_model_selection_changed: |
545 | * @model: a `GtkSelectionModel` |
546 | * @position: the first changed item |
547 | * @n_items: the number of changed items |
548 | * |
549 | * Helper function for implementations of `GtkSelectionModel`. |
550 | * |
551 | * Call this when a the selection changes to emit the |
552 | * [signal@Gtk.SelectionModel::selection-changed] signal. |
553 | */ |
554 | void |
555 | gtk_selection_model_selection_changed (GtkSelectionModel *model, |
556 | guint position, |
557 | guint n_items) |
558 | { |
559 | g_return_if_fail (GTK_IS_SELECTION_MODEL (model)); |
560 | g_return_if_fail (n_items > 0); |
561 | g_return_if_fail (position + n_items <= g_list_model_get_n_items (G_LIST_MODEL (model))); |
562 | |
563 | g_signal_emit (instance: model, signal_id: signals[SELECTION_CHANGED], detail: 0, position, n_items); |
564 | } |
565 | |