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 "gtkcolumnviewprivate.h"
23
24#include "gtkboxlayout.h"
25#include "gtkbuildable.h"
26#include "gtkcolumnlistitemfactoryprivate.h"
27#include "gtkcolumnviewcolumnprivate.h"
28#include "gtkcolumnviewlayoutprivate.h"
29#include "gtkcolumnviewsorterprivate.h"
30#include "gtkcssnodeprivate.h"
31#include "gtkdropcontrollermotion.h"
32#include "gtkintl.h"
33#include "gtklistviewprivate.h"
34#include "gtkmain.h"
35#include "gtkprivate.h"
36#include "gtkscrollable.h"
37#include "gtkwidgetprivate.h"
38#include "gtksizerequest.h"
39#include "gtkadjustment.h"
40#include "gtkgesturedrag.h"
41#include "gtkeventcontrollermotion.h"
42#include "gtkdragsourceprivate.h"
43#include "gtkeventcontrollerkey.h"
44#include "gtkgestureclick.h"
45
46/**
47 * GtkColumnView:
48 *
49 * `GtkColumnView` presents a large dynamic list of items using multiple columns
50 * with headers.
51 *
52 * `GtkColumnView` uses the factories of its columns to generate a cell widget for
53 * each column, for each visible item and displays them together as the row for
54 * this item.
55 *
56 * The [property@Gtk.ColumnView:show-row-separators] and
57 * [property@Gtk.ColumnView:show-column-separators] properties offer a simple way
58 * to display separators between the rows or columns.
59 *
60 * `GtkColumnView` allows the user to select items according to the selection
61 * characteristics of the model. For models that allow multiple selected items,
62 * it is possible to turn on *rubberband selection*, using
63 * [property@Gtk.ColumnView:enable-rubberband].
64 *
65 * The column view supports sorting that can be customized by the user by
66 * clicking on column headers. To set this up, the `GtkSorter` returned by
67 * [method@Gtk.ColumnView.get_sorter] must be attached to a sort model for the
68 * data that the view is showing, and the columns must have sorters attached to
69 * them by calling [method@Gtk.ColumnViewColumn.set_sorter]. The initial sort
70 * order can be set with [method@Gtk.ColumnView.sort_by_column].
71 *
72 * The column view also supports interactive resizing and reordering of
73 * columns, via Drag-and-Drop of the column headers. This can be enabled or
74 * disabled with the [property@Gtk.ColumnView:reorderable] and
75 * [property@Gtk.ColumnViewColumn:resizable] properties.
76 *
77 * To learn more about the list widget framework, see the
78 * [overview](section-list-widget.html).
79 *
80 * # CSS nodes
81 *
82 * ```
83 * columnview[.column-separators][.rich-list][.navigation-sidebar][.data-table]
84 * ├── header
85 * │ ├── <column header>
86 * ┊ ┊
87 * │ ╰── <column header>
88 * │
89 * ├── listview
90 * │
91 * ┊
92 * ╰── [rubberband]
93 * ```
94 *
95 * `GtkColumnView` uses a single CSS node named columnview. It may carry the
96 * .column-separators style class, when [property@Gtk.ColumnView:show-column-separators]
97 * property is set. Header widgets appear below a node with name header.
98 * The rows are contained in a `GtkListView` widget, so there is a listview
99 * node with the same structure as for a standalone `GtkListView` widget.
100 * If [property@Gtk.ColumnView:show-row-separators] is set, it will be passed
101 * on to the list view, causing its CSS node to carry the .separators style class.
102 * For rubberband selection, a node with name rubberband is used.
103 *
104 * The main columnview node may also carry style classes to select
105 * the style of [list presentation](section-list-widget.html#list-styles):
106 * .rich-list, .navigation-sidebar or .data-table.
107 *
108 * # Accessibility
109 *
110 * `GtkColumnView` uses the %GTK_ACCESSIBLE_ROLE_TREE_GRID role, header title
111 * widgets are using the %GTK_ACCESSIBLE_ROLE_COLUMN_HEADER role. The row widgets
112 * are using the %GTK_ACCESSIBLE_ROLE_ROW role, and individual cells are using
113 * the %GTK_ACCESSIBLE_ROLE_GRID_CELL role
114 */
115
116/* We create a subclass of GtkListView for the sole purpose of overriding
117 * some parameters for item creation.
118 */
119
120#define GTK_TYPE_COLUMN_LIST_VIEW (gtk_column_list_view_get_type ())
121G_DECLARE_FINAL_TYPE (GtkColumnListView, gtk_column_list_view, GTK, COLUMN_LIST_VIEW, GtkListView)
122
123struct _GtkColumnListView
124{
125 GtkListView parent_instance;
126};
127
128struct _GtkColumnListViewClass
129{
130 GtkListViewClass parent_class;
131};
132
133G_DEFINE_TYPE (GtkColumnListView, gtk_column_list_view, GTK_TYPE_LIST_VIEW)
134
135static void
136gtk_column_list_view_init (GtkColumnListView *view)
137{
138}
139
140static void
141gtk_column_list_view_class_init (GtkColumnListViewClass *klass)
142{
143 GtkListBaseClass *list_base_class = GTK_LIST_BASE_CLASS (klass);
144 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
145
146 list_base_class->list_item_name = "row";
147 list_base_class->list_item_role = GTK_ACCESSIBLE_ROLE_ROW;
148
149 gtk_widget_class_set_css_name (widget_class, I_("listview"));
150 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_LIST);
151}
152
153
154struct _GtkColumnView
155{
156 GtkWidget parent_instance;
157
158 GListStore *columns;
159
160 GtkWidget *header;
161
162 GtkListView *listview;
163 GtkColumnListItemFactory *factory;
164
165 GtkSorter *sorter;
166
167 GtkAdjustment *hadjustment;
168
169 guint reorderable : 1;
170 guint show_column_separators : 1;
171 guint in_column_resize : 1;
172 guint in_column_reorder : 1;
173
174 int drag_pos;
175 int drag_x;
176 int drag_offset;
177 int drag_column_x;
178
179 guint autoscroll_id;
180 double autoscroll_x;
181 double autoscroll_delta;
182
183 GtkGesture *drag_gesture;
184};
185
186struct _GtkColumnViewClass
187{
188 GtkWidgetClass parent_class;
189};
190
191enum
192{
193 PROP_0,
194 PROP_COLUMNS,
195 PROP_HADJUSTMENT,
196 PROP_HSCROLL_POLICY,
197 PROP_MODEL,
198 PROP_SHOW_ROW_SEPARATORS,
199 PROP_SHOW_COLUMN_SEPARATORS,
200 PROP_SORTER,
201 PROP_VADJUSTMENT,
202 PROP_VSCROLL_POLICY,
203 PROP_SINGLE_CLICK_ACTIVATE,
204 PROP_REORDERABLE,
205 PROP_ENABLE_RUBBERBAND,
206
207 N_PROPS
208};
209
210enum {
211 ACTIVATE,
212 LAST_SIGNAL
213};
214
215static GtkBuildableIface *parent_buildable_iface;
216
217static void
218gtk_column_view_buildable_add_child (GtkBuildable *buildable,
219 GtkBuilder *builder,
220 GObject *child,
221 const char *type)
222{
223 if (GTK_IS_COLUMN_VIEW_COLUMN (child))
224 {
225 if (type != NULL)
226 {
227 GTK_BUILDER_WARN_INVALID_CHILD_TYPE (buildable, type);
228 }
229 else
230 {
231 gtk_column_view_append_column (GTK_COLUMN_VIEW (buildable),
232 GTK_COLUMN_VIEW_COLUMN (child));
233 }
234 }
235 else
236 {
237 parent_buildable_iface->add_child (buildable, builder, child, type);
238 }
239}
240
241static void
242gtk_column_view_buildable_interface_init (GtkBuildableIface *iface)
243{
244 parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface);
245
246 iface->add_child = gtk_column_view_buildable_add_child;
247}
248
249static gboolean
250gtk_column_view_scrollable_get_border (GtkScrollable *scrollable,
251 GtkBorder *border)
252{
253 GtkColumnView *self = GTK_COLUMN_VIEW (scrollable);
254
255 border->top = gtk_widget_get_height (widget: self->header);
256
257 return TRUE;
258}
259
260static void
261gtk_column_view_scrollable_interface_init (GtkScrollableInterface *iface)
262{
263 iface->get_border = gtk_column_view_scrollable_get_border;
264}
265
266G_DEFINE_TYPE_WITH_CODE (GtkColumnView, gtk_column_view, GTK_TYPE_WIDGET,
267 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_column_view_buildable_interface_init)
268 G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, gtk_column_view_scrollable_interface_init))
269
270static GParamSpec *properties[N_PROPS] = { NULL, };
271static guint signals[LAST_SIGNAL] = { 0 };
272
273static void
274gtk_column_view_measure (GtkWidget *widget,
275 GtkOrientation orientation,
276 int for_size,
277 int *minimum,
278 int *natural,
279 int *minimum_baseline,
280 int *natural_baseline)
281{
282 GtkColumnView *self = GTK_COLUMN_VIEW (widget);
283
284 if (orientation == GTK_ORIENTATION_HORIZONTAL)
285 {
286 gtk_column_view_measure_across (self, minimum, natural);
287 }
288 else
289 {
290 int header_min, header_nat, list_min, list_nat;
291
292 gtk_widget_measure (GTK_WIDGET (self->header),
293 orientation, for_size,
294 minimum: &header_min, natural: &header_nat,
295 NULL, NULL);
296 gtk_widget_measure (GTK_WIDGET (self->listview),
297 orientation, for_size,
298 minimum: &list_min, natural: &list_nat,
299 NULL, NULL);
300 *minimum = header_min + list_min;
301 *natural = header_nat + list_nat;
302 }
303}
304
305void
306gtk_column_view_distribute_width (GtkColumnView *self,
307 int width,
308 GtkRequestedSize *sizes)
309{
310 GtkScrollablePolicy scroll_policy;
311 int col_min, col_nat, extra, col_size;
312 int n, n_expand, expand_size, n_extra;
313 guint i;
314
315 n = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: self->columns));
316 n_expand = 0;
317
318 for (i = 0; i < n; i++)
319 {
320 GtkColumnViewColumn *column;
321
322 column = g_list_model_get_item (list: G_LIST_MODEL (ptr: self->columns), position: i);
323 if (gtk_column_view_column_get_visible (self: column))
324 {
325 gtk_column_view_column_measure (self: column, minimum: &sizes[i].minimum_size, natural: &sizes[i].natural_size);
326 if (gtk_column_view_column_get_expand (self: column))
327 n_expand++;
328 }
329 else
330 sizes[i].minimum_size = sizes[i].natural_size = 0;
331 g_object_unref (object: column);
332 }
333
334 gtk_column_view_measure_across (self, minimum: &col_min, natural: &col_nat);
335
336 scroll_policy = gtk_scrollable_get_hscroll_policy (GTK_SCROLLABLE (self->listview));
337 if (scroll_policy == GTK_SCROLL_MINIMUM)
338 extra = MAX (width - col_min, 0);
339 else
340 extra = MAX (width - col_min, col_nat - col_min);
341
342 extra = gtk_distribute_natural_allocation (extra_space: extra, n_requested_sizes: n, sizes);
343 if (n_expand > 0)
344 {
345 expand_size = extra / n_expand;
346 n_extra = extra % n_expand;
347 }
348 else
349 expand_size = n_extra = 0;
350
351 for (i = 0; i < n; i++)
352 {
353 GtkColumnViewColumn *column;
354
355 column = g_list_model_get_item (list: G_LIST_MODEL (ptr: self->columns), position: i);
356 if (gtk_column_view_column_get_visible (self: column))
357 {
358 col_size = sizes[i].minimum_size;
359 if (gtk_column_view_column_get_expand (self: column))
360 {
361 col_size += expand_size;
362 if (n_extra > 0)
363 {
364 col_size++;
365 n_extra--;
366 }
367 }
368 sizes[i].minimum_size = col_size;
369 }
370
371 g_object_unref (object: column);
372 }
373}
374
375static int
376gtk_column_view_allocate_columns (GtkColumnView *self,
377 int width)
378{
379 guint i, n;
380 int x;
381 GtkRequestedSize *sizes;
382
383 n = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: self->columns));
384
385 sizes = g_newa (GtkRequestedSize, n);
386
387 gtk_column_view_distribute_width (self, width, sizes);
388
389 x = 0;
390 for (i = 0; i < n; i++)
391 {
392 GtkColumnViewColumn *column;
393 int col_size;
394
395 column = g_list_model_get_item (list: G_LIST_MODEL (ptr: self->columns), position: i);
396 if (gtk_column_view_column_get_visible (self: column))
397 {
398 col_size = sizes[i].minimum_size;
399
400 gtk_column_view_column_allocate (self: column, offset: x, size: col_size);
401 if (self->in_column_reorder && i == self->drag_pos)
402 gtk_column_view_column_set_header_position (self: column, offset: self->drag_x);
403
404 x += col_size;
405 }
406
407 g_object_unref (object: column);
408 }
409
410 return x;
411}
412
413static void
414gtk_column_view_allocate (GtkWidget *widget,
415 int width,
416 int height,
417 int baseline)
418{
419 GtkColumnView *self = GTK_COLUMN_VIEW (widget);
420 int full_width, header_height, min, nat, x;
421
422 x = gtk_adjustment_get_value (adjustment: self->hadjustment);
423 full_width = gtk_column_view_allocate_columns (self, width);
424
425 gtk_widget_measure (widget: self->header, orientation: GTK_ORIENTATION_VERTICAL, for_size: full_width, minimum: &min, natural: &nat, NULL, NULL);
426 if (gtk_scrollable_get_vscroll_policy (GTK_SCROLLABLE (self->listview)) == GTK_SCROLL_MINIMUM)
427 header_height = min;
428 else
429 header_height = nat;
430 gtk_widget_allocate (widget: self->header, width: full_width, height: header_height, baseline: -1,
431 transform: gsk_transform_translate (NULL, point: &GRAPHENE_POINT_INIT (-x, 0)));
432
433 gtk_widget_allocate (GTK_WIDGET (self->listview),
434 width: full_width, height: height - header_height, baseline: -1,
435 transform: gsk_transform_translate (NULL, point: &GRAPHENE_POINT_INIT (-x, header_height)));
436
437 gtk_adjustment_configure (adjustment: self->hadjustment, value: x, lower: 0, upper: full_width, step_increment: width * 0.1, page_increment: width * 0.9, page_size: width);
438}
439
440static void
441gtk_column_view_activate_cb (GtkListView *listview,
442 guint pos,
443 GtkColumnView *self)
444{
445 g_signal_emit (instance: self, signal_id: signals[ACTIVATE], detail: 0, pos);
446}
447
448static void
449adjustment_value_changed_cb (GtkAdjustment *adjustment,
450 GtkColumnView *self)
451{
452 gtk_widget_queue_allocate (GTK_WIDGET (self));
453}
454
455static void
456clear_adjustment (GtkColumnView *self)
457{
458 if (self->hadjustment == NULL)
459 return;
460
461 g_signal_handlers_disconnect_by_func (self->hadjustment, adjustment_value_changed_cb, self);
462 g_clear_object (&self->hadjustment);
463}
464
465static void
466gtk_column_view_dispose (GObject *object)
467{
468 GtkColumnView *self = GTK_COLUMN_VIEW (object);
469
470 while (g_list_model_get_n_items (list: G_LIST_MODEL (ptr: self->columns)) > 0)
471 {
472 GtkColumnViewColumn *column = g_list_model_get_item (list: G_LIST_MODEL (ptr: self->columns), position: 0);
473 gtk_column_view_remove_column (self, column);
474 g_object_unref (object: column);
475 }
476
477 g_clear_pointer (&self->header, gtk_widget_unparent);
478
479 g_clear_pointer ((GtkWidget **) &self->listview, gtk_widget_unparent);
480 g_clear_object (&self->factory);
481
482 g_clear_object (&self->sorter);
483 clear_adjustment (self);
484
485 G_OBJECT_CLASS (gtk_column_view_parent_class)->dispose (object);
486}
487
488static void
489gtk_column_view_finalize (GObject *object)
490{
491 GtkColumnView *self = GTK_COLUMN_VIEW (object);
492
493 g_object_unref (object: self->columns);
494
495 G_OBJECT_CLASS (gtk_column_view_parent_class)->finalize (object);
496}
497
498static void
499gtk_column_view_get_property (GObject *object,
500 guint property_id,
501 GValue *value,
502 GParamSpec *pspec)
503{
504 GtkColumnView *self = GTK_COLUMN_VIEW (object);
505
506 switch (property_id)
507 {
508 case PROP_COLUMNS:
509 g_value_set_object (value, v_object: self->columns);
510 break;
511
512 case PROP_HADJUSTMENT:
513 g_value_set_object (value, v_object: self->hadjustment);
514 break;
515
516 case PROP_HSCROLL_POLICY:
517 g_value_set_enum (value, v_enum: gtk_scrollable_get_hscroll_policy (GTK_SCROLLABLE (self->listview)));
518 break;
519
520 case PROP_MODEL:
521 g_value_set_object (value, v_object: gtk_list_view_get_model (self: self->listview));
522 break;
523
524 case PROP_SHOW_ROW_SEPARATORS:
525 g_value_set_boolean (value, v_boolean: gtk_list_view_get_show_separators (self: self->listview));
526 break;
527
528 case PROP_SHOW_COLUMN_SEPARATORS:
529 g_value_set_boolean (value, v_boolean: self->show_column_separators);
530 break;
531
532 case PROP_VADJUSTMENT:
533 g_value_set_object (value, v_object: gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self->listview)));
534 break;
535
536 case PROP_VSCROLL_POLICY:
537 g_value_set_enum (value, v_enum: gtk_scrollable_get_vscroll_policy (GTK_SCROLLABLE (self->listview)));
538 break;
539
540 case PROP_SORTER:
541 g_value_set_object (value, v_object: self->sorter);
542 break;
543
544 case PROP_SINGLE_CLICK_ACTIVATE:
545 g_value_set_boolean (value, v_boolean: gtk_column_view_get_single_click_activate (self));
546 break;
547
548 case PROP_REORDERABLE:
549 g_value_set_boolean (value, v_boolean: gtk_column_view_get_reorderable (self));
550 break;
551
552 case PROP_ENABLE_RUBBERBAND:
553 g_value_set_boolean (value, v_boolean: gtk_column_view_get_enable_rubberband (self));
554 break;
555
556 default:
557 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
558 break;
559 }
560}
561
562static void
563gtk_column_view_set_property (GObject *object,
564 guint property_id,
565 const GValue *value,
566 GParamSpec *pspec)
567{
568 GtkColumnView *self = GTK_COLUMN_VIEW (object);
569 GtkAdjustment *adjustment;
570
571 switch (property_id)
572 {
573 case PROP_HADJUSTMENT:
574 adjustment = g_value_get_object (value);
575 if (adjustment == NULL)
576 adjustment = gtk_adjustment_new (value: 0.0, lower: 0.0, upper: 0.0, step_increment: 0.0, page_increment: 0.0, page_size: 0.0);
577 g_object_ref_sink (adjustment);
578
579 if (self->hadjustment != adjustment)
580 {
581 clear_adjustment (self);
582
583 self->hadjustment = adjustment;
584
585 g_signal_connect (adjustment, "value-changed", G_CALLBACK (adjustment_value_changed_cb), self);
586
587 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_HADJUSTMENT]);
588 }
589 break;
590
591 case PROP_HSCROLL_POLICY:
592 if (gtk_scrollable_get_hscroll_policy (GTK_SCROLLABLE (self->listview)) != g_value_get_enum (value))
593 {
594 gtk_scrollable_set_hscroll_policy (GTK_SCROLLABLE (self->listview), policy: g_value_get_enum (value));
595 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_HSCROLL_POLICY]);
596 }
597 break;
598
599 case PROP_MODEL:
600 gtk_column_view_set_model (self, model: g_value_get_object (value));
601 break;
602
603 case PROP_SHOW_ROW_SEPARATORS:
604 gtk_column_view_set_show_row_separators (self, show_row_separators: g_value_get_boolean (value));
605 break;
606
607 case PROP_SHOW_COLUMN_SEPARATORS:
608 gtk_column_view_set_show_column_separators (self, show_column_separators: g_value_get_boolean (value));
609 break;
610
611 case PROP_VADJUSTMENT:
612 if (gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self->listview)) != g_value_get_object (value))
613 {
614 gtk_scrollable_set_vadjustment (GTK_SCROLLABLE (self->listview), vadjustment: g_value_get_object (value));
615 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_VADJUSTMENT]);
616 }
617 break;
618
619 case PROP_VSCROLL_POLICY:
620 if (gtk_scrollable_get_vscroll_policy (GTK_SCROLLABLE (self->listview)) != g_value_get_enum (value))
621 {
622 gtk_scrollable_set_vscroll_policy (GTK_SCROLLABLE (self->listview), policy: g_value_get_enum (value));
623 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_VSCROLL_POLICY]);
624 }
625 break;
626
627 case PROP_SINGLE_CLICK_ACTIVATE:
628 gtk_column_view_set_single_click_activate (self, single_click_activate: g_value_get_boolean (value));
629 break;
630
631 case PROP_REORDERABLE:
632 gtk_column_view_set_reorderable (self, reorderable: g_value_get_boolean (value));
633 break;
634
635 case PROP_ENABLE_RUBBERBAND:
636 gtk_column_view_set_enable_rubberband (self, enable_rubberband: g_value_get_boolean (value));
637 break;
638
639 default:
640 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
641 break;
642 }
643}
644
645static void
646gtk_column_view_class_init (GtkColumnViewClass *klass)
647{
648 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
649 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
650 gpointer iface;
651
652 widget_class->measure = gtk_column_view_measure;
653 widget_class->size_allocate = gtk_column_view_allocate;
654
655 gobject_class->dispose = gtk_column_view_dispose;
656 gobject_class->finalize = gtk_column_view_finalize;
657 gobject_class->get_property = gtk_column_view_get_property;
658 gobject_class->set_property = gtk_column_view_set_property;
659
660 /* GtkScrollable implementation */
661 iface = g_type_default_interface_peek (GTK_TYPE_SCROLLABLE);
662 properties[PROP_HADJUSTMENT] =
663 g_param_spec_override (name: "hadjustment",
664 overridden: g_object_interface_find_property (g_iface: iface, property_name: "hadjustment"));
665 properties[PROP_HSCROLL_POLICY] =
666 g_param_spec_override (name: "hscroll-policy",
667 overridden: g_object_interface_find_property (g_iface: iface, property_name: "hscroll-policy"));
668 properties[PROP_VADJUSTMENT] =
669 g_param_spec_override (name: "vadjustment",
670 overridden: g_object_interface_find_property (g_iface: iface, property_name: "vadjustment"));
671 properties[PROP_VSCROLL_POLICY] =
672 g_param_spec_override (name: "vscroll-policy",
673 overridden: g_object_interface_find_property (g_iface: iface, property_name: "vscroll-policy"));
674
675 /**
676 * GtkColumnView:columns: (attributes org.gtk.Property.get=gtk_column_view_get_columns)
677 *
678 * The list of columns.
679 */
680 properties[PROP_COLUMNS] =
681 g_param_spec_object (name: "columns",
682 P_("Columns"),
683 P_("List of columns"),
684 G_TYPE_LIST_MODEL,
685 flags: G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
686
687 /**
688 * GtkColumnView:model: (attributes org.gtk.Property.get=gtk_column_view_get_model org.gtk.Property.set=gtk_column_view_set_model)
689 *
690 * Model for the items displayed.
691 */
692 properties[PROP_MODEL] =
693 g_param_spec_object (name: "model",
694 P_("Model"),
695 P_("Model for the items displayed"),
696 GTK_TYPE_SELECTION_MODEL,
697 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
698
699 /**
700 * GtkColumnView:show-row-separators: (attributes org.gtk.Property.get=gtk_column_view_get_show_row_separators org.gtk.Property.set=gtk_column_view_set_show_row_separators)
701 *
702 * Show separators between rows.
703 */
704 properties[PROP_SHOW_ROW_SEPARATORS] =
705 g_param_spec_boolean (name: "show-row-separators",
706 P_("Show row separators"),
707 P_("Show separators between rows"),
708 FALSE,
709 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
710
711 /**
712 * GtkColumnView:show-column-separators: (attributes org.gtk.Property.get=gtk_column_view_get_show_column_separators org.gtk.Property.set=gtk_column_view_set_show_column_separators)
713 *
714 * Show separators between columns.
715 */
716 properties[PROP_SHOW_COLUMN_SEPARATORS] =
717 g_param_spec_boolean (name: "show-column-separators",
718 P_("Show column separators"),
719 P_("Show separators between columns"),
720 FALSE,
721 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
722
723 /**
724 * GtkColumnView:sorter: (attributes org.gtk.Property.get=gtk_column_view_get_sorter)
725 *
726 * Sorter with the sorting choices of the user.
727 */
728 properties[PROP_SORTER] =
729 g_param_spec_object (name: "sorter",
730 P_("Sorter"),
731 P_("Sorter with sorting choices of the user"),
732 GTK_TYPE_SORTER,
733 flags: G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
734
735 /**
736 * GtkColumnView:single-click-activate: (attributes org.gtk.Property.get=gtk_column_view_get_single_click_activate org.gtk.Property.set=gtk_column_view_set_single_click_activate)
737 *
738 * Activate rows on single click and select them on hover.
739 */
740 properties[PROP_SINGLE_CLICK_ACTIVATE] =
741 g_param_spec_boolean (name: "single-click-activate",
742 P_("Single click activate"),
743 P_("Activate rows on single click"),
744 FALSE,
745 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
746
747 /**
748 * GtkColumnView:reorderable: (attributes org.gtk.Property.get=gtk_column_view_get_reorderable org.gtk.Property.set=gtk_column_view_set_reorderable)
749 *
750 * Whether columns are reorderable.
751 */
752 properties[PROP_REORDERABLE] =
753 g_param_spec_boolean (name: "reorderable",
754 P_("Reorderable"),
755 P_("Whether columns are reorderable"),
756 TRUE,
757 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
758
759 /**
760 * GtkColumnView:enable-rubberband: (attributes org.gtk.Property.get=gtk_column_view_get_enable_rubberband org.gtk.Property.set=gtk_column_view_set_enable_rubberband)
761 *
762 * Allow rubberband selection.
763 */
764 properties[PROP_ENABLE_RUBBERBAND] =
765 g_param_spec_boolean (name: "enable-rubberband",
766 P_("Enable rubberband selection"),
767 P_("Allow selecting items by dragging with the mouse"),
768 FALSE,
769 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
770
771 g_object_class_install_properties (oclass: gobject_class, n_pspecs: N_PROPS, pspecs: properties);
772
773 /**
774 * GtkColumnView::activate:
775 * @self: The `GtkColumnView`
776 * @position: position of item to activate
777 *
778 * Emitted when a row has been activated by the user, usually via activating
779 * the GtkListBase|list.activate-item action.
780 *
781 * This allows for a convenient way to handle activation in a columnview.
782 * See [method@Gtk.ListItem.set_activatable] for details on how to use this
783 * signal.
784 */
785 signals[ACTIVATE] =
786 g_signal_new (I_("activate"),
787 G_TYPE_FROM_CLASS (gobject_class),
788 signal_flags: G_SIGNAL_RUN_LAST,
789 class_offset: 0,
790 NULL, NULL,
791 c_marshaller: g_cclosure_marshal_VOID__UINT,
792 G_TYPE_NONE, n_params: 1,
793 G_TYPE_UINT);
794 g_signal_set_va_marshaller (signal_id: signals[ACTIVATE],
795 G_TYPE_FROM_CLASS (gobject_class),
796 va_marshaller: g_cclosure_marshal_VOID__UINTv);
797
798 gtk_widget_class_set_css_name (widget_class, I_("columnview"));
799 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_TREE_GRID);
800}
801
802static void update_column_resize (GtkColumnView *self,
803 double x);
804static void update_column_reorder (GtkColumnView *self,
805 double x);
806
807static gboolean
808autoscroll_cb (GtkWidget *widget,
809 GdkFrameClock *frame_clock,
810 gpointer data)
811{
812 GtkColumnView *self = data;
813
814 gtk_adjustment_set_value (adjustment: self->hadjustment,
815 value: gtk_adjustment_get_value (adjustment: self->hadjustment) + self->autoscroll_delta);
816
817 self->autoscroll_x += self->autoscroll_delta;
818
819 if (self->in_column_resize)
820 update_column_resize (self, x: self->autoscroll_x);
821 else if (self->in_column_reorder)
822 update_column_reorder (self, x: self->autoscroll_x);
823
824 return G_SOURCE_CONTINUE;
825}
826
827static void
828add_autoscroll (GtkColumnView *self,
829 double x,
830 double delta)
831{
832 self->autoscroll_x = x;
833 self->autoscroll_delta = delta;
834
835 if (self->autoscroll_id == 0)
836 self->autoscroll_id = gtk_widget_add_tick_callback (GTK_WIDGET (self), callback: autoscroll_cb, user_data: self, NULL);
837}
838
839static void
840remove_autoscroll (GtkColumnView *self)
841{
842 if (self->autoscroll_id != 0)
843 {
844 gtk_widget_remove_tick_callback (GTK_WIDGET (self), id: self->autoscroll_id);
845 self->autoscroll_id = 0;
846 }
847}
848
849#define SCROLL_EDGE_SIZE 30
850
851static void
852update_autoscroll (GtkColumnView *self,
853 double x)
854{
855 double width;
856 double delta;
857 double vx, vy;
858
859 /* x is in header coordinates */
860 gtk_widget_translate_coordinates (src_widget: self->header, GTK_WIDGET (self), src_x: x, src_y: 0, dest_x: &vx, dest_y: &vy);
861 width = gtk_widget_get_width (GTK_WIDGET (self));
862
863 if (vx < SCROLL_EDGE_SIZE)
864 delta = - (SCROLL_EDGE_SIZE - vx)/3.0;
865 else if (width - vx < SCROLL_EDGE_SIZE)
866 delta = (SCROLL_EDGE_SIZE - (width - vx))/3.0;
867 else
868 delta = 0;
869
870 if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
871 delta = - delta;
872
873 if (delta != 0)
874 add_autoscroll (self, x, delta);
875 else
876 remove_autoscroll (self);
877}
878
879
880#define DRAG_WIDTH 8
881
882static gboolean
883gtk_column_view_in_resize_rect (GtkColumnView *self,
884 GtkColumnViewColumn *column,
885 double x,
886 double y)
887{
888 GtkWidget *header;
889 graphene_rect_t rect;
890 int width;
891
892 header = gtk_column_view_column_get_header (self: column);
893
894 if (!gtk_widget_compute_bounds (widget: header, target: self->header, out_bounds: &rect))
895 return FALSE;
896
897 gtk_column_view_column_get_allocation (self: column, NULL, size: &width);
898 rect.size.width = width;
899
900 rect.origin.x += rect.size.width - DRAG_WIDTH / 2;
901 rect.size.width = DRAG_WIDTH;
902
903 return graphene_rect_contains_point (r: &rect, p: &(graphene_point_t) { x, y});
904}
905
906static gboolean
907gtk_column_view_in_header (GtkColumnView *self,
908 GtkColumnViewColumn *column,
909 double x,
910 double y)
911{
912 GtkWidget *header;
913 graphene_rect_t rect;
914
915header = gtk_column_view_column_get_header (self: column);
916
917 if (!gtk_widget_compute_bounds (widget: header, target: self->header, out_bounds: &rect))
918 return FALSE;
919
920 return graphene_rect_contains_point (r: &rect, p: &(graphene_point_t) { x, y});
921}
922
923static void
924set_resize_cursor (GtkColumnView *self,
925 gboolean set)
926{
927 int i, n;
928
929 n = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: self->columns));
930 for (i = 0; i < n; i++)
931 {
932 GtkColumnViewColumn *column = g_list_model_get_item (list: G_LIST_MODEL (ptr: self->columns), position: i);
933 GtkWidget *header = gtk_column_view_column_get_header (self: column);
934 if (set)
935 gtk_widget_set_cursor_from_name (widget: header, name: "col-resize");
936 else
937 gtk_widget_set_cursor (widget: header, NULL);
938 g_object_unref (object: column);
939 }
940}
941
942static void
943header_drag_begin (GtkGestureDrag *gesture,
944 double start_x,
945 double start_y,
946 GtkColumnView *self)
947{
948 int i, n;
949
950 self->drag_pos = -1;
951
952 n = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: self->columns));
953 for (i = n - 1; !self->in_column_resize && i >= 0; i--)
954 {
955 GtkColumnViewColumn *column = g_list_model_get_item (list: G_LIST_MODEL (ptr: self->columns), position: i);
956
957 if (!gtk_column_view_column_get_visible (self: column))
958 {
959 g_object_unref (object: column);
960 continue;
961 }
962
963 if (i + 1 < n &&
964 gtk_column_view_column_get_resizable (self: column) &&
965 gtk_column_view_in_resize_rect (self, column, x: start_x, y: start_y))
966 {
967 int size;
968
969 gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED);
970 if (!gtk_widget_has_focus (GTK_WIDGET (self)))
971 gtk_widget_grab_focus (GTK_WIDGET (self));
972
973 gtk_column_view_column_get_allocation (self: column, NULL, size: &size);
974 gtk_column_view_column_set_fixed_width (self: column, fixed_width: size);
975
976 self->drag_pos = i;
977 self->drag_x = start_x - size;
978 self->in_column_resize = TRUE;
979
980 set_resize_cursor (self, TRUE);
981
982 g_object_unref (object: column);
983
984 break;
985 }
986
987 g_object_unref (object: column);
988 }
989
990 for (i = 0; !self->in_column_resize && i < n; i++)
991 {
992 GtkColumnViewColumn *column = g_list_model_get_item (list: G_LIST_MODEL (ptr: self->columns), position: i);
993
994 if (!gtk_column_view_column_get_visible (self: column))
995 {
996 g_object_unref (object: column);
997 continue;
998 }
999
1000 if (gtk_column_view_get_reorderable (self) &&
1001 gtk_column_view_in_header (self, column, x: start_x, y: start_y))
1002 {
1003 int pos;
1004
1005 gtk_column_view_column_get_allocation (self: column, offset: &pos, NULL);
1006
1007 self->drag_pos = i;
1008 self->drag_offset = start_x - pos;
1009
1010 g_object_unref (object: column);
1011 break;
1012 }
1013
1014 g_object_unref (object: column);
1015 }
1016}
1017
1018static void
1019header_drag_end (GtkGestureDrag *gesture,
1020 double offset_x,
1021 double offset_y,
1022 GtkColumnView *self)
1023{
1024 double start_x, x;
1025
1026 gtk_gesture_drag_get_start_point (gesture, x: &start_x, NULL);
1027 x = start_x + offset_x;
1028
1029 remove_autoscroll (self);
1030
1031 if (self->in_column_resize)
1032 {
1033 set_resize_cursor (self, FALSE);
1034 self->in_column_resize = FALSE;
1035 }
1036 else if (self->in_column_reorder)
1037 {
1038 GdkEventSequence *sequence;
1039 GtkColumnViewColumn *column;
1040 GtkWidget *header;
1041 int i;
1042
1043 self->in_column_reorder = FALSE;
1044
1045 if (self->drag_pos == -1)
1046 return;
1047
1048 column = g_list_model_get_item (list: G_LIST_MODEL (ptr: self->columns), position: self->drag_pos);
1049 header = gtk_column_view_column_get_header (self: column);
1050 gtk_widget_remove_css_class (widget: header, css_class: "dnd");
1051
1052 sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
1053 if (!gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence))
1054 {
1055 g_object_unref (object: column);
1056 return;
1057 }
1058
1059 for (i = 0; i < g_list_model_get_n_items (list: G_LIST_MODEL (ptr: self->columns)); i++)
1060 {
1061 GtkColumnViewColumn *col = g_list_model_get_item (list: G_LIST_MODEL (ptr: self->columns), position: i);
1062
1063 if (gtk_column_view_column_get_visible (self: col))
1064 {
1065 int pos, size;
1066
1067 gtk_column_view_column_get_allocation (self: col, offset: &pos, size: &size);
1068 if (pos <= x && x <= pos + size)
1069 {
1070 gtk_column_view_insert_column (self, position: i, column);
1071 g_object_unref (object: col);
1072 break;
1073 }
1074 }
1075
1076 g_object_unref (object: col);
1077 }
1078
1079 g_object_unref (object: column);
1080 }
1081}
1082
1083static void
1084update_column_resize (GtkColumnView *self,
1085 double x)
1086{
1087 GtkColumnViewColumn *column;
1088
1089 column = g_list_model_get_item (list: G_LIST_MODEL (ptr: self->columns), position: self->drag_pos);
1090 gtk_column_view_column_set_fixed_width (self: column, MAX (x - self->drag_x, 0));
1091 g_object_unref (object: column);
1092}
1093
1094static void
1095update_column_reorder (GtkColumnView *self,
1096 double x)
1097{
1098 GtkColumnViewColumn *column;
1099 int width;
1100 int size;
1101
1102 column = g_list_model_get_item (list: G_LIST_MODEL (ptr: self->columns), position: self->drag_pos);
1103 width = gtk_widget_get_allocated_width (GTK_WIDGET (self->header));
1104 gtk_column_view_column_get_allocation (self: column, NULL, size: &size);
1105
1106 self->drag_x = CLAMP (x - self->drag_offset, 0, width - size);
1107
1108 gtk_widget_queue_allocate (GTK_WIDGET (self));
1109 gtk_column_view_column_queue_resize (self: column);
1110 g_object_unref (object: column);
1111}
1112
1113static void
1114header_drag_update (GtkGestureDrag *gesture,
1115 double offset_x,
1116 double offset_y,
1117 GtkColumnView *self)
1118{
1119 GdkEventSequence *sequence;
1120 double start_x, x;
1121
1122 sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
1123 if (!gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence))
1124 return;
1125
1126 if (self->drag_pos == -1)
1127 return;
1128
1129 if (!self->in_column_resize && !self->in_column_reorder)
1130 {
1131 if (gtk_drag_check_threshold_double (GTK_WIDGET (self), start_x: 0, start_y: 0, current_x: offset_x, current_y: 0))
1132 {
1133 GtkColumnViewColumn *column;
1134 GtkWidget *header;
1135
1136 column = g_list_model_get_item (list: G_LIST_MODEL (ptr: self->columns), position: self->drag_pos);
1137 header = gtk_column_view_column_get_header (self: column);
1138
1139 gtk_widget_insert_after (widget: header, parent: self->header, previous_sibling: gtk_widget_get_last_child (widget: self->header));
1140 gtk_widget_add_css_class (widget: header, css_class: "dnd");
1141
1142 gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED);
1143 if (!gtk_widget_has_focus (GTK_WIDGET (self)))
1144 gtk_widget_grab_focus (GTK_WIDGET (self));
1145
1146 self->in_column_reorder = TRUE;
1147
1148 g_object_unref (object: column);
1149 }
1150 }
1151
1152 gtk_gesture_drag_get_start_point (gesture, x: &start_x, NULL);
1153 x = start_x + offset_x;
1154
1155 if (self->in_column_resize)
1156 update_column_resize (self, x);
1157 else if (self->in_column_reorder)
1158 update_column_reorder (self, x);
1159
1160 if (self->in_column_resize || self->in_column_reorder)
1161 update_autoscroll (self, x);
1162}
1163
1164static void
1165header_motion (GtkEventControllerMotion *controller,
1166 double x,
1167 double y,
1168 GtkColumnView *self)
1169{
1170 gboolean cursor_set = FALSE;
1171 int i, n;
1172
1173 if (self->in_column_resize)
1174 return;
1175
1176 n = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: self->columns));
1177 for (i = 0; i < n; i++)
1178 {
1179 GtkColumnViewColumn *column = g_list_model_get_item (list: G_LIST_MODEL (ptr: self->columns), position: i);
1180
1181 if (!gtk_column_view_column_get_visible (self: column))
1182 {
1183 g_object_unref (object: column);
1184 continue;
1185 }
1186
1187 if (i + 1 < n &&
1188 gtk_column_view_column_get_resizable (self: column) &&
1189 gtk_column_view_in_resize_rect (self, column, x, y))
1190 {
1191 gtk_widget_set_cursor_from_name (widget: self->header, name: "col-resize");
1192 cursor_set = TRUE;
1193 }
1194
1195 g_object_unref (object: column);
1196 }
1197
1198 if (!cursor_set)
1199 gtk_widget_set_cursor (widget: self->header, NULL);
1200}
1201
1202static gboolean
1203header_key_pressed (GtkEventControllerKey *controller,
1204 guint keyval,
1205 guint keycode,
1206 GdkModifierType modifiers,
1207 GtkColumnView *self)
1208{
1209 if (self->in_column_reorder)
1210 {
1211 if (keyval == GDK_KEY_Escape)
1212 gtk_gesture_set_state (gesture: self->drag_gesture, state: GTK_EVENT_SEQUENCE_DENIED);
1213 return TRUE;
1214 }
1215
1216 return FALSE;
1217}
1218
1219static void
1220header_pressed (GtkGestureClick *gesture,
1221 int n_press,
1222 double x,
1223 double y,
1224 GtkColumnView *self)
1225{
1226 int i, n;
1227
1228 if (n_press != 2)
1229 return;
1230
1231 n = g_list_model_get_n_items (list: G_LIST_MODEL (ptr: self->columns));
1232 for (i = n - 1; i >= 0; i--)
1233 {
1234 GtkColumnViewColumn *column = g_list_model_get_item (list: G_LIST_MODEL (ptr: self->columns), position: i);
1235
1236 g_object_unref (object: column);
1237
1238 if (i + 1 < n &&
1239 gtk_column_view_column_get_resizable (self: column) &&
1240 gtk_column_view_in_resize_rect (self, column, x, y))
1241 {
1242 gtk_gesture_set_state (gesture: self->drag_gesture, state: GTK_EVENT_SEQUENCE_DENIED);
1243 gtk_column_view_column_set_fixed_width (self: column, fixed_width: -1);
1244 break;
1245 }
1246 }
1247}
1248
1249static void
1250gtk_column_view_drag_motion (GtkDropControllerMotion *motion,
1251 double x,
1252 double y,
1253 gpointer unused)
1254{
1255 GtkWidget *widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (motion));
1256 GtkColumnView *self = GTK_COLUMN_VIEW (widget);
1257 double hx, hy;
1258
1259 gtk_widget_translate_coordinates (src_widget: widget, dest_widget: self->header, src_x: x, src_y: 0, dest_x: &hx, dest_y: &hy);
1260 update_autoscroll (GTK_COLUMN_VIEW (widget), x: hx);
1261}
1262
1263static void
1264gtk_column_view_drag_leave (GtkDropControllerMotion *motion,
1265 gpointer unused)
1266{
1267 GtkWidget *widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (motion));
1268
1269 remove_autoscroll (GTK_COLUMN_VIEW (widget));
1270}
1271
1272static void
1273gtk_column_view_init (GtkColumnView *self)
1274{
1275 GtkEventController *controller;
1276
1277 self->columns = g_list_store_new (GTK_TYPE_COLUMN_VIEW_COLUMN);
1278
1279 self->header = gtk_list_item_widget_new (NULL, css_name: "header", role: GTK_ACCESSIBLE_ROLE_ROW);
1280 gtk_widget_set_can_focus (widget: self->header, FALSE);
1281 gtk_widget_set_layout_manager (widget: self->header, layout_manager: gtk_column_view_layout_new (view: self));
1282 gtk_widget_set_parent (widget: self->header, GTK_WIDGET (self));
1283
1284 controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
1285 g_signal_connect (controller, "pressed", G_CALLBACK (header_pressed), self);
1286 gtk_event_controller_set_propagation_phase (controller, phase: GTK_PHASE_CAPTURE);
1287 gtk_widget_add_controller (widget: self->header, controller);
1288
1289 controller = GTK_EVENT_CONTROLLER (gtk_gesture_drag_new ());
1290 g_signal_connect (controller, "drag-begin", G_CALLBACK (header_drag_begin), self);
1291 g_signal_connect (controller, "drag-update", G_CALLBACK (header_drag_update), self);
1292 g_signal_connect (controller, "drag-end", G_CALLBACK (header_drag_end), self);
1293 gtk_event_controller_set_propagation_phase (controller, phase: GTK_PHASE_CAPTURE);
1294 gtk_widget_add_controller (widget: self->header, controller);
1295 self->drag_gesture = GTK_GESTURE (controller);
1296
1297 controller = gtk_event_controller_motion_new ();
1298 g_signal_connect (controller, "motion", G_CALLBACK (header_motion), self);
1299 gtk_widget_add_controller (widget: self->header, controller);
1300
1301 controller = gtk_event_controller_key_new ();
1302 g_signal_connect (controller, "key-pressed", G_CALLBACK (header_key_pressed), self);
1303 gtk_widget_add_controller (GTK_WIDGET (self), controller);
1304
1305 controller = gtk_drop_controller_motion_new ();
1306 g_signal_connect (controller, "motion", G_CALLBACK (gtk_column_view_drag_motion), NULL);
1307 g_signal_connect (controller, "leave", G_CALLBACK (gtk_column_view_drag_leave), NULL);
1308 gtk_widget_add_controller (GTK_WIDGET (self), controller);
1309
1310 self->sorter = GTK_SORTER (ptr: gtk_column_view_sorter_new ());
1311 self->factory = gtk_column_list_item_factory_new (view: self);
1312 self->listview = GTK_LIST_VIEW (g_object_new (GTK_TYPE_COLUMN_LIST_VIEW, NULL));
1313 gtk_list_view_set_factory (self: self->listview, GTK_LIST_ITEM_FACTORY (self->factory));
1314 gtk_widget_set_hexpand (GTK_WIDGET (self->listview), TRUE);
1315 gtk_widget_set_vexpand (GTK_WIDGET (self->listview), TRUE);
1316 g_signal_connect (self->listview, "activate", G_CALLBACK (gtk_column_view_activate_cb), self);
1317 gtk_widget_set_parent (GTK_WIDGET (self->listview), GTK_WIDGET (self));
1318
1319 gtk_css_node_add_class (cssnode: gtk_widget_get_css_node (GTK_WIDGET (self)),
1320 style_class: g_quark_from_static_string (I_("view")));
1321
1322 gtk_widget_set_overflow (GTK_WIDGET (self), overflow: GTK_OVERFLOW_HIDDEN);
1323 gtk_widget_set_focusable (GTK_WIDGET (self), TRUE);
1324
1325 self->reorderable = TRUE;
1326}
1327
1328/**
1329 * gtk_column_view_new:
1330 * @model: (nullable) (transfer full): the list model to use
1331 *
1332 * Creates a new `GtkColumnView`.
1333 *
1334 * You most likely want to call [method@Gtk.ColumnView.append_column]
1335 * to add columns next.
1336 *
1337 * Returns: a new `GtkColumnView`
1338 **/
1339GtkWidget *
1340gtk_column_view_new (GtkSelectionModel *model)
1341{
1342 GtkWidget *result;
1343
1344 g_return_val_if_fail (model == NULL || GTK_IS_SELECTION_MODEL (model), NULL);
1345
1346 result = g_object_new (GTK_TYPE_COLUMN_VIEW,
1347 first_property_name: "model", model,
1348 NULL);
1349
1350 /* consume the reference */
1351 g_clear_object (&model);
1352
1353 return result;
1354}
1355
1356/**
1357 * gtk_column_view_get_model: (attributes org.gtk.Method.get_property=model)
1358 * @self: a `GtkColumnView`
1359 *
1360 * Gets the model that's currently used to read the items displayed.
1361 *
1362 * Returns: (nullable) (transfer none): The model in use
1363 */
1364GtkSelectionModel *
1365gtk_column_view_get_model (GtkColumnView *self)
1366{
1367 g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), NULL);
1368
1369 return gtk_list_view_get_model (self: self->listview);
1370}
1371
1372/**
1373 * gtk_column_view_set_model: (attributes org.gtk.Method.set_property=model)
1374 * @self: a `GtkColumnView`
1375 * @model: (nullable) (transfer none): the model to use
1376 *
1377 * Sets the model to use.
1378 *
1379 * This must be a [iface@Gtk.SelectionModel].
1380 */
1381void
1382gtk_column_view_set_model (GtkColumnView *self,
1383 GtkSelectionModel *model)
1384{
1385 g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
1386 g_return_if_fail (model == NULL || GTK_IS_SELECTION_MODEL (model));
1387
1388 if (gtk_list_view_get_model (self: self->listview) == model)
1389 return;
1390
1391 gtk_list_view_set_model (self: self->listview, model);
1392
1393 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_MODEL]);
1394}
1395
1396/**
1397 * gtk_column_view_get_columns: (attributes org.gtk.Method.get_property=columns)
1398 * @self: a `GtkColumnView`
1399 *
1400 * Gets the list of columns in this column view.
1401 *
1402 * This list is constant over the lifetime of @self and can be used to
1403 * monitor changes to the columns of @self by connecting to the
1404 * ::items-changed signal.
1405 *
1406 * Returns: (transfer none): The list managing the columns
1407 */
1408GListModel *
1409gtk_column_view_get_columns (GtkColumnView *self)
1410{
1411 g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), NULL);
1412
1413 return G_LIST_MODEL (ptr: self->columns);
1414}
1415
1416/**
1417 * gtk_column_view_set_show_row_separators: (attributes org.gtk.Method.set_property=show-row-separators)
1418 * @self: a `GtkColumnView`
1419 * @show_row_separators: %TRUE to show row separators
1420 *
1421 * Sets whether the list should show separators
1422 * between rows.
1423 */
1424void
1425gtk_column_view_set_show_row_separators (GtkColumnView *self,
1426 gboolean show_row_separators)
1427{
1428 g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
1429
1430 if (gtk_list_view_get_show_separators (self: self->listview) == show_row_separators)
1431 return;
1432
1433 gtk_list_view_set_show_separators (self: self->listview, show_separators: show_row_separators);
1434
1435 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SHOW_ROW_SEPARATORS]);
1436}
1437
1438/**
1439 * gtk_column_view_get_show_row_separators: (attributes org.gtk.Method.get_property=show-row-separators)
1440 * @self: a `GtkColumnView`
1441 *
1442 * Returns whether the list should show separators
1443 * between rows.
1444 *
1445 * Returns: %TRUE if the list shows separators
1446 */
1447gboolean
1448gtk_column_view_get_show_row_separators (GtkColumnView *self)
1449{
1450 g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), FALSE);
1451
1452 return gtk_list_view_get_show_separators (self: self->listview);
1453}
1454
1455/**
1456 * gtk_column_view_set_show_column_separators: (attributes org.gtk.Method.set_property=show-column-separators)
1457 * @self: a `GtkColumnView`
1458 * @show_column_separators: %TRUE to show column separators
1459 *
1460 * Sets whether the list should show separators
1461 * between columns.
1462 */
1463void
1464gtk_column_view_set_show_column_separators (GtkColumnView *self,
1465 gboolean show_column_separators)
1466{
1467 g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
1468
1469 if (self->show_column_separators == show_column_separators)
1470 return;
1471
1472 self->show_column_separators = show_column_separators;
1473
1474 if (show_column_separators)
1475 gtk_widget_add_css_class (GTK_WIDGET (self), css_class: "column-separators");
1476 else
1477 gtk_widget_remove_css_class (GTK_WIDGET (self), css_class: "column-separators");
1478
1479 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SHOW_COLUMN_SEPARATORS]);
1480}
1481
1482/**
1483 * gtk_column_view_get_show_column_separators: (attributes org.gtk.Method.get_property=show-column-separators)
1484 * @self: a `GtkColumnView`
1485 *
1486 * Returns whether the list should show separators
1487 * between columns.
1488 *
1489 * Returns: %TRUE if the list shows column separators
1490 */
1491gboolean
1492gtk_column_view_get_show_column_separators (GtkColumnView *self)
1493{
1494 g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), FALSE);
1495
1496 return self->show_column_separators;
1497}
1498
1499/**
1500 * gtk_column_view_append_column:
1501 * @self: a `GtkColumnView`
1502 * @column: a `GtkColumnViewColumn` that hasn't been added to a
1503 * `GtkColumnView` yet
1504 *
1505 * Appends the @column to the end of the columns in @self.
1506 */
1507void
1508gtk_column_view_append_column (GtkColumnView *self,
1509 GtkColumnViewColumn *column)
1510{
1511 g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
1512 g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (column));
1513 g_return_if_fail (gtk_column_view_column_get_column_view (column) == NULL);
1514
1515 gtk_column_view_column_set_column_view (self: column, view: self);
1516 g_list_store_append (store: self->columns, item: column);
1517}
1518
1519/**
1520 * gtk_column_view_remove_column:
1521 * @self: a `GtkColumnView`
1522 * @column: a `GtkColumnViewColumn` that's part of @self
1523 *
1524 * Removes the @column from the list of columns of @self.
1525 */
1526void
1527gtk_column_view_remove_column (GtkColumnView *self,
1528 GtkColumnViewColumn *column)
1529{
1530 guint i;
1531
1532 g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
1533 g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (column));
1534 g_return_if_fail (gtk_column_view_column_get_column_view (column) == self);
1535
1536 for (i = 0; i < g_list_model_get_n_items (list: G_LIST_MODEL (ptr: self->columns)); i++)
1537 {
1538 GtkColumnViewColumn *item = g_list_model_get_item (list: G_LIST_MODEL (ptr: self->columns), position: i);
1539
1540 g_object_unref (object: item);
1541 if (item == column)
1542 break;
1543 }
1544
1545 gtk_column_view_sorter_remove_column (self: GTK_COLUMN_VIEW_SORTER (ptr: self->sorter), column);
1546 gtk_column_view_column_set_column_view (self: column, NULL);
1547 g_list_store_remove (store: self->columns, position: i);
1548}
1549
1550/**
1551 * gtk_column_view_insert_column:
1552 * @self: a `GtkColumnView`
1553 * @position: the position to insert @column at
1554 * @column: the `GtkColumnViewColumn` to insert
1555 *
1556 * Inserts a column at the given position in the columns of @self.
1557 *
1558 * If @column is already a column of @self, it will be repositioned.
1559 */
1560void
1561gtk_column_view_insert_column (GtkColumnView *self,
1562 guint position,
1563 GtkColumnViewColumn *column)
1564{
1565 g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
1566 g_return_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (column));
1567 g_return_if_fail (gtk_column_view_column_get_column_view (column) == NULL ||
1568 gtk_column_view_column_get_column_view (column) == self);
1569 g_return_if_fail (position <= g_list_model_get_n_items (G_LIST_MODEL (self->columns)));
1570 int old_position = -1;
1571
1572 g_object_ref (column);
1573
1574 if (gtk_column_view_column_get_column_view (self: column) == self)
1575 {
1576 guint i;
1577
1578 for (i = 0; i < g_list_model_get_n_items (list: G_LIST_MODEL (ptr: self->columns)); i++)
1579 {
1580 GtkColumnViewColumn *item = g_list_model_get_item (list: G_LIST_MODEL (ptr: self->columns), position: i);
1581
1582 g_object_unref (object: item);
1583 if (item == column)
1584 {
1585 old_position = i;
1586 g_list_store_remove (store: self->columns, position: i);
1587 break;
1588 }
1589 }
1590 }
1591
1592 g_list_store_insert (store: self->columns, position, item: column);
1593
1594 gtk_column_view_column_set_column_view (self: column, view: self);
1595
1596 if (old_position != -1 && position != old_position)
1597 gtk_column_view_column_set_position (self: column, position);
1598
1599 gtk_column_view_column_queue_resize (self: column);
1600
1601 g_object_unref (object: column);
1602}
1603
1604void
1605gtk_column_view_measure_across (GtkColumnView *self,
1606 int *minimum,
1607 int *natural)
1608{
1609 guint i;
1610 int min, nat;
1611
1612 min = 0;
1613 nat = 0;
1614
1615 for (i = 0; i < g_list_model_get_n_items (list: G_LIST_MODEL (ptr: self->columns)); i++)
1616 {
1617 GtkColumnViewColumn *column;
1618 int col_min, col_nat;
1619
1620 column = g_list_model_get_item (list: G_LIST_MODEL (ptr: self->columns), position: i);
1621 gtk_column_view_column_measure (self: column, minimum: &col_min, natural: &col_nat);
1622 min += col_min;
1623 nat += col_nat;
1624
1625 g_object_unref (object: column);
1626 }
1627
1628 *minimum = min;
1629 *natural = nat;
1630}
1631
1632GtkListItemWidget *
1633gtk_column_view_get_header_widget (GtkColumnView *self)
1634{
1635 return GTK_LIST_ITEM_WIDGET (self->header);
1636}
1637
1638GtkListView *
1639gtk_column_view_get_list_view (GtkColumnView *self)
1640{
1641 return GTK_LIST_VIEW (self->listview);
1642}
1643
1644/**
1645 * gtk_column_view_get_sorter: (attributes org.gtk.Method.get_property=sorter)
1646 * @self: a `GtkColumnView`
1647 *
1648 * Returns a special sorter that reflects the users sorting
1649 * choices in the column view.
1650 *
1651 * To allow users to customizable sorting by clicking on column
1652 * headers, this sorter needs to be set on the sort model underneath
1653 * the model that is displayed by the view.
1654 *
1655 * See [method@Gtk.ColumnViewColumn.set_sorter] for setting up
1656 * per-column sorting.
1657 *
1658 * Here is an example:
1659 * ```c
1660 * gtk_column_view_column_set_sorter (column, sorter);
1661 * gtk_column_view_append_column (view, column);
1662 * sorter = g_object_ref (gtk_column_view_get_sorter (view)));
1663 * model = gtk_sort_list_model_new (store, sorter);
1664 * selection = gtk_no_selection_new (model);
1665 * gtk_column_view_set_model (view, selection);
1666 * ```
1667 *
1668 * Returns: (nullable) (transfer none): the `GtkSorter` of @self
1669 */
1670GtkSorter *
1671gtk_column_view_get_sorter (GtkColumnView *self)
1672{
1673 g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), NULL);
1674
1675 return self->sorter;
1676}
1677
1678/**
1679 * gtk_column_view_sort_by_column:
1680 * @self: a `GtkColumnView`
1681 * @column: (nullable): the `GtkColumnViewColumn` to sort by
1682 * @direction: the direction to sort in
1683 *
1684 * Sets the sorting of the view.
1685 *
1686 * This function should be used to set up the initial sorting.
1687 * At runtime, users can change the sorting of a column view
1688 * by clicking on the list headers.
1689 *
1690 * This call only has an effect if the sorter returned by
1691 * [method@Gtk.ColumnView.get_sorter] is set on a sort model,
1692 * and [method@Gtk.ColumnViewColumn.set_sorter] has been called
1693 * on @column to associate a sorter with the column.
1694 *
1695 * If @column is %NULL, the view will be unsorted.
1696 */
1697void
1698gtk_column_view_sort_by_column (GtkColumnView *self,
1699 GtkColumnViewColumn *column,
1700 GtkSortType direction)
1701{
1702 g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
1703 g_return_if_fail (column == NULL || GTK_IS_COLUMN_VIEW_COLUMN (column));
1704 g_return_if_fail (column == NULL || gtk_column_view_column_get_column_view (column) == self);
1705
1706 if (column == NULL)
1707 gtk_column_view_sorter_clear (self: GTK_COLUMN_VIEW_SORTER (ptr: self->sorter));
1708 else
1709 gtk_column_view_sorter_set_column (self: GTK_COLUMN_VIEW_SORTER (ptr: self->sorter),
1710 column,
1711 inverted: direction == GTK_SORT_DESCENDING);
1712}
1713
1714/**
1715 * gtk_column_view_set_single_click_activate: (attributes org.gtk.Method.set_property=single-click-activate)
1716 * @self: a `GtkColumnView`
1717 * @single_click_activate: %TRUE to activate items on single click
1718 *
1719 * Sets whether rows should be activated on single click and
1720 * selected on hover.
1721 */
1722void
1723gtk_column_view_set_single_click_activate (GtkColumnView *self,
1724 gboolean single_click_activate)
1725{
1726 g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
1727
1728 if (single_click_activate == gtk_list_view_get_single_click_activate (self: self->listview))
1729 return;
1730
1731 gtk_list_view_set_single_click_activate (self: self->listview, single_click_activate);
1732
1733 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SINGLE_CLICK_ACTIVATE]);
1734}
1735
1736/**
1737 * gtk_column_view_get_single_click_activate: (attributes org.gtk.Method.get_property=single-click-activate)
1738 * @self: a `GtkColumnView`
1739 *
1740 * Returns whether rows will be activated on single click and
1741 * selected on hover.
1742 *
1743 * Returns: %TRUE if rows are activated on single click
1744 */
1745gboolean
1746gtk_column_view_get_single_click_activate (GtkColumnView *self)
1747{
1748 g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), FALSE);
1749
1750 return gtk_list_view_get_single_click_activate (self: self->listview);
1751}
1752
1753/**
1754 * gtk_column_view_set_reorderable: (attributes org.gtk.Method.set_property=reorderable)
1755 * @self: a `GtkColumnView`
1756 * @reorderable: whether columns should be reorderable
1757 *
1758 * Sets whether columns should be reorderable by dragging.
1759 */
1760void
1761gtk_column_view_set_reorderable (GtkColumnView *self,
1762 gboolean reorderable)
1763{
1764 g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
1765
1766 if (self->reorderable == reorderable)
1767 return;
1768
1769 self->reorderable = reorderable;
1770
1771 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_REORDERABLE]);
1772}
1773
1774/**
1775 * gtk_column_view_get_reorderable: (attributes org.gtk.Method.get_property=reorderable)
1776 * @self: a `GtkColumnView`
1777 *
1778 * Returns whether columns are reorderable.
1779 *
1780 * Returns: %TRUE if columns are reorderable
1781 */
1782gboolean
1783gtk_column_view_get_reorderable (GtkColumnView *self)
1784{
1785 g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), TRUE);
1786
1787 return self->reorderable;
1788}
1789
1790/**
1791 * gtk_column_view_set_enable_rubberband: (attributes org.gtk.Method.set_property=enable-rubberband)
1792 * @self: a `GtkColumnView`
1793 * @enable_rubberband: %TRUE to enable rubberband selection
1794 *
1795 * Sets whether selections can be changed by dragging with the mouse.
1796 */
1797void
1798gtk_column_view_set_enable_rubberband (GtkColumnView *self,
1799 gboolean enable_rubberband)
1800{
1801 g_return_if_fail (GTK_IS_COLUMN_VIEW (self));
1802
1803 if (enable_rubberband == gtk_list_view_get_enable_rubberband (self: self->listview))
1804 return;
1805
1806 gtk_list_view_set_enable_rubberband (self: self->listview, enable_rubberband);
1807
1808 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_ENABLE_RUBBERBAND]);
1809}
1810
1811/**
1812 * gtk_column_view_get_enable_rubberband: (attributes org.gtk.Method.get_property=enable-rubberband)
1813 * @self: a `GtkColumnView`
1814 *
1815 * Returns whether rows can be selected by dragging with the mouse.
1816 *
1817 * Returns: %TRUE if rubberband selection is enabled
1818 */
1819gboolean
1820gtk_column_view_get_enable_rubberband (GtkColumnView *self)
1821{
1822 g_return_val_if_fail (GTK_IS_COLUMN_VIEW (self), FALSE);
1823
1824 return gtk_list_view_get_enable_rubberband (self: self->listview);
1825}
1826

source code of gtk/gtk/gtkcolumnview.c