1/*
2 * Copyright (C) 2007-2010 Openismus GmbH
3 * Copyright (C) 2013 Red Hat, Inc.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 *
18 * Authors:
19 * Tristan Van Berkom <tristanvb@openismus.com>
20 * Matthias Clasen <mclasen@redhat.com>
21 * William Jon McCann <jmccann@redhat.com>
22 */
23
24/* Preamble {{{1 */
25
26/**
27 * GtkFlowBox:
28 *
29 * A `GtkFlowBox` puts child widgets in reflowing grid.
30 *
31 * For instance, with the horizontal orientation, the widgets will be
32 * arranged from left to right, starting a new row under the previous
33 * row when necessary. Reducing the width in this case will require more
34 * rows, so a larger height will be requested.
35 *
36 * Likewise, with the vertical orientation, the widgets will be arranged
37 * from top to bottom, starting a new column to the right when necessary.
38 * Reducing the height will require more columns, so a larger width will
39 * be requested.
40 *
41 * The size request of a `GtkFlowBox` alone may not be what you expect;
42 * if you need to be able to shrink it along both axes and dynamically
43 * reflow its children, you may have to wrap it in a `GtkScrolledWindow`
44 * to enable that.
45 *
46 * The children of a `GtkFlowBox` can be dynamically sorted and filtered.
47 *
48 * Although a `GtkFlowBox` must have only `GtkFlowBoxChild` children, you
49 * can add any kind of widget to it via [method@Gtk.FlowBox.insert], and a
50 * `GtkFlowBoxChild` widget will automatically be inserted between the box
51 * and the widget.
52 *
53 * Also see [class@Gtk.ListBox].
54 *
55 * # CSS nodes
56 *
57 * ```
58 * flowbox
59 * ├── flowboxchild
60 * │ ╰── <child>
61 * ├── flowboxchild
62 * │ ╰── <child>
63 * ┊
64 * ╰── [rubberband]
65 * ```
66 *
67 * `GtkFlowBox` uses a single CSS node with name flowbox. `GtkFlowBoxChild`
68 * uses a single CSS node with name flowboxchild. For rubberband selection,
69 * a subnode with name rubberband is used.
70 *
71 * # Accessibility
72 *
73 * `GtkFlowBox` uses the %GTK_ACCESSIBLE_ROLE_GRID role, and `GtkFlowBoxChild`
74 * uses the %GTK_ACCESSIBLE_ROLE_GRID_CELL role.
75 */
76
77/**
78 * GtkFlowBoxChild:
79 *
80 * `GtkFlowBoxChild` is the kind of widget that can be added to a `GtkFlowBox`.
81 */
82
83#include <config.h>
84
85#include "gtkflowboxprivate.h"
86
87#include "gtkaccessible.h"
88#include "gtkadjustment.h"
89#include "gtkbinlayout.h"
90#include "gtkbuildable.h"
91#include "gtkcsscolorvalueprivate.h"
92#include "gtkeventcontrollerkey.h"
93#include "gtkgestureclick.h"
94#include "gtkgesturedrag.h"
95#include "gtkintl.h"
96#include "gtkmain.h"
97#include "gtkmarshalers.h"
98#include "gtkorientable.h"
99#include "gtkprivate.h"
100#include "gtkrender.h"
101#include "gtksizerequest.h"
102#include "gtksnapshot.h"
103#include "gtkstylecontextprivate.h"
104#include "gtktypebuiltins.h"
105#include "gtkviewport.h"
106#include "gtkwidgetprivate.h"
107
108/* Forward declarations and utilities {{{1 */
109
110static void gtk_flow_box_update_cursor (GtkFlowBox *box,
111 GtkFlowBoxChild *child);
112static void gtk_flow_box_select_and_activate (GtkFlowBox *box,
113 GtkFlowBoxChild *child);
114static void gtk_flow_box_update_selection (GtkFlowBox *box,
115 GtkFlowBoxChild *child,
116 gboolean modify,
117 gboolean extend);
118static void gtk_flow_box_apply_filter (GtkFlowBox *box,
119 GtkFlowBoxChild *child);
120static void gtk_flow_box_apply_sort (GtkFlowBox *box,
121 GtkFlowBoxChild *child);
122static int gtk_flow_box_sort (GtkFlowBoxChild *a,
123 GtkFlowBoxChild *b,
124 GtkFlowBox *box);
125
126static void gtk_flow_box_bound_model_changed (GListModel *list,
127 guint position,
128 guint removed,
129 guint added,
130 gpointer user_data);
131
132static void gtk_flow_box_set_accept_unpaired_release (GtkFlowBox *box,
133 gboolean accept);
134
135static void gtk_flow_box_check_model_compat (GtkFlowBox *box);
136
137static void
138path_from_horizontal_line_rects (cairo_t *cr,
139 GdkRectangle *lines,
140 int n_lines)
141{
142 int start_line, end_line;
143 GdkRectangle *r;
144 int i;
145
146 /* Join rows vertically by extending to the middle */
147 for (i = 0; i < n_lines - 1; i++)
148 {
149 GdkRectangle *r1 = &lines[i];
150 GdkRectangle *r2 = &lines[i+1];
151 int gap, old;
152
153 gap = r2->y - (r1->y + r1->height);
154 r1->height += gap / 2;
155 old = r2->y;
156 r2->y = r1->y + r1->height;
157 r2->height += old - r2->y;
158 }
159
160 cairo_new_path (cr);
161 start_line = 0;
162
163 do
164 {
165 for (i = start_line; i < n_lines; i++)
166 {
167 r = &lines[i];
168 if (i == start_line)
169 cairo_move_to (cr, x: r->x + r->width, y: r->y);
170 else
171 cairo_line_to (cr, x: r->x + r->width, y: r->y);
172 cairo_line_to (cr, x: r->x + r->width, y: r->y + r->height);
173
174 if (i < n_lines - 1 &&
175 (r->x + r->width < lines[i+1].x ||
176 r->x > lines[i+1].x + lines[i+1].width))
177 {
178 i++;
179 break;
180 }
181 }
182 end_line = i;
183 for (i = end_line - 1; i >= start_line; i--)
184 {
185 r = &lines[i];
186 cairo_line_to (cr, x: r->x, y: r->y + r->height);
187 cairo_line_to (cr, x: r->x, y: r->y);
188 }
189 cairo_close_path (cr);
190 start_line = end_line;
191 }
192 while (end_line < n_lines);
193}
194
195static void
196path_from_vertical_line_rects (cairo_t *cr,
197 GdkRectangle *lines,
198 int n_lines)
199{
200 int start_line, end_line;
201 GdkRectangle *r;
202 int i;
203
204 /* Join rows horizontally by extending to the middle */
205 for (i = 0; i < n_lines - 1; i++)
206 {
207 GdkRectangle *r1 = &lines[i];
208 GdkRectangle *r2 = &lines[i+1];
209 int gap, old;
210
211 gap = r2->x - (r1->x + r1->width);
212 r1->width += gap / 2;
213 old = r2->x;
214 r2->x = r1->x + r1->width;
215 r2->width += old - r2->x;
216 }
217
218 cairo_new_path (cr);
219 start_line = 0;
220
221 do
222 {
223 for (i = start_line; i < n_lines; i++)
224 {
225 r = &lines[i];
226 if (i == start_line)
227 cairo_move_to (cr, x: r->x, y: r->y + r->height);
228 else
229 cairo_line_to (cr, x: r->x, y: r->y + r->height);
230 cairo_line_to (cr, x: r->x + r->width, y: r->y + r->height);
231
232 if (i < n_lines - 1 &&
233 (r->y + r->height < lines[i+1].y ||
234 r->y > lines[i+1].y + lines[i+1].height))
235 {
236 i++;
237 break;
238 }
239 }
240 end_line = i;
241 for (i = end_line - 1; i >= start_line; i--)
242 {
243 r = &lines[i];
244 cairo_line_to (cr, x: r->x + r->width, y: r->y);
245 cairo_line_to (cr, x: r->x, y: r->y);
246 }
247 cairo_close_path (cr);
248 start_line = end_line;
249 }
250 while (end_line < n_lines);
251}
252
253/* GtkFlowBoxChild {{{1 */
254
255/* GObject boilerplate {{{2 */
256
257enum {
258 CHILD_ACTIVATE,
259 CHILD_LAST_SIGNAL
260};
261
262static guint child_signals[CHILD_LAST_SIGNAL] = { 0 };
263
264enum {
265 PROP_CHILD = 1
266};
267
268typedef struct _GtkFlowBoxChildPrivate GtkFlowBoxChildPrivate;
269struct _GtkFlowBoxChildPrivate
270{
271 GtkWidget *child;
272 GSequenceIter *iter;
273 gboolean selected;
274};
275
276#define CHILD_PRIV(child) ((GtkFlowBoxChildPrivate*)gtk_flow_box_child_get_instance_private ((GtkFlowBoxChild*)(child)))
277
278static void gtk_flow_box_child_buildable_iface_init (GtkBuildableIface *iface);
279
280G_DEFINE_TYPE_WITH_CODE (GtkFlowBoxChild, gtk_flow_box_child, GTK_TYPE_WIDGET,
281 G_ADD_PRIVATE (GtkFlowBoxChild)
282 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
283 gtk_flow_box_child_buildable_iface_init))
284
285/* Internal API {{{2 */
286
287static GtkFlowBox *
288gtk_flow_box_child_get_box (GtkFlowBoxChild *child)
289{
290 GtkWidget *parent;
291
292 parent = gtk_widget_get_parent (GTK_WIDGET (child));
293 if (parent && GTK_IS_FLOW_BOX (parent))
294 return GTK_FLOW_BOX (parent);
295
296 return NULL;
297}
298
299static void
300gtk_flow_box_child_set_focus (GtkFlowBoxChild *child)
301{
302 GtkFlowBox *box = gtk_flow_box_child_get_box (child);
303
304 gtk_flow_box_update_selection (box, child, FALSE, FALSE);
305}
306
307/* GtkWidget implementation {{{2 */
308
309static GtkBuildableIface *parent_child_buildable_iface;
310
311static void
312gtk_flow_box_child_buildable_add_child (GtkBuildable *buildable,
313 GtkBuilder *builder,
314 GObject *child,
315 const char *type)
316{
317 if (GTK_IS_WIDGET (child))
318 gtk_flow_box_child_set_child (GTK_FLOW_BOX_CHILD (buildable), GTK_WIDGET (child));
319 else
320 parent_child_buildable_iface->add_child (buildable, builder, child, type);
321}
322
323static void
324gtk_flow_box_child_buildable_iface_init (GtkBuildableIface *iface)
325{
326 parent_child_buildable_iface = g_type_interface_peek_parent (g_iface: iface);
327
328 iface->add_child = gtk_flow_box_child_buildable_add_child;
329}
330
331static gboolean
332gtk_flow_box_child_focus (GtkWidget *widget,
333 GtkDirectionType direction)
334{
335 GtkFlowBoxChild *self = GTK_FLOW_BOX_CHILD (widget);
336 GtkFlowBoxChildPrivate *priv = CHILD_PRIV (self);
337 GtkWidget *child = priv->child;
338 gboolean had_focus = FALSE;
339
340 /* Without "focusable" flag try to pass the focus to the child immediately */
341 if (!gtk_widget_get_focusable (widget))
342 {
343 if (child)
344 {
345 if (gtk_widget_child_focus (widget: child, direction))
346 {
347 GtkFlowBox *box;
348 box = gtk_flow_box_child_get_box (GTK_FLOW_BOX_CHILD (widget));
349 if (box)
350 gtk_flow_box_update_cursor (box, GTK_FLOW_BOX_CHILD (widget));
351 return TRUE;
352 }
353 }
354 return FALSE;
355 }
356
357 g_object_get (object: widget, first_property_name: "has-focus", &had_focus, NULL);
358 if (had_focus)
359 {
360 /* If on row, going right, enter into possible container */
361 if (child &&
362 (direction == GTK_DIR_RIGHT || direction == GTK_DIR_TAB_FORWARD))
363 {
364 if (gtk_widget_child_focus (GTK_WIDGET (child), direction))
365 return TRUE;
366 }
367
368 return FALSE;
369 }
370 else if (gtk_widget_get_focus_child (widget) != NULL)
371 {
372 /* Child has focus, always navigate inside it first */
373 if (gtk_widget_child_focus (widget: child, direction))
374 return TRUE;
375
376 /* If exiting child container to the left, select child */
377 if (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD)
378 {
379 gtk_flow_box_child_set_focus (GTK_FLOW_BOX_CHILD (widget));
380 return TRUE;
381 }
382
383 return FALSE;
384 }
385 else
386 {
387 /* If coming from the left, enter into possible container */
388 if (child &&
389 (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD))
390 {
391 if (gtk_widget_child_focus (widget: child, direction))
392 return TRUE;
393 }
394
395 gtk_flow_box_child_set_focus (GTK_FLOW_BOX_CHILD (widget));
396 return TRUE;
397 }
398}
399
400static void
401gtk_flow_box_child_activate (GtkFlowBoxChild *child)
402{
403 GtkFlowBox *box;
404
405 box = gtk_flow_box_child_get_box (child);
406 if (box)
407 gtk_flow_box_select_and_activate (box, child);
408}
409
410/* Size allocation {{{3 */
411
412static GtkSizeRequestMode
413gtk_flow_box_child_get_request_mode (GtkWidget *widget)
414{
415 GtkFlowBox *box;
416
417 box = gtk_flow_box_child_get_box (GTK_FLOW_BOX_CHILD (widget));
418 if (box)
419 return gtk_widget_get_request_mode (GTK_WIDGET (box));
420 else
421 return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
422}
423
424static void
425gtk_flow_box_child_dispose (GObject *object)
426{
427 GtkFlowBoxChild *self = GTK_FLOW_BOX_CHILD (object);
428 GtkFlowBoxChildPrivate *priv = CHILD_PRIV (self);
429
430 g_clear_pointer (&priv->child, gtk_widget_unparent);
431
432 G_OBJECT_CLASS (gtk_flow_box_child_parent_class)->dispose (object);
433}
434
435static void
436gtk_flow_box_child_get_property (GObject *object,
437 guint prop_id,
438 GValue *value,
439 GParamSpec *pspec)
440{
441 GtkFlowBoxChild *self = GTK_FLOW_BOX_CHILD (object);
442
443 switch (prop_id)
444 {
445 case PROP_CHILD:
446 g_value_set_object (value, v_object: gtk_flow_box_child_get_child (self));
447 break;
448
449 default:
450 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
451 break;
452 }
453}
454
455static void
456gtk_flow_box_child_set_property (GObject *object,
457 guint prop_id,
458 const GValue *value,
459 GParamSpec *pspec)
460{
461 GtkFlowBoxChild *self = GTK_FLOW_BOX_CHILD (object);
462
463 switch (prop_id)
464 {
465 case PROP_CHILD:
466 gtk_flow_box_child_set_child (self, child: g_value_get_object (value));
467 break;
468
469 default:
470 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
471 break;
472 }
473}
474
475static void
476gtk_flow_box_child_compute_expand (GtkWidget *widget,
477 gboolean *hexpand,
478 gboolean *vexpand)
479{
480 GtkFlowBoxChild *self = GTK_FLOW_BOX_CHILD (widget);
481 GtkFlowBoxChildPrivate *priv = CHILD_PRIV (self);
482
483 if (priv->child)
484 {
485 *hexpand = gtk_widget_compute_expand (widget: priv->child, orientation: GTK_ORIENTATION_HORIZONTAL);
486 *vexpand = gtk_widget_compute_expand (widget: priv->child, orientation: GTK_ORIENTATION_VERTICAL);
487 }
488 else
489 {
490 *hexpand = FALSE;
491 *vexpand = FALSE;
492 }
493}
494
495static void
496gtk_flow_box_child_root (GtkWidget *widget)
497{
498 GtkFlowBoxChild *child = GTK_FLOW_BOX_CHILD (widget);
499
500 GTK_WIDGET_CLASS (gtk_flow_box_child_parent_class)->root (widget);
501
502 gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: child),
503 first_state: GTK_ACCESSIBLE_STATE_SELECTED, CHILD_PRIV (child)->selected,
504 -1);
505}
506
507/* GObject implementation {{{2 */
508
509static void
510gtk_flow_box_child_class_init (GtkFlowBoxChildClass *class)
511{
512 GObjectClass *object_class = G_OBJECT_CLASS (class);
513 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
514
515 object_class->dispose = gtk_flow_box_child_dispose;
516 object_class->get_property = gtk_flow_box_child_get_property;
517 object_class->set_property = gtk_flow_box_child_set_property;
518
519 widget_class->root = gtk_flow_box_child_root;
520 widget_class->get_request_mode = gtk_flow_box_child_get_request_mode;
521 widget_class->compute_expand = gtk_flow_box_child_compute_expand;
522 widget_class->focus = gtk_flow_box_child_focus;
523
524 class->activate = gtk_flow_box_child_activate;
525
526 /**
527 * GtkFlowBoxChild:child: (attributes org.gtk.Property.get=gtk_flow_box_child_get_child org.gtk.Property.set=gtk_flow_box_child_set_child)
528 *
529 * The child widget.
530 */
531 g_object_class_install_property (oclass: object_class,
532 property_id: PROP_CHILD,
533 pspec: g_param_spec_object (name: "child",
534 P_("Child"),
535 P_("The child widget"),
536 GTK_TYPE_WIDGET,
537 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
538
539 /**
540 * GtkFlowBoxChild::activate:
541 * @child: The child on which the signal is emitted
542 *
543 * Emitted when the user activates a child widget in a `GtkFlowBox`.
544 *
545 * This can be happen either by clicking or double-clicking,
546 * or via a keybinding.
547 *
548 * This is a [keybinding signal](class.SignalAction.html),
549 * but it can be used by applications for their own purposes.
550 *
551 * The default bindings are <kbd>Space</kbd> and <kbd>Enter</kbd>.
552 */
553 child_signals[CHILD_ACTIVATE] =
554 g_signal_new (I_("activate"),
555 G_OBJECT_CLASS_TYPE (object_class),
556 signal_flags: G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
557 G_STRUCT_OFFSET (GtkFlowBoxChildClass, activate),
558 NULL, NULL,
559 NULL,
560 G_TYPE_NONE, n_params: 0);
561
562 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
563 gtk_widget_class_set_css_name (widget_class, I_("flowboxchild"));
564 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_GRID_CELL);
565 gtk_widget_class_set_activate_signal (widget_class, signal_id: child_signals[CHILD_ACTIVATE]);
566}
567
568static void
569gtk_flow_box_child_init (GtkFlowBoxChild *child)
570{
571 gtk_widget_set_focusable (GTK_WIDGET (child), TRUE);
572}
573
574/* Public API {{{2 */
575
576/**
577 * gtk_flow_box_child_new:
578 *
579 * Creates a new `GtkFlowBoxChild`.
580 *
581 * This should only be used as a child of a `GtkFlowBox`.
582 *
583 * Returns: a new `GtkFlowBoxChild`
584 */
585GtkWidget *
586gtk_flow_box_child_new (void)
587{
588 return g_object_new (GTK_TYPE_FLOW_BOX_CHILD, NULL);
589}
590
591/**
592 * gtk_flow_box_child_set_child: (attributes org.gtk.Method.set_property=child)
593 * @self: a `GtkFlowBoxChild`
594 * @child: (nullable): the child widget
595 *
596 * Sets the child widget of @self.
597 */
598void
599gtk_flow_box_child_set_child (GtkFlowBoxChild *self,
600 GtkWidget *child)
601{
602 GtkFlowBoxChildPrivate *priv = CHILD_PRIV (self);
603
604 g_clear_pointer (&priv->child, gtk_widget_unparent);
605
606 priv->child = child;
607 if (child)
608 gtk_widget_set_parent (widget: child, GTK_WIDGET (self));
609 g_object_notify (G_OBJECT (self), property_name: "child");
610}
611
612/**
613 * gtk_flow_box_child_get_child: (attributes org.gtk.Method.get_property=child)
614 * @self: a `GtkFlowBoxChild`
615 *
616 * Gets the child widget of @self.
617 *
618 * Returns: (nullable) (transfer none): the child widget of @self
619 */
620GtkWidget *
621gtk_flow_box_child_get_child (GtkFlowBoxChild *self)
622{
623 GtkFlowBoxChildPrivate *priv = CHILD_PRIV (self);
624
625 return priv->child;
626}
627
628/**
629 * gtk_flow_box_child_get_index:
630 * @child: a `GtkFlowBoxChild`
631 *
632 * Gets the current index of the @child in its `GtkFlowBox` container.
633 *
634 * Returns: the index of the @child, or -1 if the @child is not
635 * in a flow box
636 */
637int
638gtk_flow_box_child_get_index (GtkFlowBoxChild *child)
639{
640 GtkFlowBoxChildPrivate *priv;
641
642 g_return_val_if_fail (GTK_IS_FLOW_BOX_CHILD (child), -1);
643
644 priv = CHILD_PRIV (child);
645
646 if (priv->iter != NULL)
647 return g_sequence_iter_get_position (iter: priv->iter);
648
649 return -1;
650}
651
652/**
653 * gtk_flow_box_child_is_selected:
654 * @child: a `GtkFlowBoxChild`
655 *
656 * Returns whether the @child is currently selected in its
657 * `GtkFlowBox` container.
658 *
659 * Returns: %TRUE if @child is selected
660 */
661gboolean
662gtk_flow_box_child_is_selected (GtkFlowBoxChild *child)
663{
664 g_return_val_if_fail (GTK_IS_FLOW_BOX_CHILD (child), FALSE);
665
666 return CHILD_PRIV (child)->selected;
667}
668
669/**
670 * gtk_flow_box_child_changed:
671 * @child: a `GtkFlowBoxChild`
672 *
673 * Marks @child as changed, causing any state that depends on this
674 * to be updated.
675 *
676 * This affects sorting and filtering.
677 *
678 * Note that calls to this method must be in sync with the data
679 * used for the sorting and filtering functions. For instance, if
680 * the list is mirroring some external data set, and *two* children
681 * changed in the external data set when you call
682 * gtk_flow_box_child_changed() on the first child, the sort function
683 * must only read the new data for the first of the two changed
684 * children, otherwise the resorting of the children will be wrong.
685 *
686 * This generally means that if you don’t fully control the data
687 * model, you have to duplicate the data that affects the sorting
688 * and filtering functions into the widgets themselves.
689 *
690 * Another alternative is to call [method@Gtk.FlowBox.invalidate_sort]
691 * on any model change, but that is more expensive.
692 */
693void
694gtk_flow_box_child_changed (GtkFlowBoxChild *child)
695{
696 GtkFlowBox *box;
697
698 g_return_if_fail (GTK_IS_FLOW_BOX_CHILD (child));
699
700 box = gtk_flow_box_child_get_box (child);
701
702 if (box == NULL)
703 return;
704
705 gtk_flow_box_apply_sort (box, child);
706 gtk_flow_box_apply_filter (box, child);
707}
708
709/* G tkFlowBox {{{1 */
710
711 /* Constants {{{2 */
712
713#define DEFAULT_MAX_CHILDREN_PER_LINE 7
714#define RUBBERBAND_START_DISTANCE 32
715#define AUTOSCROLL_FAST_DISTANCE 32
716#define AUTOSCROLL_FACTOR 20
717#define AUTOSCROLL_FACTOR_FAST 10
718
719/* GObject boilerplate {{{2 */
720
721enum {
722 CHILD_ACTIVATED,
723 SELECTED_CHILDREN_CHANGED,
724 ACTIVATE_CURSOR_CHILD,
725 TOGGLE_CURSOR_CHILD,
726 MOVE_CURSOR,
727 SELECT_ALL,
728 UNSELECT_ALL,
729 LAST_SIGNAL
730};
731
732static guint signals[LAST_SIGNAL] = { 0 };
733
734enum {
735 PROP_0,
736 PROP_HOMOGENEOUS,
737 PROP_COLUMN_SPACING,
738 PROP_ROW_SPACING,
739 PROP_MIN_CHILDREN_PER_LINE,
740 PROP_MAX_CHILDREN_PER_LINE,
741 PROP_SELECTION_MODE,
742 PROP_ACTIVATE_ON_SINGLE_CLICK,
743 PROP_ACCEPT_UNPAIRED_RELEASE,
744
745 /* orientable */
746 PROP_ORIENTATION,
747 LAST_PROP = PROP_ORIENTATION
748};
749
750static GParamSpec *props[LAST_PROP] = { NULL, };
751
752typedef struct _GtkFlowBoxClass GtkFlowBoxClass;
753
754struct _GtkFlowBox
755{
756 GtkWidget container;
757};
758
759struct _GtkFlowBoxClass
760{
761 GtkWidgetClass parent_class;
762
763 void (*child_activated) (GtkFlowBox *box,
764 GtkFlowBoxChild *child);
765 void (*selected_children_changed) (GtkFlowBox *box);
766 void (*activate_cursor_child) (GtkFlowBox *box);
767 void (*toggle_cursor_child) (GtkFlowBox *box);
768 gboolean (*move_cursor) (GtkFlowBox *box,
769 GtkMovementStep step,
770 int count,
771 gboolean extend,
772 gboolean modify);
773 void (*select_all) (GtkFlowBox *box);
774 void (*unselect_all) (GtkFlowBox *box);
775};
776
777typedef struct _GtkFlowBoxPrivate GtkFlowBoxPrivate;
778struct _GtkFlowBoxPrivate {
779 GtkOrientation orientation;
780 gboolean homogeneous;
781
782 guint row_spacing;
783 guint column_spacing;
784
785 GtkFlowBoxChild *cursor_child;
786 GtkFlowBoxChild *selected_child;
787
788 GtkFlowBoxChild *active_child;
789
790 GtkSelectionMode selection_mode;
791
792 GtkAdjustment *hadjustment;
793 GtkAdjustment *vadjustment;
794 gboolean activate_on_single_click;
795 gboolean accept_unpaired_release;
796
797 guint16 min_children_per_line;
798 guint16 max_children_per_line;
799 guint16 cur_children_per_line;
800
801 GSequence *children;
802
803 GtkFlowBoxFilterFunc filter_func;
804 gpointer filter_data;
805 GDestroyNotify filter_destroy;
806
807 GtkFlowBoxSortFunc sort_func;
808 gpointer sort_data;
809 GDestroyNotify sort_destroy;
810
811 GtkGesture *drag_gesture;
812
813 GtkFlowBoxChild *rubberband_first;
814 GtkFlowBoxChild *rubberband_last;
815 GtkCssNode *rubberband_node;
816 gboolean rubberband_select;
817 gboolean rubberband_modify;
818 gboolean rubberband_extend;
819
820 GtkScrollType autoscroll_mode;
821 guint autoscroll_id;
822
823 GListModel *bound_model;
824 GtkFlowBoxCreateWidgetFunc create_widget_func;
825 gpointer create_widget_func_data;
826 GDestroyNotify create_widget_func_data_destroy;
827
828 gboolean disable_move_cursor;
829};
830
831#define BOX_PRIV(box) ((GtkFlowBoxPrivate*)gtk_flow_box_get_instance_private ((GtkFlowBox*)(box)))
832
833static void gtk_flow_box_buildable_iface_init (GtkBuildableIface *iface);
834
835G_DEFINE_TYPE_WITH_CODE (GtkFlowBox, gtk_flow_box, GTK_TYPE_WIDGET,
836 G_ADD_PRIVATE (GtkFlowBox)
837 G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)
838 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
839 gtk_flow_box_buildable_iface_init))
840
841/* Internal API, utilities {{{2 */
842
843#define ORIENTATION_ALIGN(box) \
844 (BOX_PRIV(box)->orientation == GTK_ORIENTATION_HORIZONTAL \
845 ? gtk_widget_get_halign (GTK_WIDGET (box)) \
846 : gtk_widget_get_valign (GTK_WIDGET (box)))
847
848#define OPPOSING_ORIENTATION_ALIGN(box) \
849 (BOX_PRIV(box)->orientation == GTK_ORIENTATION_HORIZONTAL \
850 ? gtk_widget_get_valign (GTK_WIDGET (box)) \
851 : gtk_widget_get_halign (GTK_WIDGET (box)))
852
853/* Children are visible if they are shown by the app (visible)
854 * and not filtered out (child_visible) by the box
855 */
856static inline gboolean
857child_is_visible (GtkWidget *child)
858{
859 return gtk_widget_get_visible (widget: child) &&
860 gtk_widget_get_child_visible (widget: child);
861}
862
863static int
864get_visible_children (GtkFlowBox *box)
865{
866 GSequenceIter *iter;
867 int i = 0;
868
869 for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
870 !g_sequence_iter_is_end (iter);
871 iter = g_sequence_iter_next (iter))
872 {
873 GtkWidget *child;
874
875 child = g_sequence_get (iter);
876 if (child_is_visible (child))
877 i++;
878 }
879
880 return i;
881}
882
883static void
884gtk_flow_box_apply_filter (GtkFlowBox *box,
885 GtkFlowBoxChild *child)
886{
887 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
888 gboolean do_show;
889
890 do_show = TRUE;
891 if (priv->filter_func != NULL)
892 do_show = priv->filter_func (child, priv->filter_data);
893
894 gtk_widget_set_child_visible (GTK_WIDGET (child), child_visible: do_show);
895}
896
897static void
898gtk_flow_box_apply_filter_all (GtkFlowBox *box)
899{
900 GSequenceIter *iter;
901
902 for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
903 !g_sequence_iter_is_end (iter);
904 iter = g_sequence_iter_next (iter))
905 {
906 GtkFlowBoxChild *child;
907
908 child = g_sequence_get (iter);
909 gtk_flow_box_apply_filter (box, child);
910 }
911 gtk_widget_queue_resize (GTK_WIDGET (box));
912}
913
914static void
915gtk_flow_box_apply_sort (GtkFlowBox *box,
916 GtkFlowBoxChild *child)
917{
918 if (BOX_PRIV (box)->sort_func != NULL)
919 {
920 g_sequence_sort_changed (CHILD_PRIV (child)->iter,
921 cmp_func: (GCompareDataFunc)gtk_flow_box_sort, cmp_data: box);
922 gtk_widget_queue_resize (GTK_WIDGET (box));
923 }
924}
925
926/* Sel ection utilities {{{3 */
927
928static gboolean
929gtk_flow_box_child_set_selected (GtkFlowBoxChild *child,
930 gboolean selected)
931{
932 if (CHILD_PRIV (child)->selected != selected)
933 {
934 CHILD_PRIV (child)->selected = selected;
935 if (selected)
936 gtk_widget_set_state_flags (GTK_WIDGET (child),
937 flags: GTK_STATE_FLAG_SELECTED, FALSE);
938 else
939 gtk_widget_unset_state_flags (GTK_WIDGET (child),
940 flags: GTK_STATE_FLAG_SELECTED);
941
942 gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: child),
943 first_state: GTK_ACCESSIBLE_STATE_SELECTED, selected,
944 -1);
945 return TRUE;
946 }
947
948 return FALSE;
949}
950
951static gboolean
952gtk_flow_box_unselect_all_internal (GtkFlowBox *box)
953{
954 GtkFlowBoxChild *child;
955 GSequenceIter *iter;
956 gboolean dirty = FALSE;
957
958 if (BOX_PRIV (box)->selection_mode == GTK_SELECTION_NONE)
959 return FALSE;
960
961 for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
962 !g_sequence_iter_is_end (iter);
963 iter = g_sequence_iter_next (iter))
964 {
965 child = g_sequence_get (iter);
966 dirty |= gtk_flow_box_child_set_selected (child, FALSE);
967 }
968
969 return dirty;
970}
971
972static void
973gtk_flow_box_unselect_child_internal (GtkFlowBox *box,
974 GtkFlowBoxChild *child)
975{
976 if (!CHILD_PRIV (child)->selected)
977 return;
978
979 if (BOX_PRIV (box)->selection_mode == GTK_SELECTION_NONE)
980 return;
981 else if (BOX_PRIV (box)->selection_mode != GTK_SELECTION_MULTIPLE)
982 gtk_flow_box_unselect_all_internal (box);
983 else
984 gtk_flow_box_child_set_selected (child, FALSE);
985
986 g_signal_emit (instance: box, signal_id: signals[SELECTED_CHILDREN_CHANGED], detail: 0);
987}
988
989static void
990gtk_flow_box_update_cursor (GtkFlowBox *box,
991 GtkFlowBoxChild *child)
992{
993 BOX_PRIV (box)->cursor_child = child;
994 gtk_widget_grab_focus (GTK_WIDGET (child));
995}
996
997static void
998gtk_flow_box_select_child_internal (GtkFlowBox *box,
999 GtkFlowBoxChild *child)
1000{
1001 if (CHILD_PRIV (child)->selected)
1002 return;
1003
1004 if (BOX_PRIV (box)->selection_mode == GTK_SELECTION_NONE)
1005 return;
1006 if (BOX_PRIV (box)->selection_mode != GTK_SELECTION_MULTIPLE)
1007 gtk_flow_box_unselect_all_internal (box);
1008
1009 gtk_flow_box_child_set_selected (child, TRUE);
1010 BOX_PRIV (box)->selected_child = child;
1011
1012 g_signal_emit (instance: box, signal_id: signals[SELECTED_CHILDREN_CHANGED], detail: 0);
1013}
1014
1015static void
1016gtk_flow_box_select_all_between (GtkFlowBox *box,
1017 GtkFlowBoxChild *child1,
1018 GtkFlowBoxChild *child2,
1019 gboolean modify)
1020{
1021 GSequenceIter *iter, *iter1, *iter2;
1022
1023 if (child1)
1024 iter1 = CHILD_PRIV (child1)->iter;
1025 else
1026 iter1 = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
1027
1028 if (child2)
1029 iter2 = CHILD_PRIV (child2)->iter;
1030 else
1031 iter2 = g_sequence_get_end_iter (BOX_PRIV (box)->children);
1032
1033 if (g_sequence_iter_compare (a: iter2, b: iter1) < 0)
1034 {
1035 iter = iter1;
1036 iter1 = iter2;
1037 iter2 = iter;
1038 }
1039
1040 for (iter = iter1;
1041 !g_sequence_iter_is_end (iter);
1042 iter = g_sequence_iter_next (iter))
1043 {
1044 GtkWidget *child;
1045
1046 child = g_sequence_get (iter);
1047 if (child_is_visible (child))
1048 {
1049 if (modify)
1050 gtk_flow_box_child_set_selected (GTK_FLOW_BOX_CHILD (child), selected: !CHILD_PRIV (child)->selected);
1051 else
1052 gtk_flow_box_child_set_selected (GTK_FLOW_BOX_CHILD (child), TRUE);
1053 }
1054
1055 if (g_sequence_iter_compare (a: iter, b: iter2) == 0)
1056 break;
1057 }
1058}
1059
1060static void
1061gtk_flow_box_update_selection (GtkFlowBox *box,
1062 GtkFlowBoxChild *child,
1063 gboolean modify,
1064 gboolean extend)
1065{
1066 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
1067
1068 gtk_flow_box_update_cursor (box, child);
1069
1070 if (priv->selection_mode == GTK_SELECTION_NONE)
1071 return;
1072
1073 if (priv->selection_mode == GTK_SELECTION_BROWSE)
1074 {
1075 gtk_flow_box_unselect_all_internal (box);
1076 gtk_flow_box_child_set_selected (child, TRUE);
1077 priv->selected_child = child;
1078 }
1079 else if (priv->selection_mode == GTK_SELECTION_SINGLE)
1080 {
1081 gboolean was_selected;
1082
1083 was_selected = CHILD_PRIV (child)->selected;
1084 gtk_flow_box_unselect_all_internal (box);
1085 gtk_flow_box_child_set_selected (child, selected: modify ? !was_selected : TRUE);
1086 priv->selected_child = CHILD_PRIV (child)->selected ? child : NULL;
1087 }
1088 else /* GTK_SELECTION_MULTIPLE */
1089 {
1090 if (extend)
1091 {
1092 gtk_flow_box_unselect_all_internal (box);
1093 if (priv->selected_child == NULL)
1094 {
1095 gtk_flow_box_child_set_selected (child, TRUE);
1096 priv->selected_child = child;
1097 }
1098 else
1099 gtk_flow_box_select_all_between (box, child1: priv->selected_child, child2: child, FALSE);
1100 }
1101 else
1102 {
1103 if (modify)
1104 {
1105 gtk_flow_box_child_set_selected (child, selected: !CHILD_PRIV (child)->selected);
1106 }
1107 else
1108 {
1109 gtk_flow_box_unselect_all_internal (box);
1110 gtk_flow_box_child_set_selected (child, selected: !CHILD_PRIV (child)->selected);
1111 priv->selected_child = child;
1112 }
1113 }
1114 }
1115
1116 g_signal_emit (instance: box, signal_id: signals[SELECTED_CHILDREN_CHANGED], detail: 0);
1117}
1118
1119static void
1120gtk_flow_box_select_and_activate (GtkFlowBox *box,
1121 GtkFlowBoxChild *child)
1122{
1123 if (child != NULL)
1124 {
1125 gtk_flow_box_select_child_internal (box, child);
1126 gtk_flow_box_update_cursor (box, child);
1127 g_signal_emit (instance: box, signal_id: signals[CHILD_ACTIVATED], detail: 0, child);
1128 }
1129}
1130
1131/* Focus utilities {{{3 */
1132
1133static GSequenceIter *
1134gtk_flow_box_get_previous_focusable (GtkFlowBox *box,
1135 GSequenceIter *iter)
1136{
1137 GtkFlowBoxChild *child;
1138
1139 while (!g_sequence_iter_is_begin (iter))
1140 {
1141 iter = g_sequence_iter_prev (iter);
1142 child = g_sequence_get (iter);
1143 if (child_is_visible (GTK_WIDGET (child)) &&
1144 gtk_widget_is_sensitive (GTK_WIDGET (child)))
1145 return iter;
1146 }
1147
1148 return NULL;
1149}
1150
1151static GSequenceIter *
1152gtk_flow_box_get_next_focusable (GtkFlowBox *box,
1153 GSequenceIter *iter)
1154{
1155 GtkFlowBoxChild *child;
1156
1157 while (TRUE)
1158 {
1159 iter = g_sequence_iter_next (iter);
1160 if (g_sequence_iter_is_end (iter))
1161 return NULL;
1162 child = g_sequence_get (iter);
1163 if (child_is_visible (GTK_WIDGET (child)) &&
1164 gtk_widget_is_sensitive (GTK_WIDGET (child)))
1165 return iter;
1166 }
1167
1168 return NULL;
1169}
1170
1171static GSequenceIter *
1172gtk_flow_box_get_first_focusable (GtkFlowBox *box)
1173{
1174 GSequenceIter *iter;
1175 GtkFlowBoxChild *child;
1176
1177 iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
1178 if (g_sequence_iter_is_end (iter))
1179 return NULL;
1180
1181 child = g_sequence_get (iter);
1182 if (child_is_visible (GTK_WIDGET (child)) &&
1183 gtk_widget_is_sensitive (GTK_WIDGET (child)))
1184 return iter;
1185
1186 return gtk_flow_box_get_next_focusable (box, iter);
1187}
1188
1189static GSequenceIter *
1190gtk_flow_box_get_last_focusable (GtkFlowBox *box)
1191{
1192 GSequenceIter *iter;
1193
1194 iter = g_sequence_get_end_iter (BOX_PRIV (box)->children);
1195 return gtk_flow_box_get_previous_focusable (box, iter);
1196}
1197
1198
1199static GSequenceIter *
1200gtk_flow_box_get_above_focusable (GtkFlowBox *box,
1201 GSequenceIter *iter)
1202{
1203 GtkFlowBoxChild *child = NULL;
1204 int i;
1205
1206 while (TRUE)
1207 {
1208 i = 0;
1209 while (i < BOX_PRIV (box)->cur_children_per_line)
1210 {
1211 if (g_sequence_iter_is_begin (iter))
1212 return NULL;
1213 iter = g_sequence_iter_prev (iter);
1214 child = g_sequence_get (iter);
1215 if (child_is_visible (GTK_WIDGET (child)))
1216 i++;
1217 }
1218 if (child && gtk_widget_get_sensitive (GTK_WIDGET (child)))
1219 return iter;
1220 }
1221
1222 return NULL;
1223}
1224
1225static GSequenceIter *
1226gtk_flow_box_get_below_focusable (GtkFlowBox *box,
1227 GSequenceIter *iter)
1228{
1229 GtkFlowBoxChild *child = NULL;
1230 int i;
1231
1232 while (TRUE)
1233 {
1234 i = 0;
1235 while (i < BOX_PRIV (box)->cur_children_per_line)
1236 {
1237 iter = g_sequence_iter_next (iter);
1238 if (g_sequence_iter_is_end (iter))
1239 return NULL;
1240 child = g_sequence_get (iter);
1241 if (child_is_visible (GTK_WIDGET (child)))
1242 i++;
1243 }
1244 if (child && gtk_widget_get_sensitive (GTK_WIDGET (child)))
1245 return iter;
1246 }
1247
1248 return NULL;
1249}
1250
1251/* GtkWidget implementation {{{2 */
1252
1253/* Size allocation {{{3 */
1254
1255/* Used in columned modes where all items share at least their
1256 * equal widths or heights
1257 */
1258static void
1259get_max_item_size (GtkFlowBox *box,
1260 GtkOrientation orientation,
1261 int *min_size,
1262 int *nat_size)
1263{
1264 GSequenceIter *iter;
1265 int max_min_size = 0;
1266 int max_nat_size = 0;
1267
1268 for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
1269 !g_sequence_iter_is_end (iter);
1270 iter = g_sequence_iter_next (iter))
1271 {
1272 GtkWidget *child;
1273 int child_min, child_nat;
1274
1275 child = g_sequence_get (iter);
1276
1277 if (!child_is_visible (child))
1278 continue;
1279
1280 gtk_widget_measure (widget: child, orientation, for_size: -1,
1281 minimum: &child_min, natural: &child_nat,
1282 NULL, NULL);
1283
1284 max_min_size = MAX (max_min_size, child_min);
1285 max_nat_size = MAX (max_nat_size, child_nat);
1286 }
1287
1288 if (min_size)
1289 *min_size = max_min_size;
1290
1291 if (nat_size)
1292 *nat_size = max_nat_size;
1293}
1294
1295
1296/* Gets the largest minimum/natural size for a given size (used to get
1297 * the largest item heights for a fixed item width and the opposite)
1298 */
1299static void
1300get_largest_size_for_opposing_orientation (GtkFlowBox *box,
1301 GtkOrientation orientation,
1302 int item_size,
1303 int *min_item_size,
1304 int *nat_item_size)
1305{
1306 GSequenceIter *iter;
1307 int max_min_size = 0;
1308 int max_nat_size = 0;
1309
1310 for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
1311 !g_sequence_iter_is_end (iter);
1312 iter = g_sequence_iter_next (iter))
1313 {
1314 GtkWidget *child;
1315 int child_min, child_nat;
1316
1317 child = g_sequence_get (iter);
1318
1319 if (!child_is_visible (child))
1320 continue;
1321
1322 gtk_widget_measure (widget: child, orientation: 1 - orientation, for_size: item_size,
1323 minimum: &child_min, natural: &child_nat,
1324 NULL, NULL);
1325
1326 max_min_size = MAX (max_min_size, child_min);
1327 max_nat_size = MAX (max_nat_size, child_nat);
1328 }
1329
1330 if (min_item_size)
1331 *min_item_size = max_min_size;
1332
1333 if (nat_item_size)
1334 *nat_item_size = max_nat_size;
1335}
1336
1337/* Gets the largest minimum/natural size on a single line for a given size
1338 * (used to get the largest line heights for a fixed item width and the opposite
1339 * while iterating over a list of children, note the new index is returned)
1340 */
1341static GSequenceIter *
1342get_largest_size_for_line_in_opposing_orientation (GtkFlowBox *box,
1343 GtkOrientation orientation,
1344 GSequenceIter *cursor,
1345 int line_length,
1346 GtkRequestedSize *item_sizes,
1347 int extra_pixels,
1348 int *min_item_size,
1349 int *nat_item_size)
1350{
1351 GSequenceIter *iter;
1352 int max_min_size = 0;
1353 int max_nat_size = 0;
1354 int i;
1355
1356 i = 0;
1357 for (iter = cursor;
1358 !g_sequence_iter_is_end (iter) && i < line_length;
1359 iter = g_sequence_iter_next (iter))
1360 {
1361 GtkWidget *child;
1362 int child_min, child_nat, this_item_size;
1363
1364 child = g_sequence_get (iter);
1365
1366 if (!child_is_visible (child))
1367 continue;
1368
1369 /* Distribute the extra pixels to the first children in the line
1370 * (could be fancier and spread them out more evenly) */
1371 this_item_size = item_sizes[i].minimum_size;
1372 if (extra_pixels > 0 && ORIENTATION_ALIGN (box) == GTK_ALIGN_FILL)
1373 {
1374 this_item_size++;
1375 extra_pixels--;
1376 }
1377
1378 gtk_widget_measure (widget: child, orientation: 1 - orientation, for_size: this_item_size,
1379 minimum: &child_min, natural: &child_nat,
1380 NULL, NULL);
1381
1382 max_min_size = MAX (max_min_size, child_min);
1383 max_nat_size = MAX (max_nat_size, child_nat);
1384
1385 i++;
1386 }
1387
1388 if (min_item_size)
1389 *min_item_size = max_min_size;
1390
1391 if (nat_item_size)
1392 *nat_item_size = max_nat_size;
1393
1394 /* Return next item in the list */
1395 return iter;
1396}
1397
1398/* fit_aligned_item_requests() helper */
1399static int
1400gather_aligned_item_requests (GtkFlowBox *box,
1401 GtkOrientation orientation,
1402 int line_length,
1403 int item_spacing,
1404 int n_children,
1405 GtkRequestedSize *item_sizes)
1406{
1407 GSequenceIter *iter;
1408 int i;
1409 int extra_items, natural_line_size = 0;
1410
1411 extra_items = n_children % line_length;
1412
1413 i = 0;
1414 for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
1415 !g_sequence_iter_is_end (iter);
1416 iter = g_sequence_iter_next (iter))
1417 {
1418 GtkWidget *child;
1419 GtkAlign item_align;
1420 int child_min, child_nat;
1421 int position;
1422
1423 child = g_sequence_get (iter);
1424
1425 if (!child_is_visible (child))
1426 continue;
1427
1428 gtk_widget_measure (widget: child, orientation, for_size: -1,
1429 minimum: &child_min, natural: &child_nat,
1430 NULL, NULL);
1431
1432 /* Get the index and push it over for the last line when spreading to the end */
1433 position = i % line_length;
1434
1435 item_align = ORIENTATION_ALIGN (box);
1436 if (item_align == GTK_ALIGN_END && i >= n_children - extra_items)
1437 position += line_length - extra_items;
1438
1439 /* Round up the size of every column/row */
1440 item_sizes[position].minimum_size = MAX (item_sizes[position].minimum_size, child_min);
1441 item_sizes[position].natural_size = MAX (item_sizes[position].natural_size, child_nat);
1442
1443 i++;
1444 }
1445
1446 for (i = 0; i < line_length; i++)
1447 natural_line_size += item_sizes[i].natural_size;
1448
1449 natural_line_size += (line_length - 1) * item_spacing;
1450
1451 return natural_line_size;
1452}
1453
1454static GtkRequestedSize *
1455fit_aligned_item_requests (GtkFlowBox *box,
1456 GtkOrientation orientation,
1457 int avail_size,
1458 int item_spacing,
1459 int *line_length, /* in-out */
1460 int items_per_line,
1461 int n_children)
1462{
1463 GtkRequestedSize *sizes, *try_sizes;
1464 int try_line_size, try_length;
1465
1466 sizes = g_new0 (GtkRequestedSize, *line_length);
1467
1468 /* get the sizes for the initial guess */
1469 try_line_size = gather_aligned_item_requests (box,
1470 orientation,
1471 line_length: *line_length,
1472 item_spacing,
1473 n_children,
1474 item_sizes: sizes);
1475
1476 /* Try columnizing the whole thing and adding an item to the end of
1477 * the line; try to fit as many columns into the available size as
1478 * possible
1479 */
1480 for (try_length = *line_length + 1; try_line_size < avail_size; try_length++)
1481 {
1482 try_sizes = g_new0 (GtkRequestedSize, try_length);
1483 try_line_size = gather_aligned_item_requests (box,
1484 orientation,
1485 line_length: try_length,
1486 item_spacing,
1487 n_children,
1488 item_sizes: try_sizes);
1489
1490 if (try_line_size <= avail_size &&
1491 items_per_line >= try_length)
1492 {
1493 *line_length = try_length;
1494
1495 g_free (mem: sizes);
1496 sizes = try_sizes;
1497 }
1498 else
1499 {
1500 /* oops, this one failed; stick to the last size that fit and then return */
1501 g_free (mem: try_sizes);
1502 break;
1503 }
1504 }
1505
1506 return sizes;
1507}
1508
1509typedef struct {
1510 GArray *requested;
1511 int extra_pixels;
1512} AllocatedLine;
1513
1514static int
1515get_offset_pixels (GtkAlign align,
1516 int pixels)
1517{
1518 int offset;
1519
1520 switch (align) {
1521 case GTK_ALIGN_START:
1522 case GTK_ALIGN_FILL:
1523 offset = 0;
1524 break;
1525 case GTK_ALIGN_CENTER:
1526 offset = pixels / 2;
1527 break;
1528 case GTK_ALIGN_END:
1529 offset = pixels;
1530 break;
1531 case GTK_ALIGN_BASELINE:
1532 default:
1533 g_assert_not_reached ();
1534 break;
1535 }
1536
1537 return offset;
1538}
1539
1540static void
1541gtk_flow_box_size_allocate (GtkWidget *widget,
1542 int width,
1543 int height,
1544 int baseline)
1545{
1546 GtkFlowBox *box = GTK_FLOW_BOX (widget);
1547 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
1548 GtkAllocation child_allocation;
1549 int avail_size, avail_other_size, min_items, item_spacing, line_spacing;
1550 GtkAlign item_align;
1551 GtkAlign line_align;
1552 GtkRequestedSize *line_sizes = NULL;
1553 GtkRequestedSize *item_sizes = NULL;
1554 int min_item_size, nat_item_size;
1555 int line_length;
1556 int item_size = 0;
1557 int line_size = 0, min_fixed_line_size = 0, nat_fixed_line_size = 0;
1558 int line_offset, item_offset, n_children, n_lines, line_count;
1559 int extra_pixels = 0, extra_per_item = 0, extra_extra = 0;
1560 int extra_line_pixels = 0, extra_per_line = 0, extra_line_extra = 0;
1561 int i, this_line_size;
1562 GSequenceIter *iter;
1563
1564 min_items = MAX (1, priv->min_children_per_line);
1565
1566 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
1567 {
1568 avail_size = width;
1569 avail_other_size = height;
1570 item_spacing = priv->column_spacing; line_spacing = priv->row_spacing;
1571 }
1572 else /* GTK_ORIENTATION_VERTICAL */
1573 {
1574 avail_size = height;
1575 avail_other_size = width;
1576 item_spacing = priv->row_spacing;
1577 line_spacing = priv->column_spacing;
1578 }
1579
1580 item_align = ORIENTATION_ALIGN (box);
1581 line_align = OPPOSING_ORIENTATION_ALIGN (box);
1582
1583 /* Get how many lines we'll be needing to flow */
1584 n_children = get_visible_children (box);
1585 if (n_children <= 0)
1586 return;
1587
1588 /* Deal with ALIGNED/HOMOGENEOUS modes first, start with
1589 * initial guesses at item/line sizes
1590 */
1591 get_max_item_size (box, orientation: priv->orientation, min_size: &min_item_size, nat_size: &nat_item_size);
1592 if (nat_item_size <= 0)
1593 {
1594 child_allocation.x = 0;
1595 child_allocation.y = 0;
1596 child_allocation.width = 0;
1597 child_allocation.height = 0;
1598
1599 for (iter = g_sequence_get_begin_iter (seq: priv->children);
1600 !g_sequence_iter_is_end (iter);
1601 iter = g_sequence_iter_next (iter))
1602 {
1603 GtkWidget *child;
1604
1605 child = g_sequence_get (iter);
1606
1607 if (!child_is_visible (child))
1608 continue;
1609
1610 gtk_widget_size_allocate (widget: child, allocation: &child_allocation, baseline: -1);
1611 }
1612
1613 return;
1614 }
1615
1616 /* By default flow at the natural item width */
1617 line_length = avail_size / (nat_item_size + item_spacing);
1618
1619 /* After the above approximation, check if we can't fit one more on the line */
1620 if (line_length * item_spacing + (line_length + 1) * nat_item_size <= avail_size)
1621 line_length++;
1622
1623 /* Its possible we were allocated just less than the natural width of the
1624 * minimum item flow length
1625 */
1626 line_length = MAX (min_items, line_length);
1627 line_length = MIN (line_length, priv->max_children_per_line);
1628
1629 /* Here we just use the largest height-for-width and use that for the height
1630 * of all lines
1631 */
1632 if (priv->homogeneous)
1633 {
1634 n_lines = n_children / line_length;
1635 if ((n_children % line_length) > 0)
1636 n_lines++;
1637
1638 n_lines = MAX (n_lines, 1);
1639
1640 /* Now we need the real item allocation size */
1641 item_size = (avail_size - (line_length - 1) * item_spacing) / line_length;
1642
1643 /* Cut out the expand space if we're not distributing any */
1644 if (item_align != GTK_ALIGN_FILL)
1645 item_size = MIN (item_size, nat_item_size);
1646
1647 get_largest_size_for_opposing_orientation (box,
1648 orientation: priv->orientation,
1649 item_size,
1650 min_item_size: &min_fixed_line_size,
1651 nat_item_size: &nat_fixed_line_size);
1652
1653 /* resolve a fixed 'line_size' */
1654 line_size = (avail_other_size - (n_lines - 1) * line_spacing) / n_lines;
1655
1656 if (line_align != GTK_ALIGN_FILL)
1657 line_size = MIN (line_size, nat_fixed_line_size);
1658
1659 /* Get the real extra pixels in case of GTK_ALIGN_START lines */
1660 extra_pixels = avail_size - (line_length - 1) * item_spacing - item_size * line_length;
1661 extra_line_pixels = avail_other_size - (n_lines - 1) * line_spacing - line_size * n_lines;
1662 }
1663 else
1664 {
1665 gboolean first_line = TRUE;
1666
1667 /* Find the amount of columns that can fit aligned into the available space
1668 * and collect their requests.
1669 */
1670 item_sizes = fit_aligned_item_requests (box,
1671 orientation: priv->orientation,
1672 avail_size,
1673 item_spacing,
1674 line_length: &line_length,
1675 items_per_line: priv->max_children_per_line,
1676 n_children);
1677
1678 /* Calculate the number of lines after determining the final line_length */
1679 n_lines = n_children / line_length;
1680 if ((n_children % line_length) > 0)
1681 n_lines++;
1682
1683 n_lines = MAX (n_lines, 1);
1684 line_sizes = g_new0 (GtkRequestedSize, n_lines);
1685
1686 /* Get the available remaining size */
1687 avail_size -= (line_length - 1) * item_spacing;
1688 for (i = 0; i < line_length; i++)
1689 avail_size -= item_sizes[i].minimum_size;
1690
1691 /* Perform a natural allocation on the columnized items and get the remaining pixels */
1692 if (avail_size > 0)
1693 extra_pixels = gtk_distribute_natural_allocation (extra_space: avail_size, n_requested_sizes: line_length, sizes: item_sizes);
1694
1695 /* Now that we have the size of each column of items find the size of each individual
1696 * line based on the aligned item sizes.
1697 */
1698
1699 for (i = 0, iter = g_sequence_get_begin_iter (seq: priv->children);
1700 !g_sequence_iter_is_end (iter) && i < n_lines;
1701 i++)
1702 {
1703 iter = get_largest_size_for_line_in_opposing_orientation (box,
1704 orientation: priv->orientation,
1705 cursor: iter,
1706 line_length,
1707 item_sizes,
1708 extra_pixels,
1709 min_item_size: &line_sizes[i].minimum_size,
1710 nat_item_size: &line_sizes[i].natural_size);
1711
1712
1713 /* Its possible a line is made of completely invisible children */
1714 if (line_sizes[i].natural_size > 0)
1715 {
1716 if (first_line)
1717 first_line = FALSE;
1718 else
1719 avail_other_size -= line_spacing;
1720
1721 avail_other_size -= line_sizes[i].minimum_size;
1722
1723 line_sizes[i].data = GINT_TO_POINTER (i);
1724 }
1725 }
1726
1727 /* Distribute space among lines naturally */
1728 if (avail_other_size > 0)
1729 extra_line_pixels = gtk_distribute_natural_allocation (extra_space: avail_other_size, n_requested_sizes: n_lines, sizes: line_sizes);
1730 }
1731
1732 /*
1733 * Initial sizes of items/lines guessed at this point,
1734 * go on to distribute expand space if needed.
1735 */
1736
1737 priv->cur_children_per_line = line_length;
1738
1739 /* FIXME: This portion needs to consider which columns
1740 * and rows asked for expand space and distribute those
1741 * accordingly for the case of ALIGNED allocation.
1742 *
1743 * If at least one child in a column/row asked for expand;
1744 * we should make that row/column expand entirely.
1745 */
1746
1747 /* Calculate expand space per item */
1748 if (item_align == GTK_ALIGN_FILL)
1749 {
1750 extra_per_item = extra_pixels / line_length;
1751 extra_extra = extra_pixels % line_length;
1752 }
1753
1754 /* Calculate expand space per line */
1755 if (line_align == GTK_ALIGN_FILL)
1756 {
1757 extra_per_line = extra_line_pixels / n_lines;
1758 extra_line_extra = extra_line_pixels % n_lines;
1759 }
1760
1761 /* prepend extra space to item_offset/line_offset for SPREAD_END */
1762 item_offset = get_offset_pixels (align: item_align, pixels: extra_pixels);
1763 line_offset = get_offset_pixels (align: line_align, pixels: extra_line_pixels);
1764
1765 /* Get the allocation size for the first line */
1766 if (priv->homogeneous)
1767 this_line_size = line_size;
1768 else
1769 {
1770 this_line_size = line_sizes[0].minimum_size;
1771
1772 if (line_align == GTK_ALIGN_FILL)
1773 {
1774 this_line_size += extra_per_line;
1775
1776 if (extra_line_extra > 0)
1777 this_line_size++;
1778 }
1779 }
1780
1781 i = 0;
1782 line_count = 0;
1783 for (iter = g_sequence_get_begin_iter (seq: priv->children);
1784 !g_sequence_iter_is_end (iter);
1785 iter = g_sequence_iter_next (iter))
1786 {
1787 GtkWidget *child;
1788 int position;
1789 int this_item_size;
1790
1791 child = g_sequence_get (iter);
1792
1793 if (!child_is_visible (child))
1794 continue;
1795
1796 /* Get item position */
1797 position = i % line_length;
1798
1799 /* adjust the line_offset/count at the beginning of each new line */
1800 if (i > 0 && position == 0)
1801 {
1802 /* Push the line_offset */
1803 line_offset += this_line_size + line_spacing;
1804
1805 line_count++;
1806
1807 /* Get the new line size */
1808 if (priv->homogeneous)
1809 this_line_size = line_size;
1810 else
1811 {
1812 this_line_size = line_sizes[line_count].minimum_size;
1813
1814 if (line_align == GTK_ALIGN_FILL)
1815 {
1816 this_line_size += extra_per_line;
1817
1818 if (line_count < extra_line_extra)
1819 this_line_size++;
1820 }
1821 }
1822
1823 item_offset = 0;
1824
1825 if (item_align == GTK_ALIGN_CENTER)
1826 {
1827 item_offset += get_offset_pixels (align: item_align, pixels: extra_pixels);
1828 }
1829 else if (item_align == GTK_ALIGN_END)
1830 {
1831 item_offset += get_offset_pixels (align: item_align, pixels: extra_pixels);
1832
1833 /* If we're on the last line, prepend the space for
1834 * any leading items */
1835 if (line_count == n_lines -1)
1836 {
1837 int extra_items = n_children % line_length;
1838
1839 if (priv->homogeneous)
1840 {
1841 item_offset += item_size * (line_length - extra_items);
1842 item_offset += item_spacing * (line_length - extra_items);
1843 }
1844 else
1845 {
1846 int j;
1847
1848 for (j = 0; j < (line_length - extra_items); j++)
1849 {
1850 item_offset += item_sizes[j].minimum_size;
1851 item_offset += item_spacing;
1852 }
1853 }
1854 }
1855 }
1856 }
1857
1858 /* Push the index along for the last line when spreading to the end */
1859 if (item_align == GTK_ALIGN_END && line_count == n_lines -1)
1860 {
1861 int extra_items = n_children % line_length;
1862
1863 position += line_length - extra_items;
1864 }
1865
1866 if (priv->homogeneous)
1867 this_item_size = item_size;
1868 else
1869 this_item_size = item_sizes[position].minimum_size;
1870
1871 if (item_align == GTK_ALIGN_FILL)
1872 {
1873 this_item_size += extra_per_item;
1874
1875 if (position < extra_extra)
1876 this_item_size++;
1877 }
1878
1879 /* Do the actual allocation */
1880 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
1881 {
1882 child_allocation.x = item_offset;
1883 child_allocation.y = line_offset;
1884 child_allocation.width = this_item_size;
1885 child_allocation.height = this_line_size;
1886 }
1887 else /* GTK_ORIENTATION_VERTICAL */
1888 {
1889 child_allocation.x = line_offset;
1890 child_allocation.y = item_offset;
1891 child_allocation.width = this_line_size;
1892 child_allocation.height = this_item_size;
1893 }
1894
1895 if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
1896 child_allocation.x = width - child_allocation.x - child_allocation.width;
1897
1898 gtk_widget_size_allocate (widget: child, allocation: &child_allocation, baseline: -1);
1899
1900 item_offset += this_item_size;
1901 item_offset += item_spacing;
1902
1903 i++;
1904 }
1905
1906 g_free (mem: item_sizes);
1907 g_free (mem: line_sizes);
1908}
1909
1910static GtkSizeRequestMode
1911gtk_flow_box_get_request_mode (GtkWidget *widget)
1912{
1913 GtkFlowBox *box = GTK_FLOW_BOX (widget);
1914
1915 return (BOX_PRIV (box)->orientation == GTK_ORIENTATION_HORIZONTAL) ?
1916 GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH : GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT;
1917}
1918
1919/* Gets the largest minimum and natural length of
1920 * 'line_length' consecutive items when aligned into rows/columns */
1921static void
1922get_largest_aligned_line_length (GtkFlowBox *box,
1923 GtkOrientation orientation,
1924 int line_length,
1925 int *min_size,
1926 int *nat_size)
1927{
1928 GSequenceIter *iter;
1929 int max_min_size = 0;
1930 int max_nat_size = 0;
1931 int spacing, i;
1932 GtkRequestedSize *aligned_item_sizes;
1933
1934 if (orientation == GTK_ORIENTATION_HORIZONTAL)
1935 spacing = BOX_PRIV (box)->column_spacing;
1936 else
1937 spacing = BOX_PRIV (box)->row_spacing;
1938
1939 aligned_item_sizes = g_new0 (GtkRequestedSize, line_length);
1940
1941 /* Get the largest sizes of each index in the line.
1942 */
1943 i = 0;
1944 for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
1945 !g_sequence_iter_is_end (iter);
1946 iter = g_sequence_iter_next (iter))
1947 {
1948 GtkWidget *child;
1949 int child_min, child_nat;
1950
1951 child = g_sequence_get (iter);
1952 if (!child_is_visible (child))
1953 continue;
1954
1955 gtk_widget_measure (widget: child, orientation, for_size: -1,
1956 minimum: &child_min, natural: &child_nat,
1957 NULL, NULL);
1958
1959 aligned_item_sizes[i % line_length].minimum_size =
1960 MAX (aligned_item_sizes[i % line_length].minimum_size, child_min);
1961
1962 aligned_item_sizes[i % line_length].natural_size =
1963 MAX (aligned_item_sizes[i % line_length].natural_size, child_nat);
1964
1965 i++;
1966 }
1967
1968 /* Add up the largest indexes */
1969 for (i = 0; i < line_length; i++)
1970 {
1971 max_min_size += aligned_item_sizes[i].minimum_size;
1972 max_nat_size += aligned_item_sizes[i].natural_size;
1973 }
1974
1975 g_free (mem: aligned_item_sizes);
1976
1977 max_min_size += (line_length - 1) * spacing;
1978 max_nat_size += (line_length - 1) * spacing;
1979
1980 if (min_size)
1981 *min_size = max_min_size;
1982
1983 if (nat_size)
1984 *nat_size = max_nat_size;
1985}
1986
1987static void
1988gtk_flow_box_measure (GtkWidget *widget,
1989 GtkOrientation orientation,
1990 int for_size,
1991 int *minimum,
1992 int *natural,
1993 int *minimum_baseline,
1994 int *natural_baseline)
1995{
1996 GtkFlowBox *box = GTK_FLOW_BOX (widget);
1997 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
1998
1999 if (orientation == GTK_ORIENTATION_HORIZONTAL)
2000 {
2001 if (for_size < 0)
2002 {
2003 int min_item_width, nat_item_width;
2004 int min_items, nat_items;
2005 int min_width, nat_width;
2006
2007 min_items = MAX (1, priv->min_children_per_line);
2008 nat_items = MAX (min_items, priv->max_children_per_line);
2009
2010 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
2011 {
2012 min_width = nat_width = 0;
2013
2014 if (!priv->homogeneous)
2015 {
2016 /* When not homogeneous; horizontally oriented boxes
2017 * need enough width for the widest row
2018 */
2019 if (min_items == 1)
2020 {
2021 get_max_item_size (box,
2022 orientation: GTK_ORIENTATION_HORIZONTAL,
2023 min_size: &min_item_width,
2024 nat_size: &nat_item_width);
2025
2026 min_width += min_item_width;
2027 nat_width += nat_item_width;
2028 }
2029 else
2030 {
2031 int min_line_length, nat_line_length;
2032
2033 get_largest_aligned_line_length (box,
2034 orientation: GTK_ORIENTATION_HORIZONTAL,
2035 line_length: min_items,
2036 min_size: &min_line_length,
2037 nat_size: &nat_line_length);
2038
2039 if (nat_items > min_items)
2040 get_largest_aligned_line_length (box,
2041 orientation: GTK_ORIENTATION_HORIZONTAL,
2042 line_length: nat_items,
2043 NULL,
2044 nat_size: &nat_line_length);
2045
2046 min_width += min_line_length;
2047 nat_width += nat_line_length;
2048 }
2049 }
2050 else /* In homogeneous mode; horizontally oriented boxes
2051 * give the same width to all children */
2052 {
2053 get_max_item_size (box, orientation: GTK_ORIENTATION_HORIZONTAL,
2054 min_size: &min_item_width, nat_size: &nat_item_width);
2055
2056 min_width += min_item_width * min_items;
2057 min_width += (min_items -1) * priv->column_spacing;
2058
2059 nat_width += nat_item_width * nat_items;
2060 nat_width += (nat_items -1) * priv->column_spacing;
2061 }
2062 }
2063 else /* GTK_ORIENTATION_VERTICAL */
2064 {
2065 /* Return the width for the minimum height */
2066 int min_height;
2067 int dummy;
2068
2069 gtk_flow_box_measure (widget,
2070 orientation: GTK_ORIENTATION_VERTICAL,
2071 for_size: -1,
2072 minimum: &min_height, natural: &dummy,
2073 NULL, NULL);
2074 gtk_flow_box_measure (widget,
2075 orientation: GTK_ORIENTATION_HORIZONTAL,
2076 for_size: min_height,
2077 minimum: &min_width, natural: &nat_width,
2078 NULL, NULL);
2079 }
2080
2081 *minimum = min_width;
2082 *natural = nat_width;
2083 }
2084 else
2085 {
2086 int min_item_height, nat_item_height;
2087 int min_items;
2088 int min_width, nat_width;
2089 int avail_size, n_children;
2090
2091 min_items = MAX (1, priv->min_children_per_line);
2092
2093 min_width = 0;
2094 nat_width = 0;
2095
2096 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
2097 {
2098 /* Return the minimum width */
2099 gtk_flow_box_measure (widget,
2100 orientation: GTK_ORIENTATION_HORIZONTAL,
2101 for_size: -1,
2102 minimum: &min_width, natural: &nat_width,
2103 NULL, NULL);
2104 }
2105 else /* GTK_ORIENTATION_VERTICAL */
2106 {
2107 int min_height;
2108 int line_length;
2109 int item_size, extra_pixels;
2110 int dummy;
2111
2112 n_children = get_visible_children (box);
2113 if (n_children <= 0)
2114 goto out_width;
2115
2116 /* Make sure its no smaller than the minimum */
2117 gtk_flow_box_measure (widget,
2118 orientation: GTK_ORIENTATION_VERTICAL,
2119 for_size: -1,
2120 minimum: &min_height, natural: &dummy,
2121 NULL, NULL);
2122
2123 avail_size = MAX (for_size, min_height);
2124 if (avail_size <= 0)
2125 goto out_width;
2126
2127 get_max_item_size (box, orientation: GTK_ORIENTATION_VERTICAL, min_size: &min_item_height, nat_size: &nat_item_height);
2128 if (nat_item_height <= 0)
2129 goto out_width;
2130
2131 /* By default flow at the natural item width */
2132 line_length = avail_size / (nat_item_height + priv->row_spacing);
2133
2134 /* After the above approximation, check if we can't fit one more on the line */
2135 if (line_length * priv->row_spacing + (line_length + 1) * nat_item_height <= avail_size)
2136 line_length++;
2137
2138 /* Its possible we were allocated just less than the natural width of the
2139 * minimum item flow length
2140 */
2141 line_length = MAX (min_items, line_length);
2142 line_length = MIN (line_length, priv->max_children_per_line);
2143
2144 /* Now we need the real item allocation size */
2145 item_size = (avail_size - (line_length - 1) * priv->row_spacing) / line_length;
2146
2147 /* Cut out the expand space if we're not distributing any */
2148 if (gtk_widget_get_valign (widget) != GTK_ALIGN_FILL)
2149 {
2150 item_size = MIN (item_size, nat_item_height);
2151 extra_pixels = 0;
2152 }
2153 else
2154 /* Collect the extra pixels for expand children */
2155 extra_pixels = (avail_size - (line_length - 1) * priv->row_spacing) % line_length;
2156
2157 if (priv->homogeneous)
2158 {
2159 int min_item_width, nat_item_width;
2160 int lines;
2161
2162 /* Here we just use the largest height-for-width and
2163 * add up the size accordingly
2164 */
2165 get_largest_size_for_opposing_orientation (box,
2166 orientation: GTK_ORIENTATION_VERTICAL,
2167 item_size,
2168 min_item_size: &min_item_width,
2169 nat_item_size: &nat_item_width);
2170
2171 /* Round up how many lines we need to allocate for */
2172 n_children = get_visible_children (box);
2173 lines = n_children / line_length;
2174 if ((n_children % line_length) > 0)
2175 lines++;
2176
2177 min_width = min_item_width * lines;
2178 nat_width = nat_item_width * lines;
2179
2180 min_width += (lines - 1) * priv->column_spacing;
2181 nat_width += (lines - 1) * priv->column_spacing;
2182 }
2183 else
2184 {
2185 int min_line_width, nat_line_width, i;
2186 gboolean first_line = TRUE;
2187 GtkRequestedSize *item_sizes;
2188 GSequenceIter *iter;
2189
2190 /* First get the size each set of items take to span the line
2191 * when aligning the items above and below after flowping.
2192 */
2193 item_sizes = fit_aligned_item_requests (box,
2194 orientation: priv->orientation,
2195 avail_size,
2196 item_spacing: priv->row_spacing,
2197 line_length: &line_length,
2198 items_per_line: priv->max_children_per_line,
2199 n_children);
2200
2201 /* Get the available remaining size */
2202 avail_size -= (line_length - 1) * priv->column_spacing;
2203 for (i = 0; i < line_length; i++)
2204 avail_size -= item_sizes[i].minimum_size;
2205
2206 if (avail_size > 0)
2207 extra_pixels = gtk_distribute_natural_allocation (extra_space: avail_size, n_requested_sizes: line_length, sizes: item_sizes);
2208
2209 for (iter = g_sequence_get_begin_iter (seq: priv->children);
2210 !g_sequence_iter_is_end (iter);)
2211 {
2212 iter = get_largest_size_for_line_in_opposing_orientation (box,
2213 orientation: GTK_ORIENTATION_VERTICAL,
2214 cursor: iter,
2215 line_length,
2216 item_sizes,
2217 extra_pixels,
2218 min_item_size: &min_line_width,
2219 nat_item_size: &nat_line_width);
2220
2221 /* Its possible the last line only had invisible widgets */
2222 if (nat_line_width > 0)
2223 {
2224 if (first_line)
2225 first_line = FALSE;
2226 else
2227 {
2228 min_width += priv->column_spacing;
2229 nat_width += priv->column_spacing;
2230 }
2231
2232 min_width += min_line_width;
2233 nat_width += nat_line_width;
2234 }
2235 }
2236 g_free (mem: item_sizes);
2237 }
2238 }
2239
2240 out_width:
2241 *minimum = min_width;
2242 *natural = nat_width;
2243 }
2244 }
2245 else
2246 {
2247 if (for_size < 0)
2248 {
2249 int min_item_height, nat_item_height;
2250 int min_items, nat_items;
2251 int min_height, nat_height;
2252
2253 min_items = MAX (1, priv->min_children_per_line);
2254 nat_items = MAX (min_items, priv->max_children_per_line);
2255
2256 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
2257 {
2258 /* Return the height for the minimum width */
2259 int min_width;
2260 int dummy;
2261
2262 gtk_flow_box_measure (widget,
2263 orientation: GTK_ORIENTATION_HORIZONTAL,
2264 for_size: -1,
2265 minimum: &min_width, natural: &dummy,
2266 NULL, NULL);
2267 gtk_flow_box_measure (widget,
2268 orientation: GTK_ORIENTATION_VERTICAL,
2269 for_size: min_width,
2270 minimum: &min_height, natural: &nat_height,
2271 NULL, NULL);
2272 }
2273 else /* GTK_ORIENTATION_VERTICAL */
2274 {
2275 min_height = nat_height = 0;
2276
2277 if (! priv->homogeneous)
2278 {
2279 /* When not homogeneous; vertically oriented boxes
2280 * need enough height for the tallest column
2281 */
2282 if (min_items == 1)
2283 {
2284 get_max_item_size (box, orientation: GTK_ORIENTATION_VERTICAL,
2285 min_size: &min_item_height, nat_size: &nat_item_height);
2286
2287 min_height += min_item_height;
2288 nat_height += nat_item_height;
2289 }
2290 else
2291 {
2292 int min_line_length, nat_line_length;
2293
2294 get_largest_aligned_line_length (box,
2295 orientation: GTK_ORIENTATION_VERTICAL,
2296 line_length: min_items,
2297 min_size: &min_line_length,
2298 nat_size: &nat_line_length);
2299
2300 if (nat_items > min_items)
2301 get_largest_aligned_line_length (box,
2302 orientation: GTK_ORIENTATION_VERTICAL,
2303 line_length: nat_items,
2304 NULL,
2305 nat_size: &nat_line_length);
2306
2307 min_height += min_line_length;
2308 nat_height += nat_line_length;
2309 }
2310
2311 }
2312 else
2313 {
2314 /* In homogeneous mode; vertically oriented boxes
2315 * give the same height to all children
2316 */
2317 get_max_item_size (box,
2318 orientation: GTK_ORIENTATION_VERTICAL,
2319 min_size: &min_item_height,
2320 nat_size: &nat_item_height);
2321
2322 min_height += min_item_height * min_items;
2323 min_height += (min_items -1) * priv->row_spacing;
2324
2325 nat_height += nat_item_height * nat_items;
2326 nat_height += (nat_items -1) * priv->row_spacing;
2327 }
2328 }
2329
2330 *minimum = min_height;
2331 *natural = nat_height;
2332 }
2333 else
2334 {
2335 int min_item_width, nat_item_width;
2336 int min_items;
2337 int min_height, nat_height;
2338 int avail_size, n_children;
2339
2340 min_items = MAX (1, priv->min_children_per_line);
2341
2342 min_height = 0;
2343 nat_height = 0;
2344
2345 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
2346 {
2347 int min_width;
2348 int line_length;
2349 int item_size, extra_pixels;
2350 int dummy;
2351
2352 n_children = get_visible_children (box);
2353 if (n_children <= 0)
2354 goto out_height;
2355
2356 /* Make sure its no smaller than the minimum */
2357 gtk_flow_box_measure (widget,
2358 orientation: GTK_ORIENTATION_HORIZONTAL,
2359 for_size: -1,
2360 minimum: &min_width, natural: &dummy,
2361 NULL, NULL);
2362
2363 avail_size = MAX (for_size, min_width);
2364 if (avail_size <= 0)
2365 goto out_height;
2366
2367 get_max_item_size (box, orientation: GTK_ORIENTATION_HORIZONTAL, min_size: &min_item_width, nat_size: &nat_item_width);
2368 if (nat_item_width <= 0)
2369 goto out_height;
2370
2371 /* By default flow at the natural item width */
2372 line_length = avail_size / (nat_item_width + priv->column_spacing);
2373
2374 /* After the above approximation, check if we can't fit one more on the line */
2375 if (line_length * priv->column_spacing + (line_length + 1) * nat_item_width <= avail_size)
2376 line_length++;
2377
2378 /* Its possible we were allocated just less than the natural width of the
2379 * minimum item flow length
2380 */
2381 line_length = MAX (min_items, line_length);
2382 line_length = MIN (line_length, priv->max_children_per_line);
2383
2384 /* Now we need the real item allocation size */
2385 item_size = (avail_size - (line_length - 1) * priv->column_spacing) / line_length;
2386
2387 /* Cut out the expand space if we're not distributing any */
2388 if (gtk_widget_get_halign (widget) != GTK_ALIGN_FILL)
2389 {
2390 item_size = MIN (item_size, nat_item_width);
2391 extra_pixels = 0;
2392 }
2393 else
2394 /* Collect the extra pixels for expand children */
2395 extra_pixels = (avail_size - (line_length - 1) * priv->column_spacing) % line_length;
2396
2397 if (priv->homogeneous)
2398 {
2399 int min_item_height, nat_item_height;
2400 int lines;
2401
2402 /* Here we just use the largest height-for-width and
2403 * add up the size accordingly
2404 */
2405 get_largest_size_for_opposing_orientation (box,
2406 orientation: GTK_ORIENTATION_HORIZONTAL,
2407 item_size,
2408 min_item_size: &min_item_height,
2409 nat_item_size: &nat_item_height);
2410
2411 /* Round up how many lines we need to allocate for */
2412 lines = n_children / line_length;
2413 if ((n_children % line_length) > 0)
2414 lines++;
2415
2416 min_height = min_item_height * lines;
2417 nat_height = nat_item_height * lines;
2418
2419 min_height += (lines - 1) * priv->row_spacing;
2420 nat_height += (lines - 1) * priv->row_spacing;
2421 }
2422 else
2423 {
2424 int min_line_height, nat_line_height, i;
2425 gboolean first_line = TRUE;
2426 GtkRequestedSize *item_sizes;
2427 GSequenceIter *iter;
2428
2429 /* First get the size each set of items take to span the line
2430 * when aligning the items above and below after flowping.
2431 */
2432 item_sizes = fit_aligned_item_requests (box,
2433 orientation: priv->orientation,
2434 avail_size,
2435 item_spacing: priv->column_spacing,
2436 line_length: &line_length,
2437 items_per_line: priv->max_children_per_line,
2438 n_children);
2439
2440 /* Get the available remaining size */
2441 avail_size -= (line_length - 1) * priv->column_spacing;
2442 for (i = 0; i < line_length; i++)
2443 avail_size -= item_sizes[i].minimum_size;
2444
2445 if (avail_size > 0)
2446 extra_pixels = gtk_distribute_natural_allocation (extra_space: avail_size, n_requested_sizes: line_length, sizes: item_sizes);
2447
2448 for (iter = g_sequence_get_begin_iter (seq: priv->children);
2449 !g_sequence_iter_is_end (iter);)
2450 {
2451 iter = get_largest_size_for_line_in_opposing_orientation (box,
2452 orientation: GTK_ORIENTATION_HORIZONTAL,
2453 cursor: iter,
2454 line_length,
2455 item_sizes,
2456 extra_pixels,
2457 min_item_size: &min_line_height,
2458 nat_item_size: &nat_line_height);
2459 /* Its possible the line only had invisible widgets */
2460 if (nat_line_height > 0)
2461 {
2462 if (first_line)
2463 first_line = FALSE;
2464 else
2465 {
2466 min_height += priv->row_spacing;
2467 nat_height += priv->row_spacing;
2468 }
2469
2470 min_height += min_line_height;
2471 nat_height += nat_line_height;
2472 }
2473 }
2474
2475 g_free (mem: item_sizes);
2476 }
2477 }
2478 else /* GTK_ORIENTATION_VERTICAL */
2479 {
2480 /* Return the minimum height */
2481 gtk_flow_box_measure (widget,
2482 orientation: GTK_ORIENTATION_VERTICAL,
2483 for_size: -1,
2484 minimum: &min_height, natural: &nat_height,
2485 NULL, NULL);
2486 }
2487
2488 out_height:
2489 *minimum = min_height;
2490 *natural = nat_height;
2491 }
2492 }
2493}
2494
2495/* Drawing {{{3 */
2496
2497static void
2498gtk_flow_box_snapshot (GtkWidget *widget,
2499 GtkSnapshot *snapshot)
2500{
2501 GtkFlowBox *box = GTK_FLOW_BOX (widget);
2502 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
2503 int x, y, width, height;
2504
2505 GTK_WIDGET_CLASS (gtk_flow_box_parent_class)->snapshot (widget, snapshot);
2506
2507 x = 0;
2508 y = 0;
2509 width = gtk_widget_get_width (widget);
2510 height = gtk_widget_get_height (widget);
2511
2512 if (priv->rubberband_first && priv->rubberband_last)
2513 {
2514 GtkStyleContext *context;
2515 GSequenceIter *iter, *iter1, *iter2;
2516 GdkRectangle line_rect, rect;
2517 GArray *lines;
2518 gboolean vertical;
2519 cairo_t *cr;
2520
2521 vertical = priv->orientation == GTK_ORIENTATION_VERTICAL;
2522
2523 cr = gtk_snapshot_append_cairo (snapshot,
2524 bounds: &GRAPHENE_RECT_INIT (x, y, width, height));
2525
2526 context = gtk_widget_get_style_context (widget);
2527 gtk_style_context_save_to_node (context, node: priv->rubberband_node);
2528
2529 iter1 = CHILD_PRIV (priv->rubberband_first)->iter;
2530 iter2 = CHILD_PRIV (priv->rubberband_last)->iter;
2531
2532 if (g_sequence_iter_compare (a: iter2, b: iter1) < 0)
2533 {
2534 iter = iter1;
2535 iter1 = iter2;
2536 iter2 = iter;
2537 }
2538
2539 line_rect.width = 0;
2540 lines = g_array_new (FALSE, FALSE, element_size: sizeof (GdkRectangle));
2541
2542 for (iter = iter1;
2543 !g_sequence_iter_is_end (iter);
2544 iter = g_sequence_iter_next (iter))
2545 {
2546 GtkWidget *child;
2547
2548 child = g_sequence_get (iter);
2549 gtk_widget_get_allocation (GTK_WIDGET (child), allocation: &rect);
2550 if (line_rect.width == 0)
2551 line_rect = rect;
2552 else
2553 {
2554 if ((vertical && rect.x == line_rect.x) ||
2555 (!vertical && rect.y == line_rect.y))
2556 gdk_rectangle_union (src1: &rect, src2: &line_rect, dest: &line_rect);
2557 else
2558 {
2559 g_array_append_val (lines, line_rect);
2560 line_rect = rect;
2561 }
2562 }
2563
2564 if (g_sequence_iter_compare (a: iter, b: iter2) == 0)
2565 break;
2566 }
2567
2568 if (line_rect.width != 0)
2569 g_array_append_val (lines, line_rect);
2570
2571 if (lines->len > 0)
2572 {
2573 cairo_path_t *path;
2574 GtkBorder border;
2575 const GdkRGBA *border_color;
2576
2577 if (vertical)
2578 path_from_vertical_line_rects (cr, lines: (GdkRectangle *)lines->data, n_lines: lines->len);
2579 else
2580 path_from_horizontal_line_rects (cr, lines: (GdkRectangle *)lines->data, n_lines: lines->len);
2581
2582 /* For some reason we need to copy and reapply the path,
2583 * or it gets eaten by gtk_render_background()
2584 */
2585 path = cairo_copy_path (cr);
2586
2587 cairo_save (cr);
2588 cairo_clip (cr);
2589 gtk_render_background (context, cr, x, y, width, height);
2590 cairo_restore (cr);
2591
2592 cairo_append_path (cr, path);
2593 cairo_path_destroy (path);
2594
2595 border_color = gtk_css_color_value_get_rgba (color: _gtk_style_context_peek_property (context, property_id: GTK_CSS_PROPERTY_BORDER_TOP_COLOR));
2596 gtk_style_context_get_border (context, border: &border);
2597
2598 cairo_set_line_width (cr, width: border.left);
2599 gdk_cairo_set_source_rgba (cr, rgba: border_color);
2600 cairo_stroke (cr);
2601 }
2602 g_array_free (array: lines, TRUE);
2603
2604 gtk_style_context_restore (context);
2605 cairo_destroy (cr);
2606 }
2607}
2608
2609/* Autoscrolling {{{3 */
2610
2611static void
2612remove_autoscroll (GtkFlowBox *box)
2613{
2614 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
2615
2616 if (priv->autoscroll_id)
2617 {
2618 gtk_widget_remove_tick_callback (GTK_WIDGET (box), id: priv->autoscroll_id);
2619 priv->autoscroll_id = 0;
2620 }
2621
2622 priv->autoscroll_mode = GTK_SCROLL_NONE;
2623}
2624
2625static gboolean
2626autoscroll_cb (GtkWidget *widget,
2627 GdkFrameClock *frame_clock,
2628 gpointer data)
2629{
2630 GtkFlowBox *box = GTK_FLOW_BOX (data);
2631 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
2632 GtkAdjustment *adjustment;
2633 double factor;
2634 double increment;
2635 double value;
2636
2637 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
2638 adjustment = priv->vadjustment;
2639 else
2640 adjustment = priv->hadjustment;
2641
2642 switch (priv->autoscroll_mode)
2643 {
2644 case GTK_SCROLL_STEP_FORWARD:
2645 factor = AUTOSCROLL_FACTOR;
2646 break;
2647 case GTK_SCROLL_STEP_BACKWARD:
2648 factor = - AUTOSCROLL_FACTOR;
2649 break;
2650 case GTK_SCROLL_PAGE_FORWARD:
2651 factor = AUTOSCROLL_FACTOR_FAST;
2652 break;
2653 case GTK_SCROLL_PAGE_BACKWARD:
2654 factor = - AUTOSCROLL_FACTOR_FAST;
2655 break;
2656 case GTK_SCROLL_NONE:
2657 case GTK_SCROLL_JUMP:
2658 case GTK_SCROLL_STEP_UP:
2659 case GTK_SCROLL_STEP_DOWN:
2660 case GTK_SCROLL_STEP_LEFT:
2661 case GTK_SCROLL_STEP_RIGHT:
2662 case GTK_SCROLL_PAGE_UP:
2663 case GTK_SCROLL_PAGE_DOWN:
2664 case GTK_SCROLL_PAGE_LEFT:
2665 case GTK_SCROLL_PAGE_RIGHT:
2666 case GTK_SCROLL_START:
2667 case GTK_SCROLL_END:
2668 default:
2669 g_assert_not_reached ();
2670 break;
2671 }
2672
2673 increment = gtk_adjustment_get_step_increment (adjustment) / factor;
2674
2675 value = gtk_adjustment_get_value (adjustment);
2676 value += increment;
2677 gtk_adjustment_set_value (adjustment, value);
2678
2679 if (priv->rubberband_select)
2680 {
2681 GdkEventSequence *sequence;
2682 double x, y;
2683 GtkFlowBoxChild *child;
2684
2685 sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (priv->drag_gesture));
2686 gtk_gesture_get_point (gesture: priv->drag_gesture, sequence, x: &x, y: &y);
2687
2688 child = gtk_flow_box_get_child_at_pos (box, x, y);
2689
2690 if (child != NULL)
2691 priv->rubberband_last = child;
2692 }
2693
2694 return G_SOURCE_CONTINUE;
2695}
2696
2697static void
2698add_autoscroll (GtkFlowBox *box)
2699{
2700 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
2701
2702 if (priv->autoscroll_id != 0 ||
2703 priv->autoscroll_mode == GTK_SCROLL_NONE)
2704 return;
2705
2706 priv->autoscroll_id = gtk_widget_add_tick_callback (GTK_WIDGET (box),
2707 callback: autoscroll_cb,
2708 user_data: box,
2709 NULL);
2710}
2711
2712static gboolean
2713get_view_rect (GtkFlowBox *box,
2714 GdkRectangle *rect)
2715{
2716 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
2717 GtkWidget *parent;
2718
2719 parent = gtk_widget_get_parent (GTK_WIDGET (box));
2720 if (GTK_IS_VIEWPORT (parent))
2721 {
2722 rect->x = gtk_adjustment_get_value (adjustment: priv->hadjustment);
2723 rect->y = gtk_adjustment_get_value (adjustment: priv->vadjustment);
2724 rect->width = gtk_widget_get_width (widget: parent);
2725 rect->height = gtk_widget_get_height (widget: parent);
2726 return TRUE;
2727 }
2728
2729 return FALSE;
2730}
2731
2732static void
2733update_autoscroll_mode (GtkFlowBox *box,
2734 int x,
2735 int y)
2736{
2737 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
2738 GtkScrollType mode = GTK_SCROLL_NONE;
2739 GdkRectangle rect;
2740 int size, pos;
2741
2742 if (priv->rubberband_select && get_view_rect (box, rect: &rect))
2743 {
2744 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
2745 {
2746 size = rect.width;
2747 pos = x - rect.x;
2748 }
2749 else
2750 {
2751 size = rect.height;
2752 pos = y - rect.y;
2753 }
2754
2755 if (pos < 0 - AUTOSCROLL_FAST_DISTANCE)
2756 mode = GTK_SCROLL_PAGE_BACKWARD;
2757 else if (pos > size + AUTOSCROLL_FAST_DISTANCE)
2758 mode = GTK_SCROLL_PAGE_FORWARD;
2759 else if (pos < 0)
2760 mode = GTK_SCROLL_STEP_BACKWARD;
2761 else if (pos > size)
2762 mode = GTK_SCROLL_STEP_FORWARD;
2763 }
2764
2765 if (mode != priv->autoscroll_mode)
2766 {
2767 remove_autoscroll (box);
2768 priv->autoscroll_mode = mode;
2769 add_autoscroll (box);
2770 }
2771}
2772
2773/* Event handling {{{3 */
2774
2775static void
2776gtk_flow_box_drag_gesture_update (GtkGestureDrag *gesture,
2777 double offset_x,
2778 double offset_y,
2779 GtkFlowBox *box)
2780{
2781 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
2782 double start_x, start_y;
2783 GtkFlowBoxChild *child;
2784 GtkCssNode *widget_node;
2785
2786 gtk_gesture_drag_get_start_point (gesture, x: &start_x, y: &start_y);
2787
2788 if (!priv->rubberband_select &&
2789 (offset_x * offset_x) + (offset_y * offset_y) > RUBBERBAND_START_DISTANCE * RUBBERBAND_START_DISTANCE)
2790 {
2791 priv->rubberband_select = TRUE;
2792 priv->rubberband_first = gtk_flow_box_get_child_at_pos (box, x: start_x, y: start_y);
2793
2794 widget_node = gtk_widget_get_css_node (GTK_WIDGET (box));
2795 priv->rubberband_node = gtk_css_node_new ();
2796 gtk_css_node_set_name (cssnode: priv->rubberband_node, name: g_quark_from_static_string (string: "rubberband"));
2797 gtk_css_node_set_parent (cssnode: priv->rubberband_node, parent: widget_node);
2798 gtk_css_node_set_state (cssnode: priv->rubberband_node, state_flags: gtk_css_node_get_state (cssnode: widget_node));
2799 g_object_unref (object: priv->rubberband_node);
2800
2801 /* Grab focus here, so Escape-to-stop-rubberband works */
2802 if (priv->rubberband_first)
2803 gtk_flow_box_update_cursor (box, child: priv->rubberband_first);
2804 gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED);
2805 }
2806
2807 if (priv->rubberband_select)
2808 {
2809 child = gtk_flow_box_get_child_at_pos (box, x: start_x + offset_x,
2810 y: start_y + offset_y);
2811
2812 if (priv->rubberband_first == NULL)
2813 {
2814 priv->rubberband_first = child;
2815 if (priv->rubberband_first)
2816 gtk_flow_box_update_cursor (box, child: priv->rubberband_first);
2817 }
2818 if (child != NULL)
2819 priv->rubberband_last = child;
2820
2821 update_autoscroll_mode (box, x: start_x + offset_x, y: start_y + offset_y);
2822 gtk_widget_queue_draw (GTK_WIDGET (box));
2823 }
2824}
2825
2826static void
2827gtk_flow_box_click_gesture_pressed (GtkGestureClick *gesture,
2828 guint n_press,
2829 double x,
2830 double y,
2831 GtkFlowBox *box)
2832{
2833 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
2834 GtkFlowBoxChild *child;
2835
2836 child = gtk_flow_box_get_child_at_pos (box, x, y);
2837
2838 if (child == NULL)
2839 return;
2840
2841 /* The drag gesture is only triggered by first press */
2842 if (n_press != 1)
2843 gtk_gesture_set_state (gesture: priv->drag_gesture, state: GTK_EVENT_SEQUENCE_DENIED);
2844
2845 priv->active_child = child;
2846 gtk_widget_queue_draw (GTK_WIDGET (box));
2847
2848 if (n_press == 2 && !priv->activate_on_single_click)
2849 {
2850 gtk_gesture_set_state (GTK_GESTURE (gesture),
2851 state: GTK_EVENT_SEQUENCE_CLAIMED);
2852 g_signal_emit (instance: box, signal_id: signals[CHILD_ACTIVATED], detail: 0, child);
2853 }
2854}
2855
2856static void
2857gtk_flow_box_click_unpaired_release (GtkGestureClick *gesture,
2858 double x,
2859 double y,
2860 guint button,
2861 GdkEventSequence *sequence,
2862 GtkFlowBox *box)
2863{
2864 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
2865 GtkFlowBoxChild *child;
2866
2867 if (!priv->activate_on_single_click || !priv->accept_unpaired_release)
2868 return;
2869
2870 child = gtk_flow_box_get_child_at_pos (box, x, y);
2871
2872 if (child)
2873 gtk_flow_box_select_and_activate (box, child);
2874}
2875
2876static void
2877gtk_flow_box_click_gesture_released (GtkGestureClick *gesture,
2878 guint n_press,
2879 double x,
2880 double y,
2881 GtkFlowBox *box)
2882{
2883 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
2884
2885 if (priv->active_child != NULL &&
2886 priv->active_child == gtk_flow_box_get_child_at_pos (box, x, y))
2887 {
2888 gtk_gesture_set_state (GTK_GESTURE (gesture),
2889 state: GTK_EVENT_SEQUENCE_CLAIMED);
2890
2891 if (priv->activate_on_single_click)
2892 gtk_flow_box_select_and_activate (box, child: priv->active_child);
2893 else
2894 {
2895 GdkEventSequence *sequence;
2896 GdkInputSource source;
2897 GdkEvent *event;
2898 GdkModifierType state;
2899 gboolean modify;
2900 gboolean extend;
2901
2902 state = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture));
2903 modify = (state & GDK_CONTROL_MASK) != 0;
2904 extend = (state & GDK_SHIFT_MASK) != 0;
2905
2906 /* With touch, we default to modifying the selection.
2907 * You can still clear the selection and start over
2908 * by holding Ctrl.
2909 */
2910
2911 sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
2912 event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
2913 source = gdk_device_get_source (device: gdk_event_get_device (event));
2914
2915 if (source == GDK_SOURCE_TOUCHSCREEN)
2916 modify = !modify;
2917
2918 gtk_flow_box_update_selection (box, child: priv->active_child, modify, extend);
2919 }
2920 }
2921}
2922
2923static void
2924gtk_flow_box_click_gesture_stopped (GtkGestureClick *gesture,
2925 GtkFlowBox *box)
2926{
2927 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
2928
2929 priv->active_child = NULL;
2930 gtk_widget_queue_draw (GTK_WIDGET (box));
2931}
2932
2933static void
2934gtk_flow_box_drag_gesture_begin (GtkGestureDrag *gesture,
2935 double start_x,
2936 double start_y,
2937 GtkWidget *widget)
2938{
2939 GtkFlowBoxPrivate *priv = BOX_PRIV (widget);
2940 GdkModifierType state;
2941
2942 if (priv->selection_mode != GTK_SELECTION_MULTIPLE)
2943 {
2944 gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_DENIED);
2945 return;
2946 }
2947
2948 priv->rubberband_select = FALSE;
2949 priv->rubberband_first = NULL;
2950 priv->rubberband_last = NULL;
2951
2952 state = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture));
2953 priv->rubberband_modify = (state & GDK_CONTROL_MASK) != 0;
2954 priv->rubberband_extend = (state & GDK_SHIFT_MASK) != 0;
2955}
2956
2957static void
2958gtk_flow_box_stop_rubberband (GtkFlowBox *box)
2959{
2960 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
2961
2962 priv->rubberband_select = FALSE;
2963 priv->rubberband_first = NULL;
2964 priv->rubberband_last = NULL;
2965
2966 gtk_css_node_set_parent (cssnode: priv->rubberband_node, NULL);
2967 priv->rubberband_node = NULL;
2968
2969 remove_autoscroll (box);
2970
2971 gtk_widget_queue_draw (GTK_WIDGET (box));
2972}
2973
2974static void
2975gtk_flow_box_drag_gesture_end (GtkGestureDrag *gesture,
2976 double offset_x,
2977 double offset_y,
2978 GtkFlowBox *box)
2979{
2980 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
2981 GdkEventSequence *sequence;
2982
2983 if (!priv->rubberband_select)
2984 return;
2985
2986 sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
2987
2988 if (gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence))
2989 {
2990 if (!priv->rubberband_extend && !priv->rubberband_modify)
2991 gtk_flow_box_unselect_all_internal (box);
2992
2993 if (priv->rubberband_first && priv->rubberband_last)
2994 gtk_flow_box_select_all_between (box, child1: priv->rubberband_first, child2: priv->rubberband_last, modify: priv->rubberband_modify);
2995
2996 gtk_flow_box_stop_rubberband (box);
2997
2998 g_signal_emit (instance: box, signal_id: signals[SELECTED_CHILDREN_CHANGED], detail: 0);
2999 }
3000 else
3001 gtk_flow_box_stop_rubberband (box);
3002
3003 gtk_widget_queue_draw (GTK_WIDGET (box));
3004}
3005
3006static gboolean
3007gtk_flow_box_key_controller_key_pressed (GtkEventControllerKey *controller,
3008 guint keyval,
3009 guint keycode,
3010 GdkModifierType state,
3011 GtkWidget *widget)
3012{
3013 GtkFlowBox *box = GTK_FLOW_BOX (widget);
3014 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
3015
3016 if (priv->rubberband_select && keyval == GDK_KEY_Escape)
3017 {
3018 gtk_flow_box_stop_rubberband (box);
3019 return TRUE;
3020 }
3021
3022 return FALSE;
3023}
3024
3025/* Realize and map {{{3 */
3026
3027static void
3028gtk_flow_box_unmap (GtkWidget *widget)
3029{
3030 GtkFlowBox *box = GTK_FLOW_BOX (widget);
3031
3032 remove_autoscroll (box);
3033
3034 GTK_WIDGET_CLASS (gtk_flow_box_parent_class)->unmap (widget);
3035}
3036
3037/**
3038 * gtk_flow_box_remove:
3039 * @box: a `GtkFlowBox`
3040 * @widget: the child widget to remove
3041 *
3042 * Removes a child from @box.
3043 */
3044void
3045gtk_flow_box_remove (GtkFlowBox *box,
3046 GtkWidget *widget)
3047{
3048 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
3049 gboolean was_visible;
3050 gboolean was_selected;
3051 GtkFlowBoxChild *child;
3052
3053 g_return_if_fail (GTK_IS_FLOW_BOX (box));
3054 g_return_if_fail (GTK_IS_WIDGET (widget));
3055 g_return_if_fail (gtk_widget_get_parent (widget) == GTK_WIDGET (box) ||
3056 gtk_widget_get_parent (gtk_widget_get_parent (widget)) == GTK_WIDGET (box));
3057
3058 if (GTK_IS_FLOW_BOX_CHILD (widget))
3059 child = GTK_FLOW_BOX_CHILD (widget);
3060 else
3061 {
3062 child = (GtkFlowBoxChild*)gtk_widget_get_parent (widget);
3063 if (!GTK_IS_FLOW_BOX_CHILD (child))
3064 {
3065 g_warning ("Tried to remove non-child %p", widget);
3066 return;
3067 }
3068 }
3069
3070 was_visible = child_is_visible (GTK_WIDGET (child));
3071 was_selected = CHILD_PRIV (child)->selected;
3072
3073 if (child == priv->active_child)
3074 priv->active_child = NULL;
3075 if (child == priv->selected_child)
3076 priv->selected_child = NULL;
3077
3078 g_sequence_remove (CHILD_PRIV (child)->iter);
3079 gtk_widget_unparent (GTK_WIDGET (child));
3080
3081 if (was_visible && gtk_widget_get_visible (GTK_WIDGET (box)))
3082 gtk_widget_queue_resize (GTK_WIDGET (box));
3083
3084 if (was_selected && !gtk_widget_in_destruction (GTK_WIDGET (box)))
3085 g_signal_emit (instance: box, signal_id: signals[SELECTED_CHILDREN_CHANGED], detail: 0);
3086}
3087
3088/* Keynav {{{2 */
3089
3090static gboolean
3091gtk_flow_box_focus (GtkWidget *widget,
3092 GtkDirectionType direction)
3093{
3094 GtkFlowBox *box = GTK_FLOW_BOX (widget);
3095 GtkWidget *focus_child;
3096 GSequenceIter *iter;
3097 GtkFlowBoxChild *next_focus_child;
3098
3099 focus_child = gtk_widget_get_focus_child (widget);
3100 next_focus_child = NULL;
3101
3102 if (focus_child != NULL)
3103 {
3104 if (gtk_widget_child_focus (widget: focus_child, direction))
3105 return TRUE;
3106
3107 iter = CHILD_PRIV (focus_child)->iter;
3108
3109 if (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD)
3110 iter = gtk_flow_box_get_previous_focusable (box, iter);
3111 else if (direction == GTK_DIR_RIGHT || direction == GTK_DIR_TAB_FORWARD)
3112 iter = gtk_flow_box_get_next_focusable (box, iter);
3113 else if (direction == GTK_DIR_UP)
3114 iter = gtk_flow_box_get_above_focusable (box, iter);
3115 else if (direction == GTK_DIR_DOWN)
3116 iter = gtk_flow_box_get_below_focusable (box, iter);
3117
3118 if (iter != NULL)
3119 next_focus_child = g_sequence_get (iter);
3120 }
3121 else
3122 {
3123 if (BOX_PRIV (box)->selected_child)
3124 next_focus_child = BOX_PRIV (box)->selected_child;
3125 else
3126 {
3127 if (direction == GTK_DIR_UP || direction == GTK_DIR_TAB_BACKWARD)
3128 iter = gtk_flow_box_get_last_focusable (box);
3129 else
3130 iter = gtk_flow_box_get_first_focusable (box);
3131
3132 if (iter != NULL)
3133 next_focus_child = g_sequence_get (iter);
3134 }
3135 }
3136
3137 if (next_focus_child == NULL)
3138 {
3139 if (direction == GTK_DIR_UP || direction == GTK_DIR_DOWN ||
3140 direction == GTK_DIR_LEFT || direction == GTK_DIR_RIGHT)
3141 {
3142 if (gtk_widget_keynav_failed (GTK_WIDGET (box), direction))
3143 return TRUE;
3144 }
3145
3146 return FALSE;
3147 }
3148
3149 if (gtk_widget_child_focus (GTK_WIDGET (next_focus_child), direction))
3150 return TRUE;
3151
3152 return TRUE;
3153}
3154
3155static void
3156gtk_flow_box_add_move_binding (GtkWidgetClass *widget_class,
3157 guint keyval,
3158 GdkModifierType modmask,
3159 GtkMovementStep step,
3160 int count)
3161{
3162 gtk_widget_class_add_binding_signal (widget_class,
3163 keyval, mods: modmask,
3164 signal: "move-cursor",
3165 format_string: "(iibb)", step, count, FALSE, FALSE);
3166 gtk_widget_class_add_binding_signal (widget_class,
3167 keyval, mods: modmask | GDK_SHIFT_MASK,
3168 signal: "move-cursor",
3169 format_string: "(iibb)", step, count, TRUE, FALSE);
3170 gtk_widget_class_add_binding_signal (widget_class,
3171 keyval, mods: modmask | GDK_CONTROL_MASK,
3172 signal: "move-cursor",
3173 format_string: "(iibb)", step, count, FALSE, TRUE);
3174 gtk_widget_class_add_binding_signal (widget_class,
3175 keyval, mods: modmask | GDK_SHIFT_MASK | GDK_CONTROL_MASK,
3176 signal: "move-cursor",
3177 format_string: "(iibb)", step, count, TRUE, TRUE);
3178}
3179
3180static void
3181gtk_flow_box_activate_cursor_child (GtkFlowBox *box)
3182{
3183 gtk_flow_box_select_and_activate (box, BOX_PRIV (box)->cursor_child);
3184}
3185
3186static void
3187gtk_flow_box_toggle_cursor_child (GtkFlowBox *box)
3188{
3189 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
3190
3191 if (priv->cursor_child == NULL)
3192 return;
3193
3194 if ((priv->selection_mode == GTK_SELECTION_SINGLE ||
3195 priv->selection_mode == GTK_SELECTION_MULTIPLE) &&
3196 CHILD_PRIV (priv->cursor_child)->selected)
3197 gtk_flow_box_unselect_child_internal (box, child: priv->cursor_child);
3198 else
3199 gtk_flow_box_select_and_activate (box, child: priv->cursor_child);
3200}
3201
3202void
3203gtk_flow_box_disable_move_cursor (GtkFlowBox *box)
3204{
3205 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
3206
3207 priv->disable_move_cursor = TRUE;
3208}
3209
3210static gboolean
3211gtk_flow_box_move_cursor (GtkFlowBox *box,
3212 GtkMovementStep step,
3213 int count,
3214 gboolean extend,
3215 gboolean modify)
3216{
3217 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
3218 GtkFlowBoxChild *child;
3219 GtkFlowBoxChild *prev;
3220 GtkFlowBoxChild *next;
3221 GtkAllocation allocation;
3222 int page_size;
3223 GSequenceIter *iter;
3224 int start;
3225 GtkAdjustment *adjustment;
3226 gboolean vertical;
3227
3228 if (priv->disable_move_cursor)
3229 return FALSE;
3230
3231 vertical = priv->orientation == GTK_ORIENTATION_VERTICAL;
3232
3233 if (vertical)
3234 {
3235 switch ((guint) step)
3236 {
3237 case GTK_MOVEMENT_VISUAL_POSITIONS:
3238 step = GTK_MOVEMENT_DISPLAY_LINES;
3239 break;
3240 case GTK_MOVEMENT_DISPLAY_LINES:
3241 step = GTK_MOVEMENT_VISUAL_POSITIONS;
3242 break;
3243 default:
3244 break;
3245 }
3246 }
3247
3248 child = NULL;
3249 switch ((guint) step)
3250 {
3251 case GTK_MOVEMENT_VISUAL_POSITIONS:
3252 if (priv->cursor_child != NULL)
3253 {
3254 iter = CHILD_PRIV (priv->cursor_child)->iter;
3255 if (gtk_widget_get_direction (GTK_WIDGET (box)) == GTK_TEXT_DIR_RTL)
3256 count = - count;
3257
3258 while (count < 0 && iter != NULL)
3259 {
3260 iter = gtk_flow_box_get_previous_focusable (box, iter);
3261 count = count + 1;
3262 }
3263 while (count > 0 && iter != NULL)
3264 {
3265 iter = gtk_flow_box_get_next_focusable (box, iter);
3266 count = count - 1;
3267 }
3268
3269 if (iter != NULL && !g_sequence_iter_is_end (iter))
3270 child = g_sequence_get (iter);
3271 }
3272 break;
3273
3274 case GTK_MOVEMENT_BUFFER_ENDS:
3275 if (count < 0)
3276 iter = gtk_flow_box_get_first_focusable (box);
3277 else
3278 iter = gtk_flow_box_get_last_focusable (box);
3279 if (iter != NULL)
3280 child = g_sequence_get (iter);
3281 break;
3282
3283 case GTK_MOVEMENT_DISPLAY_LINES:
3284 if (priv->cursor_child != NULL)
3285 {
3286 iter = CHILD_PRIV (priv->cursor_child)->iter;
3287
3288 while (count < 0 && iter != NULL)
3289 {
3290 iter = gtk_flow_box_get_above_focusable (box, iter);
3291 count = count + 1;
3292 }
3293 while (count > 0 && iter != NULL)
3294 {
3295 iter = gtk_flow_box_get_below_focusable (box, iter);
3296 count = count - 1;
3297 }
3298
3299 if (iter != NULL)
3300 child = g_sequence_get (iter);
3301 }
3302 break;
3303
3304 case GTK_MOVEMENT_PAGES:
3305 page_size = 100;
3306 adjustment = vertical ? priv->hadjustment : priv->vadjustment;
3307 if (adjustment)
3308 page_size = gtk_adjustment_get_page_increment (adjustment);
3309
3310 if (priv->cursor_child != NULL)
3311 {
3312 child = priv->cursor_child;
3313 iter = CHILD_PRIV (child)->iter;
3314 gtk_widget_get_allocation (GTK_WIDGET (child), allocation: &allocation);
3315 start = vertical ? allocation.x : allocation.y;
3316
3317 if (count < 0)
3318 {
3319 int i = 0;
3320
3321 /* Up */
3322 while (iter != NULL)
3323 {
3324 iter = gtk_flow_box_get_previous_focusable (box, iter);
3325 if (iter == NULL)
3326 break;
3327
3328 prev = g_sequence_get (iter);
3329
3330 /* go up an even number of rows */
3331 if (i % priv->cur_children_per_line == 0)
3332 {
3333 gtk_widget_get_allocation (GTK_WIDGET (prev), allocation: &allocation);
3334 if ((vertical ? allocation.x : allocation.y) < start - page_size)
3335 break;
3336 }
3337
3338 child = prev;
3339 i++;
3340 }
3341 }
3342 else
3343 {
3344 int i = 0;
3345
3346 /* Down */
3347 while (!g_sequence_iter_is_end (iter))
3348 {
3349 iter = gtk_flow_box_get_next_focusable (box, iter);
3350 if (iter == NULL || g_sequence_iter_is_end (iter))
3351 break;
3352
3353 next = g_sequence_get (iter);
3354
3355 if (i % priv->cur_children_per_line == 0)
3356 {
3357 gtk_widget_get_allocation (GTK_WIDGET (next), allocation: &allocation);
3358 if ((vertical ? allocation.x : allocation.y) > start + page_size)
3359 break;
3360 }
3361
3362 child = next;
3363 i++;
3364 }
3365 }
3366 gtk_widget_get_allocation (GTK_WIDGET (child), allocation: &allocation);
3367 }
3368 break;
3369
3370 default:
3371 g_assert_not_reached ();
3372 }
3373
3374 if (child == NULL || child == priv->cursor_child)
3375 {
3376 GtkDirectionType direction = count < 0 ? GTK_DIR_UP : GTK_DIR_DOWN;
3377
3378 if (!gtk_widget_keynav_failed (GTK_WIDGET (box), direction))
3379 {
3380 return FALSE;
3381 }
3382
3383 return TRUE;
3384 }
3385
3386 /* If the child has its "focusable" property set to FALSE then it will
3387 * not grab the focus. We must pass the focus to its child directly.
3388 */
3389 if (!gtk_widget_get_focusable (GTK_WIDGET (child)))
3390 {
3391 GtkWidget *subchild;
3392
3393 subchild = gtk_flow_box_child_get_child (GTK_FLOW_BOX_CHILD (child));
3394 if (subchild)
3395 {
3396 GtkDirectionType direction = count < 0 ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD;
3397 gtk_widget_child_focus (widget: subchild, direction);
3398 }
3399 }
3400
3401 gtk_flow_box_update_cursor (box, child);
3402 if (!modify)
3403 gtk_flow_box_update_selection (box, child, FALSE, extend);
3404 return TRUE;
3405}
3406
3407/* Selection {{{2 */
3408
3409static void
3410gtk_flow_box_selected_children_changed (GtkFlowBox *box)
3411{
3412}
3413
3414/* GObject implementation {{{2 */
3415
3416static void
3417gtk_flow_box_get_property (GObject *object,
3418 guint prop_id,
3419 GValue *value,
3420 GParamSpec *pspec)
3421{
3422 GtkFlowBox *box = GTK_FLOW_BOX (object);
3423 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
3424
3425 switch (prop_id)
3426 {
3427 case PROP_ORIENTATION:
3428 g_value_set_enum (value, v_enum: priv->orientation);
3429 break;
3430 case PROP_HOMOGENEOUS:
3431 g_value_set_boolean (value, v_boolean: priv->homogeneous);
3432 break;
3433 case PROP_COLUMN_SPACING:
3434 g_value_set_uint (value, v_uint: priv->column_spacing);
3435 break;
3436 case PROP_ROW_SPACING:
3437 g_value_set_uint (value, v_uint: priv->row_spacing);
3438 break;
3439 case PROP_MIN_CHILDREN_PER_LINE:
3440 g_value_set_uint (value, v_uint: priv->min_children_per_line);
3441 break;
3442 case PROP_MAX_CHILDREN_PER_LINE:
3443 g_value_set_uint (value, v_uint: priv->max_children_per_line);
3444 break;
3445 case PROP_SELECTION_MODE:
3446 g_value_set_enum (value, v_enum: priv->selection_mode);
3447 break;
3448 case PROP_ACTIVATE_ON_SINGLE_CLICK:
3449 g_value_set_boolean (value, v_boolean: priv->activate_on_single_click);
3450 break;
3451 case PROP_ACCEPT_UNPAIRED_RELEASE:
3452 g_value_set_boolean (value, v_boolean: priv->accept_unpaired_release);
3453 break;
3454 default:
3455 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
3456 break;
3457 }
3458}
3459
3460static void
3461gtk_flow_box_set_property (GObject *object,
3462 guint prop_id,
3463 const GValue *value,
3464 GParamSpec *pspec)
3465{
3466 GtkFlowBox *box = GTK_FLOW_BOX (object);
3467 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
3468
3469 switch (prop_id)
3470 {
3471 case PROP_ORIENTATION:
3472 {
3473 GtkOrientation orientation = g_value_get_enum (value);
3474
3475 if (priv->orientation != orientation)
3476 {
3477 priv->orientation = orientation;
3478
3479 gtk_widget_update_orientation (GTK_WIDGET (box), orientation: priv->orientation);
3480
3481 /* Re-box the children in the new orientation */
3482 gtk_widget_queue_resize (GTK_WIDGET (box));
3483 g_object_notify_by_pspec (object, pspec);
3484 }
3485 }
3486 break;
3487 case PROP_HOMOGENEOUS:
3488 gtk_flow_box_set_homogeneous (box, homogeneous: g_value_get_boolean (value));
3489 break;
3490 case PROP_COLUMN_SPACING:
3491 gtk_flow_box_set_column_spacing (box, spacing: g_value_get_uint (value));
3492 break;
3493 case PROP_ROW_SPACING:
3494 gtk_flow_box_set_row_spacing (box, spacing: g_value_get_uint (value));
3495 break;
3496 case PROP_MIN_CHILDREN_PER_LINE:
3497 gtk_flow_box_set_min_children_per_line (box, n_children: g_value_get_uint (value));
3498 break;
3499 case PROP_MAX_CHILDREN_PER_LINE:
3500 gtk_flow_box_set_max_children_per_line (box, n_children: g_value_get_uint (value));
3501 break;
3502 case PROP_SELECTION_MODE:
3503 gtk_flow_box_set_selection_mode (box, mode: g_value_get_enum (value));
3504 break;
3505 case PROP_ACTIVATE_ON_SINGLE_CLICK:
3506 gtk_flow_box_set_activate_on_single_click (box, single: g_value_get_boolean (value));
3507 break;
3508 case PROP_ACCEPT_UNPAIRED_RELEASE:
3509 gtk_flow_box_set_accept_unpaired_release (box, accept: g_value_get_boolean (value));
3510 break;
3511 default:
3512 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
3513 break;
3514 }
3515}
3516
3517static void
3518gtk_flow_box_dispose (GObject *obj)
3519{
3520 GtkFlowBoxPrivate *priv = BOX_PRIV (obj);
3521
3522 if (priv->filter_destroy != NULL)
3523 priv->filter_destroy (priv->filter_data);
3524 if (priv->sort_destroy != NULL)
3525 priv->sort_destroy (priv->sort_data);
3526
3527 if (priv->children)
3528 {
3529 GSequenceIter *iter;
3530 GtkWidget *child;
3531
3532 for (iter = g_sequence_get_begin_iter (seq: priv->children);
3533 !g_sequence_iter_is_end (iter);
3534 iter = g_sequence_iter_next (iter))
3535 {
3536 child = g_sequence_get (iter);
3537 gtk_widget_unparent (widget: child);
3538 }
3539 g_clear_pointer (&priv->children, g_sequence_free);
3540 }
3541
3542 g_clear_object (&priv->hadjustment);
3543 g_clear_object (&priv->vadjustment);
3544
3545 if (priv->bound_model)
3546 {
3547 if (priv->create_widget_func_data_destroy)
3548 priv->create_widget_func_data_destroy (priv->create_widget_func_data);
3549
3550 g_signal_handlers_disconnect_by_func (priv->bound_model, gtk_flow_box_bound_model_changed, obj);
3551 g_clear_object (&priv->bound_model);
3552 }
3553
3554 G_OBJECT_CLASS (gtk_flow_box_parent_class)->dispose (obj);
3555}
3556
3557static void
3558gtk_flow_box_compute_expand (GtkWidget *widget,
3559 gboolean *hexpand_p,
3560 gboolean *vexpand_p)
3561{
3562 GtkWidget *w;
3563 gboolean hexpand = FALSE;
3564 gboolean vexpand = FALSE;
3565
3566 for (w = gtk_widget_get_first_child (widget);
3567 w != NULL;
3568 w = gtk_widget_get_next_sibling (widget: w))
3569 {
3570 hexpand = hexpand || gtk_widget_compute_expand (widget: w, orientation: GTK_ORIENTATION_HORIZONTAL);
3571 vexpand = vexpand || gtk_widget_compute_expand (widget: w, orientation: GTK_ORIENTATION_VERTICAL);
3572 }
3573
3574 *hexpand_p = hexpand;
3575 *vexpand_p = vexpand;
3576}
3577
3578static void
3579gtk_flow_box_class_init (GtkFlowBoxClass *class)
3580{
3581 GObjectClass *object_class = G_OBJECT_CLASS (class);
3582 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
3583
3584 object_class->dispose = gtk_flow_box_dispose;
3585 object_class->get_property = gtk_flow_box_get_property;
3586 object_class->set_property = gtk_flow_box_set_property;
3587
3588 widget_class->size_allocate = gtk_flow_box_size_allocate;
3589 widget_class->unmap = gtk_flow_box_unmap;
3590 widget_class->focus = gtk_flow_box_focus;
3591 widget_class->snapshot = gtk_flow_box_snapshot;
3592 widget_class->get_request_mode = gtk_flow_box_get_request_mode;
3593 widget_class->compute_expand = gtk_flow_box_compute_expand;
3594 widget_class->measure = gtk_flow_box_measure;
3595
3596 class->activate_cursor_child = gtk_flow_box_activate_cursor_child;
3597 class->toggle_cursor_child = gtk_flow_box_toggle_cursor_child;
3598 class->move_cursor = gtk_flow_box_move_cursor;
3599 class->select_all = gtk_flow_box_select_all;
3600 class->unselect_all = gtk_flow_box_unselect_all;
3601 class->selected_children_changed = gtk_flow_box_selected_children_changed;
3602
3603 g_object_class_override_property (oclass: object_class, property_id: PROP_ORIENTATION, name: "orientation");
3604
3605 /**
3606 * GtkFlowBox:selection-mode: (attributes org.gtk.Property.get=gtk_flow_box_get_selection_mode org.gtk.Property.set=gtk_flow_box_set_selection_mode)
3607 *
3608 * The selection mode used by the flow box.
3609 */
3610 props[PROP_SELECTION_MODE] =
3611 g_param_spec_enum (name: "selection-mode",
3612 P_("Selection mode"),
3613 P_("The selection mode"),
3614 enum_type: GTK_TYPE_SELECTION_MODE,
3615 default_value: GTK_SELECTION_SINGLE,
3616 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3617
3618 /**
3619 * GtkFlowBox:activate-on-single-click: (attributes org.gtk.Property.get=gtk_flow_box_get_activate_on_single_click org.gtk.Property.set=gtk_flow_box_set_activate_on_single_click)
3620 *
3621 * Determines whether children can be activated with a single
3622 * click, or require a double-click.
3623 */
3624 props[PROP_ACTIVATE_ON_SINGLE_CLICK] =
3625 g_param_spec_boolean (name: "activate-on-single-click",
3626 P_("Activate on Single Click"),
3627 P_("Activate row on a single click"),
3628 TRUE,
3629 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3630
3631 /**
3632 * GtkFlwoBox:accept-unpaired-release:
3633 *
3634 * Whether to accept unpaired release events.
3635 */
3636 props[PROP_ACCEPT_UNPAIRED_RELEASE] =
3637 g_param_spec_boolean (name: "accept-unpaired-release",
3638 P_("Accept unpaired release"),
3639 P_("Accept an unpaired release event"),
3640 FALSE,
3641 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3642
3643 /**
3644 * GtkFlowBox:homogeneous: (attributes org.gtk.Property.get=gtk_flow_box_get_homogeneous org.gtk.Property.set=gtk_flow_box_set_homogeneous)
3645 *
3646 * Determines whether all children should be allocated the
3647 * same size.
3648 */
3649 props[PROP_HOMOGENEOUS] =
3650 g_param_spec_boolean (name: "homogeneous",
3651 P_("Homogeneous"),
3652 P_("Whether the children should all be the same size"),
3653 FALSE,
3654 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3655
3656 /**
3657 * GtkFlowBox:min-children-per-line: (attributes org.gtk.Property.get=gtk_flow_box_get_min_children_per_line org.gtk.Property.set=gtk_flow_box_set_min_children_per_line)
3658 *
3659 * The minimum number of children to allocate consecutively
3660 * in the given orientation.
3661 *
3662 * Setting the minimum children per line ensures
3663 * that a reasonably small height will be requested
3664 * for the overall minimum width of the box.
3665 */
3666 props[PROP_MIN_CHILDREN_PER_LINE] =
3667 g_param_spec_uint (name: "min-children-per-line",
3668 P_("Minimum Children Per Line"),
3669 P_("The minimum number of children to allocate "
3670 "consecutively in the given orientation."),
3671 minimum: 0, G_MAXUINT, default_value: 0,
3672 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3673
3674 /**
3675 * GtkFlowBox:max-children-per-line: (attributes org.gtk.Property.get=gtk_flow_box_get_max_children_per_line org.gtk.Property.set=gtk_flow_box_set_max_children_per_line)
3676 *
3677 * The maximum amount of children to request space for consecutively
3678 * in the given orientation.
3679 */
3680 props[PROP_MAX_CHILDREN_PER_LINE] =
3681 g_param_spec_uint (name: "max-children-per-line",
3682 P_("Maximum Children Per Line"),
3683 P_("The maximum amount of children to request space for "
3684 "consecutively in the given orientation."),
3685 minimum: 1, G_MAXUINT, DEFAULT_MAX_CHILDREN_PER_LINE,
3686 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3687
3688 /**
3689 * GtkFlowBox:row-spacing: (attributes org.gtk.Property.get=gtk_flow_box_get_row_spacing org.gtk.Property.set=gtk_flow_box_set_row_spacing)
3690 *
3691 * The amount of vertical space between two children.
3692 */
3693 props[PROP_ROW_SPACING] =
3694 g_param_spec_uint (name: "row-spacing",
3695 P_("Vertical spacing"),
3696 P_("The amount of vertical space between two children"),
3697 minimum: 0, G_MAXUINT, default_value: 0,
3698 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3699
3700 /**
3701 * GtkFlowBox:column-spacing: (attributes org.gtk.Property.get=gtk_flow_box_get_column_spacing org.gtk.Property.set=gtk_flow_box_set_column_spacing)
3702 *
3703 * The amount of horizontal space between two children.
3704 */
3705 props[PROP_COLUMN_SPACING] =
3706 g_param_spec_uint (name: "column-spacing",
3707 P_("Horizontal spacing"),
3708 P_("The amount of horizontal space between two children"),
3709 minimum: 0, G_MAXUINT, default_value: 0,
3710 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3711
3712 g_object_class_install_properties (oclass: object_class, n_pspecs: LAST_PROP, pspecs: props);
3713
3714 /**
3715 * GtkFlowBox::child-activated:
3716 * @box: the `GtkFlowBox` on which the signal is emitted
3717 * @child: the child that is activated
3718 *
3719 * Emitted when a child has been activated by the user.
3720 */
3721 signals[CHILD_ACTIVATED] = g_signal_new (I_("child-activated"),
3722 GTK_TYPE_FLOW_BOX,
3723 signal_flags: G_SIGNAL_RUN_LAST,
3724 G_STRUCT_OFFSET (GtkFlowBoxClass, child_activated),
3725 NULL, NULL,
3726 NULL,
3727 G_TYPE_NONE, n_params: 1,
3728 GTK_TYPE_FLOW_BOX_CHILD);
3729
3730 /**
3731 * GtkFlowBox::selected-children-changed:
3732 * @box: the `GtkFlowBox` on which the signal is emitted
3733 *
3734 * Emitted when the set of selected children changes.
3735 *
3736 * Use [method@Gtk.FlowBox.selected_foreach] or
3737 * [method@Gtk.FlowBox.get_selected_children] to obtain the
3738 * selected children.
3739 */
3740 signals[SELECTED_CHILDREN_CHANGED] = g_signal_new (I_("selected-children-changed"),
3741 GTK_TYPE_FLOW_BOX,
3742 signal_flags: G_SIGNAL_RUN_FIRST,
3743 G_STRUCT_OFFSET (GtkFlowBoxClass, selected_children_changed),
3744 NULL, NULL,
3745 NULL,
3746 G_TYPE_NONE, n_params: 0);
3747
3748 /**
3749 * GtkFlowBox::activate-cursor-child:
3750 * @box: the `GtkFlowBox` on which the signal is emitted
3751 *
3752 * Emitted when the user activates the @box.
3753 *
3754 * This is a [keybinding signal](class.SignalAction.html).
3755 */
3756 signals[ACTIVATE_CURSOR_CHILD] = g_signal_new (I_("activate-cursor-child"),
3757 GTK_TYPE_FLOW_BOX,
3758 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3759 G_STRUCT_OFFSET (GtkFlowBoxClass, activate_cursor_child),
3760 NULL, NULL,
3761 NULL,
3762 G_TYPE_NONE, n_params: 0);
3763
3764 /**
3765 * GtkFlowBox::toggle-cursor-child:
3766 * @box: the `GtkFlowBox` on which the signal is emitted
3767 *
3768 * Emitted to toggle the selection of the child that has the focus.
3769 *
3770 * This is a [keybinding signal](class.SignalAction.html).
3771 *
3772 * The default binding for this signal is <kbd>Ctrl</kbd>-<kbd>Space</kbd>.
3773 */
3774 signals[TOGGLE_CURSOR_CHILD] = g_signal_new (I_("toggle-cursor-child"),
3775 GTK_TYPE_FLOW_BOX,
3776 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3777 G_STRUCT_OFFSET (GtkFlowBoxClass, toggle_cursor_child),
3778 NULL, NULL,
3779 NULL,
3780 G_TYPE_NONE, n_params: 0);
3781
3782 /**
3783 * GtkFlowBox::move-cursor:
3784 * @box: the `GtkFlowBox` on which the signal is emitted
3785 * @step: the granularity fo the move, as a `GtkMovementStep`
3786 * @count: the number of @step units to move
3787 * @extend: whether to extend the selection
3788 * @modify: whether to modify the selection
3789 *
3790 * Emitted when the user initiates a cursor movement.
3791 *
3792 * This is a [keybinding signal](class.SignalAction.html).
3793 * Applications should not connect to it, but may emit it with
3794 * g_signal_emit_by_name() if they need to control the cursor
3795 * programmatically.
3796 *
3797 * The default bindings for this signal come in two variants,
3798 * the variant with the Shift modifier extends the selection,
3799 * the variant without the Shift modifier does not.
3800 * There are too many key combinations to list them all here.
3801 *
3802 * - <kbd>←</kbd>, <kbd>→</kbd>, <kbd>↑</kbd>, <kbd>↓</kbd>
3803 * move by individual children
3804 * - <kbd>Home</kbd>, <kbd>End</kbd> move to the ends of the box
3805 * - <kbd>PgUp</kbd>, <kbd>PgDn</kbd> move vertically by pages
3806
3807 * Returns: %TRUE to stop other handlers from being invoked for the event.
3808 * %FALSE to propagate the event further.
3809 */
3810 signals[MOVE_CURSOR] = g_signal_new (I_("move-cursor"),
3811 GTK_TYPE_FLOW_BOX,
3812 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3813 G_STRUCT_OFFSET (GtkFlowBoxClass, move_cursor),
3814 NULL, NULL,
3815 c_marshaller: _gtk_marshal_BOOLEAN__ENUM_INT_BOOLEAN_BOOLEAN,
3816 G_TYPE_BOOLEAN, n_params: 4,
3817 GTK_TYPE_MOVEMENT_STEP, G_TYPE_INT, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
3818 g_signal_set_va_marshaller (signal_id: signals[MOVE_CURSOR],
3819 G_TYPE_FROM_CLASS (class),
3820 va_marshaller: _gtk_marshal_BOOLEAN__ENUM_INT_BOOLEAN_BOOLEANv);
3821 /**
3822 * GtkFlowBox::select-all:
3823 * @box: the `GtkFlowBox` on which the signal is emitted
3824 *
3825 * Emitted to select all children of the box,
3826 * if the selection mode permits it.
3827 *
3828 * This is a [keybinding signal](class.SignalAction.html).
3829 *
3830 * The default bindings for this signal is <kbd>Ctrl</kbd>-<kbd>a</kbd>.
3831 */
3832 signals[SELECT_ALL] = g_signal_new (I_("select-all"),
3833 GTK_TYPE_FLOW_BOX,
3834 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3835 G_STRUCT_OFFSET (GtkFlowBoxClass, select_all),
3836 NULL, NULL,
3837 NULL,
3838 G_TYPE_NONE, n_params: 0);
3839
3840 /**
3841 * GtkFlowBox::unselect-all:
3842 * @box: the `GtkFlowBox` on which the signal is emitted
3843 *
3844 * Emitted to unselect all children of the box,
3845 * if the selection mode permits it.
3846 *
3847 * This is a [keybinding signal](class.SignalAction.html).
3848 *
3849 * The default bindings for this signal is <kbd>Ctrl</kbd>-<kbd>Shift</kbd>-<kbd>a</kbd>.
3850 */
3851 signals[UNSELECT_ALL] = g_signal_new (I_("unselect-all"),
3852 GTK_TYPE_FLOW_BOX,
3853 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
3854 G_STRUCT_OFFSET (GtkFlowBoxClass, unselect_all),
3855 NULL, NULL,
3856 NULL,
3857 G_TYPE_NONE, n_params: 0);
3858
3859 gtk_widget_class_set_activate_signal (widget_class, signal_id: signals[ACTIVATE_CURSOR_CHILD]);
3860
3861 gtk_flow_box_add_move_binding (widget_class, GDK_KEY_Home, modmask: 0,
3862 step: GTK_MOVEMENT_BUFFER_ENDS, count: -1);
3863 gtk_flow_box_add_move_binding (widget_class, GDK_KEY_KP_Home, modmask: 0,
3864 step: GTK_MOVEMENT_BUFFER_ENDS, count: -1);
3865 gtk_flow_box_add_move_binding (widget_class, GDK_KEY_End, modmask: 0,
3866 step: GTK_MOVEMENT_BUFFER_ENDS, count: 1);
3867 gtk_flow_box_add_move_binding (widget_class, GDK_KEY_KP_End, modmask: 0,
3868 step: GTK_MOVEMENT_BUFFER_ENDS, count: 1);
3869 gtk_flow_box_add_move_binding (widget_class, GDK_KEY_Up, modmask: 0,
3870 step: GTK_MOVEMENT_DISPLAY_LINES, count: -1);
3871 gtk_flow_box_add_move_binding (widget_class, GDK_KEY_KP_Up, modmask: 0,
3872 step: GTK_MOVEMENT_DISPLAY_LINES, count: -1);
3873 gtk_flow_box_add_move_binding (widget_class, GDK_KEY_Down, modmask: 0,
3874 step: GTK_MOVEMENT_DISPLAY_LINES, count: 1);
3875 gtk_flow_box_add_move_binding (widget_class, GDK_KEY_KP_Down, modmask: 0,
3876 step: GTK_MOVEMENT_DISPLAY_LINES, count: 1);
3877 gtk_flow_box_add_move_binding (widget_class, GDK_KEY_Page_Up, modmask: 0,
3878 step: GTK_MOVEMENT_PAGES, count: -1);
3879 gtk_flow_box_add_move_binding (widget_class, GDK_KEY_KP_Page_Up, modmask: 0,
3880 step: GTK_MOVEMENT_PAGES, count: -1);
3881 gtk_flow_box_add_move_binding (widget_class, GDK_KEY_Page_Down, modmask: 0,
3882 step: GTK_MOVEMENT_PAGES, count: 1);
3883 gtk_flow_box_add_move_binding (widget_class, GDK_KEY_KP_Page_Down, modmask: 0,
3884 step: GTK_MOVEMENT_PAGES, count: 1);
3885
3886 gtk_flow_box_add_move_binding (widget_class, GDK_KEY_Right, modmask: 0,
3887 step: GTK_MOVEMENT_VISUAL_POSITIONS, count: 1);
3888 gtk_flow_box_add_move_binding (widget_class, GDK_KEY_KP_Right, modmask: 0,
3889 step: GTK_MOVEMENT_VISUAL_POSITIONS, count: 1);
3890 gtk_flow_box_add_move_binding (widget_class, GDK_KEY_Left, modmask: 0,
3891 step: GTK_MOVEMENT_VISUAL_POSITIONS, count: -1);
3892 gtk_flow_box_add_move_binding (widget_class, GDK_KEY_KP_Left, modmask: 0,
3893 step: GTK_MOVEMENT_VISUAL_POSITIONS, count: -1);
3894
3895 gtk_widget_class_add_binding_signal (widget_class,
3896 GDK_KEY_space, mods: GDK_CONTROL_MASK,
3897 signal: "toggle-cursor-child",
3898 NULL);
3899 gtk_widget_class_add_binding_signal (widget_class,
3900 GDK_KEY_KP_Space, mods: GDK_CONTROL_MASK,
3901 signal: "toggle-cursor-child",
3902 NULL);
3903
3904 gtk_widget_class_add_binding_signal (widget_class,
3905 GDK_KEY_a, mods: GDK_CONTROL_MASK,
3906 signal: "select-all",
3907 NULL);
3908 gtk_widget_class_add_binding_signal (widget_class,
3909 GDK_KEY_a, mods: GDK_CONTROL_MASK | GDK_SHIFT_MASK,
3910 signal: "unselect-all",
3911 NULL);
3912
3913 gtk_widget_class_set_css_name (widget_class, I_("flowbox"));
3914 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_GRID);
3915}
3916
3917static void
3918gtk_flow_box_init (GtkFlowBox *box)
3919{
3920 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
3921 GtkEventController *controller;
3922 GtkGesture *gesture;
3923
3924 priv->orientation = GTK_ORIENTATION_HORIZONTAL;
3925 priv->selection_mode = GTK_SELECTION_SINGLE;
3926 priv->max_children_per_line = DEFAULT_MAX_CHILDREN_PER_LINE;
3927 priv->column_spacing = 0;
3928 priv->row_spacing = 0;
3929 priv->activate_on_single_click = TRUE;
3930
3931 gtk_widget_update_orientation (GTK_WIDGET (box), orientation: priv->orientation);
3932
3933 priv->children = g_sequence_new (NULL);
3934
3935 gesture = gtk_gesture_click_new ();
3936 gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture),
3937 FALSE);
3938 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture),
3939 GDK_BUTTON_PRIMARY);
3940 gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
3941 phase: GTK_PHASE_BUBBLE);
3942 g_signal_connect (gesture, "pressed",
3943 G_CALLBACK (gtk_flow_box_click_gesture_pressed), box);
3944 g_signal_connect (gesture, "released",
3945 G_CALLBACK (gtk_flow_box_click_gesture_released), box);
3946 g_signal_connect (gesture, "stopped",
3947 G_CALLBACK (gtk_flow_box_click_gesture_stopped), box);
3948 g_signal_connect (gesture, "unpaired-release",
3949 G_CALLBACK (gtk_flow_box_click_unpaired_release), box);
3950 gtk_widget_add_controller (GTK_WIDGET (box), GTK_EVENT_CONTROLLER (gesture));
3951
3952 priv->drag_gesture = gtk_gesture_drag_new ();
3953 gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (priv->drag_gesture),
3954 FALSE);
3955 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (priv->drag_gesture),
3956 GDK_BUTTON_PRIMARY);
3957 gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->drag_gesture),
3958 phase: GTK_PHASE_CAPTURE);
3959 g_signal_connect (priv->drag_gesture, "drag-begin",
3960 G_CALLBACK (gtk_flow_box_drag_gesture_begin), box);
3961 g_signal_connect (priv->drag_gesture, "drag-update",
3962 G_CALLBACK (gtk_flow_box_drag_gesture_update), box);
3963 g_signal_connect (priv->drag_gesture, "drag-end",
3964 G_CALLBACK (gtk_flow_box_drag_gesture_end), box);
3965 gtk_widget_add_controller (GTK_WIDGET (box), GTK_EVENT_CONTROLLER (priv->drag_gesture));
3966
3967 controller = gtk_event_controller_key_new ();
3968 g_signal_connect (controller, "key-pressed",
3969 G_CALLBACK (gtk_flow_box_key_controller_key_pressed), box);
3970 gtk_widget_add_controller (GTK_WIDGET (box), controller);
3971}
3972
3973static void
3974gtk_flow_box_bound_model_changed (GListModel *list,
3975 guint position,
3976 guint removed,
3977 guint added,
3978 gpointer user_data)
3979{
3980 GtkFlowBox *box = user_data;
3981 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
3982 int i;
3983
3984 while (removed--)
3985 {
3986 GtkFlowBoxChild *child;
3987
3988 child = gtk_flow_box_get_child_at_index (box, idx: position);
3989 gtk_flow_box_remove (box, GTK_WIDGET (child));
3990 }
3991
3992 for (i = 0; i < added; i++)
3993 {
3994 GObject *item;
3995 GtkWidget *widget;
3996
3997 item = g_list_model_get_item (list, position: position + i);
3998 widget = priv->create_widget_func (item, priv->create_widget_func_data);
3999
4000 /* We need to sink the floating reference here, so that we can accept
4001 * both instances created with a floating reference (e.g. C functions
4002 * that just return the result of g_object_new()) and without (e.g.
4003 * from language bindings which will automatically sink the floating
4004 * reference).
4005 *
4006 * See the similar code in gtklistbox.c:gtk_list_box_bound_model_changed.
4007 */
4008 if (g_object_is_floating (object: widget))
4009 g_object_ref_sink (widget);
4010
4011 gtk_widget_show (widget);
4012 gtk_flow_box_insert (box, widget, position: position + i);
4013
4014 g_object_unref (object: widget);
4015 g_object_unref (object: item);
4016 }
4017}
4018
4019/* Buildable implementation {{{3 */
4020
4021static GtkBuildableIface *parent_buildable_iface;
4022
4023static void
4024gtk_flow_box_buildable_add_child (GtkBuildable *buildable,
4025 GtkBuilder *builder,
4026 GObject *child,
4027 const char *type)
4028{
4029 if (GTK_IS_WIDGET (child))
4030 gtk_flow_box_insert (GTK_FLOW_BOX (buildable), GTK_WIDGET (child), position: -1);
4031 else
4032 parent_buildable_iface->add_child (buildable, builder, child, type);
4033}
4034
4035static void
4036gtk_flow_box_buildable_iface_init (GtkBuildableIface *iface)
4037{
4038 parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface);
4039
4040 iface->add_child = gtk_flow_box_buildable_add_child;
4041}
4042 /* Public API {{{2 */
4043
4044/**
4045 * gtk_flow_box_new:
4046 *
4047 * Creates a `GtkFlowBox`.
4048 *
4049 * Returns: a new `GtkFlowBox`
4050 */
4051GtkWidget *
4052gtk_flow_box_new (void)
4053{
4054 return (GtkWidget *)g_object_new (GTK_TYPE_FLOW_BOX, NULL);
4055}
4056
4057static void
4058gtk_flow_box_insert_widget (GtkFlowBox *box,
4059 GtkWidget *child,
4060 GSequenceIter *iter)
4061{
4062 GSequenceIter *prev_iter;
4063 GtkWidget *sibling;
4064
4065 prev_iter = g_sequence_iter_prev (iter);
4066
4067 if (prev_iter != iter)
4068 sibling = g_sequence_get (iter: prev_iter);
4069 else
4070 sibling = NULL;
4071
4072 gtk_widget_insert_after (widget: child, GTK_WIDGET (box), previous_sibling: sibling);
4073}
4074
4075/**
4076 * gtk_flow_box_prepend:
4077 * @self: a `GtkFlowBox
4078 * @child: the `GtkWidget` to add
4079 *
4080 * Adds @child to the start of @self.
4081 *
4082 * If a sort function is set, the widget will
4083 * actually be inserted at the calculated position.
4084 *
4085 * See also: [method@Gtk.FlowBox.insert].
4086 *
4087 * Since: 4.6
4088 */
4089void
4090gtk_flow_box_prepend (GtkFlowBox *self,
4091 GtkWidget *child)
4092{
4093 g_return_if_fail (GTK_IS_FLOW_BOX (self));
4094 g_return_if_fail (GTK_IS_WIDGET (child));
4095
4096 gtk_flow_box_insert (box: self, widget: child, position: 0);
4097}
4098
4099/**
4100 * gtk_flow_box_append:
4101 * @self: a `GtkFlowBox
4102 * @child: the `GtkWidget` to add
4103 *
4104 * Adds @child to the end of @self.
4105 *
4106 * If a sort function is set, the widget will
4107 * actually be inserted at the calculated position.
4108 *
4109 * See also: [method@Gtk.FlowBox.insert].
4110 *
4111 * Since: 4.6
4112 */
4113void
4114gtk_flow_box_append (GtkFlowBox *self,
4115 GtkWidget *child)
4116{
4117 g_return_if_fail (GTK_IS_FLOW_BOX (self));
4118 g_return_if_fail (GTK_IS_WIDGET (child));
4119
4120 gtk_flow_box_insert (box: self, widget: child, position: -1);
4121}
4122
4123/**
4124 * gtk_flow_box_insert:
4125 * @box: a `GtkFlowBox`
4126 * @widget: the `GtkWidget` to add
4127 * @position: the position to insert @child in
4128 *
4129 * Inserts the @widget into @box at @position.
4130 *
4131 * If a sort function is set, the widget will actually be inserted
4132 * at the calculated position.
4133 *
4134 * If @position is -1, or larger than the total number of children
4135 * in the @box, then the @widget will be appended to the end.
4136 */
4137void
4138gtk_flow_box_insert (GtkFlowBox *box,
4139 GtkWidget *widget,
4140 int position)
4141{
4142 GtkFlowBoxPrivate *priv;
4143 GtkFlowBoxChild *child;
4144 GSequenceIter *iter;
4145
4146 g_return_if_fail (GTK_IS_FLOW_BOX (box));
4147 g_return_if_fail (GTK_IS_WIDGET (widget));
4148
4149 priv = BOX_PRIV (box);
4150
4151 if (GTK_IS_FLOW_BOX_CHILD (widget))
4152 child = GTK_FLOW_BOX_CHILD (widget);
4153 else
4154 {
4155 child = GTK_FLOW_BOX_CHILD (gtk_flow_box_child_new ());
4156 gtk_flow_box_child_set_child (self: child, child: widget);
4157 }
4158
4159 if (priv->sort_func != NULL)
4160 iter = g_sequence_insert_sorted (seq: priv->children, data: child,
4161 cmp_func: (GCompareDataFunc)gtk_flow_box_sort, cmp_data: box);
4162 else if (position == 0)
4163 iter = g_sequence_prepend (seq: priv->children, data: child);
4164 else if (position == -1)
4165 iter = g_sequence_append (seq: priv->children, data: child);
4166 else
4167 {
4168 GSequenceIter *pos;
4169 pos = g_sequence_get_iter_at_pos (seq: priv->children, pos: position);
4170 iter = g_sequence_insert_before (iter: pos, data: child);
4171 }
4172
4173 CHILD_PRIV (child)->iter = iter;
4174 gtk_flow_box_insert_widget (box, GTK_WIDGET (child), iter);
4175 gtk_flow_box_apply_filter (box, child);
4176}
4177
4178/**
4179 * gtk_flow_box_get_child_at_index:
4180 * @box: a `GtkFlowBox`
4181 * @idx: the position of the child
4182 *
4183 * Gets the nth child in the @box.
4184 *
4185 * Returns: (transfer none) (nullable): the child widget, which will
4186 * always be a `GtkFlowBoxChild` or %NULL in case no child widget
4187 * with the given index exists.
4188 */
4189GtkFlowBoxChild *
4190gtk_flow_box_get_child_at_index (GtkFlowBox *box,
4191 int idx)
4192{
4193 GSequenceIter *iter;
4194
4195 g_return_val_if_fail (GTK_IS_FLOW_BOX (box), NULL);
4196
4197 iter = g_sequence_get_iter_at_pos (BOX_PRIV (box)->children, pos: idx);
4198 if (!g_sequence_iter_is_end (iter))
4199 return g_sequence_get (iter);
4200
4201 return NULL;
4202}
4203
4204/**
4205 * gtk_flow_box_get_child_at_pos:
4206 * @box: a `GtkFlowBox`
4207 * @x: the x coordinate of the child
4208 * @y: the y coordinate of the child
4209 *
4210 * Gets the child in the (@x, @y) position.
4211 *
4212 * Both @x and @y are assumed to be relative to the origin of @box.
4213 *
4214 * Returns: (transfer none) (nullable): the child widget, which will
4215 * always be a `GtkFlowBoxChild` or %NULL in case no child widget
4216 * exists for the given x and y coordinates.
4217 */
4218GtkFlowBoxChild *
4219gtk_flow_box_get_child_at_pos (GtkFlowBox *box,
4220 int x,
4221 int y)
4222{
4223 GtkWidget *child = gtk_widget_pick (GTK_WIDGET (box), x, y, flags: GTK_PICK_DEFAULT);
4224
4225 if (!child)
4226 return NULL;
4227
4228 return (GtkFlowBoxChild *)gtk_widget_get_ancestor (widget: child, GTK_TYPE_FLOW_BOX_CHILD);
4229}
4230
4231/**
4232 * gtk_flow_box_set_hadjustment:
4233 * @box: a `GtkFlowBox`
4234 * @adjustment: an adjustment which should be adjusted
4235 * when the focus is moved among the descendents of @container
4236 *
4237 * Hooks up an adjustment to focus handling in @box.
4238 *
4239 * The adjustment is also used for autoscrolling during
4240 * rubberband selection. See [method@Gtk.ScrolledWindow.get_hadjustment]
4241 * for a typical way of obtaining the adjustment, and
4242 * [method@Gtk.FlowBox.set_vadjustment] for setting the vertical
4243 * adjustment.
4244 *
4245 * The adjustments have to be in pixel units and in the same
4246 * coordinate system as the allocation for immediate children
4247 * of the box.
4248 */
4249void
4250gtk_flow_box_set_hadjustment (GtkFlowBox *box,
4251 GtkAdjustment *adjustment)
4252{
4253 GtkFlowBoxPrivate *priv;
4254
4255 g_return_if_fail (GTK_IS_FLOW_BOX (box));
4256 g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
4257
4258 priv = BOX_PRIV (box);
4259
4260 g_object_ref (adjustment);
4261 if (priv->hadjustment)
4262 g_object_unref (object: priv->hadjustment);
4263 priv->hadjustment = adjustment;
4264}
4265
4266/**
4267 * gtk_flow_box_set_vadjustment:
4268 * @box: a `GtkFlowBox`
4269 * @adjustment: an adjustment which should be adjusted
4270 * when the focus is moved among the descendents of @container
4271 *
4272 * Hooks up an adjustment to focus handling in @box.
4273 *
4274 * The adjustment is also used for autoscrolling during
4275 * rubberband selection. See [method@Gtk.ScrolledWindow.get_vadjustment]
4276 * for a typical way of obtaining the adjustment, and
4277 * [method@Gtk.FlowBox.set_hadjustment] for setting the horizontal
4278 * adjustment.
4279 *
4280 * The adjustments have to be in pixel units and in the same
4281 * coordinate system as the allocation for immediate children
4282 * of the box.
4283 */
4284void
4285gtk_flow_box_set_vadjustment (GtkFlowBox *box,
4286 GtkAdjustment *adjustment)
4287{
4288 GtkFlowBoxPrivate *priv;
4289
4290 g_return_if_fail (GTK_IS_FLOW_BOX (box));
4291 g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
4292
4293 priv = BOX_PRIV (box);
4294
4295 g_object_ref (adjustment);
4296 if (priv->vadjustment)
4297 g_object_unref (object: priv->vadjustment);
4298 priv->vadjustment = adjustment;
4299}
4300
4301static void
4302gtk_flow_box_check_model_compat (GtkFlowBox *box)
4303{
4304 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
4305
4306 if (priv->bound_model &&
4307 (priv->sort_func || priv->filter_func))
4308 g_warning ("GtkFlowBox with a model will ignore sort and filter functions");
4309}
4310
4311/**
4312 * gtk_flow_box_bind_model:
4313 * @box: a `GtkFlowBox`
4314 * @model: (nullable): the `GListModel` to be bound to @box
4315 * @create_widget_func: a function that creates widgets for items
4316 * @user_data: (closure): user data passed to @create_widget_func
4317 * @user_data_free_func: function for freeing @user_data
4318 *
4319 * Binds @model to @box.
4320 *
4321 * If @box was already bound to a model, that previous binding is
4322 * destroyed.
4323 *
4324 * The contents of @box are cleared and then filled with widgets that
4325 * represent items from @model. @box is updated whenever @model changes.
4326 * If @model is %NULL, @box is left empty.
4327 *
4328 * It is undefined to add or remove widgets directly (for example, with
4329 * [method@Gtk.FlowBox.insert]) while @box is bound to a model.
4330 *
4331 * Note that using a model is incompatible with the filtering and sorting
4332 * functionality in `GtkFlowBox`. When using a model, filtering and sorting
4333 * should be implemented by the model.
4334 */
4335void
4336gtk_flow_box_bind_model (GtkFlowBox *box,
4337 GListModel *model,
4338 GtkFlowBoxCreateWidgetFunc create_widget_func,
4339 gpointer user_data,
4340 GDestroyNotify user_data_free_func)
4341{
4342 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
4343 GtkWidget *child;
4344
4345 g_return_if_fail (GTK_IS_FLOW_BOX (box));
4346 g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
4347 g_return_if_fail (model == NULL || create_widget_func != NULL);
4348
4349 if (priv->bound_model)
4350 {
4351 if (priv->create_widget_func_data_destroy)
4352 priv->create_widget_func_data_destroy (priv->create_widget_func_data);
4353
4354 g_signal_handlers_disconnect_by_func (priv->bound_model, gtk_flow_box_bound_model_changed, box);
4355 g_clear_object (&priv->bound_model);
4356 }
4357
4358 while ((child = gtk_widget_get_first_child (GTK_WIDGET (box))))
4359 gtk_flow_box_remove (box, widget: child);
4360
4361 if (model == NULL)
4362 return;
4363
4364 priv->bound_model = g_object_ref (model);
4365 priv->create_widget_func = create_widget_func;
4366 priv->create_widget_func_data = user_data;
4367 priv->create_widget_func_data_destroy = user_data_free_func;
4368
4369 gtk_flow_box_check_model_compat (box);
4370
4371 g_signal_connect (priv->bound_model, "items-changed", G_CALLBACK (gtk_flow_box_bound_model_changed), box);
4372 gtk_flow_box_bound_model_changed (list: model, position: 0, removed: 0, added: g_list_model_get_n_items (list: model), user_data: box);
4373}
4374
4375/* Setters and getters {{{2 */
4376
4377/**
4378 * gtk_flow_box_get_homogeneous: (attributes org.gtk.Method.get_property=homogeneous)
4379 * @box: a `GtkFlowBox`
4380 *
4381 * Returns whether the box is homogeneous.
4382 *
4383 * Returns: %TRUE if the box is homogeneous.
4384 */
4385gboolean
4386gtk_flow_box_get_homogeneous (GtkFlowBox *box)
4387{
4388 g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE);
4389
4390 return BOX_PRIV (box)->homogeneous;
4391}
4392
4393/**
4394 * gtk_flow_box_set_homogeneous: (attributes org.gtk.Method.set_property=homogeneous)
4395 * @box: a `GtkFlowBox`
4396 * @homogeneous: %TRUE to create equal allotments,
4397 * %FALSE for variable allotments
4398 *
4399 * Sets whether or not all children of @box are given
4400 * equal space in the box.
4401 */
4402void
4403gtk_flow_box_set_homogeneous (GtkFlowBox *box,
4404 gboolean homogeneous)
4405{
4406 g_return_if_fail (GTK_IS_FLOW_BOX (box));
4407
4408 homogeneous = homogeneous != FALSE;
4409
4410 if (BOX_PRIV (box)->homogeneous != homogeneous)
4411 {
4412 BOX_PRIV (box)->homogeneous = homogeneous;
4413
4414 g_object_notify_by_pspec (G_OBJECT (box), pspec: props[PROP_HOMOGENEOUS]);
4415 gtk_widget_queue_resize (GTK_WIDGET (box));
4416 }
4417}
4418
4419/**
4420 * gtk_flow_box_set_row_spacing: (attributes org.gtk.Method.set_property=row-spacing)
4421 * @box: a `GtkFlowBox`
4422 * @spacing: the spacing to use
4423 *
4424 * Sets the vertical space to add between children.
4425 */
4426void
4427gtk_flow_box_set_row_spacing (GtkFlowBox *box,
4428 guint spacing)
4429{
4430 g_return_if_fail (GTK_IS_FLOW_BOX (box));
4431
4432 if (BOX_PRIV (box)->row_spacing != spacing)
4433 {
4434 BOX_PRIV (box)->row_spacing = spacing;
4435
4436 gtk_widget_queue_resize (GTK_WIDGET (box));
4437 g_object_notify_by_pspec (G_OBJECT (box), pspec: props[PROP_ROW_SPACING]);
4438 }
4439}
4440
4441/**
4442 * gtk_flow_box_get_row_spacing: (attributes org.gtk.Method.get_property=row-spacing)
4443 * @box: a `GtkFlowBox`
4444 *
4445 * Gets the vertical spacing.
4446 *
4447 * Returns: the vertical spacing
4448 */
4449guint
4450gtk_flow_box_get_row_spacing (GtkFlowBox *box)
4451{
4452 g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE);
4453
4454 return BOX_PRIV (box)->row_spacing;
4455}
4456
4457/**
4458 * gtk_flow_box_set_column_spacing: (attributes org.gtk.Method.set_property=column-spacing)
4459 * @box: a `GtkFlowBox`
4460 * @spacing: the spacing to use
4461 *
4462 * Sets the horizontal space to add between children.
4463 */
4464void
4465gtk_flow_box_set_column_spacing (GtkFlowBox *box,
4466 guint spacing)
4467{
4468 g_return_if_fail (GTK_IS_FLOW_BOX (box));
4469
4470 if (BOX_PRIV (box)->column_spacing != spacing)
4471 {
4472 BOX_PRIV (box)->column_spacing = spacing;
4473
4474 gtk_widget_queue_resize (GTK_WIDGET (box));
4475 g_object_notify_by_pspec (G_OBJECT (box), pspec: props[PROP_COLUMN_SPACING]);
4476 }
4477}
4478
4479/**
4480 * gtk_flow_box_get_column_spacing: (attributes org.gtk.Method.get_property=column-spacing)
4481 * @box: a `GtkFlowBox`
4482 *
4483 * Gets the horizontal spacing.
4484 *
4485 * Returns: the horizontal spacing
4486 */
4487guint
4488gtk_flow_box_get_column_spacing (GtkFlowBox *box)
4489{
4490 g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE);
4491
4492 return BOX_PRIV (box)->column_spacing;
4493}
4494
4495/**
4496 * gtk_flow_box_set_min_children_per_line: (attributes org.gtk.Method.set_property=min-children-per-line)
4497 * @box: a `GtkFlowBox`
4498 * @n_children: the minimum number of children per line
4499 *
4500 * Sets the minimum number of children to line up
4501 * in @box’s orientation before flowing.
4502 */
4503void
4504gtk_flow_box_set_min_children_per_line (GtkFlowBox *box,
4505 guint n_children)
4506{
4507 g_return_if_fail (GTK_IS_FLOW_BOX (box));
4508
4509 if (BOX_PRIV (box)->min_children_per_line != n_children)
4510 {
4511 BOX_PRIV (box)->min_children_per_line = n_children;
4512
4513 gtk_widget_queue_resize (GTK_WIDGET (box));
4514 g_object_notify_by_pspec (G_OBJECT (box), pspec: props[PROP_MIN_CHILDREN_PER_LINE]);
4515 }
4516}
4517
4518/**
4519 * gtk_flow_box_get_min_children_per_line: (attributes org.gtk.Method.get_property=min-children-per-line)
4520 * @box: a `GtkFlowBox`
4521 *
4522 * Gets the minimum number of children per line.
4523 *
4524 * Returns: the minimum number of children per line
4525 */
4526guint
4527gtk_flow_box_get_min_children_per_line (GtkFlowBox *box)
4528{
4529 g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE);
4530
4531 return BOX_PRIV (box)->min_children_per_line;
4532}
4533
4534/**
4535 * gtk_flow_box_set_max_children_per_line: (attributes org.gtk.Method.set_property=max-children-per-line)
4536 * @box: a `GtkFlowBox`
4537 * @n_children: the maximum number of children per line
4538 *
4539 * Sets the maximum number of children to request and
4540 * allocate space for in @box’s orientation.
4541 *
4542 * Setting the maximum number of children per line
4543 * limits the overall natural size request to be no more
4544 * than @n_children children long in the given orientation.
4545 */
4546void
4547gtk_flow_box_set_max_children_per_line (GtkFlowBox *box,
4548 guint n_children)
4549{
4550 g_return_if_fail (GTK_IS_FLOW_BOX (box));
4551 g_return_if_fail (n_children > 0);
4552
4553 if (BOX_PRIV (box)->max_children_per_line != n_children)
4554 {
4555 BOX_PRIV (box)->max_children_per_line = n_children;
4556
4557 gtk_widget_queue_resize (GTK_WIDGET (box));
4558 g_object_notify_by_pspec (G_OBJECT (box), pspec: props[PROP_MAX_CHILDREN_PER_LINE]);
4559 }
4560}
4561
4562/**
4563 * gtk_flow_box_get_max_children_per_line: (attributes org.gtk.Method.get_property=max-children-per-line)
4564 * @box: a `GtkFlowBox`
4565 *
4566 * Gets the maximum number of children per line.
4567 *
4568 * Returns: the maximum number of children per line
4569 */
4570guint
4571gtk_flow_box_get_max_children_per_line (GtkFlowBox *box)
4572{
4573 g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE);
4574
4575 return BOX_PRIV (box)->max_children_per_line;
4576}
4577
4578/**
4579 * gtk_flow_box_set_activate_on_single_click: (attributes org.gtk.Method.set_property=activate-on-single-click)
4580 * @box: a `GtkFlowBox`
4581 * @single: %TRUE to emit child-activated on a single click
4582 *
4583 * If @single is %TRUE, children will be activated when you click
4584 * on them, otherwise you need to double-click.
4585 */
4586void
4587gtk_flow_box_set_activate_on_single_click (GtkFlowBox *box,
4588 gboolean single)
4589{
4590 g_return_if_fail (GTK_IS_FLOW_BOX (box));
4591
4592 single = single != FALSE;
4593
4594 if (BOX_PRIV (box)->activate_on_single_click != single)
4595 {
4596 BOX_PRIV (box)->activate_on_single_click = single;
4597 g_object_notify_by_pspec (G_OBJECT (box), pspec: props[PROP_ACTIVATE_ON_SINGLE_CLICK]);
4598 }
4599}
4600
4601/**
4602 * gtk_flow_box_get_activate_on_single_click: (attributes org.gtk.Method.get_property=activate-on-single-click)
4603 * @box: a `GtkFlowBox`
4604 *
4605 * Returns whether children activate on single clicks.
4606 *
4607 * Returns: %TRUE if children are activated on single click,
4608 * %FALSE otherwise
4609 */
4610gboolean
4611gtk_flow_box_get_activate_on_single_click (GtkFlowBox *box)
4612{
4613 g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE);
4614
4615 return BOX_PRIV (box)->activate_on_single_click;
4616}
4617
4618static void
4619gtk_flow_box_set_accept_unpaired_release (GtkFlowBox *box,
4620 gboolean accept)
4621{
4622 if (BOX_PRIV (box)->accept_unpaired_release == accept)
4623 return;
4624
4625 BOX_PRIV (box)->accept_unpaired_release = accept;
4626 g_object_notify_by_pspec (G_OBJECT (box), pspec: props[PROP_ACCEPT_UNPAIRED_RELEASE]);
4627}
4628
4629 /* Selection handling {{{2 */
4630
4631/**
4632 * gtk_flow_box_get_selected_children:
4633 * @box: a `GtkFlowBox`
4634 *
4635 * Creates a list of all selected children.
4636 *
4637 * Returns: (element-type GtkFlowBoxChild) (transfer container):
4638 * A `GList` containing the `GtkWidget` for each selected child.
4639 * Free with g_list_free() when done.
4640 */
4641GList *
4642gtk_flow_box_get_selected_children (GtkFlowBox *box)
4643{
4644 GtkFlowBoxChild *child;
4645 GSequenceIter *iter;
4646 GList *selected = NULL;
4647
4648 g_return_val_if_fail (GTK_IS_FLOW_BOX (box), NULL);
4649
4650 for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
4651 !g_sequence_iter_is_end (iter);
4652 iter = g_sequence_iter_next (iter))
4653 {
4654 child = g_sequence_get (iter);
4655 if (CHILD_PRIV (child)->selected)
4656 selected = g_list_prepend (list: selected, data: child);
4657 }
4658
4659 return g_list_reverse (list: selected);
4660}
4661
4662/**
4663 * gtk_flow_box_select_child:
4664 * @box: a `GtkFlowBox`
4665 * @child: a child of @box
4666 *
4667 * Selects a single child of @box, if the selection
4668 * mode allows it.
4669 */
4670void
4671gtk_flow_box_select_child (GtkFlowBox *box,
4672 GtkFlowBoxChild *child)
4673{
4674 g_return_if_fail (GTK_IS_FLOW_BOX (box));
4675 g_return_if_fail (GTK_IS_FLOW_BOX_CHILD (child));
4676
4677 gtk_flow_box_select_child_internal (box, child);
4678}
4679
4680/**
4681 * gtk_flow_box_unselect_child:
4682 * @box: a `GtkFlowBox`
4683 * @child: a child of @box
4684 *
4685 * Unselects a single child of @box, if the selection
4686 * mode allows it.
4687 */
4688void
4689gtk_flow_box_unselect_child (GtkFlowBox *box,
4690 GtkFlowBoxChild *child)
4691{
4692 g_return_if_fail (GTK_IS_FLOW_BOX (box));
4693 g_return_if_fail (GTK_IS_FLOW_BOX_CHILD (child));
4694
4695 gtk_flow_box_unselect_child_internal (box, child);
4696}
4697
4698/**
4699 * gtk_flow_box_select_all:
4700 * @box: a `GtkFlowBox`
4701 *
4702 * Select all children of @box, if the selection
4703 * mode allows it.
4704 */
4705void
4706gtk_flow_box_select_all (GtkFlowBox *box)
4707{
4708 g_return_if_fail (GTK_IS_FLOW_BOX (box));
4709
4710 if (BOX_PRIV (box)->selection_mode != GTK_SELECTION_MULTIPLE)
4711 return;
4712
4713 if (g_sequence_get_length (BOX_PRIV (box)->children) > 0)
4714 {
4715 gtk_flow_box_select_all_between (box, NULL, NULL, FALSE);
4716 g_signal_emit (instance: box, signal_id: signals[SELECTED_CHILDREN_CHANGED], detail: 0);
4717 }
4718}
4719
4720/**
4721 * gtk_flow_box_unselect_all:
4722 * @box: a `GtkFlowBox`
4723 *
4724 * Unselect all children of @box, if the selection
4725 * mode allows it.
4726 */
4727void
4728gtk_flow_box_unselect_all (GtkFlowBox *box)
4729{
4730 gboolean dirty = FALSE;
4731
4732 g_return_if_fail (GTK_IS_FLOW_BOX (box));
4733
4734 if (BOX_PRIV (box)->selection_mode == GTK_SELECTION_BROWSE)
4735 return;
4736
4737 dirty = gtk_flow_box_unselect_all_internal (box);
4738
4739 if (dirty)
4740 g_signal_emit (instance: box, signal_id: signals[SELECTED_CHILDREN_CHANGED], detail: 0);
4741}
4742
4743/**
4744 * GtkFlowBoxForeachFunc:
4745 * @box: a `GtkFlowBox`
4746 * @child: a `GtkFlowBoxChild`
4747 * @user_data: (closure): user data
4748 *
4749 * A function used by gtk_flow_box_selected_foreach().
4750 *
4751 * It will be called on every selected child of the @box.
4752 */
4753
4754/**
4755 * gtk_flow_box_selected_foreach:
4756 * @box: a `GtkFlowBox`
4757 * @func: (scope call): the function to call for each selected child
4758 * @data: user data to pass to the function
4759 *
4760 * Calls a function for each selected child.
4761 *
4762 * Note that the selection cannot be modified from within
4763 * this function.
4764 */
4765void
4766gtk_flow_box_selected_foreach (GtkFlowBox *box,
4767 GtkFlowBoxForeachFunc func,
4768 gpointer data)
4769{
4770 GtkFlowBoxChild *child;
4771 GSequenceIter *iter;
4772
4773 g_return_if_fail (GTK_IS_FLOW_BOX (box));
4774
4775 for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
4776 !g_sequence_iter_is_end (iter);
4777 iter = g_sequence_iter_next (iter))
4778 {
4779 child = g_sequence_get (iter);
4780 if (CHILD_PRIV (child)->selected)
4781 (*func) (box, child, data);
4782 }
4783}
4784
4785/**
4786 * gtk_flow_box_set_selection_mode: (attributes org.gtk.Method.set_property=selection-mode)
4787 * @box: a `GtkFlowBox`
4788 * @mode: the new selection mode
4789 *
4790 * Sets how selection works in @box.
4791 */
4792void
4793gtk_flow_box_set_selection_mode (GtkFlowBox *box,
4794 GtkSelectionMode mode)
4795{
4796 gboolean dirty = FALSE;
4797
4798 g_return_if_fail (GTK_IS_FLOW_BOX (box));
4799
4800 if (mode == BOX_PRIV (box)->selection_mode)
4801 return;
4802
4803 if (mode == GTK_SELECTION_NONE ||
4804 BOX_PRIV (box)->selection_mode == GTK_SELECTION_MULTIPLE)
4805 {
4806 dirty = gtk_flow_box_unselect_all_internal (box);
4807 BOX_PRIV (box)->selected_child = NULL;
4808 }
4809
4810 BOX_PRIV (box)->selection_mode = mode;
4811
4812 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: box),
4813 first_property: GTK_ACCESSIBLE_PROPERTY_MULTI_SELECTABLE, mode == GTK_SELECTION_MULTIPLE,
4814 -1);
4815
4816 g_object_notify_by_pspec (G_OBJECT (box), pspec: props[PROP_SELECTION_MODE]);
4817
4818 if (dirty)
4819 g_signal_emit (instance: box, signal_id: signals[SELECTED_CHILDREN_CHANGED], detail: 0);
4820}
4821
4822/**
4823 * gtk_flow_box_get_selection_mode: (attributes org.gtk.Method.get_property=selection-mode)
4824 * @box: a `GtkFlowBox`
4825 *
4826 * Gets the selection mode of @box.
4827 *
4828 * Returns: the `GtkSelectionMode`
4829 */
4830GtkSelectionMode
4831gtk_flow_box_get_selection_mode (GtkFlowBox *box)
4832{
4833 g_return_val_if_fail (GTK_IS_FLOW_BOX (box), GTK_SELECTION_SINGLE);
4834
4835 return BOX_PRIV (box)->selection_mode;
4836}
4837
4838/* Filtering {{{2 */
4839
4840/**
4841 * GtkFlowBoxFilterFunc:
4842 * @child: a `GtkFlowBoxChild` that may be filtered
4843 * @user_data: (closure): user data
4844 *
4845 * A function that will be called whenever a child changes
4846 * or is added.
4847 *
4848 * It lets you control if the child should be visible or not.
4849 *
4850 * Returns: %TRUE if the row should be visible, %FALSE otherwise
4851 */
4852
4853/**
4854 * gtk_flow_box_set_filter_func:
4855 * @box: a `GtkFlowBox`
4856 * @filter_func: (nullable): callback that
4857 * lets you filter which children to show
4858 * @user_data: (closure): user data passed to @filter_func
4859 * @destroy: destroy notifier for @user_data
4860 *
4861 * By setting a filter function on the @box one can decide dynamically
4862 * which of the children to show.
4863 *
4864 * For instance, to implement a search function that only shows the
4865 * children matching the search terms.
4866 *
4867 * The @filter_func will be called for each child after the call, and
4868 * it will continue to be called each time a child changes (via
4869 * [method@Gtk.FlowBoxChild.changed]) or when
4870 * [method@Gtk.FlowBox.invalidate_filter] is called.
4871 *
4872 * Note that using a filter function is incompatible with using a model
4873 * (see [method@Gtk.FlowBox.bind_model]).
4874 */
4875void
4876gtk_flow_box_set_filter_func (GtkFlowBox *box,
4877 GtkFlowBoxFilterFunc filter_func,
4878 gpointer user_data,
4879 GDestroyNotify destroy)
4880{
4881 GtkFlowBoxPrivate *priv;
4882
4883 g_return_if_fail (GTK_IS_FLOW_BOX (box));
4884
4885 priv = BOX_PRIV (box);
4886
4887 if (priv->filter_destroy != NULL)
4888 priv->filter_destroy (priv->filter_data);
4889
4890 priv->filter_func = filter_func;
4891 priv->filter_data = user_data;
4892 priv->filter_destroy = destroy;
4893
4894 gtk_flow_box_check_model_compat (box);
4895
4896 gtk_flow_box_apply_filter_all (box);
4897}
4898
4899/**
4900 * gtk_flow_box_invalidate_filter:
4901 * @box: a `GtkFlowBox`
4902 *
4903 * Updates the filtering for all children.
4904 *
4905 * Call this function when the result of the filter
4906 * function on the @box is changed due ot an external
4907 * factor. For instance, this would be used if the
4908 * filter function just looked for a specific search
4909 * term, and the entry with the string has changed.
4910 */
4911void
4912gtk_flow_box_invalidate_filter (GtkFlowBox *box)
4913{
4914 g_return_if_fail (GTK_IS_FLOW_BOX (box));
4915
4916 if (BOX_PRIV (box)->filter_func != NULL)
4917 gtk_flow_box_apply_filter_all (box);
4918}
4919
4920/* Sorting {{{2 */
4921
4922/**
4923 * GtkFlowBoxSortFunc:
4924 * @child1: the first child
4925 * @child2: the second child
4926 * @user_data: (closure): user data
4927 *
4928 * A function to compare two children to determine which
4929 * should come first.
4930 *
4931 * Returns: < 0 if @child1 should be before @child2, 0 if
4932 * the are equal, and > 0 otherwise
4933 */
4934
4935/**
4936 * gtk_flow_box_set_sort_func:
4937 * @box: a `GtkFlowBox`
4938 * @sort_func: (nullable): the sort function
4939 * @user_data: (closure): user data passed to @sort_func
4940 * @destroy: destroy notifier for @user_data
4941 *
4942 * By setting a sort function on the @box, one can dynamically
4943 * reorder the children of the box, based on the contents of
4944 * the children.
4945 *
4946 * The @sort_func will be called for each child after the call,
4947 * and will continue to be called each time a child changes (via
4948 * [method@Gtk.FlowBoxChild.changed]) and when
4949 * [method@Gtk.FlowBox.invalidate_sort] is called.
4950 *
4951 * Note that using a sort function is incompatible with using a model
4952 * (see [method@Gtk.FlowBox.bind_model]).
4953 */
4954void
4955gtk_flow_box_set_sort_func (GtkFlowBox *box,
4956 GtkFlowBoxSortFunc sort_func,
4957 gpointer user_data,
4958 GDestroyNotify destroy)
4959{
4960 GtkFlowBoxPrivate *priv;
4961
4962 g_return_if_fail (GTK_IS_FLOW_BOX (box));
4963
4964 priv = BOX_PRIV (box);
4965
4966 if (priv->sort_destroy != NULL)
4967 priv->sort_destroy (priv->sort_data);
4968
4969 priv->sort_func = sort_func;
4970 priv->sort_data = user_data;
4971 priv->sort_destroy = destroy;
4972
4973 gtk_flow_box_check_model_compat (box);
4974
4975 gtk_flow_box_invalidate_sort (box);
4976}
4977
4978static int
4979gtk_flow_box_sort (GtkFlowBoxChild *a,
4980 GtkFlowBoxChild *b,
4981 GtkFlowBox *box)
4982{
4983 GtkFlowBoxPrivate *priv = BOX_PRIV (box);
4984
4985 return priv->sort_func (a, b, priv->sort_data);
4986}
4987
4988static void
4989gtk_flow_box_reorder_foreach (gpointer data,
4990 gpointer user_data)
4991{
4992 GtkWidget **previous = user_data;
4993 GtkWidget *row = data;
4994
4995 if (*previous)
4996 gtk_widget_insert_after (widget: row, parent: _gtk_widget_get_parent (widget: row), previous_sibling: *previous);
4997
4998 *previous = row;
4999}
5000
5001/**
5002 * gtk_flow_box_invalidate_sort:
5003 * @box: a `GtkFlowBox`
5004 *
5005 * Updates the sorting for all children.
5006 *
5007 * Call this when the result of the sort function on
5008 * @box is changed due to an external factor.
5009 */
5010void
5011gtk_flow_box_invalidate_sort (GtkFlowBox *box)
5012{
5013 GtkFlowBoxPrivate *priv;
5014 GtkWidget *previous = NULL;
5015
5016 g_return_if_fail (GTK_IS_FLOW_BOX (box));
5017
5018 priv = BOX_PRIV (box);
5019
5020 if (priv->sort_func != NULL)
5021 {
5022 g_sequence_sort (seq: priv->children, cmp_func: (GCompareDataFunc)gtk_flow_box_sort, cmp_data: box);
5023 g_sequence_foreach (seq: priv->children, func: gtk_flow_box_reorder_foreach, user_data: &previous);
5024 gtk_widget_queue_resize (GTK_WIDGET (box));
5025 }
5026}
5027
5028/* vim:set foldmethod=marker expandtab: */
5029

source code of gtk/gtk/gtkflowbox.c