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 | |
86 | typedef struct _Cell Cell; |
87 | typedef struct _CellAugment CellAugment; |
88 | |
89 | struct _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 | |
102 | struct _GtkGridViewClass |
103 | { |
104 | GtkListBaseClass parent_class; |
105 | }; |
106 | |
107 | struct _Cell |
108 | { |
109 | GtkListItemManagerItem parent; |
110 | guint size; /* total, only counting cells in first column */ |
111 | }; |
112 | |
113 | struct _CellAugment |
114 | { |
115 | GtkListItemManagerItemAugment parent; |
116 | guint size; /* total, only counting first column */ |
117 | }; |
118 | |
119 | enum |
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 | |
132 | enum { |
133 | ACTIVATE, |
134 | LAST_SIGNAL |
135 | }; |
136 | |
137 | G_DEFINE_TYPE (GtkGridView, gtk_grid_view, GTK_TYPE_LIST_BASE) |
138 | |
139 | static GParamSpec *properties[N_PROPS] = { NULL, }; |
140 | static guint signals[LAST_SIGNAL] = { 0 }; |
141 | |
142 | static void G_GNUC_UNUSED |
143 | dump (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 | |
170 | static void |
171 | cell_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 | **/ |
219 | static Cell * |
220 | gtk_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 | |
315 | static gboolean |
316 | gtk_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 | |
396 | static gboolean |
397 | gtk_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 | |
416 | static int |
417 | gtk_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 | |
429 | static gboolean |
430 | gtk_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 | |
476 | static GtkBitset * |
477 | gtk_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 | |
509 | static guint |
510 | gtk_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 | |
533 | static guint |
534 | gtk_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 | |
549 | static int |
550 | compare_ints (gconstpointer first, |
551 | gconstpointer second) |
552 | { |
553 | return *(int *) first - *(int *) second; |
554 | } |
555 | |
556 | static int |
557 | gtk_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 | |
568 | static void |
569 | gtk_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 | |
600 | static void |
601 | gtk_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 | |
614 | static guint |
615 | gtk_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 | |
634 | static void |
635 | gtk_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 | |
716 | static void |
717 | gtk_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 | |
733 | static void |
734 | cell_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 | |
744 | static void |
745 | gtk_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 | |
923 | static void |
924 | gtk_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 | |
933 | static void |
934 | gtk_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 | |
973 | static void |
974 | gtk_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 | |
1013 | static void |
1014 | gtk_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 | |
1031 | static void |
1032 | gtk_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 | |
1176 | static void |
1177 | gtk_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 | */ |
1209 | GtkWidget * |
1210 | gtk_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 | **/ |
1238 | GtkSelectionModel * |
1239 | gtk_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 | */ |
1255 | void |
1256 | gtk_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 | */ |
1280 | GtkListItemFactory * |
1281 | gtk_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 | */ |
1295 | void |
1296 | gtk_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 | */ |
1318 | guint |
1319 | gtk_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 | */ |
1338 | void |
1339 | gtk_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 | */ |
1367 | guint |
1368 | gtk_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 | */ |
1387 | void |
1388 | gtk_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 | */ |
1412 | void |
1413 | gtk_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 | */ |
1435 | gboolean |
1436 | gtk_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 | */ |
1450 | void |
1451 | gtk_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 | */ |
1472 | gboolean |
1473 | gtk_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 | |