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
70G_DEFINE_INTERFACE (GtkSelectionModel, gtk_selection_model, G_TYPE_LIST_MODEL)
71
72enum {
73 SELECTION_CHANGED,
74 LAST_SIGNAL
75};
76
77static guint signals[LAST_SIGNAL] = { 0 };
78
79static gboolean
80gtk_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
93static GtkBitset *
94gtk_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
112static gboolean
113gtk_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
141static gboolean
142gtk_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
161static gboolean
162gtk_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
191static gboolean
192gtk_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
212static gboolean
213gtk_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
218static gboolean
219gtk_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
224static gboolean
225gtk_selection_model_default_set_selection (GtkSelectionModel *model,
226 GtkBitset *selected,
227 GtkBitset *mask)
228{
229 return FALSE;
230}
231
232static void
233gtk_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 */
280gboolean
281gtk_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 */
306GtkBitset *
307gtk_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 */
332GtkBitset *
333gtk_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 */
359gboolean
360gtk_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 */
382gboolean
383gtk_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 */
406gboolean
407gtk_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 */
431gboolean
432gtk_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 */
453gboolean
454gtk_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 */
473gboolean
474gtk_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 */
528gboolean
529gtk_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 */
554void
555gtk_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

source code of gtk/gtk/gtkselectionmodel.c