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 | |
110 | static void gtk_flow_box_update_cursor (GtkFlowBox *box, |
111 | GtkFlowBoxChild *child); |
112 | static void gtk_flow_box_select_and_activate (GtkFlowBox *box, |
113 | GtkFlowBoxChild *child); |
114 | static void gtk_flow_box_update_selection (GtkFlowBox *box, |
115 | GtkFlowBoxChild *child, |
116 | gboolean modify, |
117 | gboolean extend); |
118 | static void gtk_flow_box_apply_filter (GtkFlowBox *box, |
119 | GtkFlowBoxChild *child); |
120 | static void gtk_flow_box_apply_sort (GtkFlowBox *box, |
121 | GtkFlowBoxChild *child); |
122 | static int gtk_flow_box_sort (GtkFlowBoxChild *a, |
123 | GtkFlowBoxChild *b, |
124 | GtkFlowBox *box); |
125 | |
126 | static void gtk_flow_box_bound_model_changed (GListModel *list, |
127 | guint position, |
128 | guint removed, |
129 | guint added, |
130 | gpointer user_data); |
131 | |
132 | static void gtk_flow_box_set_accept_unpaired_release (GtkFlowBox *box, |
133 | gboolean accept); |
134 | |
135 | static void gtk_flow_box_check_model_compat (GtkFlowBox *box); |
136 | |
137 | static void |
138 | path_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 | |
195 | static void |
196 | path_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 | |
257 | enum { |
258 | CHILD_ACTIVATE, |
259 | CHILD_LAST_SIGNAL |
260 | }; |
261 | |
262 | static guint child_signals[CHILD_LAST_SIGNAL] = { 0 }; |
263 | |
264 | enum { |
265 | PROP_CHILD = 1 |
266 | }; |
267 | |
268 | typedef struct _GtkFlowBoxChildPrivate GtkFlowBoxChildPrivate; |
269 | struct _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 | |
278 | static void gtk_flow_box_child_buildable_iface_init (GtkBuildableIface *iface); |
279 | |
280 | G_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 | |
287 | static GtkFlowBox * |
288 | gtk_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 | |
299 | static void |
300 | gtk_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 | |
309 | static GtkBuildableIface *parent_child_buildable_iface; |
310 | |
311 | static void |
312 | gtk_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 | |
323 | static void |
324 | gtk_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 | |
331 | static gboolean |
332 | gtk_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 | |
400 | static void |
401 | gtk_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 | |
412 | static GtkSizeRequestMode |
413 | gtk_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 | |
424 | static void |
425 | gtk_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 | |
435 | static void |
436 | gtk_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 | |
455 | static void |
456 | gtk_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 | |
475 | static void |
476 | gtk_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 | |
495 | static void |
496 | gtk_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 | |
509 | static void |
510 | gtk_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 | |
568 | static void |
569 | gtk_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 | */ |
585 | GtkWidget * |
586 | gtk_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 | */ |
598 | void |
599 | gtk_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 | */ |
620 | GtkWidget * |
621 | gtk_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 | */ |
637 | int |
638 | gtk_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 | */ |
661 | gboolean |
662 | gtk_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 | */ |
693 | void |
694 | gtk_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 | |
721 | enum { |
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 | |
732 | static guint signals[LAST_SIGNAL] = { 0 }; |
733 | |
734 | enum { |
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 | |
750 | static GParamSpec *props[LAST_PROP] = { NULL, }; |
751 | |
752 | typedef struct _GtkFlowBoxClass GtkFlowBoxClass; |
753 | |
754 | struct _GtkFlowBox |
755 | { |
756 | GtkWidget container; |
757 | }; |
758 | |
759 | struct _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 | |
777 | typedef struct _GtkFlowBoxPrivate GtkFlowBoxPrivate; |
778 | struct _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 | |
833 | static void gtk_flow_box_buildable_iface_init (GtkBuildableIface *iface); |
834 | |
835 | G_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 | */ |
856 | static inline gboolean |
857 | child_is_visible (GtkWidget *child) |
858 | { |
859 | return gtk_widget_get_visible (widget: child) && |
860 | gtk_widget_get_child_visible (widget: child); |
861 | } |
862 | |
863 | static int |
864 | get_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 | |
883 | static void |
884 | gtk_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 | |
897 | static void |
898 | gtk_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 | |
914 | static void |
915 | gtk_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 | |
928 | static gboolean |
929 | gtk_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 | |
951 | static gboolean |
952 | gtk_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 | |
972 | static void |
973 | gtk_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 | |
989 | static void |
990 | gtk_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 | |
997 | static void |
998 | gtk_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 | |
1015 | static void |
1016 | gtk_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 | |
1060 | static void |
1061 | gtk_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 | |
1119 | static void |
1120 | gtk_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 | |
1133 | static GSequenceIter * |
1134 | gtk_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 | |
1151 | static GSequenceIter * |
1152 | gtk_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 | |
1171 | static GSequenceIter * |
1172 | gtk_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 | |
1189 | static GSequenceIter * |
1190 | gtk_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 | |
1199 | static GSequenceIter * |
1200 | gtk_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 | |
1225 | static GSequenceIter * |
1226 | gtk_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 | */ |
1258 | static void |
1259 | get_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 | */ |
1299 | static void |
1300 | get_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 | */ |
1341 | static GSequenceIter * |
1342 | get_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 , |
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 */ |
1399 | static int |
1400 | gather_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 , 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 | |
1454 | static GtkRequestedSize * |
1455 | fit_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 | |
1509 | typedef struct { |
1510 | GArray *requested; |
1511 | int ; |
1512 | } AllocatedLine; |
1513 | |
1514 | static int |
1515 | get_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 | |
1540 | static void |
1541 | gtk_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 = 0, = 0, = 0; |
1560 | int = 0, = 0, = 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 = 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 = 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 | |
1910 | static GtkSizeRequestMode |
1911 | gtk_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 */ |
1921 | static void |
1922 | get_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 | |
1987 | static void |
1988 | gtk_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, ; |
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, ; |
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 | |
2497 | static void |
2498 | gtk_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 | |
2611 | static void |
2612 | remove_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 | |
2625 | static gboolean |
2626 | autoscroll_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 | |
2697 | static void |
2698 | add_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 | |
2712 | static gboolean |
2713 | get_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 | |
2732 | static void |
2733 | update_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 | |
2775 | static void |
2776 | gtk_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 | |
2826 | static void |
2827 | gtk_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 | |
2856 | static void |
2857 | gtk_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 | |
2876 | static void |
2877 | gtk_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 | |
2923 | static void |
2924 | gtk_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 | |
2933 | static void |
2934 | gtk_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 | |
2957 | static void |
2958 | gtk_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 | |
2974 | static void |
2975 | gtk_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 | |
3006 | static gboolean |
3007 | gtk_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 | |
3027 | static void |
3028 | gtk_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 | */ |
3044 | void |
3045 | gtk_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 | |
3090 | static gboolean |
3091 | gtk_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 | |
3155 | static void |
3156 | gtk_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 | |
3180 | static void |
3181 | gtk_flow_box_activate_cursor_child (GtkFlowBox *box) |
3182 | { |
3183 | gtk_flow_box_select_and_activate (box, BOX_PRIV (box)->cursor_child); |
3184 | } |
3185 | |
3186 | static void |
3187 | gtk_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 | |
3202 | void |
3203 | gtk_flow_box_disable_move_cursor (GtkFlowBox *box) |
3204 | { |
3205 | GtkFlowBoxPrivate *priv = BOX_PRIV (box); |
3206 | |
3207 | priv->disable_move_cursor = TRUE; |
3208 | } |
3209 | |
3210 | static gboolean |
3211 | gtk_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 | |
3409 | static void |
3410 | gtk_flow_box_selected_children_changed (GtkFlowBox *box) |
3411 | { |
3412 | } |
3413 | |
3414 | /* GObject implementation {{{2 */ |
3415 | |
3416 | static void |
3417 | gtk_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 | |
3460 | static void |
3461 | gtk_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 | |
3517 | static void |
3518 | gtk_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 | |
3557 | static void |
3558 | gtk_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 | |
3578 | static void |
3579 | gtk_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 | |
3917 | static void |
3918 | gtk_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 | |
3973 | static void |
3974 | gtk_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 | |
4021 | static GtkBuildableIface *parent_buildable_iface; |
4022 | |
4023 | static void |
4024 | gtk_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 | |
4035 | static void |
4036 | gtk_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 | */ |
4051 | GtkWidget * |
4052 | gtk_flow_box_new (void) |
4053 | { |
4054 | return (GtkWidget *)g_object_new (GTK_TYPE_FLOW_BOX, NULL); |
4055 | } |
4056 | |
4057 | static void |
4058 | gtk_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 | */ |
4089 | void |
4090 | gtk_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 | */ |
4113 | void |
4114 | gtk_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 | */ |
4137 | void |
4138 | gtk_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 | */ |
4189 | GtkFlowBoxChild * |
4190 | gtk_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 | */ |
4218 | GtkFlowBoxChild * |
4219 | gtk_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 | */ |
4249 | void |
4250 | gtk_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 | */ |
4284 | void |
4285 | gtk_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 | |
4301 | static void |
4302 | gtk_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 | */ |
4335 | void |
4336 | gtk_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 | */ |
4385 | gboolean |
4386 | gtk_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 | */ |
4402 | void |
4403 | gtk_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 | */ |
4426 | void |
4427 | gtk_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 | */ |
4449 | guint |
4450 | gtk_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 | */ |
4464 | void |
4465 | gtk_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 | */ |
4487 | guint |
4488 | gtk_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 | */ |
4503 | void |
4504 | gtk_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 | */ |
4526 | guint |
4527 | gtk_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 | */ |
4546 | void |
4547 | gtk_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 | */ |
4570 | guint |
4571 | gtk_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 | */ |
4586 | void |
4587 | gtk_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 | */ |
4610 | gboolean |
4611 | gtk_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 | |
4618 | static void |
4619 | gtk_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 | */ |
4641 | GList * |
4642 | gtk_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 | */ |
4670 | void |
4671 | gtk_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 | */ |
4688 | void |
4689 | gtk_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 | */ |
4705 | void |
4706 | gtk_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 | */ |
4727 | void |
4728 | gtk_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 | */ |
4765 | void |
4766 | gtk_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 | */ |
4792 | void |
4793 | gtk_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 | */ |
4830 | GtkSelectionMode |
4831 | gtk_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 | */ |
4875 | void |
4876 | gtk_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 | */ |
4911 | void |
4912 | gtk_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 | */ |
4954 | void |
4955 | gtk_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 | |
4978 | static int |
4979 | gtk_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 | |
4988 | static void |
4989 | gtk_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 | */ |
5010 | void |
5011 | gtk_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 | |