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 "gtklistviewprivate.h"
23
24#include "gtkbitset.h"
25#include "gtkintl.h"
26#include "gtklistbaseprivate.h"
27#include "gtklistitemmanagerprivate.h"
28#include "gtkmain.h"
29#include "gtkprivate.h"
30#include "gtkrbtreeprivate.h"
31#include "gtkwidgetprivate.h"
32#include "gtkmultiselection.h"
33
34/* Maximum number of list items created by the listview.
35 * For debugging, you can set this to G_MAXUINT to ensure
36 * there's always a list item for every row.
37 */
38#define GTK_LIST_VIEW_MAX_LIST_ITEMS 200
39
40/* Extra items to keep above + below every tracker */
41#define GTK_LIST_VIEW_EXTRA_ITEMS 2
42
43/**
44 * GtkListView:
45 *
46 * `GtkListView` presents a large dynamic list of items.
47 *
48 * `GtkListView` uses its factory to generate one row widget for each visible
49 * item and shows them in a linear display, either vertically or horizontally.
50 *
51 * The [property@Gtk.ListView:show-separators] property offers a simple way to
52 * display separators between the rows.
53 *
54 * `GtkListView` allows the user to select items according to the selection
55 * characteristics of the model. For models that allow multiple selected items,
56 * it is possible to turn on _rubberband selection_, using
57 * [property@Gtk.ListView:enable-rubberband].
58 *
59 * If you need multiple columns with headers, see [class@Gtk.ColumnView].
60 *
61 * To learn more about the list widget framework, see the
62 * [overview](section-list-widget.html).
63 *
64 * An example of using `GtkListView`:
65 * ```c
66 * static void
67 * setup_listitem_cb (GtkListItemFactory *factory,
68 * GtkListItem *list_item)
69 * {
70 * GtkWidget *image;
71 *
72 * image = gtk_image_new ();
73 * gtk_image_set_icon_size (GTK_IMAGE (image), GTK_ICON_SIZE_LARGE);
74 * gtk_list_item_set_child (list_item, image);
75 * }
76 *
77 * static void
78 * bind_listitem_cb (GtkListItemFactory *factory,
79 * GtkListItem *list_item)
80 * {
81 * GtkWidget *image;
82 * GAppInfo *app_info;
83 *
84 * image = gtk_list_item_get_child (list_item);
85 * app_info = gtk_list_item_get_item (list_item);
86 * gtk_image_set_from_gicon (GTK_IMAGE (image), g_app_info_get_icon (app_info));
87 * }
88 *
89 * static void
90 * activate_cb (GtkListView *list,
91 * guint position,
92 * gpointer unused)
93 * {
94 * GAppInfo *app_info;
95 *
96 * app_info = g_list_model_get_item (G_LIST_MODEL (gtk_list_view_get_model (list)), position);
97 * g_app_info_launch (app_info, NULL, NULL, NULL);
98 * g_object_unref (app_info);
99 * }
100 *
101 * ...
102 *
103 * model = create_application_list ();
104 *
105 * factory = gtk_signal_list_item_factory_new ();
106 * g_signal_connect (factory, "setup", G_CALLBACK (setup_listitem_cb), NULL);
107 * g_signal_connect (factory, "bind", G_CALLBACK (bind_listitem_cb), NULL);
108 *
109 * list = gtk_list_view_new (GTK_SELECTION_MODEL (gtk_single_selection_new (model)), factory);
110 *
111 * g_signal_connect (list, "activate", G_CALLBACK (activate_cb), NULL);
112 *
113 * gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), list);
114 * ```
115 *
116 * # CSS nodes
117 *
118 * ```
119 * listview[.separators][.rich-list][.navigation-sidebar][.data-table]
120 * ├── row[.activatable]
121 * │
122 * ├── row[.activatable]
123 * │
124 * ┊
125 * ╰── [rubberband]
126 * ```
127 *
128 * `GtkListView` uses a single CSS node named `listview`. It may carry the
129 * `.separators` style class, when [property@Gtk.ListView:show-separators]
130 * property is set. Each child widget uses a single CSS node named `row`.
131 * If the [property@Gtk.ListItem:activatable] property is set, the
132 * corresponding row will have the `.activatable` style class. For
133 * rubberband selection, a node with name `rubberband` is used.
134 *
135 * The main listview node may also carry style classes to select
136 * the style of [list presentation](ListContainers.html#list-styles):
137 * .rich-list, .navigation-sidebar or .data-table.
138 *
139 * # Accessibility
140 *
141 * `GtkListView` uses the %GTK_ACCESSIBLE_ROLE_LIST role, and the list
142 * items use the %GTK_ACCESSIBLE_ROLE_LIST_ITEM role.
143 */
144
145typedef struct _ListRow ListRow;
146typedef struct _ListRowAugment ListRowAugment;
147
148struct _ListRow
149{
150 GtkListItemManagerItem parent;
151 guint height; /* per row */
152};
153
154struct _ListRowAugment
155{
156 GtkListItemManagerItemAugment parent;
157 guint height; /* total */
158};
159
160enum
161{
162 PROP_0,
163 PROP_FACTORY,
164 PROP_MODEL,
165 PROP_SHOW_SEPARATORS,
166 PROP_SINGLE_CLICK_ACTIVATE,
167 PROP_ENABLE_RUBBERBAND,
168
169 N_PROPS
170};
171
172enum {
173 ACTIVATE,
174 LAST_SIGNAL
175};
176
177G_DEFINE_TYPE (GtkListView, gtk_list_view, GTK_TYPE_LIST_BASE)
178
179static GParamSpec *properties[N_PROPS] = { NULL, };
180static guint signals[LAST_SIGNAL] = { 0 };
181
182static void G_GNUC_UNUSED
183dump (GtkListView *self)
184{
185 ListRow *row;
186 guint n_widgets, n_list_rows;
187
188 n_widgets = 0;
189 n_list_rows = 0;
190 //g_print ("ANCHOR: %u - %u\n", self->anchor_start, self->anchor_end);
191 for (row = gtk_list_item_manager_get_first (self: self->item_manager);
192 row;
193 row = gtk_rb_tree_node_get_next (node: row))
194 {
195 if (row->parent.widget)
196 n_widgets++;
197 n_list_rows++;
198 g_print (format: " %4u%s (%upx)\n", row->parent.n_items, row->parent.widget ? " (widget)" : "", row->height);
199 }
200
201 g_print (format: " => %u widgets in %u list rows\n", n_widgets, n_list_rows);
202}
203
204static void
205list_row_augment (GtkRbTree *tree,
206 gpointer node_augment,
207 gpointer node,
208 gpointer left,
209 gpointer right)
210{
211 ListRow *row = node;
212 ListRowAugment *aug = node_augment;
213
214 gtk_list_item_manager_augment_node (tree, node_augment, node, left, right);
215
216 aug->height = row->height * row->parent.n_items;
217
218 if (left)
219 {
220 ListRowAugment *left_aug = gtk_rb_tree_get_augment (tree, node: left);
221
222 aug->height += left_aug->height;
223 }
224
225 if (right)
226 {
227 ListRowAugment *right_aug = gtk_rb_tree_get_augment (tree, node: right);
228
229 aug->height += right_aug->height;
230 }
231}
232
233static ListRow *
234gtk_list_view_get_row_at_y (GtkListView *self,
235 int y,
236 int *offset)
237{
238 ListRow *row, *tmp;
239
240 row = gtk_list_item_manager_get_root (self: self->item_manager);
241
242 while (row)
243 {
244 tmp = gtk_rb_tree_node_get_left (node: row);
245 if (tmp)
246 {
247 ListRowAugment *aug = gtk_list_item_manager_get_item_augment (self: self->item_manager, item: tmp);
248 if (y < aug->height)
249 {
250 row = tmp;
251 continue;
252 }
253 y -= aug->height;
254 }
255
256 if (y < row->height * row->parent.n_items)
257 break;
258 y -= row->height * row->parent.n_items;
259
260 row = gtk_rb_tree_node_get_right (node: row);
261 }
262
263 if (offset)
264 *offset = row ? y : 0;
265
266 return row;
267}
268
269static int
270list_row_get_y (GtkListView *self,
271 ListRow *row)
272{
273 ListRow *parent, *left;
274 int y;
275
276 left = gtk_rb_tree_node_get_left (node: row);
277 if (left)
278 {
279 ListRowAugment *aug = gtk_list_item_manager_get_item_augment (self: self->item_manager, item: left);
280 y = aug->height;
281 }
282 else
283 y = 0;
284
285 for (parent = gtk_rb_tree_node_get_parent (node: row);
286 parent != NULL;
287 parent = gtk_rb_tree_node_get_parent (node: row))
288 {
289 left = gtk_rb_tree_node_get_left (node: parent);
290
291 if (left != row)
292 {
293 if (left)
294 {
295 ListRowAugment *aug = gtk_list_item_manager_get_item_augment (self: self->item_manager, item: left);
296 y += aug->height;
297 }
298 y += parent->height * parent->parent.n_items;
299 }
300
301 row = parent;
302 }
303
304 return y ;
305}
306
307static int
308gtk_list_view_get_list_height (GtkListView *self)
309{
310 ListRow *row;
311 ListRowAugment *aug;
312
313 row = gtk_list_item_manager_get_root (self: self->item_manager);
314 if (row == NULL)
315 return 0;
316
317 aug = gtk_list_item_manager_get_item_augment (self: self->item_manager, item: row);
318 return aug->height;
319}
320
321static gboolean
322gtk_list_view_get_allocation_along (GtkListBase *base,
323 guint pos,
324 int *offset,
325 int *size)
326{
327 GtkListView *self = GTK_LIST_VIEW (base);
328 ListRow *row;
329 guint skip;
330 int y;
331
332 row = gtk_list_item_manager_get_nth (self: self->item_manager, position: pos, offset: &skip);
333 if (row == NULL)
334 {
335 if (offset)
336 *offset = 0;
337 if (size)
338 *size = 0;
339 return FALSE;
340 }
341
342 y = list_row_get_y (self, row);
343 y += skip * row->height;
344
345 if (offset)
346 *offset = y;
347 if (size)
348 *size = row->height;
349
350 return TRUE;
351}
352
353static gboolean
354gtk_list_view_get_allocation_across (GtkListBase *base,
355 guint pos,
356 int *offset,
357 int *size)
358{
359 GtkListView *self = GTK_LIST_VIEW (base);
360
361 if (offset)
362 *offset = 0;
363 if (size)
364 *size = self->list_width;
365
366 return TRUE;
367}
368
369static GtkBitset *
370gtk_list_view_get_items_in_rect (GtkListBase *base,
371 const cairo_rectangle_int_t *rect)
372{
373 GtkListView *self = GTK_LIST_VIEW (base);
374 guint first, last, n_items;
375 GtkBitset *result;
376 ListRow *row;
377
378 result = gtk_bitset_new_empty ();
379
380 if (rect->y >= gtk_list_view_get_list_height (self))
381 return result;
382
383 n_items = gtk_list_base_get_n_items (self: base);
384 if (n_items == 0)
385 return result;
386
387 row = gtk_list_view_get_row_at_y (self, y: rect->y, NULL);
388 if (row)
389 first = gtk_list_item_manager_get_item_position (self: self->item_manager, item: row);
390 else
391 first = rect->y < 0 ? 0 : n_items - 1;
392 row = gtk_list_view_get_row_at_y (self, y: rect->y + rect->height, NULL);
393 if (row)
394 last = gtk_list_item_manager_get_item_position (self: self->item_manager, item: row);
395 else
396 last = rect->y + rect->height < 0 ? 0 : n_items - 1;
397
398 gtk_bitset_add_range_closed (self: result, first, last);
399 return result;
400}
401
402static guint
403gtk_list_view_move_focus_along (GtkListBase *base,
404 guint pos,
405 int steps)
406{
407 if (steps < 0)
408 return pos - MIN (pos, -steps);
409 else
410 {
411 pos += MIN (gtk_list_base_get_n_items (base) - pos - 1, steps);
412 }
413
414 return pos;
415}
416
417static gboolean
418gtk_list_view_get_position_from_allocation (GtkListBase *base,
419 int across,
420 int along,
421 guint *pos,
422 cairo_rectangle_int_t *area)
423{
424 GtkListView *self = GTK_LIST_VIEW (base);
425 ListRow *row;
426 int remaining;
427
428 if (across >= self->list_width)
429 return FALSE;
430
431 along = CLAMP (along, 0, gtk_list_view_get_list_height (self) - 1);
432
433 row = gtk_list_view_get_row_at_y (self, y: along, offset: &remaining);
434 if (row == NULL)
435 return FALSE;
436
437 *pos = gtk_list_item_manager_get_item_position (self: self->item_manager, item: row);
438 g_assert (remaining < row->height * row->parent.n_items);
439 *pos += remaining / row->height;
440
441 if (area)
442 {
443 area->x = 0;
444 area->width = self->list_width;
445 area->y = along - remaining % row->height;
446 area->height = row->height;
447 }
448
449 return TRUE;
450}
451
452static guint
453gtk_list_view_move_focus_across (GtkListBase *base,
454 guint pos,
455 int steps)
456{
457 return pos;
458}
459
460static int
461compare_ints (gconstpointer first,
462 gconstpointer second)
463{
464 return *(int *) first - *(int *) second;
465}
466
467static guint
468gtk_list_view_get_unknown_row_height (GtkListView *self,
469 GArray *heights)
470{
471 g_return_val_if_fail (heights->len > 0, 0);
472
473 /* return the median and hope rows are generally uniform with few outliers */
474 g_array_sort (array: heights, compare_func: compare_ints);
475
476 return g_array_index (heights, int, heights->len / 2);
477}
478
479static void
480gtk_list_view_measure_across (GtkWidget *widget,
481 GtkOrientation orientation,
482 int for_size,
483 int *minimum,
484 int *natural)
485{
486 GtkListView *self = GTK_LIST_VIEW (widget);
487 ListRow *row;
488 int min, nat, child_min, child_nat;
489 /* XXX: Figure out how to split a given height into per-row heights.
490 * Good luck! */
491 for_size = -1;
492
493 min = 0;
494 nat = 0;
495
496 for (row = gtk_list_item_manager_get_first (self: self->item_manager);
497 row != NULL;
498 row = gtk_rb_tree_node_get_next (node: row))
499 {
500 /* ignore unavailable rows */
501 if (row->parent.widget == NULL)
502 continue;
503
504 gtk_widget_measure (widget: row->parent.widget,
505 orientation, for_size,
506 minimum: &child_min, natural: &child_nat, NULL, NULL);
507 min = MAX (min, child_min);
508 nat = MAX (nat, child_nat);
509 }
510
511 *minimum = min;
512 *natural = nat;
513}
514
515static void
516gtk_list_view_measure_list (GtkWidget *widget,
517 GtkOrientation orientation,
518 int for_size,
519 int *minimum,
520 int *natural)
521{
522 GtkListView *self = GTK_LIST_VIEW (widget);
523 ListRow *row;
524 int min, nat, child_min, child_nat;
525 GArray *min_heights, *nat_heights;
526 guint n_unknown;
527
528 min_heights = g_array_new (FALSE, FALSE, element_size: sizeof (int));
529 nat_heights = g_array_new (FALSE, FALSE, element_size: sizeof (int));
530 n_unknown = 0;
531 min = 0;
532 nat = 0;
533
534 for (row = gtk_list_item_manager_get_first (self: self->item_manager);
535 row != NULL;
536 row = gtk_rb_tree_node_get_next (node: row))
537 {
538 if (row->parent.widget)
539 {
540 gtk_widget_measure (widget: row->parent.widget,
541 orientation, for_size,
542 minimum: &child_min, natural: &child_nat, NULL, NULL);
543 g_array_append_val (min_heights, child_min);
544 g_array_append_val (nat_heights, child_nat);
545 min += child_min;
546 nat += child_nat;
547 }
548 else
549 {
550 n_unknown += row->parent.n_items;
551 }
552 }
553
554 if (n_unknown)
555 {
556 min += n_unknown * gtk_list_view_get_unknown_row_height (self, heights: min_heights);
557 nat += n_unknown * gtk_list_view_get_unknown_row_height (self, heights: nat_heights);
558 }
559 g_array_free (array: min_heights, TRUE);
560 g_array_free (array: nat_heights, TRUE);
561
562 *minimum = min;
563 *natural = nat;
564}
565
566static void
567gtk_list_view_measure (GtkWidget *widget,
568 GtkOrientation orientation,
569 int for_size,
570 int *minimum,
571 int *natural,
572 int *minimum_baseline,
573 int *natural_baseline)
574{
575 GtkListView *self = GTK_LIST_VIEW (widget);
576
577 if (orientation == gtk_list_base_get_orientation (GTK_LIST_BASE (self)))
578 gtk_list_view_measure_list (widget, orientation, for_size, minimum, natural);
579 else
580 gtk_list_view_measure_across (widget, orientation, for_size, minimum, natural);
581}
582
583static void
584gtk_list_view_size_allocate (GtkWidget *widget,
585 int width,
586 int height,
587 int baseline)
588{
589 GtkListView *self = GTK_LIST_VIEW (widget);
590 ListRow *row;
591 GArray *heights;
592 int min, nat, row_height;
593 int x, y;
594 GtkOrientation orientation, opposite_orientation;
595 GtkScrollablePolicy scroll_policy, opposite_scroll_policy;
596
597 orientation = gtk_list_base_get_orientation (GTK_LIST_BASE (self));
598 opposite_orientation = OPPOSITE_ORIENTATION (orientation);
599 scroll_policy = gtk_list_base_get_scroll_policy (GTK_LIST_BASE (self), orientation);
600 opposite_scroll_policy = gtk_list_base_get_scroll_policy (GTK_LIST_BASE (self), orientation: opposite_orientation);
601
602 /* step 0: exit early if list is empty */
603 if (gtk_list_item_manager_get_root (self: self->item_manager) == NULL)
604 return;
605
606 /* step 1: determine width of the list */
607 gtk_widget_measure (widget, orientation: opposite_orientation,
608 for_size: -1,
609 minimum: &min, natural: &nat, NULL, NULL);
610 self->list_width = orientation == GTK_ORIENTATION_VERTICAL ? width : height;
611 if (opposite_scroll_policy == GTK_SCROLL_MINIMUM)
612 self->list_width = MAX (min, self->list_width);
613 else
614 self->list_width = MAX (nat, self->list_width);
615
616 /* step 2: determine height of known list items */
617 heights = g_array_new (FALSE, FALSE, element_size: sizeof (int));
618
619 for (row = gtk_list_item_manager_get_first (self: self->item_manager);
620 row != NULL;
621 row = gtk_rb_tree_node_get_next (node: row))
622 {
623 if (row->parent.widget == NULL)
624 continue;
625
626 gtk_widget_measure (widget: row->parent.widget, orientation,
627 for_size: self->list_width,
628 minimum: &min, natural: &nat, NULL, NULL);
629 if (scroll_policy == GTK_SCROLL_MINIMUM)
630 row_height = min;
631 else
632 row_height = nat;
633 if (row->height != row_height)
634 {
635 row->height = row_height;
636 gtk_rb_tree_node_mark_dirty (node: row);
637 }
638 g_array_append_val (heights, row_height);
639 }
640
641 /* step 3: determine height of unknown items */
642 row_height = gtk_list_view_get_unknown_row_height (self, heights);
643 g_array_free (array: heights, TRUE);
644
645 for (row = gtk_list_item_manager_get_first (self: self->item_manager);
646 row != NULL;
647 row = gtk_rb_tree_node_get_next (node: row))
648 {
649 if (row->parent.widget)
650 continue;
651
652 if (row->height != row_height)
653 {
654 row->height = row_height;
655 gtk_rb_tree_node_mark_dirty (node: row);
656 }
657 }
658
659 /* step 3: update the adjustments */
660 gtk_list_base_update_adjustments (GTK_LIST_BASE (self),
661 total_across: self->list_width,
662 total_along: gtk_list_view_get_list_height (self),
663 page_across: gtk_widget_get_size (widget, orientation: opposite_orientation),
664 page_along: gtk_widget_get_size (widget, orientation),
665 across: &x, along: &y);
666 x = -x;
667 y = -y;
668
669 /* step 4: actually allocate the widgets */
670
671 for (row = gtk_list_item_manager_get_first (self: self->item_manager);
672 row != NULL;
673 row = gtk_rb_tree_node_get_next (node: row))
674 {
675 if (row->parent.widget)
676 {
677 gtk_list_base_size_allocate_child (GTK_LIST_BASE (self),
678 child: row->parent.widget,
679 x,
680 y,
681 width: self->list_width,
682 height: row->height);
683 }
684
685 y += row->height * row->parent.n_items;
686 }
687
688 gtk_list_base_allocate_rubberband (GTK_LIST_BASE (self));
689}
690
691static void
692gtk_list_view_dispose (GObject *object)
693{
694 GtkListView *self = GTK_LIST_VIEW (object);
695
696 self->item_manager = NULL;
697
698 G_OBJECT_CLASS (gtk_list_view_parent_class)->dispose (object);
699}
700
701static void
702gtk_list_view_get_property (GObject *object,
703 guint property_id,
704 GValue *value,
705 GParamSpec *pspec)
706{
707 GtkListView *self = GTK_LIST_VIEW (object);
708
709 switch (property_id)
710 {
711 case PROP_FACTORY:
712 g_value_set_object (value, v_object: gtk_list_item_manager_get_factory (self: self->item_manager));
713 break;
714
715 case PROP_MODEL:
716 g_value_set_object (value, v_object: gtk_list_base_get_model (GTK_LIST_BASE (self)));
717 break;
718
719 case PROP_SHOW_SEPARATORS:
720 g_value_set_boolean (value, v_boolean: self->show_separators);
721 break;
722
723 case PROP_SINGLE_CLICK_ACTIVATE:
724 g_value_set_boolean (value, v_boolean: gtk_list_item_manager_get_single_click_activate (self: self->item_manager));
725 break;
726
727 case PROP_ENABLE_RUBBERBAND:
728 g_value_set_boolean (value, v_boolean: gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self)));
729 break;
730
731 default:
732 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
733 break;
734 }
735}
736
737static void
738gtk_list_view_set_property (GObject *object,
739 guint property_id,
740 const GValue *value,
741 GParamSpec *pspec)
742{
743 GtkListView *self = GTK_LIST_VIEW (object);
744
745 switch (property_id)
746 {
747 case PROP_FACTORY:
748 gtk_list_view_set_factory (self, factory: g_value_get_object (value));
749 break;
750
751 case PROP_MODEL:
752 gtk_list_view_set_model (self, model: g_value_get_object (value));
753 break;
754
755 case PROP_SHOW_SEPARATORS:
756 gtk_list_view_set_show_separators (self, show_separators: g_value_get_boolean (value));
757 break;
758
759 case PROP_SINGLE_CLICK_ACTIVATE:
760 gtk_list_view_set_single_click_activate (self, single_click_activate: g_value_get_boolean (value));
761 break;
762
763 case PROP_ENABLE_RUBBERBAND:
764 gtk_list_view_set_enable_rubberband (self, enable_rubberband: g_value_get_boolean (value));
765 break;
766
767 default:
768 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
769 break;
770 }
771}
772
773static void
774gtk_list_view_activate_item (GtkWidget *widget,
775 const char *action_name,
776 GVariant *parameter)
777{
778 GtkListView *self = GTK_LIST_VIEW (widget);
779 guint pos;
780
781 if (!g_variant_check_format_string (value: parameter, format_string: "u", FALSE))
782 return;
783
784 g_variant_get (value: parameter, format_string: "u", &pos);
785 if (pos >= gtk_list_base_get_n_items (GTK_LIST_BASE (self)))
786 return;
787
788 g_signal_emit (instance: widget, signal_id: signals[ACTIVATE], detail: 0, pos);
789}
790
791static void
792gtk_list_view_class_init (GtkListViewClass *klass)
793{
794 GtkListBaseClass *list_base_class = GTK_LIST_BASE_CLASS (klass);
795 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
796 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
797
798 list_base_class->list_item_name = "row";
799 list_base_class->list_item_role = GTK_ACCESSIBLE_ROLE_LIST_ITEM;
800 list_base_class->list_item_size = sizeof (ListRow);
801 list_base_class->list_item_augment_size = sizeof (ListRowAugment);
802 list_base_class->list_item_augment_func = list_row_augment;
803 list_base_class->get_allocation_along = gtk_list_view_get_allocation_along;
804 list_base_class->get_allocation_across = gtk_list_view_get_allocation_across;
805 list_base_class->get_items_in_rect = gtk_list_view_get_items_in_rect;
806 list_base_class->get_position_from_allocation = gtk_list_view_get_position_from_allocation;
807 list_base_class->move_focus_along = gtk_list_view_move_focus_along;
808 list_base_class->move_focus_across = gtk_list_view_move_focus_across;
809
810 widget_class->measure = gtk_list_view_measure;
811 widget_class->size_allocate = gtk_list_view_size_allocate;
812
813 gobject_class->dispose = gtk_list_view_dispose;
814 gobject_class->get_property = gtk_list_view_get_property;
815 gobject_class->set_property = gtk_list_view_set_property;
816
817 /**
818 * GtkListView:factory: (attributes org.gtk.Property.get=gtk_list_view_get_factory org.gtk.Property.set=gtk_list_view_set_factory)
819 *
820 * Factory for populating list items.
821 */
822 properties[PROP_FACTORY] =
823 g_param_spec_object (name: "factory",
824 P_("Factory"),
825 P_("Factory for populating list items"),
826 GTK_TYPE_LIST_ITEM_FACTORY,
827 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
828
829 /**
830 * GtkListView:model: (attributes org.gtk.Property.get=gtk_list_view_get_model org.gtk.Property.set=gtk_list_view_set_model)
831 *
832 * Model for the items displayed.
833 */
834 properties[PROP_MODEL] =
835 g_param_spec_object (name: "model",
836 P_("Model"),
837 P_("Model for the items displayed"),
838 GTK_TYPE_SELECTION_MODEL,
839 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
840
841 /**
842 * GtkListView:show-separators: (attributes org.gtk.Property.get=gtk_list_view_get_show_separators org.gtk.Property.set=gtk_list_view_set_show_separators)
843 *
844 * Show separators between rows.
845 */
846 properties[PROP_SHOW_SEPARATORS] =
847 g_param_spec_boolean (name: "show-separators",
848 P_("Show separators"),
849 P_("Show separators between rows"),
850 FALSE,
851 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
852
853 /**
854 * GtkListView:single-click-activate: (attributes org.gtk.Property.get=gtk_list_view_get_single_click_activate org.gtk.Property.set=gtk_list_view_set_single_click_activate)
855 *
856 * Activate rows on single click and select them on hover.
857 */
858 properties[PROP_SINGLE_CLICK_ACTIVATE] =
859 g_param_spec_boolean (name: "single-click-activate",
860 P_("Single click activate"),
861 P_("Activate rows on single click"),
862 FALSE,
863 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
864
865 /**
866 * GtkListView:enable-rubberband: (attributes org.gtk.Property.get=gtk_list_view_get_enable_rubberband org.gtk.Property.set=gtk_list_view_set_enable_rubberband)
867 *
868 * Allow rubberband selection.
869 */
870 properties[PROP_ENABLE_RUBBERBAND] =
871 g_param_spec_boolean (name: "enable-rubberband",
872 P_("Enable rubberband selection"),
873 P_("Allow selecting items by dragging with the mouse"),
874 FALSE,
875 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
876
877 g_object_class_install_properties (oclass: gobject_class, n_pspecs: N_PROPS, pspecs: properties);
878
879 /**
880 * GtkListView::activate:
881 * @self: The `GtkListView`
882 * @position: position of item to activate
883 *
884 * Emitted when a row has been activated by the user,
885 * usually via activating the GtkListView|list.activate-item action.
886 *
887 * This allows for a convenient way to handle activation in a listview.
888 * See [method@Gtk.ListItem.set_activatable] for details on how to use
889 * this signal.
890 */
891 signals[ACTIVATE] =
892 g_signal_new (I_("activate"),
893 G_TYPE_FROM_CLASS (gobject_class),
894 signal_flags: G_SIGNAL_RUN_LAST,
895 class_offset: 0,
896 NULL, NULL,
897 c_marshaller: g_cclosure_marshal_VOID__UINT,
898 G_TYPE_NONE, n_params: 1,
899 G_TYPE_UINT);
900 g_signal_set_va_marshaller (signal_id: signals[ACTIVATE],
901 G_TYPE_FROM_CLASS (gobject_class),
902 va_marshaller: g_cclosure_marshal_VOID__UINTv);
903
904 /**
905 * GtkListView|list.activate-item:
906 * @position: position of item to activate
907 *
908 * Activates the item given in @position by emitting the
909 * [signal@Gtk.ListView::activate] signal.
910 */
911 gtk_widget_class_install_action (widget_class,
912 action_name: "list.activate-item",
913 parameter_type: "u",
914 activate: gtk_list_view_activate_item);
915
916 gtk_widget_class_set_css_name (widget_class, I_("listview"));
917 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_LIST);
918}
919
920static void
921gtk_list_view_init (GtkListView *self)
922{
923 self->item_manager = gtk_list_base_get_manager (GTK_LIST_BASE (self));
924
925 gtk_list_base_set_anchor_max_widgets (GTK_LIST_BASE (self),
926 GTK_LIST_VIEW_MAX_LIST_ITEMS,
927 GTK_LIST_VIEW_EXTRA_ITEMS);
928
929 gtk_widget_add_css_class (GTK_WIDGET (self), css_class: "view");
930}
931
932/**
933 * gtk_list_view_new:
934 * @model: (nullable) (transfer full): the model to use
935 * @factory: (nullable) (transfer full): The factory to populate items with
936 *
937 * Creates a new `GtkListView` that uses the given @factory for
938 * mapping items to widgets.
939 *
940 * The function takes ownership of the
941 * arguments, so you can write code like
942 * ```c
943 * list_view = gtk_list_view_new (create_model (),
944 * gtk_builder_list_item_factory_new_from_resource ("/resource.ui"));
945 * ```
946 *
947 * Returns: a new `GtkListView` using the given @model and @factory
948 */
949GtkWidget *
950gtk_list_view_new (GtkSelectionModel *model,
951 GtkListItemFactory *factory)
952{
953 GtkWidget *result;
954
955 g_return_val_if_fail (model == NULL || GTK_IS_SELECTION_MODEL (model), NULL);
956 g_return_val_if_fail (factory == NULL || GTK_IS_LIST_ITEM_FACTORY (factory), NULL);
957
958 result = g_object_new (GTK_TYPE_LIST_VIEW,
959 first_property_name: "model", model,
960 "factory", factory,
961 NULL);
962
963 /* consume the references */
964 g_clear_object (&model);
965 g_clear_object (&factory);
966
967 return result;
968}
969
970/**
971 * gtk_list_view_get_model: (attributes org.gtk.Method.get_property=model)
972 * @self: a `GtkListView`
973 *
974 * Gets the model that's currently used to read the items displayed.
975 *
976 * Returns: (nullable) (transfer none): The model in use
977 */
978GtkSelectionModel *
979gtk_list_view_get_model (GtkListView *self)
980{
981 g_return_val_if_fail (GTK_IS_LIST_VIEW (self), NULL);
982
983 return gtk_list_base_get_model (GTK_LIST_BASE (self));
984}
985
986/**
987 * gtk_list_view_set_model: (attributes org.gtk.Method.set_property=model)
988 * @self: a `GtkListView`
989 * @model: (nullable) (transfer none): the model to use
990 *
991 * Sets the model to use.
992 *
993 * This must be a [iface@Gtk.SelectionModel] to use.
994 */
995void
996gtk_list_view_set_model (GtkListView *self,
997 GtkSelectionModel *model)
998{
999 g_return_if_fail (GTK_IS_LIST_VIEW (self));
1000 g_return_if_fail (model == NULL || GTK_IS_SELECTION_MODEL (model));
1001
1002 if (!gtk_list_base_set_model (GTK_LIST_BASE (self), model))
1003 return;
1004
1005 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: self),
1006 first_property: GTK_ACCESSIBLE_PROPERTY_MULTI_SELECTABLE, GTK_IS_MULTI_SELECTION (ptr: model),
1007 -1);
1008
1009 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_MODEL]);
1010}
1011
1012/**
1013 * gtk_list_view_get_factory: (attributes org.gtk.Method.get_property=factory)
1014 * @self: a `GtkListView`
1015 *
1016 * Gets the factory that's currently used to populate list items.
1017 *
1018 * Returns: (nullable) (transfer none): The factory in use
1019 */
1020GtkListItemFactory *
1021gtk_list_view_get_factory (GtkListView *self)
1022{
1023 g_return_val_if_fail (GTK_IS_LIST_VIEW (self), NULL);
1024
1025 return gtk_list_item_manager_get_factory (self: self->item_manager);
1026}
1027
1028/**
1029 * gtk_list_view_set_factory: (attributes org.gtk.Method.set_property=factory)
1030 * @self: a `GtkListView`
1031 * @factory: (nullable) (transfer none): the factory to use
1032 *
1033 * Sets the `GtkListItemFactory` to use for populating list items.
1034 */
1035void
1036gtk_list_view_set_factory (GtkListView *self,
1037 GtkListItemFactory *factory)
1038{
1039 g_return_if_fail (GTK_IS_LIST_VIEW (self));
1040 g_return_if_fail (factory == NULL || GTK_IS_LIST_ITEM_FACTORY (factory));
1041
1042 if (factory == gtk_list_item_manager_get_factory (self: self->item_manager))
1043 return;
1044
1045 gtk_list_item_manager_set_factory (self: self->item_manager, factory);
1046
1047 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_FACTORY]);
1048}
1049
1050/**
1051 * gtk_list_view_set_show_separators: (attributes org.gtk.Method.set_property=show-separators)
1052 * @self: a `GtkListView`
1053 * @show_separators: %TRUE to show separators
1054 *
1055 * Sets whether the list box should show separators
1056 * between rows.
1057 */
1058void
1059gtk_list_view_set_show_separators (GtkListView *self,
1060 gboolean show_separators)
1061{
1062 g_return_if_fail (GTK_IS_LIST_VIEW (self));
1063
1064 if (self->show_separators == show_separators)
1065 return;
1066
1067 self->show_separators = show_separators;
1068
1069 if (show_separators)
1070 gtk_widget_add_css_class (GTK_WIDGET (self), css_class: "separators");
1071 else
1072 gtk_widget_remove_css_class (GTK_WIDGET (self), css_class: "separators");
1073
1074 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SHOW_SEPARATORS]);
1075}
1076
1077/**
1078 * gtk_list_view_get_show_separators: (attributes org.gtk.Method.get_property=show-separators)
1079 * @self: a `GtkListView`
1080 *
1081 * Returns whether the list box should show separators
1082 * between rows.
1083 *
1084 * Returns: %TRUE if the list box shows separators
1085 */
1086gboolean
1087gtk_list_view_get_show_separators (GtkListView *self)
1088{
1089 g_return_val_if_fail (GTK_IS_LIST_VIEW (self), FALSE);
1090
1091 return self->show_separators;
1092}
1093
1094/**
1095 * gtk_list_view_set_single_click_activate: (attributes org.gtk.Method.set_property=single-click-activate)
1096 * @self: a `GtkListView`
1097 * @single_click_activate: %TRUE to activate items on single click
1098 *
1099 * Sets whether rows should be activated on single click and
1100 * selected on hover.
1101 */
1102void
1103gtk_list_view_set_single_click_activate (GtkListView *self,
1104 gboolean single_click_activate)
1105{
1106 g_return_if_fail (GTK_IS_LIST_VIEW (self));
1107
1108 if (single_click_activate == gtk_list_item_manager_get_single_click_activate (self: self->item_manager))
1109 return;
1110
1111 gtk_list_item_manager_set_single_click_activate (self: self->item_manager, single_click_activate);
1112
1113 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SINGLE_CLICK_ACTIVATE]);
1114}
1115
1116/**
1117 * gtk_list_view_get_single_click_activate: (attributes org.gtk.Method.set_property=single-click-activate)
1118 * @self: a `GtkListView`
1119 *
1120 * Returns whether rows will be activated on single click and
1121 * selected on hover.
1122 *
1123 * Returns: %TRUE if rows are activated on single click
1124 */
1125gboolean
1126gtk_list_view_get_single_click_activate (GtkListView *self)
1127{
1128 g_return_val_if_fail (GTK_IS_LIST_VIEW (self), FALSE);
1129
1130 return gtk_list_item_manager_get_single_click_activate (self: self->item_manager);
1131}
1132
1133/**
1134 * gtk_list_view_set_enable_rubberband: (attributes org.gtk.Method.set_property=enable-rubberband)
1135 * @self: a `GtkListView`
1136 * @enable_rubberband: %TRUE to enable rubberband selection
1137 *
1138 * Sets whether selections can be changed by dragging with the mouse.
1139 */
1140void
1141gtk_list_view_set_enable_rubberband (GtkListView *self,
1142 gboolean enable_rubberband)
1143{
1144 g_return_if_fail (GTK_IS_LIST_VIEW (self));
1145
1146 if (enable_rubberband == gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self)))
1147 return;
1148
1149 gtk_list_base_set_enable_rubberband (GTK_LIST_BASE (self), enable: enable_rubberband);
1150
1151 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_ENABLE_RUBBERBAND]);
1152}
1153
1154/**
1155 * gtk_list_view_get_enable_rubberband: (attributes org.gtk.Method.get_property=enable-rubberband)
1156 * @self: a `GtkListView`
1157 *
1158 * Returns whether rows can be selected by dragging with the mouse.
1159 *
1160 * Returns: %TRUE if rubberband selection is enabled
1161 */
1162gboolean
1163gtk_list_view_get_enable_rubberband (GtkListView *self)
1164{
1165 g_return_val_if_fail (GTK_IS_LIST_VIEW (self), FALSE);
1166
1167 return gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self));
1168}
1169

source code of gtk/gtk/gtklistview.c