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 "gtksingleselection.h"
23
24#include "gtkbitset.h"
25#include "gtkintl.h"
26#include "gtkselectionmodel.h"
27
28/**
29 * GtkSingleSelection:
30 *
31 * `GtkSingleSelection` is a `GtkSelectionModel` that allows selecting a single
32 * item.
33 *
34 * Note that the selection is *persistent* -- if the selected item is removed
35 * and re-added in the same [signal@Gio.ListModel::items-changed] emission, it
36 * stays selected. In particular, this means that changing the sort order of an
37 * underlying sort model will preserve the selection.
38 */
39struct _GtkSingleSelection
40{
41 GObject parent_instance;
42
43 GListModel *model;
44 guint selected;
45 gpointer selected_item;
46
47 guint autoselect : 1;
48 guint can_unselect : 1;
49};
50
51struct _GtkSingleSelectionClass
52{
53 GObjectClass parent_class;
54};
55
56enum {
57 PROP_0,
58 PROP_AUTOSELECT,
59 PROP_CAN_UNSELECT,
60 PROP_SELECTED,
61 PROP_SELECTED_ITEM,
62 PROP_MODEL,
63 N_PROPS
64};
65
66static GParamSpec *properties[N_PROPS] = { NULL, };
67
68static GType
69gtk_single_selection_get_item_type (GListModel *list)
70{
71 return G_TYPE_OBJECT;
72}
73
74static guint
75gtk_single_selection_get_n_items (GListModel *list)
76{
77 GtkSingleSelection *self = GTK_SINGLE_SELECTION (ptr: list);
78
79 if (self->model == NULL)
80 return 0;
81
82 return g_list_model_get_n_items (list: self->model);
83}
84
85static gpointer
86gtk_single_selection_get_item (GListModel *list,
87 guint position)
88{
89 GtkSingleSelection *self = GTK_SINGLE_SELECTION (ptr: list);
90
91 if (self->model == NULL)
92 return NULL;
93
94 return g_list_model_get_item (list: self->model, position);
95}
96
97static void
98gtk_single_selection_list_model_init (GListModelInterface *iface)
99{
100 iface->get_item_type = gtk_single_selection_get_item_type;
101 iface->get_n_items = gtk_single_selection_get_n_items;
102 iface->get_item = gtk_single_selection_get_item;
103}
104
105static gboolean
106gtk_single_selection_is_selected (GtkSelectionModel *model,
107 guint position)
108{
109 GtkSingleSelection *self = GTK_SINGLE_SELECTION (ptr: model);
110
111 return self->selected == position;
112}
113
114static GtkBitset *
115gtk_single_selection_get_selection_in_range (GtkSelectionModel *model,
116 guint position,
117 guint n_items)
118{
119 GtkSingleSelection *self = GTK_SINGLE_SELECTION (ptr: model);
120 GtkBitset *result;
121
122 result = gtk_bitset_new_empty ();
123 if (self->selected != GTK_INVALID_LIST_POSITION)
124 gtk_bitset_add (self: result, value: self->selected);
125
126 return result;
127}
128
129static gboolean
130gtk_single_selection_select_item (GtkSelectionModel *model,
131 guint position,
132 gboolean exclusive)
133{
134 GtkSingleSelection *self = GTK_SINGLE_SELECTION (ptr: model);
135
136 /* XXX: Should we check that position < n_items here? */
137 gtk_single_selection_set_selected (self, position);
138
139 return TRUE;
140}
141
142static gboolean
143gtk_single_selection_unselect_item (GtkSelectionModel *model,
144 guint position)
145{
146 GtkSingleSelection *self = GTK_SINGLE_SELECTION (ptr: model);
147
148 if (!self->can_unselect)
149 return FALSE;
150
151 if (self->selected == position)
152 gtk_single_selection_set_selected (self, GTK_INVALID_LIST_POSITION);
153
154 return TRUE;
155}
156
157static void
158gtk_single_selection_selection_model_init (GtkSelectionModelInterface *iface)
159{
160 iface->is_selected = gtk_single_selection_is_selected;
161 iface->get_selection_in_range = gtk_single_selection_get_selection_in_range;
162 iface->select_item = gtk_single_selection_select_item;
163 iface->unselect_item = gtk_single_selection_unselect_item;
164}
165
166G_DEFINE_TYPE_EXTENDED (GtkSingleSelection, gtk_single_selection, G_TYPE_OBJECT, 0,
167 G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
168 gtk_single_selection_list_model_init)
169 G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL,
170 gtk_single_selection_selection_model_init))
171
172static void
173gtk_single_selection_items_changed_cb (GListModel *model,
174 guint position,
175 guint removed,
176 guint added,
177 GtkSingleSelection *self)
178{
179 g_object_freeze_notify (G_OBJECT (self));
180
181 if (self->selected_item == NULL)
182 {
183 if (self->autoselect)
184 {
185 self->selected_item = g_list_model_get_item (list: self->model, position: 0);
186 if (self->selected_item)
187 {
188 self->selected = 0;
189 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SELECTED]);
190 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SELECTED_ITEM]);
191 }
192 }
193 }
194 else if (self->selected < position)
195 {
196 /* unchanged */
197 }
198 else if (self->selected >= position + removed)
199 {
200 self->selected += added - removed;
201 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SELECTED]);
202 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SELECTED_ITEM]);
203 }
204 else
205 {
206 guint i;
207
208 for (i = 0; i < added; i++)
209 {
210 gpointer item = g_list_model_get_item (list: model, position: position + i);
211 if (item == self->selected_item)
212 {
213 /* the item moved */
214 if (self->selected != position + i)
215 {
216 self->selected = position + i;
217 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SELECTED]);
218 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SELECTED_ITEM]);
219 }
220
221 g_object_unref (object: item);
222 break;
223 }
224 g_object_unref (object: item);
225 }
226 if (i == added)
227 {
228 /* the item really was deleted */
229 g_clear_object (&self->selected_item);
230 if (self->autoselect)
231 {
232 self->selected = position + (self->selected - position) * added / removed;
233 self->selected_item = g_list_model_get_item (list: self->model, position: self->selected);
234 if (self->selected_item == NULL)
235 {
236 if (position > 0)
237 {
238 self->selected = position - 1;
239 self->selected_item = g_list_model_get_item (list: self->model, position: self->selected);
240 g_assert (self->selected_item);
241 /* We pretend the newly selected item was part of the original model change.
242 * This way we get around inconsistent state (no item selected) during
243 * the items-changed emission. */
244 position--;
245 removed++;
246 added++;
247 }
248 else
249 self->selected = GTK_INVALID_LIST_POSITION;
250 }
251 else
252 {
253 if (self->selected == position + added)
254 {
255 /* We pretend the newly selected item was part of the original model change.
256 * This way we get around inconsistent state (no item selected) during
257 * the items-changed emission. */
258 removed++;
259 added++;
260 }
261 }
262 }
263 else
264 {
265 g_clear_object (&self->selected_item);
266 self->selected = GTK_INVALID_LIST_POSITION;
267 }
268 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SELECTED]);
269 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SELECTED_ITEM]);
270 }
271 }
272
273 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position, removed, added);
274
275 g_object_thaw_notify (G_OBJECT (self));
276}
277
278static void
279gtk_single_selection_clear_model (GtkSingleSelection *self)
280{
281 if (self->model == NULL)
282 return;
283
284 g_signal_handlers_disconnect_by_func (self->model,
285 gtk_single_selection_items_changed_cb,
286 self);
287 g_clear_object (&self->model);
288}
289
290static void
291gtk_single_selection_set_property (GObject *object,
292 guint prop_id,
293 const GValue *value,
294 GParamSpec *pspec)
295
296{
297 GtkSingleSelection *self = GTK_SINGLE_SELECTION (ptr: object);
298
299 switch (prop_id)
300 {
301 case PROP_AUTOSELECT:
302 gtk_single_selection_set_autoselect (self, autoselect: g_value_get_boolean (value));
303 break;
304
305 case PROP_CAN_UNSELECT:
306 gtk_single_selection_set_can_unselect (self, can_unselect: g_value_get_boolean (value));
307 break;
308
309 case PROP_MODEL:
310 gtk_single_selection_set_model (self, model: g_value_get_object (value));
311 break;
312
313 case PROP_SELECTED:
314 gtk_single_selection_set_selected (self, position: g_value_get_uint (value));
315 break;
316
317 default:
318 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
319 break;
320 }
321}
322
323static void
324gtk_single_selection_get_property (GObject *object,
325 guint prop_id,
326 GValue *value,
327 GParamSpec *pspec)
328{
329 GtkSingleSelection *self = GTK_SINGLE_SELECTION (ptr: object);
330
331 switch (prop_id)
332 {
333 case PROP_AUTOSELECT:
334 g_value_set_boolean (value, v_boolean: self->autoselect);
335 break;
336
337 case PROP_CAN_UNSELECT:
338 g_value_set_boolean (value, v_boolean: self->can_unselect);
339 break;
340 case PROP_MODEL:
341 g_value_set_object (value, v_object: self->model);
342 break;
343
344 case PROP_SELECTED:
345 g_value_set_uint (value, v_uint: self->selected);
346 break;
347
348 case PROP_SELECTED_ITEM:
349 g_value_set_object (value, v_object: self->selected_item);
350 break;
351
352 default:
353 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
354 break;
355 }
356}
357
358static void
359gtk_single_selection_dispose (GObject *object)
360{
361 GtkSingleSelection *self = GTK_SINGLE_SELECTION (ptr: object);
362
363 gtk_single_selection_clear_model (self);
364
365 self->selected = GTK_INVALID_LIST_POSITION;
366 g_clear_object (&self->selected_item);
367
368 G_OBJECT_CLASS (gtk_single_selection_parent_class)->dispose (object);
369}
370
371static void
372gtk_single_selection_class_init (GtkSingleSelectionClass *klass)
373{
374 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
375
376 gobject_class->get_property = gtk_single_selection_get_property;
377 gobject_class->set_property = gtk_single_selection_set_property;
378 gobject_class->dispose = gtk_single_selection_dispose;
379
380 /**
381 * GtkSingleSelection:autoselect: (attributes org.gtk.Property.get=gtk_single_selection_get_autoselect org.gtk.Property.set=gtk_single_selection_set_autoselect)
382 *
383 * If the selection will always select an item.
384 */
385 properties[PROP_AUTOSELECT] =
386 g_param_spec_boolean (name: "autoselect",
387 P_("Autoselect"),
388 P_("If the selection will always select an item"),
389 TRUE,
390 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
391
392 /**
393 * GtkSingleSelection:can-unselect: (attributes org.gtk.Property.get=gtk_single_selection_get_can_unselect org.gtk.Property.set=gtk_single_selection_set_can_unselect)
394 *
395 * If unselecting the selected item is allowed.
396 */
397 properties[PROP_CAN_UNSELECT] =
398 g_param_spec_boolean (name: "can-unselect",
399 P_("Can unselect"),
400 P_("If unselecting the selected item is allowed"),
401 FALSE,
402 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
403
404 /**
405 * GtkSingleSelection:selected: (attributes org.gtk.Property.get=gtk_single_selection_get_selected org.gtk.Property.set=gtk_single_selection_set_selected)
406 *
407 * Position of the selected item.
408 */
409 properties[PROP_SELECTED] =
410 g_param_spec_uint (name: "selected",
411 P_("Selected"),
412 P_("Position of the selected item"),
413 minimum: 0, G_MAXUINT, GTK_INVALID_LIST_POSITION,
414 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
415
416 /**
417 * GtkSingleSelection:selected-item: (attributes org.gtk.Property.get=gtk_single_selection_get_selected_item)
418 *
419 * The selected item.
420 */
421 properties[PROP_SELECTED_ITEM] =
422 g_param_spec_object (name: "selected-item",
423 P_("Selected Item"),
424 P_("The selected item"),
425 G_TYPE_OBJECT,
426 flags: G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
427
428 /**
429 * GtkSingleSelection:model: (attributes org.gtk.Property.get=gtk_single_selection_get_model org.gtk.Property.set=gtk_single_selection_set_model)
430 *
431 * The model being managed.
432 */
433 properties[PROP_MODEL] =
434 g_param_spec_object (name: "model",
435 P_("The model"),
436 P_("The model being managed"),
437 G_TYPE_LIST_MODEL,
438 flags: G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
439
440 g_object_class_install_properties (oclass: gobject_class, n_pspecs: N_PROPS, pspecs: properties);
441}
442
443static void
444gtk_single_selection_init (GtkSingleSelection *self)
445{
446 self->selected = GTK_INVALID_LIST_POSITION;
447 self->autoselect = TRUE;
448}
449
450/**
451 * gtk_single_selection_new:
452 * @model: (nullable) (transfer full): the `GListModel` to manage
453 *
454 * Creates a new selection to handle @model.
455 *
456 * Returns: (transfer full) (type GtkSingleSelection): a new `GtkSingleSelection`
457 */
458GtkSingleSelection *
459gtk_single_selection_new (GListModel *model)
460{
461 GtkSingleSelection *self;
462
463 g_return_val_if_fail (model == NULL || G_IS_LIST_MODEL (model), NULL);
464
465 self = g_object_new (GTK_TYPE_SINGLE_SELECTION,
466 first_property_name: "model", model,
467 NULL);
468
469 /* consume the reference */
470 g_clear_object (&model);
471
472 return self;
473}
474
475/**
476 * gtk_single_selection_get_model: (attributes org.gtk.Method.get_property=model)
477 * @self: a `GtkSingleSelection`
478 *
479 * Gets the model that @self is wrapping.
480 *
481 * Returns: (transfer none) (nullable): The model being wrapped
482 */
483GListModel *
484gtk_single_selection_get_model (GtkSingleSelection *self)
485{
486 g_return_val_if_fail (GTK_IS_SINGLE_SELECTION (self), NULL);
487
488 return self->model;
489}
490
491/**
492 * gtk_single_selection_set_model: (attributes org.gtk.Method.set_property=model)
493 * @self: a `GtkSingleSelection`
494 * @model: (nullable): A `GListModel` to wrap
495 *
496 * Sets the model that @self should wrap.
497 *
498 * If @model is %NULL, @self will be empty.
499 */
500void
501gtk_single_selection_set_model (GtkSingleSelection *self,
502 GListModel *model)
503{
504 guint n_items_before;
505
506 g_return_if_fail (GTK_IS_SINGLE_SELECTION (self));
507 g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
508
509 if (self->model == model)
510 return;
511
512 g_object_freeze_notify (G_OBJECT (self));
513
514 n_items_before = self->model ? g_list_model_get_n_items (list: self->model) : 0;
515 gtk_single_selection_clear_model (self);
516
517 if (model)
518 {
519 self->model = g_object_ref (model);
520 g_signal_connect (self->model, "items-changed",
521 G_CALLBACK (gtk_single_selection_items_changed_cb), self);
522 gtk_single_selection_items_changed_cb (model: self->model,
523 position: 0,
524 removed: n_items_before,
525 added: g_list_model_get_n_items (list: model),
526 self);
527 }
528 else
529 {
530 if (self->selected != GTK_INVALID_LIST_POSITION)
531 {
532 self->selected = GTK_INVALID_LIST_POSITION;
533 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SELECTED]);
534 }
535 if (self->selected_item)
536 {
537 g_clear_object (&self->selected_item);
538 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SELECTED_ITEM]);
539 }
540 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position: 0, removed: n_items_before, added: 0);
541 }
542
543 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_MODEL]);
544
545 g_object_thaw_notify (G_OBJECT (self));
546}
547
548/**
549 * gtk_single_selection_get_selected: (attributes org.gtk.Method.get_property=selected)
550 * @self: a `GtkSingleSelection`
551 *
552 * Gets the position of the selected item.
553 *
554 * If no item is selected, %GTK_INVALID_LIST_POSITION is returned.
555 *
556 * Returns: The position of the selected item
557 */
558guint
559gtk_single_selection_get_selected (GtkSingleSelection *self)
560{
561 g_return_val_if_fail (GTK_IS_SINGLE_SELECTION (self), GTK_INVALID_LIST_POSITION);
562
563 return self->selected;
564}
565
566/**
567 * gtk_single_selection_set_selected: (attributes org.gtk.Method.set_property=selected)
568 * @self: a `GtkSingleSelection`
569 * @position: the item to select or %GTK_INVALID_LIST_POSITION
570 *
571 * Selects the item at the given position.
572 *
573 * If the list does not have an item at @position or
574 * %GTK_INVALID_LIST_POSITION is given, the behavior depends on the
575 * value of the [property@Gtk.SingleSelection:autoselect] property:
576 * If it is set, no change will occur and the old item will stay
577 * selected. If it is unset, the selection will be unset and no item
578 * will be selected.
579 */
580void
581gtk_single_selection_set_selected (GtkSingleSelection *self,
582 guint position)
583{
584 gpointer new_selected = NULL;
585 guint old_position;
586
587 g_return_if_fail (GTK_IS_SINGLE_SELECTION (self));
588
589 if (self->selected == position)
590 return;
591
592 if (self->model)
593 new_selected = g_list_model_get_item (list: self->model, position);
594
595 if (new_selected == NULL)
596 position = GTK_INVALID_LIST_POSITION;
597
598 if (self->selected == position)
599 return;
600
601 old_position = self->selected;
602 self->selected = position;
603 g_clear_object (&self->selected_item);
604 self->selected_item = new_selected;
605
606 if (old_position == GTK_INVALID_LIST_POSITION)
607 gtk_selection_model_selection_changed (model: GTK_SELECTION_MODEL (ptr: self), position, n_items: 1);
608 else if (position == GTK_INVALID_LIST_POSITION)
609 gtk_selection_model_selection_changed (model: GTK_SELECTION_MODEL (ptr: self), position: old_position, n_items: 1);
610 else if (position < old_position)
611 gtk_selection_model_selection_changed (model: GTK_SELECTION_MODEL (ptr: self), position, n_items: old_position - position + 1);
612 else
613 gtk_selection_model_selection_changed (model: GTK_SELECTION_MODEL (ptr: self), position: old_position, n_items: position - old_position + 1);
614
615 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SELECTED]);
616 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SELECTED_ITEM]);
617}
618
619/**
620 * gtk_single_selection_get_selected_item: (attributes org.gtk.Method.get_property=selected-item)
621 * @self: a `GtkSingleSelection`
622 *
623 * Gets the selected item.
624 *
625 * If no item is selected, %NULL is returned.
626 *
627 * Returns: (transfer none) (type GObject) (nullable): The selected item
628 */
629gpointer
630gtk_single_selection_get_selected_item (GtkSingleSelection *self)
631{
632 g_return_val_if_fail (GTK_IS_SINGLE_SELECTION (self), NULL);
633
634 return self->selected_item;
635}
636
637/**
638 * gtk_single_selection_get_autoselect: (attributes org.gtk.Method.get_property=autoselect)
639 * @self: a `GtkSingleSelection`
640 *
641 * Checks if autoselect has been enabled or disabled via
642 * gtk_single_selection_set_autoselect().
643 *
644 * Returns: %TRUE if autoselect is enabled
645 **/
646gboolean
647gtk_single_selection_get_autoselect (GtkSingleSelection *self)
648{
649 g_return_val_if_fail (GTK_IS_SINGLE_SELECTION (self), TRUE);
650
651 return self->autoselect;
652}
653
654/**
655 * gtk_single_selection_set_autoselect: (attributes org.gtk.Method.set_property=autoselect)
656 * @self: a `GtkSingleSelection`
657 * @autoselect: %TRUE to always select an item
658 *
659 * Enables or disables autoselect.
660 *
661 * If @autoselect is %TRUE, @self will enforce that an item is always
662 * selected. It will select a new item when the currently selected
663 * item is deleted and it will disallow unselecting the current item.
664 */
665void
666gtk_single_selection_set_autoselect (GtkSingleSelection *self,
667 gboolean autoselect)
668{
669 g_return_if_fail (GTK_IS_SINGLE_SELECTION (self));
670
671 if (self->autoselect == autoselect)
672 return;
673
674 self->autoselect = autoselect;
675
676 g_object_freeze_notify (G_OBJECT (self));
677
678 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_AUTOSELECT]);
679
680 if (self->autoselect && !self->selected_item)
681 gtk_single_selection_set_selected (self, position: 0);
682
683 g_object_thaw_notify (G_OBJECT (self));
684}
685
686/**
687 * gtk_single_selection_get_can_unselect: (attributes org.gtk.Method.get_property=can-unselect)
688 * @self: a `GtkSingleSelection`
689 *
690 * If %TRUE, gtk_selection_model_unselect_item() is supported and allows
691 * unselecting the selected item.
692 *
693 * Returns: %TRUE to support unselecting
694 */
695gboolean
696gtk_single_selection_get_can_unselect (GtkSingleSelection *self)
697{
698 g_return_val_if_fail (GTK_IS_SINGLE_SELECTION (self), FALSE);
699
700 return self->can_unselect;
701}
702
703/**
704 * gtk_single_selection_set_can_unselect: (attributes org.gtk.Method.set_property=can-unselect)
705 * @self: a `GtkSingleSelection`
706 * @can_unselect: %TRUE to allow unselecting
707 *
708 * If %TRUE, unselecting the current item via
709 * gtk_selection_model_unselect_item() is supported.
710 *
711 * Note that setting [property@Gtk.SingleSelection:autoselect] will
712 * cause unselecting to not work, so it practically makes no sense
713 * to set both at the same time the same time.
714 */
715void
716gtk_single_selection_set_can_unselect (GtkSingleSelection *self,
717 gboolean can_unselect)
718{
719 g_return_if_fail (GTK_IS_SINGLE_SELECTION (self));
720
721 if (self->can_unselect == can_unselect)
722 return;
723
724 self->can_unselect = can_unselect;
725
726 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_CAN_UNSELECT]);
727}
728

source code of gtk/gtk/gtksingleselection.c