1/*
2 * Copyright © 2019 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 "gtkgridview.h"
23
24#include "gtkbitset.h"
25#include "gtkintl.h"
26#include "gtklistbaseprivate.h"
27#include "gtklistitemfactory.h"
28#include "gtklistitemmanagerprivate.h"
29#include "gtkmain.h"
30#include "gtkprivate.h"
31#include "gtksingleselection.h"
32#include "gtkwidgetprivate.h"
33#include "gtkmultiselection.h"
34
35/* Maximum number of list items created by the gridview.
36 * For debugging, you can set this to G_MAXUINT to ensure
37 * there's always a list item for every row.
38 *
39 * We multiply this number with GtkGridView:max-columns so
40 * that we can always display at least this many rows.
41 */
42#define GTK_GRID_VIEW_MAX_VISIBLE_ROWS (30)
43
44#define DEFAULT_MAX_COLUMNS (7)
45
46/**
47 * GtkGridView:
48 *
49 * `GtkGridView` presents a large dynamic grid of items.
50 *
51 * `GtkGridView` uses its factory to generate one child widget for each
52 * visible item and shows them in a grid. The orientation of the grid view
53 * determines if the grid reflows vertically or horizontally.
54 *
55 * `GtkGridView` allows the user to select items according to the selection
56 * characteristics of the model. For models that allow multiple selected items,
57 * it is possible to turn on _rubberband selection_, using
58 * [property@Gtk.GridView:enable-rubberband].
59 *
60 * To learn more about the list widget framework, see the
61 * [overview](section-list-widget.html).
62 *
63 * # CSS nodes
64 *
65 * ```
66 * gridview
67 * ├── child[.activatable]
68 * │
69 * ├── child[.activatable]
70 * │
71 * ┊
72 * ╰── [rubberband]
73 * ```
74 *
75 * `GtkGridView` uses a single CSS node with name `gridview`. Each child uses
76 * a single CSS node with name `child`. If the [property@Gtk.ListItem:activatable]
77 * property is set, the corresponding row will have the `.activatable` style
78 * class. For rubberband selection, a subnode with name `rubberband` is used.
79 *
80 * # Accessibility
81 *
82 * `GtkGridView` uses the %GTK_ACCESSIBLE_ROLE_GRID role, and the items
83 * use the %GTK_ACCESSIBLE_ROLE_GRID_CELL role.
84 */
85
86typedef struct _Cell Cell;
87typedef struct _CellAugment CellAugment;
88
89struct _GtkGridView
90{
91 GtkListBase parent_instance;
92
93 GtkListItemManager *item_manager;
94 guint min_columns;
95 guint max_columns;
96 /* set in size_allocate */
97 guint n_columns;
98 int unknown_row_height;
99 double column_width;
100};
101
102struct _GtkGridViewClass
103{
104 GtkListBaseClass parent_class;
105};
106
107struct _Cell
108{
109 GtkListItemManagerItem parent;
110 guint size; /* total, only counting cells in first column */
111};
112
113struct _CellAugment
114{
115 GtkListItemManagerItemAugment parent;
116 guint size; /* total, only counting first column */
117};
118
119enum
120{
121 PROP_0,
122 PROP_FACTORY,
123 PROP_MAX_COLUMNS,
124 PROP_MIN_COLUMNS,
125 PROP_MODEL,
126 PROP_SINGLE_CLICK_ACTIVATE,
127 PROP_ENABLE_RUBBERBAND,
128
129 N_PROPS
130};
131
132enum {
133 ACTIVATE,
134 LAST_SIGNAL
135};
136
137G_DEFINE_TYPE (GtkGridView, gtk_grid_view, GTK_TYPE_LIST_BASE)
138
139static GParamSpec *properties[N_PROPS] = { NULL, };
140static guint signals[LAST_SIGNAL] = { 0 };
141
142static void G_GNUC_UNUSED
143dump (GtkGridView *self)
144{
145 Cell *cell;
146 guint n_widgets, n_list_rows, n_items;
147
148 n_widgets = 0;
149 n_list_rows = 0;
150 n_items = 0;
151 //g_print ("ANCHOR: %u - %u\n", self->anchor_start, self->anchor_end);
152 for (cell = gtk_list_item_manager_get_first (self: self->item_manager);
153 cell;
154 cell = gtk_rb_tree_node_get_next (node: cell))
155 {
156 if (cell->parent.widget)
157 n_widgets++;
158 n_list_rows++;
159 n_items += cell->parent.n_items;
160 g_print (format: "%6u%6u %5ux%3u %s (%upx)\n",
161 cell->parent.n_items, n_items,
162 n_items / (self->n_columns ? self->n_columns : self->min_columns),
163 n_items % (self->n_columns ? self->n_columns : self->min_columns),
164 cell->parent.widget ? " (widget)" : "", cell->size);
165 }
166
167 g_print (format: " => %u widgets in %u list rows\n", n_widgets, n_list_rows);
168}
169
170static void
171cell_augment (GtkRbTree *tree,
172 gpointer node_augment,
173 gpointer node,
174 gpointer left,
175 gpointer right)
176{
177 Cell *cell = node;
178 CellAugment *aug = node_augment;
179
180 gtk_list_item_manager_augment_node (tree, node_augment, node, left, right);
181
182 aug->size = cell->size;
183
184 if (left)
185 {
186 CellAugment *left_aug = gtk_rb_tree_get_augment (tree, node: left);
187
188 aug->size += left_aug->size;
189 }
190
191 if (right)
192 {
193 CellAugment *right_aug = gtk_rb_tree_get_augment (tree, node: right);
194
195 aug->size += right_aug->size;
196 }
197}
198
199/*<private>
200 * gtk_grid_view_get_cell_at_y:
201 * @self: a `GtkGridView`
202 * @y: an offset in direction of @self's orientation
203 * @position: (out caller-allocates) (optional): stores the position
204 * index of the returned row
205 * @offset: (out caller-allocates) (optional): stores the offset
206 * in pixels between y and top of cell.
207 * @size: (out caller-allocates) (optional): stores the height
208 * of the cell
209 *
210 * Gets the Cell that occupies the leftmost position in the row at offset
211 * @y into the primary direction.
212 *
213 * If y is larger than the height of all cells, %NULL will be returned.
214 * In particular that means that for an empty grid, %NULL is returned
215 * for any value.
216 *
217 * Returns: (nullable): The first cell at offset y
218 **/
219static Cell *
220gtk_grid_view_get_cell_at_y (GtkGridView *self,
221 int y,
222 guint *position,
223 int *offset,
224 int *size)
225{
226 Cell *cell, *tmp;
227 guint pos;
228
229 cell = gtk_list_item_manager_get_root (self: self->item_manager);
230 pos = 0;
231
232 while (cell)
233 {
234 tmp = gtk_rb_tree_node_get_left (node: cell);
235 if (tmp)
236 {
237 CellAugment *aug = gtk_list_item_manager_get_item_augment (self: self->item_manager, item: tmp);
238 if (y < aug->size)
239 {
240 cell = tmp;
241 continue;
242 }
243 y -= aug->size;
244 pos += aug->parent.n_items;
245 }
246
247 if (y < cell->size)
248 break;
249 y -= cell->size;
250 pos += cell->parent.n_items;
251
252 cell = gtk_rb_tree_node_get_right (node: cell);
253 }
254
255 if (cell == NULL)
256 {
257 if (position)
258 *position = 0;
259 if (offset)
260 *offset = 0;
261 if (size)
262 *size = 0;
263 return NULL;
264 }
265
266 /* We know have the (range of) cell(s) that contains this offset.
267 * Now for the hard part of computing which index this actually is.
268 */
269 if (offset || position || size)
270 {
271 guint n_items = cell->parent.n_items;
272 guint no_widget_rows, skip;
273
274 /* skip remaining items at end of row */
275 if (pos % self->n_columns)
276 {
277 skip = self->n_columns - pos % self->n_columns;
278 if (n_items <= skip)
279 {
280 g_warning ("ran out of items");
281 if (position)
282 *position = 0;
283 if (offset)
284 *offset = 0;
285 if (size)
286 *size = 0;
287 return NULL;
288 }
289 n_items -= skip;
290 pos += skip;
291 }
292
293 /* Skip all the rows this index doesn't go into */
294 no_widget_rows = (n_items - 1) / self->n_columns;
295 skip = MIN (y / self->unknown_row_height, no_widget_rows);
296 y -= skip * self->unknown_row_height;
297 pos += self->n_columns * skip;
298
299 if (position)
300 *position = pos;
301 if (offset)
302 *offset = y;
303 if (size)
304 {
305 if (skip < no_widget_rows)
306 *size = self->unknown_row_height;
307 else
308 *size = cell->size - no_widget_rows * self->unknown_row_height;
309 }
310 }
311
312 return cell;
313}
314
315static gboolean
316gtk_grid_view_get_allocation_along (GtkListBase *base,
317 guint pos,
318 int *offset,
319 int *size)
320{
321 GtkGridView *self = GTK_GRID_VIEW (base);
322 Cell *cell, *tmp;
323 int y;
324
325 cell = gtk_list_item_manager_get_root (self: self->item_manager);
326 y = 0;
327 pos -= pos % self->n_columns;
328
329 while (cell)
330 {
331 tmp = gtk_rb_tree_node_get_left (node: cell);
332 if (tmp)
333 {
334 CellAugment *aug = gtk_list_item_manager_get_item_augment (self: self->item_manager, item: tmp);
335 if (pos < aug->parent.n_items)
336 {
337 cell = tmp;
338 continue;
339 }
340 pos -= aug->parent.n_items;
341 y += aug->size;
342 }
343
344 if (pos < cell->parent.n_items)
345 break;
346 y += cell->size;
347 pos -= cell->parent.n_items;
348
349 cell = gtk_rb_tree_node_get_right (node: cell);
350 }
351
352 if (cell == NULL)
353 {
354 if (offset)
355 *offset = 0;
356 if (size)
357 *size = 0;
358 return FALSE;
359 }
360
361 /* We know have the (range of) cell(s) that contains this offset.
362 * Now for the hard part of computing which index this actually is.
363 */
364 if (offset || size)
365 {
366 guint n_items = cell->parent.n_items;
367 guint skip;
368
369 /* skip remaining items at end of row */
370 if (pos % self->n_columns)
371 {
372 skip = pos % self->n_columns;
373 n_items -= skip;
374 pos -= skip;
375 }
376
377 /* Skip all the rows this index doesn't go into */
378 skip = pos / self->n_columns;
379 n_items -= skip * self->n_columns;
380 y += skip * self->unknown_row_height;
381
382 if (offset)
383 *offset = y;
384 if (size)
385 {
386 if (n_items > self->n_columns)
387 *size = self->unknown_row_height;
388 else
389 *size = cell->size - skip * self->unknown_row_height;
390 }
391 }
392
393 return TRUE;
394}
395
396static gboolean
397gtk_grid_view_get_allocation_across (GtkListBase *base,
398 guint pos,
399 int *offset,
400 int *size)
401{
402 GtkGridView *self = GTK_GRID_VIEW (base);
403 guint start;
404
405 pos %= self->n_columns;
406 start = ceil (x: self->column_width * pos);
407
408 if (offset)
409 *offset = start;
410 if (size)
411 *size = ceil (x: self->column_width * (pos + 1)) - start;
412
413 return TRUE;
414}
415
416static int
417gtk_grid_view_compute_total_height (GtkGridView *self)
418{
419 Cell *cell;
420 CellAugment *aug;
421
422 cell = gtk_list_item_manager_get_root (self: self->item_manager);
423 if (cell == NULL)
424 return 0;
425 aug = gtk_list_item_manager_get_item_augment (self: self->item_manager, item: cell);
426 return aug->size;
427}
428
429static gboolean
430gtk_grid_view_get_position_from_allocation (GtkListBase *base,
431 int across,
432 int along,
433 guint *position,
434 cairo_rectangle_int_t *area)
435{
436 GtkGridView *self = GTK_GRID_VIEW (base);
437 int offset, size;
438 guint pos, n_items;
439
440 if (across >= self->column_width * self->n_columns)
441 return FALSE;
442
443 n_items = gtk_list_base_get_n_items (self: base);
444 along = CLAMP (along, 0, gtk_grid_view_compute_total_height (self) - 1);
445 across = across < 0 ? 0 : across;
446
447 if (!gtk_grid_view_get_cell_at_y (self,
448 y: along,
449 position: &pos,
450 offset: &offset,
451 size: &size))
452 return FALSE;
453
454 pos += floor (x: across / self->column_width);
455
456 if (pos >= n_items)
457 {
458 /* Ugh, we're in the last row and don't have enough items
459 * to fill the row.
460 * Do it the hard way then... */
461 pos = n_items - 1;
462 }
463
464 *position = pos;
465 if (area)
466 {
467 area->x = ceil (x: self->column_width * (pos % self->n_columns));
468 area->width = ceil (x: self->column_width * (1 + pos % self->n_columns)) - area->x;
469 area->y = along - offset;
470 area->height = size;
471 }
472
473 return TRUE;
474}
475
476static GtkBitset *
477gtk_grid_view_get_items_in_rect (GtkListBase *base,
478 const GdkRectangle *rect)
479{
480 GtkGridView *self = GTK_GRID_VIEW (base);
481 guint first_row, last_row, first_column, last_column, n_items;
482 GtkBitset *result;
483
484 result = gtk_bitset_new_empty ();
485
486 if (rect->y >= gtk_grid_view_compute_total_height (self))
487 return result;
488
489 n_items = gtk_list_base_get_n_items (self: base);
490 if (n_items == 0)
491 return result;
492
493 first_column = fmax (x: floor (x: rect->x / self->column_width), y: 0);
494 last_column = fmin (x: floor (x: (rect->x + rect->width) / self->column_width), y: self->n_columns - 1);
495 if (!gtk_grid_view_get_cell_at_y (self, y: rect->y, position: &first_row, NULL, NULL))
496 first_row = rect->y < 0 ? 0 : n_items - 1;
497 if (!gtk_grid_view_get_cell_at_y (self, y: rect->y + rect->height, position: &last_row, NULL, NULL))
498 last_row = rect->y + rect->height < 0 ? 0 : n_items - 1;
499
500 gtk_bitset_add_rectangle (self: result,
501 start: first_row + first_column,
502 width: last_column - first_column + 1,
503 height: (last_row - first_row) / self->n_columns + 1,
504 stride: self->n_columns);
505
506 return result;
507}
508
509static guint
510gtk_grid_view_move_focus_along (GtkListBase *base,
511 guint pos,
512 int steps)
513{
514 GtkGridView *self = GTK_GRID_VIEW (base);
515
516 steps *= self->n_columns;
517
518 if (steps < 0)
519 {
520 if (pos >= self->n_columns)
521 pos -= MIN (pos, -steps);
522 }
523 else
524 {
525 guint n_items = gtk_list_base_get_n_items (self: base);
526 if (n_items / self->n_columns > pos / self->n_columns)
527 pos += MIN (n_items - pos - 1, steps);
528 }
529
530 return pos;
531}
532
533static guint
534gtk_grid_view_move_focus_across (GtkListBase *base,
535 guint pos,
536 int steps)
537{
538 if (steps < 0)
539 return pos - MIN (pos, -steps);
540 else
541 {
542 guint n_items = gtk_list_base_get_n_items (self: base);
543 pos += MIN (n_items - pos - 1, steps);
544 }
545
546 return pos;
547}
548
549static int
550compare_ints (gconstpointer first,
551 gconstpointer second)
552{
553 return *(int *) first - *(int *) second;
554}
555
556static int
557gtk_grid_view_get_unknown_row_size (GtkGridView *self,
558 GArray *heights)
559{
560 g_return_val_if_fail (heights->len > 0, 0);
561
562 /* return the median and hope rows are generally uniform with few outliers */
563 g_array_sort (array: heights, compare_func: compare_ints);
564
565 return g_array_index (heights, int, heights->len / 2);
566}
567
568static void
569gtk_grid_view_measure_column_size (GtkGridView *self,
570 int *minimum,
571 int *natural)
572{
573 GtkOrientation opposite;
574 Cell *cell;
575 int min, nat, child_min, child_nat;
576
577 min = 0;
578 nat = 0;
579 opposite = gtk_list_base_get_opposite_orientation (GTK_LIST_BASE (self));
580
581 for (cell = gtk_list_item_manager_get_first (self: self->item_manager);
582 cell != NULL;
583 cell = gtk_rb_tree_node_get_next (node: cell))
584 {
585 /* ignore unavailable cells */
586 if (cell->parent.widget == NULL)
587 continue;
588
589 gtk_widget_measure (widget: cell->parent.widget,
590 orientation: opposite, for_size: -1,
591 minimum: &child_min, natural: &child_nat, NULL, NULL);
592 min = MAX (min, child_min);
593 nat = MAX (nat, child_nat);
594 }
595
596 *minimum = min;
597 *natural = nat;
598}
599
600static void
601gtk_grid_view_measure_across (GtkWidget *widget,
602 int for_size,
603 int *minimum,
604 int *natural)
605{
606 GtkGridView *self = GTK_GRID_VIEW (widget);
607
608 gtk_grid_view_measure_column_size (self, minimum, natural);
609
610 *minimum *= self->min_columns;
611 *natural *= self->max_columns;
612}
613
614static guint
615gtk_grid_view_compute_n_columns (GtkGridView *self,
616 guint for_size,
617 int min,
618 int nat)
619{
620 guint n_columns;
621
622 /* rounding down is exactly what we want here, so int division works */
623 if (gtk_list_base_get_scroll_policy (GTK_LIST_BASE (self),
624 gtk_list_base_get_opposite_orientation (GTK_LIST_BASE (self))) == GTK_SCROLL_MINIMUM)
625 n_columns = for_size / MAX (1, min);
626 else
627 n_columns = for_size / MAX (1, nat);
628
629 n_columns = CLAMP (n_columns, self->min_columns, self->max_columns);
630
631 return n_columns;
632}
633
634static void
635gtk_grid_view_measure_list (GtkWidget *widget,
636 int for_size,
637 int *minimum,
638 int *natural)
639{
640 GtkGridView *self = GTK_GRID_VIEW (widget);
641 GtkScrollablePolicy scroll_policy;
642 Cell *cell;
643 int height, row_height, child_min, child_nat, column_size, col_min, col_nat;
644 gboolean measured;
645 GArray *heights;
646 guint n_unknown, n_columns;
647 guint i;
648
649 scroll_policy = gtk_list_base_get_scroll_policy (GTK_LIST_BASE (self), orientation: gtk_list_base_get_orientation (GTK_LIST_BASE (self)));
650 heights = g_array_new (FALSE, FALSE, element_size: sizeof (int));
651 n_unknown = 0;
652 height = 0;
653
654 gtk_grid_view_measure_column_size (self, minimum: &col_min, natural: &col_nat);
655 for_size = MAX (for_size, col_min * (int) self->min_columns);
656 n_columns = gtk_grid_view_compute_n_columns (self, for_size, min: col_min, nat: col_nat);
657 column_size = for_size / n_columns;
658
659 i = 0;
660 row_height = 0;
661 measured = FALSE;
662 for (cell = gtk_list_item_manager_get_first (self: self->item_manager);
663 cell != NULL;
664 cell = gtk_rb_tree_node_get_next (node: cell))
665 {
666 if (cell->parent.widget)
667 {
668 gtk_widget_measure (widget: cell->parent.widget,
669 orientation: gtk_list_base_get_orientation (GTK_LIST_BASE (self)),
670 for_size: column_size,
671 minimum: &child_min, natural: &child_nat, NULL, NULL);
672 if (scroll_policy == GTK_SCROLL_MINIMUM)
673 row_height = MAX (row_height, child_min);
674 else
675 row_height = MAX (row_height, child_nat);
676 measured = TRUE;
677 }
678
679 i += cell->parent.n_items;
680
681 if (i >= n_columns)
682 {
683 if (measured)
684 {
685 g_array_append_val (heights, row_height);
686 i -= n_columns;
687 height += row_height;
688 measured = FALSE;
689 row_height = 0;
690 }
691 n_unknown += i / n_columns;
692 i %= n_columns;
693 }
694 }
695
696 if (i > 0)
697 {
698 if (measured)
699 {
700 g_array_append_val (heights, row_height);
701 height += row_height;
702 }
703 else
704 n_unknown++;
705 }
706
707 if (n_unknown)
708 height += n_unknown * gtk_grid_view_get_unknown_row_size (self, heights);
709
710 g_array_free (array: heights, TRUE);
711
712 *minimum = height;
713 *natural = height;
714}
715
716static void
717gtk_grid_view_measure (GtkWidget *widget,
718 GtkOrientation orientation,
719 int for_size,
720 int *minimum,
721 int *natural,
722 int *minimum_baseline,
723 int *natural_baseline)
724{
725 GtkGridView *self = GTK_GRID_VIEW (widget);
726
727 if (orientation == gtk_list_base_get_orientation (GTK_LIST_BASE (self)))
728 gtk_grid_view_measure_list (widget, for_size, minimum, natural);
729 else
730 gtk_grid_view_measure_across (widget, for_size, minimum, natural);
731}
732
733static void
734cell_set_size (Cell *cell,
735 guint size)
736{
737 if (cell->size == size)
738 return;
739
740 cell->size = size;
741 gtk_rb_tree_node_mark_dirty (node: cell);
742}
743
744static void
745gtk_grid_view_size_allocate (GtkWidget *widget,
746 int width,
747 int height,
748 int baseline)
749{
750 GtkGridView *self = GTK_GRID_VIEW (widget);
751 Cell *cell, *start;
752 GArray *heights;
753 int min_row_height, row_height, col_min, col_nat;
754 GtkOrientation orientation, opposite_orientation;
755 GtkScrollablePolicy scroll_policy;
756 gboolean known;
757 int x, y;
758 guint i;
759
760 orientation = gtk_list_base_get_orientation (GTK_LIST_BASE (self));
761 scroll_policy = gtk_list_base_get_scroll_policy (GTK_LIST_BASE (self), orientation);
762 opposite_orientation = OPPOSITE_ORIENTATION (orientation);
763 min_row_height = ceil (x: (double) height / GTK_GRID_VIEW_MAX_VISIBLE_ROWS);
764
765 /* step 0: exit early if list is empty */
766 if (gtk_list_item_manager_get_root (self: self->item_manager) == NULL)
767 return;
768
769 /* step 1: determine width of the list */
770 gtk_grid_view_measure_column_size (self, minimum: &col_min, natural: &col_nat);
771 self->n_columns = gtk_grid_view_compute_n_columns (self,
772 for_size: orientation == GTK_ORIENTATION_VERTICAL ? width : height,
773 min: col_min, nat: col_nat);
774 self->column_width = (orientation == GTK_ORIENTATION_VERTICAL ? width : height) / self->n_columns;
775 self->column_width = MAX (self->column_width, col_min);
776
777 /* step 2: determine height of known rows */
778 heights = g_array_new (FALSE, FALSE, element_size: sizeof (int));
779
780 i = 0;
781 row_height = 0;
782 start = NULL;
783 for (cell = gtk_list_item_manager_get_first (self: self->item_manager);
784 cell != NULL;
785 cell = gtk_rb_tree_node_get_next (node: cell))
786 {
787 if (i == 0)
788 start = cell;
789
790 if (cell->parent.widget)
791 {
792 int min, nat, size;
793 gtk_widget_measure (widget: cell->parent.widget,
794 orientation: gtk_list_base_get_orientation (GTK_LIST_BASE (self)),
795 for_size: self->column_width,
796 minimum: &min, natural: &nat, NULL, NULL);
797 if (scroll_policy == GTK_SCROLL_MINIMUM)
798 size = min;
799 else
800 size = nat;
801 size = MAX (size, min_row_height);
802 g_array_append_val (heights, size);
803 row_height = MAX (row_height, size);
804 }
805 cell_set_size (cell, size: 0);
806 i += cell->parent.n_items;
807
808 if (i >= self->n_columns)
809 {
810 i %= self->n_columns;
811
812 cell_set_size (cell: start, size: start->size + row_height);
813 start = cell;
814 row_height = 0;
815 }
816 }
817 if (i > 0)
818 cell_set_size (cell: start, size: start->size + row_height);
819
820 /* step 3: determine height of rows with only unknown items */
821 self->unknown_row_height = gtk_grid_view_get_unknown_row_size (self, heights);
822 g_array_free (array: heights, TRUE);
823
824 i = 0;
825 known = FALSE;
826 for (start = cell = gtk_list_item_manager_get_first (self: self->item_manager);
827 cell != NULL;
828 cell = gtk_rb_tree_node_get_next (node: cell))
829 {
830 if (i == 0)
831 start = cell;
832
833 if (cell->parent.widget)
834 known = TRUE;
835
836 i += cell->parent.n_items;
837 if (i >= self->n_columns)
838 {
839 if (!known)
840 cell_set_size (cell: start, size: start->size + self->unknown_row_height);
841
842 i -= self->n_columns;
843 known = FALSE;
844
845 if (i >= self->n_columns)
846 {
847 cell_set_size (cell, size: cell->size + self->unknown_row_height * (i / self->n_columns));
848 i %= self->n_columns;
849 }
850 start = cell;
851 }
852 }
853 if (i > 0 && !known)
854 cell_set_size (cell: start, size: start->size + self->unknown_row_height);
855
856 /* step 4: update the adjustments */
857 gtk_list_base_update_adjustments (GTK_LIST_BASE (self),
858 total_across: self->column_width * self->n_columns,
859 total_along: gtk_grid_view_compute_total_height (self),
860 page_across: gtk_widget_get_size (widget, orientation: opposite_orientation),
861 page_along: gtk_widget_get_size (widget, orientation),
862 across: &x, along: &y);
863
864 /* step 5: run the size_allocate loop */
865 x = -x;
866 y = -y;
867 i = 0;
868 row_height = 0;
869
870 for (cell = gtk_list_item_manager_get_first (self: self->item_manager);
871 cell != NULL;
872 cell = gtk_rb_tree_node_get_next (node: cell))
873 {
874 if (cell->parent.widget)
875 {
876 row_height += cell->size;
877
878 gtk_list_base_size_allocate_child (GTK_LIST_BASE (self),
879 child: cell->parent.widget,
880 x: x + ceil (x: self->column_width * i),
881 y,
882 width: ceil (x: self->column_width * (i + 1)) - ceil (x: self->column_width * i),
883 height: row_height);
884 i++;
885 if (i >= self->n_columns)
886 {
887 y += row_height;
888 i -= self->n_columns;
889 row_height = 0;
890 }
891 }
892 else
893 {
894 i += cell->parent.n_items;
895 /* skip remaining row if we didn't start one */
896 if (i > cell->parent.n_items && i >= self->n_columns)
897 {
898 i -= self->n_columns;
899 y += row_height;
900 row_height = 0;
901 }
902
903 row_height += cell->size;
904
905 /* skip rows that are completely contained by this cell */
906 if (i >= self->n_columns)
907 {
908 guint unknown_rows, unknown_height;
909
910 unknown_rows = i / self->n_columns;
911 unknown_height = unknown_rows * self->unknown_row_height;
912 row_height -= unknown_height;
913 y += unknown_height;
914 i %= self->n_columns;
915 g_assert (row_height >= 0);
916 }
917 }
918 }
919
920 gtk_list_base_allocate_rubberband (GTK_LIST_BASE (widget));
921}
922
923static void
924gtk_grid_view_dispose (GObject *object)
925{
926 GtkGridView *self = GTK_GRID_VIEW (object);
927
928 self->item_manager = NULL;
929
930 G_OBJECT_CLASS (gtk_grid_view_parent_class)->dispose (object);
931}
932
933static void
934gtk_grid_view_get_property (GObject *object,
935 guint property_id,
936 GValue *value,
937 GParamSpec *pspec)
938{
939 GtkGridView *self = GTK_GRID_VIEW (object);
940
941 switch (property_id)
942 {
943 case PROP_FACTORY:
944 g_value_set_object (value, v_object: gtk_list_item_manager_get_factory (self: self->item_manager));
945 break;
946
947 case PROP_MAX_COLUMNS:
948 g_value_set_uint (value, v_uint: self->max_columns);
949 break;
950
951 case PROP_MIN_COLUMNS:
952 g_value_set_uint (value, v_uint: self->min_columns);
953 break;
954
955 case PROP_MODEL:
956 g_value_set_object (value, v_object: gtk_list_base_get_model (GTK_LIST_BASE (self)));
957 break;
958
959 case PROP_SINGLE_CLICK_ACTIVATE:
960 g_value_set_boolean (value, v_boolean: gtk_list_item_manager_get_single_click_activate (self: self->item_manager));
961 break;
962
963 case PROP_ENABLE_RUBBERBAND:
964 g_value_set_boolean (value, v_boolean: gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self)));
965 break;
966
967 default:
968 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
969 break;
970 }
971}
972
973static void
974gtk_grid_view_set_property (GObject *object,
975 guint property_id,
976 const GValue *value,
977 GParamSpec *pspec)
978{
979 GtkGridView *self = GTK_GRID_VIEW (object);
980
981 switch (property_id)
982 {
983 case PROP_FACTORY:
984 gtk_grid_view_set_factory (self, factory: g_value_get_object (value));
985 break;
986
987 case PROP_MAX_COLUMNS:
988 gtk_grid_view_set_max_columns (self, max_columns: g_value_get_uint (value));
989 break;
990
991 case PROP_MIN_COLUMNS:
992 gtk_grid_view_set_min_columns (self, min_columns: g_value_get_uint (value));
993 break;
994
995 case PROP_MODEL:
996 gtk_grid_view_set_model (self, model: g_value_get_object (value));
997 break;
998
999 case PROP_SINGLE_CLICK_ACTIVATE:
1000 gtk_grid_view_set_single_click_activate (self, single_click_activate: g_value_get_boolean (value));
1001 break;
1002
1003 case PROP_ENABLE_RUBBERBAND:
1004 gtk_grid_view_set_enable_rubberband (self, enable_rubberband: g_value_get_boolean (value));
1005 break;
1006
1007 default:
1008 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1009 break;
1010 }
1011}
1012
1013static void
1014gtk_grid_view_activate_item (GtkWidget *widget,
1015 const char *action_name,
1016 GVariant *parameter)
1017{
1018 GtkGridView *self = GTK_GRID_VIEW (widget);
1019 guint pos;
1020
1021 if (!g_variant_check_format_string (value: parameter, format_string: "u", FALSE))
1022 return;
1023
1024 g_variant_get (value: parameter, format_string: "u", &pos);
1025 if (pos >= gtk_list_base_get_n_items (GTK_LIST_BASE (self)))
1026 return;
1027
1028 g_signal_emit (instance: widget, signal_id: signals[ACTIVATE], detail: 0, pos);
1029}
1030
1031static void
1032gtk_grid_view_class_init (GtkGridViewClass *klass)
1033{
1034 GtkListBaseClass *list_base_class = GTK_LIST_BASE_CLASS (klass);
1035 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1036 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1037
1038 list_base_class->list_item_name = "child";
1039 list_base_class->list_item_role = GTK_ACCESSIBLE_ROLE_GRID_CELL;
1040 list_base_class->list_item_size = sizeof (Cell);
1041 list_base_class->list_item_augment_size = sizeof (CellAugment);
1042 list_base_class->list_item_augment_func = cell_augment;
1043 list_base_class->get_allocation_along = gtk_grid_view_get_allocation_along;
1044 list_base_class->get_allocation_across = gtk_grid_view_get_allocation_across;
1045 list_base_class->get_items_in_rect = gtk_grid_view_get_items_in_rect;
1046 list_base_class->get_position_from_allocation = gtk_grid_view_get_position_from_allocation;
1047 list_base_class->move_focus_along = gtk_grid_view_move_focus_along;
1048 list_base_class->move_focus_across = gtk_grid_view_move_focus_across;
1049
1050 widget_class->measure = gtk_grid_view_measure;
1051 widget_class->size_allocate = gtk_grid_view_size_allocate;
1052
1053 gobject_class->dispose = gtk_grid_view_dispose;
1054 gobject_class->get_property = gtk_grid_view_get_property;
1055 gobject_class->set_property = gtk_grid_view_set_property;
1056
1057 /**
1058 * GtkGridView:factory: (attributes org.gtk.Property.get=gtk_grid_view_get_factory org.gtk.Property.set=gtk_grid_view_set_factory)
1059 *
1060 * Factory for populating list items.
1061 */
1062 properties[PROP_FACTORY] =
1063 g_param_spec_object (name: "factory",
1064 P_("Factory"),
1065 P_("Factory for populating list items"),
1066 GTK_TYPE_LIST_ITEM_FACTORY,
1067 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
1068
1069
1070 /**
1071 * GtkGridView:max-columns: (attributes org.gtk.Property.get=gtk_grid_view_get_max_columns org.gtk.Property.set=gtk_grid_view_set_max_columns)
1072 *
1073 * Maximum number of columns per row.
1074 *
1075 * If this number is smaller than [property@Gtk.GridView:min-columns],
1076 * that value is used instead.
1077 */
1078 properties[PROP_MAX_COLUMNS] =
1079 g_param_spec_uint (name: "max-columns",
1080 P_("Max columns"),
1081 P_("Maximum number of columns per row"),
1082 minimum: 1, G_MAXUINT, DEFAULT_MAX_COLUMNS,
1083 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
1084
1085 /**
1086 * GtkGridView:min-columns: (attributes org.gtk.Property.get=gtk_grid_view_get_min_columns org.gtk.Property.set=gtk_grid_view_set_min_columns)
1087 *
1088 * Minimum number of columns per row.
1089 */
1090 properties[PROP_MIN_COLUMNS] =
1091 g_param_spec_uint (name: "min-columns",
1092 P_("Min columns"),
1093 P_("Minimum number of columns per row"),
1094 minimum: 1, G_MAXUINT, default_value: 1,
1095 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
1096
1097 /**
1098 * GtkGridView:model: (attributes org.gtk.Property.get=gtk_grid_view_get_model org.gtk.Property.set=gtk_grid_view_set_model)
1099 *
1100 * Model for the items displayed.
1101 */
1102 properties[PROP_MODEL] =
1103 g_param_spec_object (name: "model",
1104 P_("Model"),
1105 P_("Model for the items displayed"),
1106 GTK_TYPE_SELECTION_MODEL,
1107 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
1108
1109 /**
1110 * GtkGridView:single-click-activate: (attributes org.gtk.Property.get=gtk_grid_view_get_single_click_activate org.gtk.Property.set=gtk_grid_view_set_single_click_activate)
1111 *
1112 * Activate rows on single click and select them on hover.
1113 */
1114 properties[PROP_SINGLE_CLICK_ACTIVATE] =
1115 g_param_spec_boolean (name: "single-click-activate",
1116 P_("Single click activate"),
1117 P_("Activate rows on single click"),
1118 FALSE,
1119 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1120
1121 /**
1122 * GtkGridView:enable-rubberband: (attributes org.gtk.Property.get=gtk_grid_view_get_enable_rubberband org.gtk.Property.set=gtk_grid_view_set_enable_rubberband)
1123 *
1124 * Allow rubberband selection.
1125 */
1126 properties[PROP_ENABLE_RUBBERBAND] =
1127 g_param_spec_boolean (name: "enable-rubberband",
1128 P_("Enable rubberband selection"),
1129 P_("Allow selecting items by dragging with the mouse"),
1130 FALSE,
1131 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1132
1133 g_object_class_install_properties (oclass: gobject_class, n_pspecs: N_PROPS, pspecs: properties);
1134
1135 /**
1136 * GtkGridView::activate:
1137 * @self: The `GtkGridView`
1138 * @position: position of item to activate
1139 *
1140 * Emitted when a cell has been activated by the user,
1141 * usually via activating the GtkGridView|list.activate-item action.
1142 *
1143 * This allows for a convenient way to handle activation in a gridview.
1144 * See [property@Gtk.ListItem:activatable] for details on how to use
1145 * this signal.
1146 */
1147 signals[ACTIVATE] =
1148 g_signal_new (I_("activate"),
1149 G_TYPE_FROM_CLASS (gobject_class),
1150 signal_flags: G_SIGNAL_RUN_LAST,
1151 class_offset: 0,
1152 NULL, NULL,
1153 c_marshaller: g_cclosure_marshal_VOID__UINT,
1154 G_TYPE_NONE, n_params: 1,
1155 G_TYPE_UINT);
1156 g_signal_set_va_marshaller (signal_id: signals[ACTIVATE],
1157 G_TYPE_FROM_CLASS (gobject_class),
1158 va_marshaller: g_cclosure_marshal_VOID__UINTv);
1159
1160 /**
1161 * GtkGridView|list.activate-item:
1162 * @position: position of item to activate
1163 *
1164 * Activates the item given in @position by emitting the
1165 * [signal@Gtk.GridView::activate] signal.
1166 */
1167 gtk_widget_class_install_action (widget_class,
1168 action_name: "list.activate-item",
1169 parameter_type: "u",
1170 activate: gtk_grid_view_activate_item);
1171
1172 gtk_widget_class_set_css_name (widget_class, I_("gridview"));
1173 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_GRID);
1174}
1175
1176static void
1177gtk_grid_view_init (GtkGridView *self)
1178{
1179 self->item_manager = gtk_list_base_get_manager (GTK_LIST_BASE (self));
1180
1181 self->min_columns = 1;
1182 self->max_columns = DEFAULT_MAX_COLUMNS;
1183 self->n_columns = 1;
1184
1185 gtk_list_base_set_anchor_max_widgets (GTK_LIST_BASE (self),
1186 n_center: self->max_columns * GTK_GRID_VIEW_MAX_VISIBLE_ROWS,
1187 n_above_below: self->max_columns);
1188
1189 gtk_widget_add_css_class (GTK_WIDGET (self), css_class: "view");
1190}
1191
1192/**
1193 * gtk_grid_view_new:
1194 * @model: (nullable) (transfer full): the model to use
1195 * @factory: (nullable) (transfer full): The factory to populate items with
1196 *
1197 * Creates a new `GtkGridView` that uses the given @factory for
1198 * mapping items to widgets.
1199 *
1200 * The function takes ownership of the
1201 * arguments, so you can write code like
1202 * ```c
1203 * grid_view = gtk_grid_view_new (create_model (),
1204 * gtk_builder_list_item_factory_new_from_resource ("/resource.ui"));
1205 * ```
1206 *
1207 * Returns: a new `GtkGridView` using the given @model and @factory
1208 */
1209GtkWidget *
1210gtk_grid_view_new (GtkSelectionModel *model,
1211 GtkListItemFactory *factory)
1212{
1213 GtkWidget *result;
1214
1215 g_return_val_if_fail (model == NULL || GTK_IS_SELECTION_MODEL (model), NULL);
1216 g_return_val_if_fail (factory == NULL || GTK_IS_LIST_ITEM_FACTORY (factory), NULL);
1217
1218 result = g_object_new (GTK_TYPE_GRID_VIEW,
1219 first_property_name: "model", model,
1220 "factory", factory,
1221 NULL);
1222
1223 /* consume the references */
1224 g_clear_object (&model);
1225 g_clear_object (&factory);
1226
1227 return result;
1228}
1229
1230/**
1231 * gtk_grid_view_get_model: (attributes org.gtk.Method.get_property=model)
1232 * @self: a `GtkGridView`
1233 *
1234 * Gets the model that's currently used to read the items displayed.
1235 *
1236 * Returns: (nullable) (transfer none): The model in use
1237 **/
1238GtkSelectionModel *
1239gtk_grid_view_get_model (GtkGridView *self)
1240{
1241 g_return_val_if_fail (GTK_IS_GRID_VIEW (self), NULL);
1242
1243 return gtk_list_base_get_model (GTK_LIST_BASE (self));
1244}
1245
1246/**
1247 * gtk_grid_view_set_model: (attributes org.gtk.Method.set_property=model)
1248 * @self: a `GtkGridView`
1249 * @model: (nullable) (transfer none): the model to use
1250 *
1251 * Sets the imodel to use.
1252 *
1253 * This must be a [iface@Gtk.SelectionModel].
1254 */
1255void
1256gtk_grid_view_set_model (GtkGridView *self,
1257 GtkSelectionModel *model)
1258{
1259 g_return_if_fail (GTK_IS_GRID_VIEW (self));
1260 g_return_if_fail (model == NULL || GTK_IS_SELECTION_MODEL (model));
1261
1262 if (!gtk_list_base_set_model (GTK_LIST_BASE (self), model))
1263 return;
1264
1265 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: self),
1266 first_property: GTK_ACCESSIBLE_PROPERTY_MULTI_SELECTABLE, GTK_IS_MULTI_SELECTION (ptr: model),
1267 -1);
1268
1269 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_MODEL]);
1270}
1271
1272/**
1273 * gtk_grid_view_get_factory: (attributes org.gtk.Method.get_property=factory)
1274 * @self: a `GtkGridView`
1275 *
1276 * Gets the factory that's currently used to populate list items.
1277 *
1278 * Returns: (nullable) (transfer none): The factory in use
1279 */
1280GtkListItemFactory *
1281gtk_grid_view_get_factory (GtkGridView *self)
1282{
1283 g_return_val_if_fail (GTK_IS_GRID_VIEW (self), NULL);
1284
1285 return gtk_list_item_manager_get_factory (self: self->item_manager);
1286}
1287
1288/**
1289 * gtk_grid_view_set_factory: (attributes org.gtk.Method.set_property=factory)
1290 * @self: a `GtkGridView`
1291 * @factory: (nullable) (transfer none): the factory to use
1292 *
1293 * Sets the `GtkListItemFactory` to use for populating list items.
1294 */
1295void
1296gtk_grid_view_set_factory (GtkGridView *self,
1297 GtkListItemFactory *factory)
1298{
1299 g_return_if_fail (GTK_IS_GRID_VIEW (self));
1300 g_return_if_fail (factory == NULL || GTK_IS_LIST_ITEM_FACTORY (factory));
1301
1302 if (factory == gtk_list_item_manager_get_factory (self: self->item_manager))
1303 return;
1304
1305 gtk_list_item_manager_set_factory (self: self->item_manager, factory);
1306
1307 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_FACTORY]);
1308}
1309
1310/**
1311 * gtk_grid_view_get_max_columns: (attributes org.gtk.Method.get_property=max-columns)
1312 * @self: a `GtkGridView`
1313 *
1314 * Gets the maximum number of columns that the grid will use.
1315 *
1316 * Returns: The maximum number of columns
1317 */
1318guint
1319gtk_grid_view_get_max_columns (GtkGridView *self)
1320{
1321 g_return_val_if_fail (GTK_IS_GRID_VIEW (self), DEFAULT_MAX_COLUMNS);
1322
1323 return self->max_columns;
1324}
1325
1326/**
1327 * gtk_grid_view_set_max_columns: (attributes org.gtk.Method.set_property=max-columns)
1328 * @self: a `GtkGridView`
1329 * @max_columns: The maximum number of columns
1330 *
1331 * Sets the maximum number of columns to use.
1332 *
1333 * This number must be at least 1.
1334 *
1335 * If @max_columns is smaller than the minimum set via
1336 * [method@Gtk.GridView.set_min_columns], that value is used instead.
1337 */
1338void
1339gtk_grid_view_set_max_columns (GtkGridView *self,
1340 guint max_columns)
1341{
1342 g_return_if_fail (GTK_IS_GRID_VIEW (self));
1343 g_return_if_fail (max_columns > 0);
1344
1345 if (self->max_columns == max_columns)
1346 return;
1347
1348 self->max_columns = max_columns;
1349
1350 gtk_list_base_set_anchor_max_widgets (GTK_LIST_BASE (self),
1351 n_center: self->max_columns * GTK_GRID_VIEW_MAX_VISIBLE_ROWS,
1352 n_above_below: self->max_columns);
1353
1354 gtk_widget_queue_resize (GTK_WIDGET (self));
1355
1356 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_MAX_COLUMNS]);
1357}
1358
1359/**
1360 * gtk_grid_view_get_min_columns: (attributes org.gtk.Method.get_property=min-columns)
1361 * @self: a `GtkGridView`
1362 *
1363 * Gets the minimum number of columns that the grid will use.
1364 *
1365 * Returns: The minimum number of columns
1366 */
1367guint
1368gtk_grid_view_get_min_columns (GtkGridView *self)
1369{
1370 g_return_val_if_fail (GTK_IS_GRID_VIEW (self), 1);
1371
1372 return self->min_columns;
1373}
1374
1375/**
1376 * gtk_grid_view_set_min_columns: (attributes org.gtk.Method.set_property=min-columns)
1377 * @self: a `GtkGridView`
1378 * @min_columns: The minimum number of columns
1379 *
1380 * Sets the minimum number of columns to use.
1381 *
1382 * This number must be at least 1.
1383 *
1384 * If @min_columns is smaller than the minimum set via
1385 * [method@Gtk.GridView.set_max_columns], that value is ignored.
1386 */
1387void
1388gtk_grid_view_set_min_columns (GtkGridView *self,
1389 guint min_columns)
1390{
1391 g_return_if_fail (GTK_IS_GRID_VIEW (self));
1392 g_return_if_fail (min_columns > 0);
1393
1394 if (self->min_columns == min_columns)
1395 return;
1396
1397 self->min_columns = min_columns;
1398
1399 gtk_widget_queue_resize (GTK_WIDGET (self));
1400
1401 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_MIN_COLUMNS]);
1402}
1403
1404/**
1405 * gtk_grid_view_set_single_click_activate: (attributes org.gtk.Method.set_property=single-click-activate)
1406 * @self: a `GtkGridView`
1407 * @single_click_activate: %TRUE to activate items on single click
1408 *
1409 * Sets whether items should be activated on single click and
1410 * selected on hover.
1411 */
1412void
1413gtk_grid_view_set_single_click_activate (GtkGridView *self,
1414 gboolean single_click_activate)
1415{
1416 g_return_if_fail (GTK_IS_GRID_VIEW (self));
1417
1418 if (single_click_activate == gtk_list_item_manager_get_single_click_activate (self: self->item_manager))
1419 return;
1420
1421 gtk_list_item_manager_set_single_click_activate (self: self->item_manager, single_click_activate);
1422
1423 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SINGLE_CLICK_ACTIVATE]);
1424}
1425
1426/**
1427 * gtk_grid_view_get_single_click_activate: (attributes org.gtk.Method.get_property=single-click-activate)
1428 * @self: a `GtkGridView`
1429 *
1430 * Returns whether items will be activated on single click and
1431 * selected on hover.
1432 *
1433 * Returns: %TRUE if items are activated on single click
1434 */
1435gboolean
1436gtk_grid_view_get_single_click_activate (GtkGridView *self)
1437{
1438 g_return_val_if_fail (GTK_IS_GRID_VIEW (self), FALSE);
1439
1440 return gtk_list_item_manager_get_single_click_activate (self: self->item_manager);
1441}
1442
1443/**
1444 * gtk_grid_view_set_enable_rubberband: (attributes org.gtk.Method.set_property=enable-rubberband)
1445 * @self: a `GtkGridView`
1446 * @enable_rubberband: %TRUE to enable rubberband selection
1447 *
1448 * Sets whether selections can be changed by dragging with the mouse.
1449 */
1450void
1451gtk_grid_view_set_enable_rubberband (GtkGridView *self,
1452 gboolean enable_rubberband)
1453{
1454 g_return_if_fail (GTK_IS_GRID_VIEW (self));
1455
1456 if (enable_rubberband == gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self)))
1457 return;
1458
1459 gtk_list_base_set_enable_rubberband (GTK_LIST_BASE (self), enable: enable_rubberband);
1460
1461 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_ENABLE_RUBBERBAND]);
1462}
1463
1464/**
1465 * gtk_grid_view_get_enable_rubberband: (attributes org.gtk.Method.get_property=enable-rubberband)
1466 * @self: a `GtkGridView`
1467 *
1468 * Returns whether rows can be selected by dragging with the mouse.
1469 *
1470 * Returns: %TRUE if rubberband selection is enabled
1471 */
1472gboolean
1473gtk_grid_view_get_enable_rubberband (GtkGridView *self)
1474{
1475 g_return_val_if_fail (GTK_IS_GRID_VIEW (self), FALSE);
1476
1477 return gtk_list_base_get_enable_rubberband (GTK_LIST_BASE (self));
1478}
1479

source code of gtk/gtk/gtkgridview.c