1/* gtkgridlayout.c: Layout manager for grid-like widgets
2 * Copyright 2019 GNOME Foundation
3 *
4 * SPDX-License-Identifier: LGPL-2.1-or-later
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20/**
21 * GtkGridLayout:
22 *
23 * `GtkGridLayout` is a layout manager which arranges child widgets in
24 * rows and columns.
25 *
26 * Children have an "attach point" defined by the horizontal and vertical
27 * index of the cell they occupy; children can span multiple rows or columns.
28 * The layout properties for setting the attach points and spans are set
29 * using the [class@Gtk.GridLayoutChild] associated to each child widget.
30 *
31 * The behaviour of `GtkGridLayout` when several children occupy the same
32 * grid cell is undefined.
33 *
34 * `GtkGridLayout` can be used like a `GtkBoxLayout` if all children are
35 * attached to the same row or column; however, if you only ever need a
36 * single row or column, you should consider using `GtkBoxLayout`.
37 */
38
39/**
40 * GtkGridLayoutChild:
41 *
42 * `GtkLayoutChild` subclass for children in a `GtkGridLayout`.
43 */
44#include "config.h"
45
46#include "gtkgridlayout.h"
47
48#include "gtkcsspositionvalueprivate.h"
49#include "gtkdebug.h"
50#include "gtkintl.h"
51#include "gtklayoutchild.h"
52#include "gtkorientable.h"
53#include "gtkprivate.h"
54#include "gtksizerequest.h"
55#include "gtkwidgetprivate.h"
56#include "gtkcssnodeprivate.h"
57
58/* {{{ GtkGridLayoutChild */
59typedef struct {
60 int pos;
61 int span;
62} GridChildAttach;
63
64struct _GtkGridLayoutChild
65{
66 GtkLayoutChild parent_instance;
67
68 GridChildAttach attach[2];
69};
70
71#define CHILD_COLUMN(child) ((child)->attach[GTK_ORIENTATION_HORIZONTAL].pos)
72#define CHILD_COL_SPAN(child) ((child)->attach[GTK_ORIENTATION_HORIZONTAL].span)
73#define CHILD_ROW(child) ((child)->attach[GTK_ORIENTATION_VERTICAL].pos)
74#define CHILD_ROW_SPAN(child) ((child)->attach[GTK_ORIENTATION_VERTICAL].span)
75
76enum {
77 PROP_CHILD_COLUMN = 1,
78 PROP_CHILD_ROW,
79 PROP_CHILD_COLUMN_SPAN,
80 PROP_CHILD_ROW_SPAN,
81
82 N_CHILD_PROPERTIES
83};
84
85static GParamSpec *child_props[N_CHILD_PROPERTIES];
86
87G_DEFINE_TYPE (GtkGridLayoutChild, gtk_grid_layout_child, GTK_TYPE_LAYOUT_CHILD)
88
89static void
90gtk_grid_layout_child_set_property (GObject *gobject,
91 guint prop_id,
92 const GValue *value,
93 GParamSpec *pspec)
94{
95 GtkGridLayoutChild *self = GTK_GRID_LAYOUT_CHILD (ptr: gobject);
96
97 switch (prop_id)
98 {
99 case PROP_CHILD_COLUMN:
100 gtk_grid_layout_child_set_column (child: self, column: g_value_get_int (value));
101 break;
102
103 case PROP_CHILD_ROW :
104 gtk_grid_layout_child_set_row (child: self, row: g_value_get_int (value));
105 break;
106
107 case PROP_CHILD_COLUMN_SPAN:
108 gtk_grid_layout_child_set_column_span (child: self, span: g_value_get_int (value));
109 break;
110
111 case PROP_CHILD_ROW_SPAN:
112 gtk_grid_layout_child_set_row_span (child: self, span: g_value_get_int (value));
113 break;
114
115 default:
116 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
117 break;
118 }
119}
120
121static void
122gtk_grid_layout_child_get_property (GObject *gobject,
123 guint prop_id,
124 GValue *value,
125 GParamSpec *pspec)
126{
127 GtkGridLayoutChild *self = GTK_GRID_LAYOUT_CHILD (ptr: gobject);
128
129 switch (prop_id)
130 {
131 case PROP_CHILD_COLUMN:
132 g_value_set_int (value, CHILD_COLUMN (self));
133 break;
134
135 case PROP_CHILD_ROW:
136 g_value_set_int (value, CHILD_ROW (self));
137 break;
138
139 case PROP_CHILD_COLUMN_SPAN:
140 g_value_set_int (value, CHILD_COL_SPAN (self));
141 break;
142
143 case PROP_CHILD_ROW_SPAN:
144 g_value_set_int (value, CHILD_ROW_SPAN (self));
145 break;
146
147 default:
148 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
149 break;
150 }
151}
152
153static void
154gtk_grid_layout_child_class_init (GtkGridLayoutChildClass *klass)
155{
156 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
157
158 gobject_class->set_property = gtk_grid_layout_child_set_property;
159 gobject_class->get_property = gtk_grid_layout_child_get_property;
160
161 /**
162 * GtkGridLayoutChild:column: (attributes org.gtk.Property.get=gtk_grid_layout_child_get_column org.gtk.Property.set=gtk_grid_layout_child_set_column)
163 *
164 * The column to place the child in.
165 */
166 child_props[PROP_CHILD_COLUMN] =
167 g_param_spec_int (name: "column",
168 P_("Column"),
169 P_("The column to place the child in"),
170 G_MININT, G_MAXINT, default_value: 0,
171 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
172
173 /**
174 * GtkGridLayoutChild:row: (attributes org.gtk.Property.get=gtk_grid_layout_child_get_row org.gtk.Property.set=gtk_grid_layout_child_set_row)
175 *
176 * The row to place the child in.
177 */
178 child_props[PROP_CHILD_ROW] =
179 g_param_spec_int (name: "row",
180 P_("Row"),
181 P_("The row to place the child in"),
182 G_MININT, G_MAXINT, default_value: 0,
183 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
184
185 /**
186 * GtkGridLayoutChild:column-span: (attributes org.gtk.Property.get=gtk_grid_layout_child_get_column_span org.gtk.Property.set=gtk_grid_layout_child_set_column_span)
187 *
188 * The number of columns the child spans to.
189 */
190 child_props[PROP_CHILD_COLUMN_SPAN] =
191 g_param_spec_int (name: "column-span",
192 P_("Column span"),
193 P_("The number of columns that a child spans"),
194 minimum: 1, G_MAXINT, default_value: 1,
195 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
196
197 /**
198 * GtkGridLayoutChild:row-span: (attributes org.gtk.Property.get=gtk_grid_layout_child_get_row_span org.gtk.Property.set=gtk_grid_layout_child_set_row_span)
199 *
200 * The number of rows the child spans to.
201 */
202 child_props[PROP_CHILD_ROW_SPAN] =
203 g_param_spec_int (name: "row-span",
204 P_("Row span"),
205 P_("The number of rows that a child spans"),
206 minimum: 1, G_MAXINT, default_value: 1,
207 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
208
209 g_object_class_install_properties (oclass: gobject_class, n_pspecs: N_CHILD_PROPERTIES, pspecs: child_props);
210}
211
212static void
213gtk_grid_layout_child_init (GtkGridLayoutChild *self)
214{
215 CHILD_ROW_SPAN (self) = 1;
216 CHILD_COL_SPAN (self) = 1;
217}
218
219/**
220 * gtk_grid_layout_child_set_row: (attributes org.gtk.Method.set_property=row)
221 * @child: a `GtkGridLayoutChild`
222 * @row: the row for @child
223 *
224 * Sets the row to place @child in.
225 */
226void
227gtk_grid_layout_child_set_row (GtkGridLayoutChild *child,
228 int row)
229{
230 g_return_if_fail (GTK_IS_GRID_LAYOUT_CHILD (child));
231
232 if (CHILD_ROW (child) == row)
233 return;
234
235 CHILD_ROW (child) = row;
236
237 gtk_layout_manager_layout_changed (manager: gtk_layout_child_get_layout_manager (layout_child: GTK_LAYOUT_CHILD (ptr: child)));
238
239 g_object_notify_by_pspec (G_OBJECT (child), pspec: child_props[PROP_CHILD_ROW]);
240}
241
242/**
243 * gtk_grid_layout_child_get_row: (attributes org.gtk.Method.get_property=row)
244 * @child: a `GtkGridLayoutChild`
245 *
246 * Retrieves the row number to which @child attaches its top side.
247 *
248 * Returns: the row number
249 */
250int
251gtk_grid_layout_child_get_row (GtkGridLayoutChild *child)
252{
253 g_return_val_if_fail (GTK_IS_GRID_LAYOUT_CHILD (child), 0);
254
255 return CHILD_ROW (child);
256}
257
258/**
259 * gtk_grid_layout_child_set_column: (attributes org.gtk.Method.set_property=column)
260 * @child: a `GtkGridLayoutChild`
261 * @column: the attach point for @child
262 *
263 * Sets the column number to attach the left side of @child.
264 */
265void
266gtk_grid_layout_child_set_column (GtkGridLayoutChild *child,
267 int column)
268{
269 g_return_if_fail (GTK_IS_GRID_LAYOUT_CHILD (child));
270
271 if (CHILD_COLUMN (child) == column)
272 return;
273
274 CHILD_COLUMN (child) = column;
275
276 gtk_layout_manager_layout_changed (manager: gtk_layout_child_get_layout_manager (layout_child: GTK_LAYOUT_CHILD (ptr: child)));
277
278 g_object_notify_by_pspec (G_OBJECT (child), pspec: child_props[PROP_CHILD_COLUMN]);
279}
280
281/**
282 * gtk_grid_layout_child_get_column: (attributes org.gtk.Method.get_property=column)
283 * @child: a `GtkGridLayoutChild`
284 *
285 * Retrieves the column number to which @child attaches its left side.
286 *
287 * Returns: the column number
288 */
289int
290gtk_grid_layout_child_get_column (GtkGridLayoutChild *child)
291{
292 g_return_val_if_fail (GTK_IS_GRID_LAYOUT_CHILD (child), 0);
293
294 return CHILD_COLUMN (child);
295}
296
297/**
298 * gtk_grid_layout_child_set_column_span: (attributes org.gtk.Method.set_property=column-span)
299 * @child: a `GtkGridLayoutChild`
300 * @span: the span of @child
301 *
302 * Sets the number of columns @child spans to.
303 */
304void
305gtk_grid_layout_child_set_column_span (GtkGridLayoutChild *child,
306 int span)
307{
308 g_return_if_fail (GTK_IS_GRID_LAYOUT_CHILD (child));
309
310 if (CHILD_COL_SPAN (child) == span)
311 return;
312
313 CHILD_COL_SPAN (child) = span;
314
315 gtk_layout_manager_layout_changed (manager: gtk_layout_child_get_layout_manager (layout_child: GTK_LAYOUT_CHILD (ptr: child)));
316
317 g_object_notify_by_pspec (G_OBJECT (child), pspec: child_props[PROP_CHILD_COLUMN_SPAN]);
318}
319
320/**
321 * gtk_grid_layout_child_get_column_span: (attributes org.gtk.Method.get_property=column-span)
322 * @child: a `GtkGridLayoutChild`
323 *
324 * Retrieves the number of columns that @child spans to.
325 *
326 * Returns: the number of columns
327 */
328int
329gtk_grid_layout_child_get_column_span (GtkGridLayoutChild *child)
330{
331 g_return_val_if_fail (GTK_IS_GRID_LAYOUT_CHILD (child), 1);
332
333 return CHILD_COL_SPAN (child);
334}
335
336/**
337 * gtk_grid_layout_child_set_row_span: (attributes org.gtk.Method.set_property=row-span)
338 * @child: a `GtkGridLayoutChild`
339 * @span: the span of @child
340 *
341 * Sets the number of rows @child spans to.
342 */
343void
344gtk_grid_layout_child_set_row_span (GtkGridLayoutChild *child,
345 int span)
346{
347 g_return_if_fail (GTK_IS_GRID_LAYOUT_CHILD (child));
348
349 if (CHILD_ROW_SPAN (child) == span)
350 return;
351
352 CHILD_ROW_SPAN (child) = span;
353
354 gtk_layout_manager_layout_changed (manager: gtk_layout_child_get_layout_manager (layout_child: GTK_LAYOUT_CHILD (ptr: child)));
355
356 g_object_notify_by_pspec (G_OBJECT (child), pspec: child_props[PROP_CHILD_ROW_SPAN]);
357}
358
359/**
360 * gtk_grid_layout_child_get_row_span: (attributes org.gtk.Method.get_property=row-span)
361 * @child: a `GtkGridLayoutChild`
362 *
363 * Retrieves the number of rows that @child spans to.
364 *
365 * Returns: the number of row
366 */
367int
368gtk_grid_layout_child_get_row_span (GtkGridLayoutChild *child)
369{
370 g_return_val_if_fail (GTK_IS_GRID_LAYOUT_CHILD (child), 1);
371
372 return CHILD_ROW_SPAN (child);
373}
374
375/* }}} */
376
377/* {{{ GtkGridLayout */
378
379typedef struct {
380 int row;
381 GtkBaselinePosition baseline_position;
382} GridRowProperties;
383
384static const GridRowProperties grid_row_properties_default = {
385 0,
386 GTK_BASELINE_POSITION_CENTER
387};
388
389/* A GridLineData struct contains row/column specific parts
390 * of the grid.
391 */
392typedef struct {
393 gint16 spacing;
394 guint homogeneous : 1;
395} GridLineData;
396
397#define ROWS(layout) (&(layout)->linedata[GTK_ORIENTATION_HORIZONTAL])
398#define COLUMNS(layout) (&(layout)->linedata[GTK_ORIENTATION_VERTICAL])
399
400/* A GridLine struct represents a single row or column
401 * during size requests
402 */
403typedef struct {
404 int minimum;
405 int natural;
406 int minimum_above;
407 int minimum_below;
408 int natural_above;
409 int natural_below;
410
411 int position;
412 int allocation;
413 int allocated_baseline;
414
415 guint need_expand : 1;
416 guint expand : 1;
417 guint empty : 1;
418} GridLine;
419
420typedef struct {
421 GridLine *lines;
422 int min, max;
423} GridLines;
424
425typedef struct {
426 GtkGridLayout *layout;
427 GtkWidget *widget;
428
429 GridLines lines[2];
430} GridRequest;
431
432struct _GtkGridLayout
433{
434 GtkLayoutManager parent_instance;
435
436 /* Array<GridRowProperties> */
437 GArray *row_properties;
438
439 GtkOrientation orientation;
440 int baseline_row;
441
442 GridLineData linedata[2];
443};
444
445enum {
446 PROP_ROW_SPACING = 1,
447 PROP_COLUMN_SPACING,
448 PROP_ROW_HOMOGENEOUS,
449 PROP_COLUMN_HOMOGENEOUS,
450 PROP_BASELINE_ROW,
451
452 N_PROPERTIES
453};
454
455static GParamSpec *layout_props[N_PROPERTIES];
456
457G_DEFINE_TYPE (GtkGridLayout, gtk_grid_layout, GTK_TYPE_LAYOUT_MANAGER)
458
459static inline GtkGridLayoutChild *
460get_grid_child (GtkGridLayout *self,
461 GtkWidget *child)
462{
463 GtkLayoutManager *manager = GTK_LAYOUT_MANAGER (ptr: self);
464
465 return (GtkGridLayoutChild *) gtk_layout_manager_get_layout_child (manager, child);
466}
467
468static int
469get_spacing (GtkGridLayout *self,
470 GtkWidget *widget,
471 GtkOrientation orientation)
472{
473 GtkCssNode *node = gtk_widget_get_css_node (widget);
474 GtkCssStyle *style = gtk_css_node_get_style (cssnode: node);
475 GtkCssValue *border_spacing;
476 int css_spacing;
477
478 border_spacing = style->size->border_spacing;
479
480 if (orientation == GTK_ORIENTATION_HORIZONTAL)
481 css_spacing = _gtk_css_position_value_get_x (position: border_spacing, one_hundred_percent: 100);
482 else
483 css_spacing = _gtk_css_position_value_get_y (position: border_spacing, one_hundred_percent: 100);
484
485 return css_spacing + self->linedata[orientation].spacing;
486}
487
488/* Calculates the min and max numbers for both orientations. */
489static void
490grid_request_count_lines (GridRequest *request)
491{
492 GtkWidget *child;
493 int min[2];
494 int max[2];
495
496 min[0] = min[1] = G_MAXINT;
497 max[0] = max[1] = G_MININT;
498
499 for (child = _gtk_widget_get_first_child (widget: request->widget);
500 child != NULL;
501 child = _gtk_widget_get_next_sibling (widget: child))
502 {
503 GtkGridLayoutChild *grid_child = get_grid_child (self: request->layout, child);
504 GridChildAttach *attach = grid_child->attach;
505
506 min[0] = MIN (min[0], attach[0].pos);
507 max[0] = MAX (max[0], attach[0].pos + attach[0].span);
508 min[1] = MIN (min[1], attach[1].pos);
509 max[1] = MAX (max[1], attach[1].pos + attach[1].span);
510 }
511
512 request->lines[0].min = min[0];
513 request->lines[0].max = max[0];
514 request->lines[1].min = min[1];
515 request->lines[1].max = max[1];
516}
517
518/* Sets line sizes to 0 and marks lines as expand
519 * if they have a non-spanning expanding child.
520 */
521static void
522grid_request_init (GridRequest *request,
523 GtkOrientation orientation)
524{
525 GtkWidget *child;
526 GridLines *lines;
527 int i;
528
529 lines = &request->lines[orientation];
530
531 for (i = 0; i < lines->max - lines->min; i++)
532 {
533 lines->lines[i].minimum = 0;
534 lines->lines[i].natural = 0;
535 lines->lines[i].minimum_above = -1;
536 lines->lines[i].minimum_below = -1;
537 lines->lines[i].natural_above = -1;
538 lines->lines[i].natural_below = -1;
539 lines->lines[i].expand = FALSE;
540 lines->lines[i].empty = TRUE;
541 }
542
543
544 for (child = _gtk_widget_get_first_child (widget: request->widget);
545 child != NULL;
546 child = _gtk_widget_get_next_sibling (widget: child))
547 {
548 GtkGridLayoutChild *grid_child = get_grid_child (self: request->layout, child);
549 GridChildAttach *attach;
550
551 attach = &grid_child->attach[orientation];
552 if (attach->span == 1 && gtk_widget_compute_expand (widget: child, orientation))
553 lines->lines[attach->pos - lines->min].expand = TRUE;
554 }
555}
556
557/* Sums allocations for lines spanned by child and their spacing.
558 */
559static int
560compute_allocation_for_child (GridRequest *request,
561 GtkGridLayoutChild *child,
562 GtkOrientation orientation)
563{
564 GridLines *lines;
565 GridLine *line;
566 GridChildAttach *attach;
567 int size;
568 int i;
569
570 lines = &request->lines[orientation];
571 attach = &child->attach[orientation];
572
573 size = (attach->span - 1) * get_spacing (self: request->layout, widget: request->widget, orientation);
574 for (i = 0; i < attach->span; i++)
575 {
576 line = &lines->lines[attach->pos - lines->min + i];
577 size += line->allocation;
578 }
579
580 return size;
581}
582
583static void
584compute_request_for_child (GridRequest *request,
585 GtkWidget *child,
586 GtkGridLayoutChild *grid_child,
587 GtkOrientation orientation,
588 gboolean contextual,
589 int *minimum,
590 int *natural,
591 int *minimum_baseline,
592 int *natural_baseline)
593{
594 if (minimum_baseline != NULL)
595 *minimum_baseline = -1;
596 if (natural_baseline != NULL)
597 *natural_baseline = -1;
598
599 if (contextual)
600 {
601 int size;
602
603 size = compute_allocation_for_child (request, child: grid_child, orientation: 1 - orientation);
604
605 gtk_widget_measure (widget: child,
606 orientation,
607 for_size: size,
608 minimum, natural,
609 minimum_baseline, natural_baseline);
610 }
611 else
612 {
613 gtk_widget_measure (widget: child,
614 orientation,
615 for_size: -1,
616 minimum, natural,
617 minimum_baseline, natural_baseline);
618 }
619}
620
621/* Sets requisition to max. of non-spanning children.
622 * If contextual is TRUE, requires allocations of
623 * lines in the opposite orientation to be set.
624 */
625static void
626grid_request_non_spanning (GridRequest *request,
627 GtkOrientation orientation,
628 gboolean contextual)
629{
630 GtkWidget *child;
631 GridLines *lines;
632 GridLine *line;
633 int i;
634 GtkBaselinePosition baseline_pos;
635 int minimum, minimum_baseline;
636 int natural, natural_baseline;
637
638 lines = &request->lines[orientation];
639
640 for (child = _gtk_widget_get_first_child (widget: request->widget);
641 child != NULL;
642 child = _gtk_widget_get_next_sibling (widget: child))
643 {
644 GtkGridLayoutChild *grid_child = get_grid_child (self: request->layout, child);
645 GridChildAttach *attach;
646
647 if (!gtk_widget_should_layout (widget: child))
648 continue;
649
650 attach = &grid_child->attach[orientation];
651 if (attach->span != 1)
652 continue;
653
654 compute_request_for_child (request, child, grid_child, orientation, contextual, minimum: &minimum, natural: &natural, minimum_baseline: &minimum_baseline, natural_baseline: &natural_baseline);
655
656 line = &lines->lines[attach->pos - lines->min];
657
658 if (minimum_baseline != -1)
659 {
660 line->minimum_above = MAX (line->minimum_above, minimum_baseline);
661 line->minimum_below = MAX (line->minimum_below, minimum - minimum_baseline);
662 line->natural_above = MAX (line->natural_above, natural_baseline);
663 line->natural_below = MAX (line->natural_below, natural - natural_baseline);
664 }
665 else
666 {
667 line->minimum = MAX (line->minimum, minimum);
668 line->natural = MAX (line->natural, natural);
669 }
670 }
671
672 for (i = 0; i < lines->max - lines->min; i++)
673 {
674 line = &lines->lines[i];
675
676 if (line->minimum_above != -1)
677 {
678 line->minimum = MAX (line->minimum, line->minimum_above + line->minimum_below);
679 line->natural = MAX (line->natural, line->natural_above + line->natural_below);
680
681 baseline_pos = gtk_grid_layout_get_row_baseline_position (grid: request->layout, row: i + lines->min);
682
683 switch (baseline_pos)
684 {
685 case GTK_BASELINE_POSITION_TOP:
686 line->minimum_above += 0;
687 line->minimum_below += line->minimum - (line->minimum_above + line->minimum_below);
688 line->natural_above += 0;
689 line->natural_below += line->natural - (line->natural_above + line->natural_below);
690 break;
691
692 case GTK_BASELINE_POSITION_CENTER:
693 line->minimum_above += (line->minimum - (line->minimum_above + line->minimum_below))/2;
694 line->minimum_below += (line->minimum - (line->minimum_above + line->minimum_below))/2;
695 line->natural_above += (line->natural - (line->natural_above + line->natural_below))/2;
696 line->natural_below += (line->natural - (line->natural_above + line->natural_below))/2;
697 break;
698
699 case GTK_BASELINE_POSITION_BOTTOM:
700 line->minimum_above += line->minimum - (line->minimum_above + line->minimum_below);
701 line->minimum_below += 0;
702 line->natural_above += line->natural - (line->natural_above + line->natural_below);
703 line->natural_below += 0;
704 break;
705
706 default:
707 break;
708 }
709 }
710 }
711}
712
713/* Enforce homogeneous sizes */
714static void
715grid_request_homogeneous (GridRequest *request,
716 GtkOrientation orientation)
717{
718 GtkGridLayout *self = request->layout;
719 GridLineData *linedata;
720 GridLines *lines;
721 int minimum, natural;
722 int i;
723
724 linedata = &self->linedata[orientation];
725 lines = &request->lines[orientation];
726
727 if (!linedata->homogeneous)
728 return;
729
730 minimum = 0;
731 natural = 0;
732
733 for (i = 0; i < lines->max - lines->min; i++)
734 {
735 minimum = MAX (minimum, lines->lines[i].minimum);
736 natural = MAX (natural, lines->lines[i].natural);
737 }
738
739 for (i = 0; i < lines->max - lines->min; i++)
740 {
741 lines->lines[i].minimum = minimum;
742 lines->lines[i].natural = natural;
743
744 /* TODO: Do we want to adjust the baseline here too?
745 * And if so, also in the homogeneous resize.
746 */
747 }
748}
749
750/* Deals with spanning children.
751 * Requires expand fields of lines to be set for
752 * non-spanning children.
753 */
754static void
755grid_request_spanning (GridRequest *request,
756 GtkOrientation orientation,
757 gboolean contextual)
758{
759 GtkGridLayout *self = request->layout;
760 GtkWidget *child;
761 GridChildAttach *attach;
762 GridLineData *linedata;
763 GridLines *lines;
764 GridLine *line;
765 int minimum, natural;
766 int span_minimum, span_natural;
767 int span_expand;
768 gboolean force_expand;
769 int spacing;
770 int extra;
771 int expand;
772 int line_extra;
773 int i;
774
775 linedata = &self->linedata[orientation];
776 lines = &request->lines[orientation];
777 spacing = get_spacing (self: request->layout, widget: request->widget, orientation);
778
779 for (child = _gtk_widget_get_first_child (widget: request->widget);
780 child != NULL;
781 child = _gtk_widget_get_next_sibling (widget: child))
782 {
783 GtkGridLayoutChild *grid_child = get_grid_child (self: request->layout, child);
784
785 if (!gtk_widget_should_layout (widget: child))
786 continue;
787
788 attach = &grid_child->attach[orientation];
789 if (attach->span == 1)
790 continue;
791
792 /* We ignore baselines for spanning children */
793 compute_request_for_child (request, child, grid_child, orientation, contextual, minimum: &minimum, natural: &natural, NULL, NULL);
794
795 span_minimum = (attach->span - 1) * spacing;
796 span_natural = (attach->span - 1) * spacing;
797 span_expand = 0;
798 force_expand = FALSE;
799 for (i = 0; i < attach->span; i++)
800 {
801 line = &lines->lines[attach->pos - lines->min + i];
802 span_minimum += line->minimum;
803 span_natural += line->natural;
804 if (line->expand)
805 span_expand += 1;
806 }
807 if (span_expand == 0)
808 {
809 span_expand = attach->span;
810 force_expand = TRUE;
811 }
812
813 /* If we need to request more space for this child to fill
814 * its requisition, then divide up the needed space amongst the
815 * lines it spans, favoring expandable lines if any.
816 *
817 * When doing homogeneous allocation though, try to keep the
818 * line allocations even, since we're going to force them to
819 * be the same anyway, and we don't want to introduce unnecessary
820 * extra space.
821 */
822 if (span_minimum < minimum)
823 {
824 if (linedata->homogeneous)
825 {
826 int total, m;
827
828 total = minimum - (attach->span - 1) * spacing;
829 m = total / attach->span + (total % attach->span ? 1 : 0);
830 for (i = 0; i < attach->span; i++)
831 {
832 line = &lines->lines[attach->pos - lines->min + i];
833 line->minimum = MAX (line->minimum, m);
834 }
835 }
836 else
837 {
838 extra = minimum - span_minimum;
839 expand = span_expand;
840 for (i = 0; i < attach->span; i++)
841 {
842 line = &lines->lines[attach->pos - lines->min + i];
843 if (force_expand || line->expand)
844 {
845 line_extra = extra / expand;
846 line->minimum += line_extra;
847 extra -= line_extra;
848 expand -= 1;
849 }
850 }
851 }
852 }
853
854 if (span_natural < natural)
855 {
856 if (linedata->homogeneous)
857 {
858 int total, n;
859
860 total = natural - (attach->span - 1) * spacing;
861 n = total / attach->span + (total % attach->span ? 1 : 0);
862 for (i = 0; i < attach->span; i++)
863 {
864 line = &lines->lines[attach->pos - lines->min + i];
865 line->natural = MAX (line->natural, n);
866 }
867 }
868 else
869 {
870 extra = natural - span_natural;
871 expand = span_expand;
872 for (i = 0; i < attach->span; i++)
873 {
874 line = &lines->lines[attach->pos - lines->min + i];
875 if (force_expand || line->expand)
876 {
877 line_extra = extra / expand;
878 line->natural += line_extra;
879 extra -= line_extra;
880 expand -= 1;
881 }
882 }
883 }
884 }
885 }
886}
887
888/* Marks empty and expanding lines and counts them */
889static void
890grid_request_compute_expand (GridRequest *request,
891 GtkOrientation orientation,
892 int min,
893 int max,
894 int *nonempty_lines,
895 int *expand_lines)
896{
897 GtkWidget *child;
898 GridChildAttach *attach;
899 int i;
900 GridLines *lines;
901 GridLine *line;
902 gboolean has_expand;
903 int expand;
904 int empty;
905
906 lines = &request->lines[orientation];
907
908 min = MAX (min, lines->min);
909 max = MIN (max, lines->max);
910
911 for (i = min - lines->min; i < max - lines->min; i++)
912 {
913 lines->lines[i].need_expand = FALSE;
914 lines->lines[i].expand = FALSE;
915 lines->lines[i].empty = TRUE;
916 }
917
918 for (child = _gtk_widget_get_first_child (widget: request->widget);
919 child != NULL;
920 child = _gtk_widget_get_next_sibling (widget: child))
921 {
922 GtkGridLayoutChild *grid_child = get_grid_child (self: request->layout, child);
923
924 if (!gtk_widget_should_layout (widget: child))
925 continue;
926
927 attach = &grid_child->attach[orientation];
928 if (attach->span != 1)
929 continue;
930
931 if (attach->pos >= max || attach->pos < min)
932 continue;
933
934 line = &lines->lines[attach->pos - lines->min];
935 line->empty = FALSE;
936 if (gtk_widget_compute_expand (widget: child, orientation))
937 line->expand = TRUE;
938 }
939
940 for (child = _gtk_widget_get_first_child (widget: request->widget);
941 child != NULL;
942 child = _gtk_widget_get_next_sibling (widget: child))
943 {
944 GtkGridLayoutChild *grid_child = get_grid_child (self: request->layout, child);
945
946 if (!gtk_widget_should_layout (widget: child))
947 continue;
948
949 attach = &grid_child->attach[orientation];
950 if (attach->span == 1)
951 continue;
952
953 has_expand = FALSE;
954 for (i = 0; i < attach->span; i++)
955 {
956 line = &lines->lines[attach->pos - lines->min + i];
957
958 if (line->expand)
959 has_expand = TRUE;
960
961 if (attach->pos + i >= max || attach->pos + 1 < min)
962 continue;
963
964 line->empty = FALSE;
965 }
966
967 if (!has_expand && gtk_widget_compute_expand (widget: child, orientation))
968 {
969 for (i = 0; i < attach->span; i++)
970 {
971 if (attach->pos + i >= max || attach->pos + 1 < min)
972 continue;
973
974 line = &lines->lines[attach->pos - lines->min + i];
975 line->need_expand = TRUE;
976 }
977 }
978 }
979
980 empty = 0;
981 expand = 0;
982 for (i = min - lines->min; i < max - lines->min; i++)
983 {
984 line = &lines->lines[i];
985
986 if (line->need_expand)
987 line->expand = TRUE;
988
989 if (line->empty)
990 empty += 1;
991
992 if (line->expand)
993 expand += 1;
994 }
995
996 if (nonempty_lines)
997 *nonempty_lines = max - min - empty;
998
999 if (expand_lines)
1000 *expand_lines = expand;
1001}
1002
1003/* Sums the minimum and natural fields of lines and their spacing */
1004static void
1005grid_request_sum (GridRequest *request,
1006 GtkOrientation orientation,
1007 int *minimum,
1008 int *natural,
1009 int *minimum_baseline,
1010 int *natural_baseline)
1011{
1012 GtkGridLayout *self = request->layout;
1013 GridLines *lines;
1014 int i;
1015 int min, nat;
1016 int nonempty;
1017 int spacing;
1018
1019 grid_request_compute_expand (request, orientation, G_MININT, G_MAXINT, nonempty_lines: &nonempty, NULL);
1020
1021 lines = &request->lines[orientation];
1022 spacing = get_spacing (self: request->layout, widget: request->widget, orientation);
1023
1024 min = 0;
1025 nat = 0;
1026 for (i = 0; i < lines->max - lines->min; i++)
1027 {
1028 if (orientation == GTK_ORIENTATION_VERTICAL &&
1029 lines->min + i == self->baseline_row &&
1030 lines->lines[i].minimum_above != -1)
1031 {
1032 if (minimum_baseline)
1033 *minimum_baseline = min + lines->lines[i].minimum_above;
1034 if (natural_baseline)
1035 *natural_baseline = nat + lines->lines[i].natural_above;
1036 }
1037
1038 min += lines->lines[i].minimum;
1039 nat += lines->lines[i].natural;
1040
1041 if (!lines->lines[i].empty)
1042 {
1043 min += spacing;
1044 nat += spacing;
1045 }
1046 }
1047
1048 /* Remove last spacing, if any was applied */
1049 if (nonempty > 0)
1050 {
1051 min -= spacing;
1052 nat -= spacing;
1053 }
1054
1055 *minimum = min;
1056 *natural = nat;
1057}
1058
1059/* Computes minimum and natural fields of lines.
1060 * When contextual is TRUE, requires allocation of
1061 * lines in the opposite orientation to be set.
1062 */
1063static void
1064grid_request_run (GridRequest *request,
1065 GtkOrientation orientation,
1066 gboolean contextual)
1067{
1068 grid_request_init (request, orientation);
1069 grid_request_non_spanning (request, orientation, contextual);
1070 grid_request_homogeneous (request, orientation);
1071 grid_request_spanning (request, orientation, contextual);
1072 grid_request_homogeneous (request, orientation);
1073}
1074
1075static void
1076grid_distribute_non_homogeneous (GridLines *lines,
1077 int nonempty,
1078 int expand,
1079 int size,
1080 int min,
1081 int max)
1082{
1083 GtkRequestedSize *sizes;
1084 GridLine *line;
1085 int extra;
1086 int rest;
1087 int i, j;
1088
1089 if (nonempty == 0)
1090 return;
1091
1092 sizes = g_newa (GtkRequestedSize, nonempty);
1093
1094 j = 0;
1095 for (i = min - lines->min; i < max - lines->min; i++)
1096 {
1097 line = &lines->lines[i];
1098 if (line->empty)
1099 continue;
1100
1101 size -= line->minimum;
1102
1103 sizes[j].minimum_size = line->minimum;
1104 sizes[j].natural_size = line->natural;
1105 sizes[j].data = line;
1106 j++;
1107 }
1108
1109 size = gtk_distribute_natural_allocation (MAX (0, size), n_requested_sizes: nonempty, sizes);
1110
1111 if (expand > 0)
1112 {
1113 extra = size / expand;
1114 rest = size % expand;
1115 }
1116 else
1117 {
1118 extra = 0;
1119 rest = 0;
1120 }
1121
1122 j = 0;
1123 for (i = min - lines->min; i < max - lines->min; i++)
1124 {
1125 line = &lines->lines[i];
1126 if (line->empty)
1127 continue;
1128
1129 g_assert (line == sizes[j].data);
1130
1131 line->allocation = sizes[j].minimum_size;
1132 if (line->expand)
1133 {
1134 line->allocation += extra;
1135 if (rest > 0)
1136 {
1137 line->allocation += 1;
1138 rest -= 1;
1139 }
1140 }
1141
1142 j++;
1143 }
1144}
1145
1146/* Requires that the minimum and natural fields of lines
1147 * have been set, computes the allocation field of lines
1148 * by distributing total_size among lines.
1149 */
1150static void
1151grid_request_allocate (GridRequest *request,
1152 GtkOrientation orientation,
1153 int total_size)
1154{
1155 GtkGridLayout *self = request->layout;
1156 GridLineData *linedata;
1157 GridLines *lines;
1158 GridLine *line;
1159 int nonempty1, nonempty2;
1160 int expand1, expand2;
1161 int i;
1162 GtkBaselinePosition baseline_pos;
1163 int baseline;
1164 int extra, extra2;
1165 int rest;
1166 int size1, size2;
1167 int split, split_pos;
1168 int spacing;
1169
1170 linedata = &self->linedata[orientation];
1171 lines = &request->lines[orientation];
1172 spacing = get_spacing (self: request->layout, widget: request->widget, orientation);
1173
1174 baseline = gtk_widget_get_allocated_baseline (widget: request->widget);
1175
1176 if (orientation == GTK_ORIENTATION_VERTICAL && baseline != -1 &&
1177 self->baseline_row >= lines->min && self->baseline_row < lines->max &&
1178 lines->lines[self->baseline_row - lines->min].minimum_above != -1)
1179 {
1180 split = self->baseline_row;
1181 split_pos = baseline - lines->lines[self->baseline_row - lines->min].minimum_above;
1182 grid_request_compute_expand (request, orientation, min: lines->min, max: split, nonempty_lines: &nonempty1, expand_lines: &expand1);
1183 grid_request_compute_expand (request, orientation, min: split, max: lines->max, nonempty_lines: &nonempty2, expand_lines: &expand2);
1184
1185 if (nonempty2 > 0)
1186 {
1187 size1 = split_pos - (nonempty1) * spacing;
1188 size2 = (total_size - split_pos) - (nonempty2 - 1) * spacing;
1189 }
1190 else
1191 {
1192 size1 = total_size - (nonempty1 - 1) * spacing;
1193 size2 = 0;
1194 }
1195 }
1196 else
1197 {
1198 grid_request_compute_expand (request, orientation, min: lines->min, max: lines->max, nonempty_lines: &nonempty1, expand_lines: &expand1);
1199 nonempty2 = expand2 = 0;
1200 split = lines->max;
1201
1202 size1 = total_size - (nonempty1 - 1) * spacing;
1203 size2 = 0;
1204 }
1205
1206 if (nonempty1 == 0 && nonempty2 == 0)
1207 return;
1208
1209 if (linedata->homogeneous)
1210 {
1211 if (nonempty1 > 0)
1212 {
1213 extra = size1 / nonempty1;
1214 rest = size1 % nonempty1;
1215 }
1216 else
1217 {
1218 extra = 0;
1219 rest = 0;
1220 }
1221 if (nonempty2 > 0)
1222 {
1223 extra2 = size2 / nonempty2;
1224 if (extra2 < extra || nonempty1 == 0)
1225 {
1226 extra = extra2;
1227 rest = size2 % nonempty2;
1228 }
1229 }
1230
1231 for (i = 0; i < lines->max - lines->min; i++)
1232 {
1233 line = &lines->lines[i];
1234 if (line->empty)
1235 continue;
1236
1237 line->allocation = extra;
1238 if (rest > 0)
1239 {
1240 line->allocation += 1;
1241 rest -= 1;
1242 }
1243 }
1244 }
1245 else
1246 {
1247 grid_distribute_non_homogeneous (lines,
1248 nonempty: nonempty1,
1249 expand: expand1,
1250 size: size1,
1251 min: lines->min,
1252 max: split);
1253 grid_distribute_non_homogeneous (lines,
1254 nonempty: nonempty2,
1255 expand: expand2,
1256 size: size2,
1257 min: split,
1258 max: lines->max);
1259 }
1260
1261 for (i = 0; i < lines->max - lines->min; i++)
1262 {
1263 line = &lines->lines[i];
1264
1265 if (line->empty)
1266 continue;
1267
1268 if (line->minimum_above != -1)
1269 {
1270 /* Note: This is overridden in grid_request_position for the allocated baseline */
1271 baseline_pos = gtk_grid_layout_get_row_baseline_position (grid: request->layout, row: i + lines->min);
1272
1273 switch (baseline_pos)
1274 {
1275 case GTK_BASELINE_POSITION_TOP:
1276 line->allocated_baseline = line->minimum_above;
1277 break;
1278 case GTK_BASELINE_POSITION_CENTER:
1279 line->allocated_baseline = line->minimum_above +
1280 (line->allocation - (line->minimum_above + line->minimum_below)) / 2;
1281 break;
1282 case GTK_BASELINE_POSITION_BOTTOM:
1283 line->allocated_baseline = line->allocation - line->minimum_below;
1284 break;
1285 default:
1286 break;
1287 }
1288 }
1289 else
1290 line->allocated_baseline = -1;
1291 }
1292}
1293
1294/* Computes the position fields from allocation and spacing */
1295static void
1296grid_request_position (GridRequest *request,
1297 GtkOrientation orientation)
1298{
1299 GtkGridLayout *self = request->layout;
1300 GridLines *lines;
1301 GridLine *line;
1302 int position, old_position;
1303 int allocated_baseline;
1304 int spacing;
1305 int i, j;
1306
1307 lines = &request->lines[orientation];
1308 spacing = get_spacing (self: request->layout, widget: request->widget, orientation);
1309
1310 allocated_baseline = gtk_widget_get_allocated_baseline (widget: request->widget);
1311
1312 position = 0;
1313 for (i = 0; i < lines->max - lines->min; i++)
1314 {
1315 line = &lines->lines[i];
1316
1317 if (orientation == GTK_ORIENTATION_VERTICAL &&
1318 i + lines->min == self->baseline_row &&
1319 allocated_baseline != -1 &&
1320 lines->lines[i].minimum_above != -1)
1321 {
1322 old_position = position;
1323 position = allocated_baseline - line->minimum_above;
1324
1325 /* Back-patch previous rows */
1326 for (j = 0; j < i; j++)
1327 {
1328 if (!lines->lines[j].empty)
1329 lines->lines[j].position += position - old_position;
1330 }
1331 }
1332
1333 if (!line->empty)
1334 {
1335 line->position = position;
1336 position += line->allocation + spacing;
1337
1338 if (orientation == GTK_ORIENTATION_VERTICAL &&
1339 i + lines->min == self->baseline_row &&
1340 allocated_baseline != -1 &&
1341 lines->lines[i].minimum_above != -1)
1342 line->allocated_baseline = allocated_baseline - line->position;
1343 }
1344 }
1345}
1346
1347static void
1348gtk_grid_layout_get_size (GtkGridLayout *self,
1349 GtkWidget *widget,
1350 GtkOrientation orientation,
1351 int *minimum,
1352 int *natural,
1353 int *minimum_baseline,
1354 int *natural_baseline)
1355{
1356 GridRequest request;
1357 GridLines *lines;
1358
1359 *minimum = 0;
1360 *natural = 0;
1361
1362 if (minimum_baseline)
1363 *minimum_baseline = -1;
1364
1365 if (natural_baseline)
1366 *natural_baseline = -1;
1367
1368 if (_gtk_widget_get_first_child (widget) == NULL)
1369 return;
1370
1371 request.layout = self;
1372 request.widget = widget;
1373 grid_request_count_lines (request: &request);
1374
1375 lines = &request.lines[orientation];
1376 lines->lines = g_newa (GridLine, lines->max - lines->min);
1377 memset (s: lines->lines, c: 0, n: (lines->max - lines->min) * sizeof (GridLine));
1378
1379 grid_request_run (request: &request, orientation, FALSE);
1380 grid_request_sum (request: &request, orientation,
1381 minimum, natural,
1382 minimum_baseline, natural_baseline);
1383}
1384
1385static void
1386gtk_grid_layout_get_size_for_size (GtkGridLayout *self,
1387 GtkWidget *widget,
1388 GtkOrientation orientation,
1389 int size,
1390 int *minimum,
1391 int *natural,
1392 int *minimum_baseline,
1393 int *natural_baseline)
1394{
1395 GridRequest request;
1396 GridLines *lines;
1397 int min_size, nat_size;
1398
1399 *minimum = 0;
1400 *natural = 0;
1401
1402 if (minimum_baseline)
1403 *minimum_baseline = -1;
1404
1405 if (natural_baseline)
1406 *natural_baseline = -1;
1407
1408 if (_gtk_widget_get_first_child (widget) == NULL)
1409 return;
1410
1411 request.layout = self;
1412 request.widget = widget;
1413 grid_request_count_lines (request: &request);
1414
1415 lines = &request.lines[0];
1416 lines->lines = g_newa (GridLine, lines->max - lines->min);
1417 memset (s: lines->lines, c: 0, n: (lines->max - lines->min) * sizeof (GridLine));
1418 lines = &request.lines[1];
1419 lines->lines = g_newa (GridLine, lines->max - lines->min);
1420 memset (s: lines->lines, c: 0, n: (lines->max - lines->min) * sizeof (GridLine));
1421
1422 grid_request_run (request: &request, orientation: 1 - orientation, FALSE);
1423 grid_request_sum (request: &request, orientation: 1 - orientation, minimum: &min_size, natural: &nat_size, NULL, NULL);
1424 grid_request_allocate (request: &request, orientation: 1 - orientation, MAX (size, min_size));
1425
1426 grid_request_run (request: &request, orientation, TRUE);
1427 grid_request_sum (request: &request, orientation,
1428 minimum, natural,
1429 minimum_baseline, natural_baseline);
1430}
1431
1432static void
1433gtk_grid_layout_measure (GtkLayoutManager *manager,
1434 GtkWidget *widget,
1435 GtkOrientation orientation,
1436 int for_size,
1437 int *minimum,
1438 int *natural,
1439 int *minimum_baseline,
1440 int *natural_baseline)
1441{
1442 GtkGridLayout *self = GTK_GRID_LAYOUT (ptr: manager);
1443
1444 if ((orientation == GTK_ORIENTATION_HORIZONTAL &&
1445 gtk_widget_get_request_mode (widget) == GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT) ||
1446 (orientation == GTK_ORIENTATION_VERTICAL &&
1447 gtk_widget_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH))
1448 gtk_grid_layout_get_size_for_size (self, widget, orientation, size: for_size,
1449 minimum, natural,
1450 minimum_baseline, natural_baseline);
1451 else
1452 gtk_grid_layout_get_size (self, widget, orientation,
1453 minimum, natural,
1454 minimum_baseline, natural_baseline);
1455}
1456
1457static void
1458allocate_child (GridRequest *request,
1459 GtkOrientation orientation,
1460 GtkWidget *child,
1461 GtkGridLayoutChild *grid_child,
1462 int *position,
1463 int *size,
1464 int *baseline)
1465{
1466 GridLines *lines;
1467 GridLine *line;
1468 GridChildAttach *attach;
1469 int i;
1470
1471 lines = &request->lines[orientation];
1472 attach = &grid_child->attach[orientation];
1473
1474 *position = lines->lines[attach->pos - lines->min].position;
1475 if (attach->span == 1 && gtk_widget_get_valign (widget: child) == GTK_ALIGN_BASELINE)
1476 *baseline = lines->lines[attach->pos - lines->min].allocated_baseline;
1477 else
1478 *baseline = -1;
1479
1480 *size = (attach->span - 1) * get_spacing (self: request->layout, widget: request->widget, orientation);
1481 for (i = 0; i < attach->span; i++)
1482 {
1483 line = &lines->lines[attach->pos - lines->min + i];
1484 *size += line->allocation;
1485 }
1486}
1487
1488static void
1489grid_request_allocate_children (GridRequest *request,
1490 int grid_width,
1491 int grid_height)
1492{
1493 GtkWidget *child;
1494 GtkAllocation child_allocation;
1495 int x, y, width, height, baseline, ignore;
1496
1497
1498 for (child = _gtk_widget_get_first_child (widget: request->widget);
1499 child != NULL;
1500 child = _gtk_widget_get_next_sibling (widget: child))
1501 {
1502 GtkGridLayoutChild *grid_child = get_grid_child (self: request->layout, child);
1503
1504 if (!gtk_widget_should_layout (widget: child))
1505 continue;
1506
1507 allocate_child (request, orientation: GTK_ORIENTATION_HORIZONTAL, child, grid_child, position: &x, size: &width, baseline: &ignore);
1508 allocate_child (request, orientation: GTK_ORIENTATION_VERTICAL, child, grid_child, position: &y, size: &height, baseline: &baseline);
1509
1510 child_allocation.x = x;
1511 child_allocation.y = y;
1512 child_allocation.width = width;
1513 child_allocation.height = height;
1514
1515 if (_gtk_widget_get_direction (widget: request->widget) == GTK_TEXT_DIR_RTL)
1516 child_allocation.x = grid_width - child_allocation.x - child_allocation.width;
1517
1518 gtk_widget_size_allocate (widget: child, allocation: &child_allocation, baseline);
1519 }
1520}
1521
1522#define GET_SIZE(width, height, orientation) (orientation == GTK_ORIENTATION_HORIZONTAL ? width : height)
1523
1524static void
1525gtk_grid_layout_allocate (GtkLayoutManager *manager,
1526 GtkWidget *widget,
1527 int width,
1528 int height,
1529 int baseline)
1530{
1531 GtkGridLayout *self = GTK_GRID_LAYOUT (ptr: manager);
1532 GridRequest request;
1533 GridLines *lines;
1534 GtkOrientation orientation;
1535
1536 if (_gtk_widget_get_first_child (widget) == NULL)
1537 return;
1538
1539 request.layout = self;
1540 request.widget = widget;
1541
1542 grid_request_count_lines (request: &request);
1543 lines = &request.lines[0];
1544 lines->lines = g_newa (GridLine, lines->max - lines->min);
1545 memset (s: lines->lines, c: 0, n: (lines->max - lines->min) * sizeof (GridLine));
1546 lines = &request.lines[1];
1547 lines->lines = g_newa (GridLine, lines->max - lines->min);
1548 memset (s: lines->lines, c: 0, n: (lines->max - lines->min) * sizeof (GridLine));
1549
1550 if (gtk_widget_get_request_mode (widget) == GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT)
1551 orientation = GTK_ORIENTATION_HORIZONTAL;
1552 else
1553 orientation = GTK_ORIENTATION_VERTICAL;
1554
1555 grid_request_run (request: &request, OPPOSITE_ORIENTATION (orientation), FALSE);
1556 grid_request_allocate (request: &request, OPPOSITE_ORIENTATION (orientation),
1557 GET_SIZE (width, height, OPPOSITE_ORIENTATION (orientation)));
1558
1559 grid_request_run (request: &request, orientation, TRUE);
1560 grid_request_allocate (request: &request, orientation, GET_SIZE (width, height, orientation));
1561
1562 grid_request_position (request: &request, orientation: 0);
1563 grid_request_position (request: &request, orientation: 1);
1564 grid_request_allocate_children (request: &request, grid_width: width, grid_height: height);
1565}
1566
1567static void
1568gtk_grid_layout_set_property (GObject *gobject,
1569 guint prop_id,
1570 const GValue *value,
1571 GParamSpec *pspec)
1572{
1573 GtkGridLayout *self = GTK_GRID_LAYOUT (ptr: gobject);
1574
1575 switch (prop_id)
1576 {
1577 case PROP_ROW_SPACING:
1578 gtk_grid_layout_set_row_spacing (grid: self, spacing: g_value_get_int (value));
1579 break;
1580
1581 case PROP_COLUMN_SPACING:
1582 gtk_grid_layout_set_column_spacing (grid: self, spacing: g_value_get_int (value));
1583 break;
1584
1585 case PROP_ROW_HOMOGENEOUS:
1586 gtk_grid_layout_set_row_homogeneous (grid: self, homogeneous: g_value_get_boolean (value));
1587 break;
1588
1589 case PROP_COLUMN_HOMOGENEOUS:
1590 gtk_grid_layout_set_column_homogeneous (grid: self, homogeneous: g_value_get_boolean (value));
1591 break;
1592
1593 case PROP_BASELINE_ROW:
1594 gtk_grid_layout_set_baseline_row (grid: self, row: g_value_get_int (value));
1595 break;
1596
1597 default:
1598 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
1599 break;
1600 }
1601}
1602
1603static void
1604gtk_grid_layout_get_property (GObject *gobject,
1605 guint prop_id,
1606 GValue *value,
1607 GParamSpec *pspec)
1608{
1609 GtkGridLayout *self = GTK_GRID_LAYOUT (ptr: gobject);
1610
1611 switch (prop_id)
1612 {
1613 case PROP_ROW_SPACING:
1614 g_value_set_int (value, COLUMNS (self)->spacing);
1615 break;
1616
1617 case PROP_COLUMN_SPACING:
1618 g_value_set_int (value, ROWS (self)->spacing);
1619 break;
1620
1621 case PROP_ROW_HOMOGENEOUS:
1622 g_value_set_boolean (value, COLUMNS (self)->homogeneous);
1623 break;
1624
1625 case PROP_COLUMN_HOMOGENEOUS:
1626 g_value_set_boolean (value, ROWS (self)->homogeneous);
1627 break;
1628
1629 case PROP_BASELINE_ROW:
1630 g_value_set_int (value, v_int: self->baseline_row);
1631 break;
1632
1633 default:
1634 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
1635 break;
1636 }
1637}
1638
1639static void
1640gtk_grid_layout_finalize (GObject *gobject)
1641{
1642 GtkGridLayout *self = GTK_GRID_LAYOUT (ptr: gobject);
1643
1644 g_clear_pointer (&self->row_properties, g_array_unref);
1645
1646 G_OBJECT_CLASS (gtk_grid_layout_parent_class)->finalize (gobject);
1647}
1648
1649static void
1650gtk_grid_layout_class_init (GtkGridLayoutClass *klass)
1651{
1652 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1653 GtkLayoutManagerClass *layout_class = GTK_LAYOUT_MANAGER_CLASS (ptr: klass);
1654
1655 layout_class->layout_child_type = GTK_TYPE_GRID_LAYOUT_CHILD;
1656 layout_class->measure = gtk_grid_layout_measure;
1657 layout_class->allocate = gtk_grid_layout_allocate;
1658
1659 gobject_class->set_property = gtk_grid_layout_set_property;
1660 gobject_class->get_property = gtk_grid_layout_get_property;
1661 gobject_class->finalize = gtk_grid_layout_finalize;
1662
1663 /**
1664 * GtkGridLayout:row-spacing: (attributes org.gtk.Property.get=gtk_grid_layout_get_row_spacing org.gtk.Property.set=gtk_grid_layout_set_row_spacing)
1665 *
1666 * The amount of space between to consecutive rows.
1667 */
1668 layout_props[PROP_ROW_SPACING] =
1669 g_param_spec_int (name: "row-spacing",
1670 P_("Row spacing"),
1671 P_("The amount of space between two consecutive rows"),
1672 minimum: 0, G_MAXINT16, default_value: 0,
1673 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1674
1675 /**
1676 * GtkGridLayout:column-spacing: (attributes org.gtk.Property.get=gtk_grid_layout_get_column_spacing org.gtk.Property.set=gtk_grid_layout_set_column_spacing)
1677 *
1678 * The amount of space between to consecutive columns.
1679 */
1680 layout_props[PROP_COLUMN_SPACING] =
1681 g_param_spec_int (name: "column-spacing",
1682 P_("Column spacing"),
1683 P_("The amount of space between two consecutive columns"),
1684 minimum: 0, G_MAXINT16, default_value: 0,
1685 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1686
1687 /**
1688 * GtkGridLayout:row-homogeneous: (attributes org.gtk.Property.get=gtk_grid_layout_get_row_homogeneous org.gtk.Property.set=gtk_grid_layout_set_row_homogeneous)
1689 *
1690 * Whether all the rows in the grid have the same height.
1691 */
1692 layout_props[PROP_ROW_HOMOGENEOUS] =
1693 g_param_spec_boolean (name: "row-homogeneous",
1694 P_("Row Homogeneous"),
1695 P_("If TRUE, the rows are all the same height"),
1696 FALSE,
1697 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1698
1699 /**
1700 * GtkGridLayout:column-homogeneous: (attributes org.gtk.Property.get=gtk_grid_layout_get_column_homogeneous org.gtk.Property.set=gtk_grid_layout_set_column_homogeneous)
1701 *
1702 * Whether all the columns in the grid have the same width.
1703 */
1704 layout_props[PROP_COLUMN_HOMOGENEOUS] =
1705 g_param_spec_boolean (name: "column-homogeneous",
1706 P_("Column Homogeneous"),
1707 P_("If TRUE, the columns are all the same width"),
1708 FALSE,
1709 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1710
1711 /**
1712 * GtkGridLayout:baseline-row: (attributes org.gtk.Property.get=gtk_grid_layout_get_baseline_row org.gtk.Property.set=gtk_grid_layout_set_baseline_row)
1713 *
1714 * The row to align to the baseline, when `GtkWidget:valign` is set
1715 * to %GTK_ALIGN_BASELINE.
1716 */
1717 layout_props[PROP_BASELINE_ROW] =
1718 g_param_spec_int (name: "baseline-row",
1719 P_("Baseline Row"),
1720 P_("The row to align the to the baseline when valign is GTK_ALIGN_BASELINE"),
1721 minimum: 0, G_MAXINT, default_value: 0,
1722 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
1723
1724 g_object_class_install_properties (oclass: gobject_class, n_pspecs: N_PROPERTIES, pspecs: layout_props);
1725}
1726
1727static void
1728gtk_grid_layout_init (GtkGridLayout *self)
1729{
1730}
1731
1732/**
1733 * gtk_grid_layout_new:
1734 *
1735 * Creates a new `GtkGridLayout`.
1736 *
1737 * Returns: the newly created `GtkGridLayout`
1738 */
1739GtkLayoutManager *
1740gtk_grid_layout_new (void)
1741{
1742 return g_object_new (GTK_TYPE_GRID_LAYOUT, NULL);
1743}
1744
1745/**
1746 * gtk_grid_layout_set_row_homogeneous: (attributes org.gtk.Method.set_property=row-homogeneous)
1747 * @grid: a `GtkGridLayout`
1748 * @homogeneous: %TRUE to make rows homogeneous
1749 *
1750 * Sets whether all rows of @grid should have the same height.
1751 */
1752void
1753gtk_grid_layout_set_row_homogeneous (GtkGridLayout *grid,
1754 gboolean homogeneous)
1755{
1756 g_return_if_fail (GTK_IS_GRID_LAYOUT (grid));
1757
1758 /* Yes, homogeneous rows means all the columns have the same size */
1759 if (COLUMNS (grid)->homogeneous == !!homogeneous)
1760 return;
1761
1762 COLUMNS (grid)->homogeneous = !!homogeneous;
1763
1764 gtk_layout_manager_layout_changed (manager: GTK_LAYOUT_MANAGER (ptr: grid));
1765 g_object_notify_by_pspec (G_OBJECT (grid), pspec: layout_props[PROP_ROW_HOMOGENEOUS]);
1766}
1767
1768/**
1769 * gtk_grid_layout_get_row_homogeneous: (attributes org.gtk.Method.get_property=row-homogeneous)
1770 * @grid: a `GtkGridLayout`
1771 *
1772 * Checks whether all rows of @grid should have the same height.
1773 *
1774 * Returns: %TRUE if the rows are homogeneous, and %FALSE otherwise
1775 */
1776gboolean
1777gtk_grid_layout_get_row_homogeneous (GtkGridLayout *grid)
1778{
1779 g_return_val_if_fail (GTK_IS_GRID_LAYOUT (grid), FALSE);
1780
1781 return COLUMNS (grid)->homogeneous;
1782}
1783
1784/**
1785 * gtk_grid_layout_set_row_spacing: (attributes org.gtk.Method.set_property=row-spacing)
1786 * @grid: a `GtkGridLayout`
1787 * @spacing: the amount of space between rows, in pixels
1788 *
1789 * Sets the amount of space to insert between consecutive rows.
1790 */
1791void
1792gtk_grid_layout_set_row_spacing (GtkGridLayout *grid,
1793 guint spacing)
1794{
1795 g_return_if_fail (GTK_IS_GRID_LAYOUT (grid));
1796 g_return_if_fail (spacing <= G_MAXINT16);
1797
1798 if (COLUMNS (grid)->spacing == spacing)
1799 return;
1800
1801 COLUMNS (grid)->spacing = spacing;
1802
1803 gtk_layout_manager_layout_changed (manager: GTK_LAYOUT_MANAGER (ptr: grid));
1804 g_object_notify_by_pspec (G_OBJECT (grid), pspec: layout_props[PROP_ROW_SPACING]);
1805}
1806
1807/**
1808 * gtk_grid_layout_get_row_spacing: (attributes org.gtk.Method.get_property=row-spacing)
1809 * @grid: a `GtkGridLayout`
1810 *
1811 * Retrieves the spacing set with gtk_grid_layout_set_row_spacing().
1812 *
1813 * Returns: the spacing between consecutive rows
1814 */
1815guint
1816gtk_grid_layout_get_row_spacing (GtkGridLayout *grid)
1817{
1818 g_return_val_if_fail (GTK_IS_GRID_LAYOUT (grid), 0);
1819
1820 return COLUMNS (grid)->spacing;
1821}
1822
1823/**
1824 * gtk_grid_layout_set_column_homogeneous: (attributes org.gtk.Method.set_property=column-homogeneous)
1825 * @grid: a `GtkGridLayout`
1826 * @homogeneous: %TRUE to make columns homogeneous
1827 *
1828 * Sets whether all columns of @grid should have the same width.
1829 */
1830void
1831gtk_grid_layout_set_column_homogeneous (GtkGridLayout *grid,
1832 gboolean homogeneous)
1833{
1834 g_return_if_fail (GTK_IS_GRID_LAYOUT (grid));
1835
1836 /* Yes, homogeneous columns means all the rows have the same size */
1837 if (ROWS (grid)->homogeneous == !!homogeneous)
1838 return;
1839
1840 ROWS (grid)->homogeneous = !!homogeneous;
1841
1842 gtk_layout_manager_layout_changed (manager: GTK_LAYOUT_MANAGER (ptr: grid));
1843 g_object_notify_by_pspec (G_OBJECT (grid), pspec: layout_props[PROP_COLUMN_HOMOGENEOUS]);
1844}
1845
1846/**
1847 * gtk_grid_layout_get_column_homogeneous: (attributes org.gtk.Method.get_property=column-homogeneous)
1848 * @grid: a `GtkGridLayout`
1849 *
1850 * Checks whether all columns of @grid should have the same width.
1851 *
1852 * Returns: %TRUE if the columns are homogeneous, and %FALSE otherwise
1853 */
1854gboolean
1855gtk_grid_layout_get_column_homogeneous (GtkGridLayout *grid)
1856{
1857 g_return_val_if_fail (GTK_IS_GRID_LAYOUT (grid), FALSE);
1858
1859 return ROWS (grid)->homogeneous;
1860}
1861
1862/**
1863 * gtk_grid_layout_set_column_spacing: (attributes org.gtk.Method.set_property=column-spacing)
1864 * @grid: a `GtkGridLayout`
1865 * @spacing: the amount of space between columns, in pixels
1866 *
1867 * Sets the amount of space to insert between consecutive columns.
1868 */
1869void
1870gtk_grid_layout_set_column_spacing (GtkGridLayout *grid,
1871 guint spacing)
1872{
1873 g_return_if_fail (GTK_IS_GRID_LAYOUT (grid));
1874 g_return_if_fail (spacing <= G_MAXINT16);
1875
1876 if (ROWS (grid)->spacing == spacing)
1877 return;
1878
1879 ROWS (grid)->spacing = spacing;
1880
1881 gtk_layout_manager_layout_changed (manager: GTK_LAYOUT_MANAGER (ptr: grid));
1882 g_object_notify_by_pspec (G_OBJECT (grid), pspec: layout_props[PROP_COLUMN_SPACING]);
1883}
1884
1885/**
1886 * gtk_grid_layout_get_column_spacing: (attributes org.gtk.Method.get_property=column-spacing)
1887 * @grid: a `GtkGridLayout`
1888 *
1889 * Retrieves the spacing set with gtk_grid_layout_set_column_spacing().
1890 *
1891 * Returns: the spacing between consecutive columns
1892 */
1893guint
1894gtk_grid_layout_get_column_spacing (GtkGridLayout *grid)
1895{
1896 g_return_val_if_fail (GTK_IS_GRID_LAYOUT (grid), 0);
1897
1898 return ROWS (grid)->spacing;
1899}
1900
1901static GridRowProperties *
1902find_row_properties (GtkGridLayout *self,
1903 int row)
1904{
1905 int i;
1906
1907 if (self->row_properties == NULL)
1908 return NULL;
1909
1910 for (i = 0; i < self->row_properties->len; i++)
1911 {
1912 GridRowProperties *prop = &g_array_index (self->row_properties, GridRowProperties, i);
1913
1914 if (prop->row == row)
1915 return prop;
1916 }
1917
1918 return NULL;
1919}
1920
1921static GridRowProperties *
1922get_row_properties_or_create (GtkGridLayout *self,
1923 int row)
1924{
1925 GridRowProperties *props;
1926
1927 props = find_row_properties (self, row);
1928 if (props != NULL)
1929 return props;
1930
1931 /* This is the only place where we create the row properties array;
1932 * find_row_properties() is used by getters, so we should not create
1933 * the array there.
1934 */
1935 if (self->row_properties == NULL)
1936 self->row_properties = g_array_new (FALSE, FALSE, element_size: sizeof (GridRowProperties));
1937
1938 g_array_append_vals (array: self->row_properties, data: &grid_row_properties_default, len: 1);
1939 props = &g_array_index (self->row_properties, GridRowProperties, self->row_properties->len - 1);
1940 props->row = row;
1941
1942 return props;
1943}
1944
1945static const GridRowProperties *
1946get_row_properties_or_default (GtkGridLayout *self,
1947 int row)
1948{
1949 GridRowProperties *props;
1950
1951 props = find_row_properties (self, row);
1952 if (props != NULL)
1953 return props;
1954
1955 return &grid_row_properties_default;
1956}
1957
1958/**
1959 * gtk_grid_layout_set_row_baseline_position:
1960 * @grid: a `GtkGridLayout`
1961 * @row: a row index
1962 * @pos: a `GtkBaselinePosition`
1963 *
1964 * Sets how the baseline should be positioned on @row of the
1965 * grid, in case that row is assigned more space than is requested.
1966 */
1967void
1968gtk_grid_layout_set_row_baseline_position (GtkGridLayout *grid,
1969 int row,
1970 GtkBaselinePosition pos)
1971{
1972 GridRowProperties *props;
1973
1974 g_return_if_fail (GTK_IS_GRID_LAYOUT (grid));
1975
1976 props = get_row_properties_or_create (self: grid, row);
1977
1978 if (props->baseline_position == pos)
1979 return;
1980
1981 props->baseline_position = pos;
1982 gtk_layout_manager_layout_changed (manager: GTK_LAYOUT_MANAGER (ptr: grid));
1983}
1984
1985/**
1986 * gtk_grid_layout_get_row_baseline_position:
1987 * @grid: a `GtkGridLayout`
1988 * @row: a row index
1989 *
1990 * Returns the baseline position of @row.
1991 *
1992 * If no value has been set with
1993 * [method@Gtk.GridLayout.set_row_baseline_position],
1994 * the default value of %GTK_BASELINE_POSITION_CENTER
1995 * is returned.
1996 *
1997 * Returns: the baseline position of @row
1998 */
1999GtkBaselinePosition
2000gtk_grid_layout_get_row_baseline_position (GtkGridLayout *grid,
2001 int row)
2002{
2003 const GridRowProperties *props;
2004
2005 g_return_val_if_fail (GTK_IS_GRID_LAYOUT (grid), GTK_BASELINE_POSITION_CENTER);
2006
2007 props = get_row_properties_or_default (self: grid, row);
2008
2009 return props->baseline_position;
2010}
2011
2012/**
2013 * gtk_grid_layout_set_baseline_row: (attributes org.gtk.Method.set_property=baseline-row)
2014 * @grid: a `GtkGridLayout`
2015 * @row: the row index
2016 *
2017 * Sets which row defines the global baseline for the entire grid.
2018 *
2019 * Each row in the grid can have its own local baseline, but only
2020 * one of those is global, meaning it will be the baseline in the
2021 * parent of the @grid.
2022 */
2023void
2024gtk_grid_layout_set_baseline_row (GtkGridLayout *grid,
2025 int row)
2026{
2027 g_return_if_fail (GTK_IS_GRID_LAYOUT (grid));
2028
2029 if (grid->baseline_row == row)
2030 return;
2031
2032 grid->baseline_row = row;
2033 gtk_layout_manager_layout_changed (manager: GTK_LAYOUT_MANAGER (ptr: grid));
2034 g_object_notify_by_pspec (G_OBJECT (grid), pspec: layout_props[PROP_BASELINE_ROW]);
2035}
2036
2037/**
2038 * gtk_grid_layout_get_baseline_row: (attributes org.gtk.Method.get_property=baseline-row)
2039 * @grid: a `GtkGridLayout`
2040 *
2041 * Retrieves the row set with gtk_grid_layout_set_baseline_row().
2042 *
2043 * Returns: the global baseline row
2044 */
2045int
2046gtk_grid_layout_get_baseline_row (GtkGridLayout *grid)
2047{
2048 g_return_val_if_fail (GTK_IS_GRID_LAYOUT (grid), GTK_BASELINE_POSITION_CENTER);
2049
2050 return grid->baseline_row;
2051}
2052/* }}} */
2053

source code of gtk/gtk/gtkgridlayout.c