Merge branch 'wip/layout-manager' into 'master'

Add Layout Managers

See merge request GNOME/gtk!534
This commit is contained in:
Matthias Clasen
2019-03-26 12:38:18 +00:00
23 changed files with 2584 additions and 636 deletions

View File

@@ -100,6 +100,13 @@
<xi:include href="xml/gtkfixed.xml" />
</chapter>
<chapter id="LayoutManagers">
<title>Layout Managers</title>
<xi:include href="xml/gtklayoutmanager.xml" />
<xi:include href="xml/gtklayoutchild.xml" />
<xi:include href="xml/gtkboxlayout.xml" />
</chapter>
<chapter id="DisplayWidgets">
<title>Display Widgets</title>
<xi:include href="xml/gtklabel.xml" />

View File

@@ -4590,6 +4590,8 @@ gtk_widget_get_first_child
gtk_widget_get_last_child
gtk_widget_insert_before
gtk_widget_insert_after
gtk_widget_set_layout_manager
gtk_widget_get_layout_manager
<SUBSECTION>
gtk_widget_get_path
@@ -7152,3 +7154,51 @@ gtk_root_get_for_surface
<SUBSECTION Private>
gtk_root_get_type
</SECTION>
<SECTION>
<FILE>gtklayoutmanager</FILE>
GtkLayoutManager
GtkLayoutManagerClass
gtk_layout_manager_measure
gtk_layout_manager_allocate
gtk_layout_manager_get_request_mode
gtk_layout_manager_get_widget
gtk_layout_manager_get_layout_child
gtk_layout_manager_layout_changed
<SUBSECTION Standard>
GTK_TYPE_LAYOUT_MANAGER
gtk_layout_manager_get_type
</SECTION>
<SECTION>
<FILE>gtklayoutchild</FILE>
GtkLayoutChild
GtkLayoutChildClass
gtk_layout_child_get_layout_manager
gtk_layout_child_get_child_widget
<SUBSECTION Standard>
GTK_TYPE_LAYOUT_CHILD
gtk_layout_child_get_type
</SECTION>
<SECTION>
<FILE>gtkboxlayout</FILE>
GtkBoxLayout
GtkBoxLayoutClass
gtk_box_layout_new
gtk_box_layout_set_homogeneous
gtk_box_layout_get_homogeneous
gtk_box_layout_set_spacing
gtk_box_layout_get_spacing
gtk_box_layout_set_baseline_position
gtk_box_layout_get_baseline_position
<SUBSECTION Standard>
GTK_TYPE_BOX_LAYOUT
gtk_box_layout_get_type
</SECTION>

View File

@@ -45,8 +45,10 @@
#include <gtk/gtkaspectframe.h>
#include <gtk/gtkassistant.h>
#include <gtk/gtkbin.h>
#include <gtk/gtkbinlayout.h>
#include <gtk/gtkbindings.h>
#include <gtk/gtkborder.h>
#include <gtk/gtkboxlayout.h>
#include <gtk/gtkbox.h>
#include <gtk/gtkbuildable.h>
#include <gtk/gtkbuilder.h>
@@ -80,6 +82,7 @@
#include <gtk/gtkcontainer.h>
#include <gtk/gtkcssprovider.h>
#include <gtk/gtkcsssection.h>
#include <gtk/gtkcustomlayout.h>
#include <gtk/gtkdebug.h>
#include <gtk/gtkdialog.h>
#include <gtk/gtkdnd.h>
@@ -134,6 +137,8 @@
#include <gtk/gtkinfobar.h>
#include <gtk/gtklabel.h>
#include <gtk/gtklayout.h>
#include <gtk/gtklayoutmanager.h>
#include <gtk/gtklayoutchild.h>
#include <gtk/gtklevelbar.h>
#include <gtk/gtklinkbutton.h>
#include <gtk/gtklistbox.h>

124
gtk/gtkbinlayout.c Normal file
View File

@@ -0,0 +1,124 @@
/* gtkbinlayout.c: Layout manager for bin-like widgets
* Copyright 2019 GNOME Foundation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* SECTION:gtkbinlayout
* @Title: GtkBinLayout
* @Short_description: A layout manager for bin-like widgets
* @Include: gtk/gtk.h
*
* GtkBinLayout is a #GtkLayoutManager subclass useful for create "bins" of
* widgets. GtkBinLayout will stack each child of a widget on top of each
* other, using the #GtkWidget:hexpand, #GtkWidget:vexpand, #GtkWidget:halign,
* and #GtkWidget:valign properties of each child to determine where they
* should be positioned.
*/
#include "config.h"
#include "gtkbinlayout.h"
#include "gtkwidgetprivate.h"
struct _GtkBinLayout
{
GtkLayoutManager parent_instance;
};
G_DEFINE_TYPE (GtkBinLayout, gtk_bin_layout, GTK_TYPE_LAYOUT_MANAGER)
static void
gtk_bin_layout_measure (GtkLayoutManager *layout_manager,
GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkWidget *child;
for (child = _gtk_widget_get_first_child (widget);
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
if (gtk_widget_get_visible (child))
{
int child_min = 0;
int child_nat = 0;
int child_min_baseline = -1;
int child_nat_baseline = -1;
gtk_widget_measure (child, orientation, for_size,
&child_min, &child_nat,
&child_min_baseline, &child_nat_baseline);
*minimum = MAX (*minimum, child_min);
*natural = MAX (*natural, child_nat);
if (child_min_baseline > -1)
*minimum_baseline = MAX (*minimum_baseline, child_min_baseline);
if (child_nat_baseline > -1)
*natural_baseline = MAX (*natural_baseline, child_nat_baseline);
}
}
}
static void
gtk_bin_layout_allocate (GtkLayoutManager *layout_manager,
GtkWidget *widget,
int width,
int height,
int baseline)
{
GtkWidget *child;
for (child = _gtk_widget_get_first_child (widget);
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
if (child && gtk_widget_get_visible (child))
gtk_widget_allocate (child, width, height, baseline, NULL);
}
}
static void
gtk_bin_layout_class_init (GtkBinLayoutClass *klass)
{
GtkLayoutManagerClass *layout_manager_class = GTK_LAYOUT_MANAGER_CLASS (klass);
layout_manager_class->measure = gtk_bin_layout_measure;
layout_manager_class->allocate = gtk_bin_layout_allocate;
}
static void
gtk_bin_layout_init (GtkBinLayout *self)
{
}
/**
* gtk_bin_layout_new:
*
* Creates a new #GtkBinLayout instance.
*
* Returns: the newly created #GtkBinLayout
*/
GtkLayoutManager *
gtk_bin_layout_new (void)
{
return g_object_new (GTK_TYPE_BIN_LAYOUT, NULL);
}

31
gtk/gtkbinlayout.h Normal file
View File

@@ -0,0 +1,31 @@
/* gtkbinlayout.h: Layout manager for bin-like widgets
* Copyright 2019 GNOME Foundation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <gtk/gtklayoutmanager.h>
G_BEGIN_DECLS
#define GTK_TYPE_BIN_LAYOUT (gtk_bin_layout_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkBinLayout, gtk_bin_layout, GTK, BIN_LAYOUT, GtkLayoutManager)
GDK_AVAILABLE_IN_ALL
GtkLayoutManager * gtk_bin_layout_new (void);
G_END_DECLS

View File

@@ -57,6 +57,7 @@
#include "config.h"
#include "gtkbox.h"
#include "gtkboxlayout.h"
#include "gtkcsspositionvalueprivate.h"
#include "gtkintl.h"
#include "gtkorientable.h"
@@ -92,11 +93,6 @@ typedef struct
static GParamSpec *props[LAST_PROP] = { NULL, };
static void gtk_box_size_allocate (GtkWidget *widget,
int width,
int height,
int baseline);
static void gtk_box_set_property (GObject *object,
guint prop_id,
const GValue *value,
@@ -116,13 +112,6 @@ static GType gtk_box_child_type (GtkContainer *container);
static GtkWidgetPath * gtk_box_get_path_for_child
(GtkContainer *container,
GtkWidget *child);
static void gtk_box_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline);
G_DEFINE_TYPE_WITH_CODE (GtkBox, gtk_box, GTK_TYPE_CONTAINER,
G_ADD_PRIVATE (GtkBox)
@@ -138,9 +127,6 @@ gtk_box_class_init (GtkBoxClass *class)
object_class->set_property = gtk_box_set_property;
object_class->get_property = gtk_box_get_property;
widget_class->size_allocate = gtk_box_size_allocate;
widget_class->measure = gtk_box_measure;
container_class->add = gtk_box_add;
container_class->remove = gtk_box_remove;
container_class->forall = gtk_box_forall;
@@ -187,6 +173,7 @@ gtk_box_set_property (GObject *object,
{
GtkBox *box = GTK_BOX (object);
GtkBoxPrivate *priv = gtk_box_get_instance_private (box);
GtkLayoutManager *box_layout = gtk_widget_get_layout_manager (GTK_WIDGET (box));
switch (prop_id)
{
@@ -196,8 +183,9 @@ gtk_box_set_property (GObject *object,
if (priv->orientation != orientation)
{
priv->orientation = orientation;
gtk_orientable_set_orientation (GTK_ORIENTABLE (box_layout),
priv->orientation);
_gtk_orientable_set_style_classes (GTK_ORIENTABLE (box));
gtk_widget_queue_resize (GTK_WIDGET (box));
g_object_notify (object, "orientation");
}
}
@@ -225,6 +213,7 @@ gtk_box_get_property (GObject *object,
{
GtkBox *box = GTK_BOX (object);
GtkBoxPrivate *priv = gtk_box_get_instance_private (box);
GtkBoxLayout *box_layout = GTK_BOX_LAYOUT (gtk_widget_get_layout_manager (GTK_WIDGET (box)));
switch (prop_id)
{
@@ -232,13 +221,13 @@ gtk_box_get_property (GObject *object,
g_value_set_enum (value, priv->orientation);
break;
case PROP_SPACING:
g_value_set_int (value, priv->spacing);
g_value_set_int (value, gtk_box_layout_get_spacing (box_layout));
break;
case PROP_BASELINE_POSITION:
g_value_set_enum (value, priv->baseline_pos);
g_value_set_enum (value, gtk_box_layout_get_baseline_position (box_layout));
break;
case PROP_HOMOGENEOUS:
g_value_set_boolean (value, priv->homogeneous);
g_value_set_boolean (value, gtk_box_layout_get_homogeneous (box_layout));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -246,285 +235,6 @@ gtk_box_get_property (GObject *object,
}
}
static void
count_expand_children (GtkBox *box,
gint *visible_children,
gint *expand_children)
{
GtkBoxPrivate *priv = gtk_box_get_instance_private (box);
GtkWidget *child;
*visible_children = *expand_children = 0;
for (child = _gtk_widget_get_first_child (GTK_WIDGET (box));
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
if (_gtk_widget_get_visible (child))
{
*visible_children += 1;
if (gtk_widget_compute_expand (child, priv->orientation))
*expand_children += 1;
}
}
}
static gint
get_spacing (GtkBox *box)
{
GtkBoxPrivate *priv = gtk_box_get_instance_private (box);
GtkCssValue *border_spacing;
gint css_spacing;
border_spacing = _gtk_style_context_peek_property (gtk_widget_get_style_context (GTK_WIDGET (box)), GTK_CSS_PROPERTY_BORDER_SPACING);
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
css_spacing = _gtk_css_position_value_get_x (border_spacing, 100);
else
css_spacing = _gtk_css_position_value_get_y (border_spacing, 100);
return css_spacing + priv->spacing;
}
static void
gtk_box_size_allocate (GtkWidget *widget,
int width,
int height,
int baseline)
{
GtkBox *box = GTK_BOX (widget);
GtkBoxPrivate *priv = gtk_box_get_instance_private (box);
GtkWidget *child;
gint nvis_children;
gint nexpand_children;
GtkTextDirection direction;
GtkAllocation child_allocation;
GtkRequestedSize *sizes;
gint child_minimum_baseline, child_natural_baseline;
gint minimum_above, natural_above;
gint minimum_below, natural_below;
gboolean have_baseline;
gint extra_space;
gint children_minimum_size = 0;
gint size_given_to_child;
gint n_extra_widgets = 0; /* Number of widgets that receive 1 extra px */
gint x = 0, y = 0, i;
gint child_size;
gint spacing;
count_expand_children (box, &nvis_children, &nexpand_children);
/* If there is no visible child, simply return. */
if (nvis_children <= 0)
return;
direction = _gtk_widget_get_direction (widget);
sizes = g_newa (GtkRequestedSize, nvis_children);
spacing = get_spacing (box);
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
extra_space = width - (nvis_children - 1) * spacing;
else
extra_space = height - (nvis_children - 1) * spacing;
have_baseline = FALSE;
minimum_above = natural_above = 0;
minimum_below = natural_below = 0;
/* Retrieve desired size for visible children. */
for (i = 0, child = _gtk_widget_get_first_child (widget);
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
if (!_gtk_widget_get_visible (child))
continue;
gtk_widget_measure (child,
priv->orientation,
priv->orientation == GTK_ORIENTATION_HORIZONTAL ? height : width,
&sizes[i].minimum_size, &sizes[i].natural_size,
NULL, NULL);
children_minimum_size += sizes[i].minimum_size;
sizes[i].data = child;
i++;
}
if (priv->homogeneous)
{
/* We still need to run the above loop to populate the minimum sizes for
* children that aren't going to fill.
*/
size_given_to_child = extra_space / nvis_children;
n_extra_widgets = extra_space % nvis_children;
}
else
{
/* Bring children up to size first */
extra_space -= children_minimum_size;
extra_space = MAX (0, extra_space);
extra_space = gtk_distribute_natural_allocation (extra_space, nvis_children, sizes);
/* Calculate space which hasn't distributed yet,
* and is available for expanding children.
*/
if (nexpand_children > 0)
{
size_given_to_child = extra_space / nexpand_children;
n_extra_widgets = extra_space % nexpand_children;
}
else
{
size_given_to_child = 0;
}
}
/* Allocate child sizes. */
for (i = 0, child = _gtk_widget_get_first_child (widget);
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
/* If widget is not visible, skip it. */
if (!_gtk_widget_get_visible (child))
continue;
/* Assign the child's size. */
if (priv->homogeneous)
{
child_size = size_given_to_child;
if (n_extra_widgets > 0)
{
child_size++;
n_extra_widgets--;
}
}
else
{
child_size = sizes[i].minimum_size;
if (gtk_widget_compute_expand (child, priv->orientation))
{
child_size += size_given_to_child;
if (n_extra_widgets > 0)
{
child_size++;
n_extra_widgets--;
}
}
}
sizes[i].natural_size = child_size;
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL &&
gtk_widget_get_valign (child) == GTK_ALIGN_BASELINE)
{
int child_allocation_width;
int child_minimum_height, child_natural_height;
child_allocation_width = child_size;
child_minimum_baseline = -1;
child_natural_baseline = -1;
gtk_widget_measure (child, GTK_ORIENTATION_VERTICAL,
child_allocation_width,
&child_minimum_height, &child_natural_height,
&child_minimum_baseline, &child_natural_baseline);
if (child_minimum_baseline >= 0)
{
have_baseline = TRUE;
minimum_below = MAX (minimum_below, child_minimum_height - child_minimum_baseline);
natural_below = MAX (natural_below, child_natural_height - child_natural_baseline);
minimum_above = MAX (minimum_above, child_minimum_baseline);
natural_above = MAX (natural_above, child_natural_baseline);
}
}
i++;
}
if (priv->orientation == GTK_ORIENTATION_VERTICAL)
baseline = -1;
/* we only calculate our own baseline if we don't get one passed from the parent
* and any of the child widgets explicitly request one */
if (baseline == -1 && have_baseline)
{
/* TODO: This is purely based on the minimum baseline, when things fit we should
use the natural one? */
switch (priv->baseline_pos)
{
case GTK_BASELINE_POSITION_TOP:
baseline = minimum_above;
break;
case GTK_BASELINE_POSITION_CENTER:
baseline = minimum_above + (height - (minimum_above + minimum_below)) / 2;
break;
case GTK_BASELINE_POSITION_BOTTOM:
baseline = height - minimum_below;
break;
default:
break;
}
}
/* Allocate child positions. */
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
{
child_allocation.y = 0;
child_allocation.height = height;
x = 0;
}
else
{
child_allocation.x = 0;
child_allocation.width = width;
y = 0;
}
for (i = 0, child = _gtk_widget_get_first_child (widget);
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
/* If widget is not visible, skip it. */
if (!_gtk_widget_get_visible (child))
continue;
child_size = sizes[i].natural_size;
/* Assign the child's position. */
if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
{
child_allocation.width = child_size;
child_allocation.x = x;
x += child_size + spacing;
if (direction == GTK_TEXT_DIR_RTL)
child_allocation.x = width - child_allocation.x - child_allocation.width;
}
else /* (priv->orientation == GTK_ORIENTATION_VERTICAL) */
{
child_allocation.height = child_size;
child_allocation.y = y;
y += child_size + spacing;
}
gtk_widget_size_allocate (child, &child_allocation, baseline);
i++;
}
}
static GType
gtk_box_child_type (GtkContainer *container)
{
@@ -630,263 +340,17 @@ gtk_box_get_path_for_child (GtkContainer *container,
return path;
}
static void
gtk_box_compute_size_for_opposing_orientation (GtkBox *box,
int for_size,
gint *minimum_size,
gint *natural_size,
gint *minimum_baseline,
gint *natural_baseline)
{
GtkBoxPrivate *priv = gtk_box_get_instance_private (box);
GtkWidget *widget = GTK_WIDGET (box);
GtkWidget *child;
gint nvis_children;
gint nexpand_children;
gint computed_minimum = 0, computed_natural = 0;
gint computed_minimum_above = 0, computed_natural_above = 0;
gint computed_minimum_below = 0, computed_natural_below = 0;
gint computed_minimum_baseline = -1, computed_natural_baseline = -1;
GtkRequestedSize *sizes;
gint extra_space, size_given_to_child, i;
gint children_minimum_size = 0;
gint child_size, child_minimum, child_natural;
gint child_minimum_baseline, child_natural_baseline;
gint n_extra_widgets = 0;
gint spacing;
gboolean have_baseline;
count_expand_children (box, &nvis_children, &nexpand_children);
if (nvis_children <= 0)
return;
spacing = get_spacing (box);
sizes = g_newa (GtkRequestedSize, nvis_children);
extra_space = MAX (0, for_size - (nvis_children - 1) * spacing);
/* Retrieve desired size for visible children */
for (i = 0, child = _gtk_widget_get_first_child (widget);
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
if (_gtk_widget_get_visible (child))
{
gtk_widget_measure (child,
priv->orientation,
-1,
&sizes[i].minimum_size, &sizes[i].natural_size,
NULL, NULL);
children_minimum_size += sizes[i].minimum_size;
i += 1;
}
}
if (priv->homogeneous)
{
/* We still need to run the above loop to populate the minimum sizes for
* children that aren't going to fill.
*/
size_given_to_child = extra_space / nvis_children;
n_extra_widgets = extra_space % nvis_children;
}
else
{
/* Bring children up to size first */
extra_space -= children_minimum_size;
extra_space = MAX (0, extra_space);
extra_space = gtk_distribute_natural_allocation (extra_space, nvis_children, sizes);
/* Calculate space which hasn't distributed yet,
* and is available for expanding children.
*/
if (nexpand_children > 0)
{
size_given_to_child = extra_space / nexpand_children;
n_extra_widgets = extra_space % nexpand_children;
}
else
{
size_given_to_child = 0;
}
}
have_baseline = FALSE;
for (i = 0, child = _gtk_widget_get_first_child (widget);
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
/* If widget is not visible, skip it. */
if (!_gtk_widget_get_visible (child))
continue;
/* Assign the child's size. */
if (priv->homogeneous)
{
child_size = size_given_to_child;
if (n_extra_widgets > 0)
{
child_size++;
n_extra_widgets--;
}
}
else
{
child_size = sizes[i].minimum_size;
if (gtk_widget_compute_expand (child, priv->orientation))
{
child_size += size_given_to_child;
if (n_extra_widgets > 0)
{
child_size++;
n_extra_widgets--;
}
}
}
child_minimum_baseline = child_natural_baseline = -1;
/* Assign the child's position. */
gtk_widget_measure (child,
OPPOSITE_ORIENTATION (priv->orientation),
child_size,
&child_minimum, &child_natural,
&child_minimum_baseline, &child_natural_baseline);
if (child_minimum_baseline >= 0)
{
have_baseline = TRUE;
computed_minimum_below = MAX (computed_minimum_below, child_minimum - child_minimum_baseline);
computed_natural_below = MAX (computed_natural_below, child_natural - child_natural_baseline);
computed_minimum_above = MAX (computed_minimum_above, child_minimum_baseline);
computed_natural_above = MAX (computed_natural_above, child_natural_baseline);
}
else
{
computed_minimum = MAX (computed_minimum, child_minimum);
computed_natural = MAX (computed_natural, child_natural);
}
i += 1;
}
if (have_baseline)
{
computed_minimum = MAX (computed_minimum, computed_minimum_below + computed_minimum_above);
computed_natural = MAX (computed_natural, computed_natural_below + computed_natural_above);
switch (priv->baseline_pos)
{
case GTK_BASELINE_POSITION_TOP:
computed_minimum_baseline = computed_minimum_above;
computed_natural_baseline = computed_natural_above;
break;
case GTK_BASELINE_POSITION_CENTER:
computed_minimum_baseline = computed_minimum_above + MAX((computed_minimum - (computed_minimum_above + computed_minimum_below)) / 2, 0);
computed_natural_baseline = computed_natural_above + MAX((computed_natural - (computed_natural_above + computed_natural_below)) / 2, 0);
break;
case GTK_BASELINE_POSITION_BOTTOM:
computed_minimum_baseline = computed_minimum - computed_minimum_below;
computed_natural_baseline = computed_natural - computed_natural_below;
break;
default:
break;
}
}
*minimum_size = computed_minimum;
*natural_size = MAX (computed_natural, computed_natural_below + computed_natural_above);
*minimum_baseline = computed_minimum_baseline;
*natural_baseline = computed_natural_baseline;
}
static void
gtk_box_compute_size_for_orientation (GtkBox *box,
int for_size,
int *minimum_size,
int *natural_size)
{
GtkBoxPrivate *priv = gtk_box_get_instance_private (box);
GtkWidget *child;
const int spacing = get_spacing (box);
int nvis_children = 0;
int required_size = 0, required_natural = 0;
int largest_child = 0, largest_natural = 0;
for (child = _gtk_widget_get_first_child (GTK_WIDGET (box));
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
if (_gtk_widget_get_visible (child))
{
int child_size, child_natural;
gtk_widget_measure (child,
priv->orientation,
for_size,
&child_size, &child_natural,
NULL, NULL);
largest_child = MAX (largest_child, child_size);
largest_natural = MAX (largest_natural, child_natural);
required_size += child_size;
required_natural += child_natural;
nvis_children += 1;
}
}
if (nvis_children > 0)
{
if (priv->homogeneous)
{
required_size = largest_child * nvis_children;
required_natural = largest_natural * nvis_children;
}
required_size += (nvis_children - 1) * spacing;
required_natural += (nvis_children - 1) * spacing;
}
*minimum_size = required_size;
*natural_size = required_natural;
}
static void
gtk_box_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkBox *box = GTK_BOX (widget);
GtkBoxPrivate *priv = gtk_box_get_instance_private (box);
if (priv->orientation != orientation)
gtk_box_compute_size_for_opposing_orientation (box, for_size, minimum, natural, minimum_baseline, natural_baseline);
else
gtk_box_compute_size_for_orientation (box, for_size, minimum, natural);
}
static void
gtk_box_init (GtkBox *box)
{
GtkBoxPrivate *priv = gtk_box_get_instance_private (box);
GtkLayoutManager *box_layout = gtk_box_layout_new (GTK_ORIENTATION_HORIZONTAL);
gtk_widget_set_has_surface (GTK_WIDGET (box), FALSE);
priv->orientation = GTK_ORIENTATION_HORIZONTAL;
priv->homogeneous = FALSE;
priv->spacing = 0;
priv->baseline_pos = GTK_BASELINE_POSITION_CENTER;
gtk_widget_set_layout_manager (GTK_WIDGET (box), box_layout);
priv->orientation = GTK_ORIENTATION_HORIZONTAL;
_gtk_orientable_set_style_classes (GTK_ORIENTABLE (box));
}
@@ -905,7 +369,7 @@ gtk_box_new (GtkOrientation orientation,
{
return g_object_new (GTK_TYPE_BOX,
"orientation", orientation,
"spacing", spacing,
"spacing", spacing,
NULL);
}
@@ -923,18 +387,18 @@ void
gtk_box_set_homogeneous (GtkBox *box,
gboolean homogeneous)
{
GtkBoxPrivate *priv = gtk_box_get_instance_private (box);
GtkBoxLayout *box_layout;
g_return_if_fail (GTK_IS_BOX (box));
homogeneous = homogeneous != FALSE;
homogeneous = !!homogeneous;
if (priv->homogeneous != homogeneous)
{
priv->homogeneous = homogeneous;
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_HOMOGENEOUS]);
gtk_widget_queue_resize (GTK_WIDGET (box));
}
box_layout = GTK_BOX_LAYOUT (gtk_widget_get_layout_manager (GTK_WIDGET (box)));
if (homogeneous == gtk_box_layout_get_homogeneous (box_layout))
return;
gtk_box_layout_set_homogeneous (box_layout, homogeneous);
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_HOMOGENEOUS]);
}
/**
@@ -949,11 +413,13 @@ gtk_box_set_homogeneous (GtkBox *box,
gboolean
gtk_box_get_homogeneous (GtkBox *box)
{
GtkBoxPrivate *priv = gtk_box_get_instance_private (box);
GtkLayoutManager *box_layout;
g_return_val_if_fail (GTK_IS_BOX (box), FALSE);
return priv->homogeneous;
box_layout = gtk_widget_get_layout_manager (GTK_WIDGET (box));
return gtk_box_layout_get_homogeneous (GTK_BOX_LAYOUT (box_layout));
}
/**
@@ -968,18 +434,16 @@ void
gtk_box_set_spacing (GtkBox *box,
gint spacing)
{
GtkBoxPrivate *priv = gtk_box_get_instance_private (box);
GtkBoxLayout *box_layout;
g_return_if_fail (GTK_IS_BOX (box));
if (priv->spacing != spacing)
{
priv->spacing = spacing;
box_layout = GTK_BOX_LAYOUT (gtk_widget_get_layout_manager (GTK_WIDGET (box)));
if (spacing == gtk_box_layout_get_spacing (box_layout))
return;
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_SPACING]);
gtk_widget_queue_resize (GTK_WIDGET (box));
}
gtk_box_layout_set_spacing (box_layout, spacing);
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_SPACING]);
}
/**
@@ -993,10 +457,13 @@ gtk_box_set_spacing (GtkBox *box,
gint
gtk_box_get_spacing (GtkBox *box)
{
GtkBoxPrivate *priv = gtk_box_get_instance_private (box);
GtkLayoutManager *box_layout;
g_return_val_if_fail (GTK_IS_BOX (box), 0);
return priv->spacing;
box_layout = gtk_widget_get_layout_manager (GTK_WIDGET (box));
return gtk_box_layout_get_spacing (GTK_BOX_LAYOUT (box_layout));
}
/**
@@ -1015,18 +482,16 @@ void
gtk_box_set_baseline_position (GtkBox *box,
GtkBaselinePosition position)
{
GtkBoxPrivate *priv = gtk_box_get_instance_private (box);
GtkBoxLayout *box_layout;
g_return_if_fail (GTK_IS_BOX (box));
if (priv->baseline_pos != position)
{
priv->baseline_pos = position;
box_layout = GTK_BOX_LAYOUT (gtk_widget_get_layout_manager (GTK_WIDGET (box)));
if (position == gtk_box_layout_get_baseline_position (box_layout))
return;
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_BASELINE_POSITION]);
gtk_widget_queue_resize (GTK_WIDGET (box));
}
gtk_box_layout_set_baseline_position (box_layout, position);
g_object_notify_by_pspec (G_OBJECT (box), props[PROP_BASELINE_POSITION]);
}
/**
@@ -1040,11 +505,13 @@ gtk_box_set_baseline_position (GtkBox *box,
GtkBaselinePosition
gtk_box_get_baseline_position (GtkBox *box)
{
GtkBoxPrivate *priv = gtk_box_get_instance_private (box);
GtkLayoutManager *box_layout;
g_return_val_if_fail (GTK_IS_BOX (box), GTK_BASELINE_POSITION_CENTER);
return priv->baseline_pos;
box_layout = gtk_widget_get_layout_manager (GTK_WIDGET (box));
return gtk_box_layout_get_baseline_position (GTK_BOX_LAYOUT (box_layout));
}
static void

877
gtk/gtkboxlayout.c Normal file
View File

@@ -0,0 +1,877 @@
/* gtkboxlayout.c: Box layout manager
*
* Copyright 2019 GNOME Foundation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gtkboxlayout.h"
#include "gtkcsspositionvalueprivate.h"
#include "gtkintl.h"
#include "gtkorientableprivate.h"
#include "gtkprivate.h"
#include "gtksizerequest.h"
#include "gtkstylecontextprivate.h"
#include "gtktypebuiltins.h"
#include "gtkwidgetprivate.h"
/**
* SECTION:gtkboxlayout
* @Title: GtkBoxLayout
* @Short_description: Layout manager for placing all children in a single row or column
*
* A GtkBoxLayout is a layout manager that arranges the children of any
* widget using it into a single row or column, depending on the value
* of its #GtkOrientable:orientation property. Within the other dimension
* all children all allocated the same size. The GtkBoxLayout will respect
* the #GtkWidget:halign and #GtkWidget:valign properties of each child
* widget.
*
* If you want all children to be assigned the same size, you can use
* the #GtkBoxLayout:homogeneous property.
*
* If you want to specify the amount of space placed between each child,
* you can use the #GtkBoxLayout:spacing property.
*/
struct _GtkBoxLayout
{
GtkLayoutManager parent_instance;
gboolean homogeneous;
guint spacing;
GtkOrientation orientation;
GtkBaselinePosition baseline_position;
};
G_DEFINE_TYPE_WITH_CODE (GtkBoxLayout, gtk_box_layout, GTK_TYPE_LAYOUT_MANAGER,
G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
enum {
PROP_HOMOGENEOUS = 1,
PROP_SPACING,
PROP_BASELINE_POSITION,
/* From GtkOrientable */
PROP_ORIENTATION,
N_PROPS = PROP_ORIENTATION
};
static GParamSpec *box_layout_props[N_PROPS];
static void
gtk_box_layout_set_orientation (GtkBoxLayout *self,
GtkOrientation orientation)
{
GtkLayoutManager *layout_manager = GTK_LAYOUT_MANAGER (self);
GtkWidget *widget;
if (self->orientation == orientation)
return;
self->orientation = orientation;
widget = gtk_layout_manager_get_widget (layout_manager);
if (widget != NULL && GTK_IS_ORIENTABLE (widget))
_gtk_orientable_set_style_classes (GTK_ORIENTABLE (widget));
gtk_layout_manager_layout_changed (layout_manager);
}
static void
gtk_box_layout_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkBoxLayout *self = GTK_BOX_LAYOUT (gobject);
switch (prop_id)
{
case PROP_HOMOGENEOUS:
gtk_box_layout_set_homogeneous (self, g_value_get_boolean (value));
break;
case PROP_SPACING:
gtk_box_layout_set_spacing (self, g_value_get_int (value));
break;
case PROP_BASELINE_POSITION:
gtk_box_layout_set_baseline_position (self, g_value_get_enum (value));
break;
case PROP_ORIENTATION:
gtk_box_layout_set_orientation (self, g_value_get_enum (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
gtk_box_layout_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkBoxLayout *box_layout = GTK_BOX_LAYOUT (gobject);
switch (prop_id)
{
case PROP_HOMOGENEOUS:
g_value_set_boolean (value, box_layout->homogeneous);
break;
case PROP_SPACING:
g_value_set_int (value, box_layout->spacing);
break;
case PROP_BASELINE_POSITION:
g_value_set_enum (value, box_layout->baseline_position);
break;
case PROP_ORIENTATION:
g_value_set_enum (value, box_layout->orientation);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
count_expand_children (GtkWidget *widget,
GtkOrientation orientation,
gint *visible_children,
gint *expand_children)
{
GtkWidget *child;
*visible_children = *expand_children = 0;
for (child = _gtk_widget_get_first_child (widget);
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
if (_gtk_widget_get_visible (child))
{
*visible_children += 1;
if (gtk_widget_compute_expand (child, orientation))
*expand_children += 1;
}
}
}
static gint
get_spacing (GtkBoxLayout *self,
GtkStyleContext *style_context)
{
GtkCssValue *border_spacing;
gint css_spacing;
border_spacing = _gtk_style_context_peek_property (style_context, GTK_CSS_PROPERTY_BORDER_SPACING);
if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
css_spacing = _gtk_css_position_value_get_x (border_spacing, 100);
else
css_spacing = _gtk_css_position_value_get_y (border_spacing, 100);
return css_spacing + self->spacing;
}
static GtkSizeRequestMode
gtk_box_layout_get_request_mode (GtkLayoutManager *layout_manager,
GtkWidget *widget)
{
GtkBoxLayout *self = GTK_BOX_LAYOUT (layout_manager);
return self->orientation == GTK_ORIENTATION_HORIZONTAL
? GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT
: GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
}
static void
gtk_box_layout_compute_size (GtkBoxLayout *self,
GtkWidget *widget,
int for_size,
int *minimum,
int *natural)
{
GtkWidget *child;
int n_visible_children = 0;
int required_min = 0, required_nat = 0;
int largest_min = 0, largest_nat = 0;
int spacing = get_spacing (self, _gtk_widget_get_style_context (widget));
for (child = gtk_widget_get_first_child (widget);
child != NULL;
child = gtk_widget_get_next_sibling (child))
{
int child_min = 0;
int child_nat = 0;
if (!_gtk_widget_get_visible (child))
continue;
gtk_widget_measure (child, self->orientation,
for_size,
&child_min, &child_nat,
NULL, NULL);
largest_min = MAX (largest_min, child_min);
largest_nat = MAX (largest_nat, child_nat);
required_min += child_min;
required_nat += child_nat;
n_visible_children += 1;
}
if (n_visible_children > 0)
{
if (self->homogeneous)
{
required_min = largest_min * n_visible_children;
required_nat = largest_nat * n_visible_children;
}
required_min += (n_visible_children - 1) * spacing;
required_nat += (n_visible_children - 1) * spacing;
}
if (minimum != NULL)
*minimum = required_min;
if (natural != NULL)
*natural = required_nat;
}
static void
gtk_box_layout_compute_opposite_size (GtkBoxLayout *self,
GtkWidget *widget,
int for_size,
int *minimum,
int *natural,
int *min_baseline,
int *nat_baseline)
{
GtkWidget *child;
int nvis_children;
int nexpand_children;
int computed_minimum = 0, computed_natural = 0;
int computed_minimum_above = 0, computed_natural_above = 0;
int computed_minimum_below = 0, computed_natural_below = 0;
int computed_minimum_baseline = -1, computed_natural_baseline = -1;
GtkRequestedSize *sizes;
int extra_space, size_given_to_child, i;
int children_minimum_size = 0;
int child_size, child_minimum, child_natural;
int child_minimum_baseline, child_natural_baseline;
int n_extra_widgets = 0;
int spacing;
gboolean have_baseline;
count_expand_children (widget, self->orientation, &nvis_children, &nexpand_children);
if (nvis_children <= 0)
return;
spacing = get_spacing (self, _gtk_widget_get_style_context (widget));
sizes = g_newa (GtkRequestedSize, nvis_children);
extra_space = MAX (0, for_size - (nvis_children - 1) * spacing);
/* Retrieve desired size for visible children */
for (i = 0, child = _gtk_widget_get_first_child (widget);
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
if (_gtk_widget_get_visible (child))
{
gtk_widget_measure (child,
self->orientation,
-1,
&sizes[i].minimum_size, &sizes[i].natural_size,
NULL, NULL);
children_minimum_size += sizes[i].minimum_size;
i += 1;
}
}
if (self->homogeneous)
{
/* We still need to run the above loop to populate the minimum sizes for
* children that aren't going to fill.
*/
size_given_to_child = extra_space / nvis_children;
n_extra_widgets = extra_space % nvis_children;
}
else
{
/* Bring children up to size first */
extra_space -= children_minimum_size;
extra_space = MAX (0, extra_space);
extra_space = gtk_distribute_natural_allocation (extra_space, nvis_children, sizes);
/* Calculate space which hasn't distributed yet,
* and is available for expanding children.
*/
if (nexpand_children > 0)
{
size_given_to_child = extra_space / nexpand_children;
n_extra_widgets = extra_space % nexpand_children;
}
else
{
size_given_to_child = 0;
}
}
have_baseline = FALSE;
for (i = 0, child = _gtk_widget_get_first_child (widget);
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
/* If widget is not visible, skip it. */
if (!_gtk_widget_get_visible (child))
continue;
/* Assign the child's size. */
if (self->homogeneous)
{
child_size = size_given_to_child;
if (n_extra_widgets > 0)
{
child_size++;
n_extra_widgets--;
}
}
else
{
child_size = sizes[i].minimum_size;
if (gtk_widget_compute_expand (child, self->orientation))
{
child_size += size_given_to_child;
if (n_extra_widgets > 0)
{
child_size++;
n_extra_widgets--;
}
}
}
child_minimum_baseline = child_natural_baseline = -1;
/* Assign the child's position. */
gtk_widget_measure (child,
OPPOSITE_ORIENTATION (self->orientation),
child_size,
&child_minimum, &child_natural,
&child_minimum_baseline, &child_natural_baseline);
if (child_minimum_baseline >= 0)
{
have_baseline = TRUE;
computed_minimum_below = MAX (computed_minimum_below, child_minimum - child_minimum_baseline);
computed_natural_below = MAX (computed_natural_below, child_natural - child_natural_baseline);
computed_minimum_above = MAX (computed_minimum_above, child_minimum_baseline);
computed_natural_above = MAX (computed_natural_above, child_natural_baseline);
}
else
{
computed_minimum = MAX (computed_minimum, child_minimum);
computed_natural = MAX (computed_natural, child_natural);
}
i += 1;
}
if (have_baseline)
{
computed_minimum = MAX (computed_minimum, computed_minimum_below + computed_minimum_above);
computed_natural = MAX (computed_natural, computed_natural_below + computed_natural_above);
switch (self->baseline_position)
{
case GTK_BASELINE_POSITION_TOP:
computed_minimum_baseline = computed_minimum_above;
computed_natural_baseline = computed_natural_above;
break;
case GTK_BASELINE_POSITION_CENTER:
computed_minimum_baseline = computed_minimum_above + MAX((computed_minimum - (computed_minimum_above + computed_minimum_below)) / 2, 0);
computed_natural_baseline = computed_natural_above + MAX((computed_natural - (computed_natural_above + computed_natural_below)) / 2, 0);
break;
case GTK_BASELINE_POSITION_BOTTOM:
computed_minimum_baseline = computed_minimum - computed_minimum_below;
computed_natural_baseline = computed_natural - computed_natural_below;
break;
default:
break;
}
}
if (minimum != NULL)
*minimum = computed_minimum;
if (natural != NULL)
*natural = MAX (computed_natural, computed_natural_below + computed_natural_above);
if (min_baseline != NULL)
*min_baseline = computed_minimum_baseline;
if (nat_baseline != NULL)
*nat_baseline = computed_natural_baseline;
}
static void
gtk_box_layout_measure (GtkLayoutManager *layout_manager,
GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *min_baseline,
int *nat_baseline)
{
GtkBoxLayout *self = GTK_BOX_LAYOUT (layout_manager);
if (self->orientation != orientation)
{
gtk_box_layout_compute_opposite_size (self, widget, for_size,
minimum, natural,
min_baseline, nat_baseline);
}
else
{
gtk_box_layout_compute_size (self, widget, for_size,
minimum, natural);
}
}
static void
gtk_box_layout_allocate (GtkLayoutManager *layout_manager,
GtkWidget *widget,
int width,
int height,
int baseline)
{
GtkBoxLayout *self = GTK_BOX_LAYOUT (layout_manager);
GtkWidget *child;
gint nvis_children;
gint nexpand_children;
GtkTextDirection direction;
GtkAllocation child_allocation;
GtkRequestedSize *sizes;
gint child_minimum_baseline, child_natural_baseline;
gint minimum_above, natural_above;
gint minimum_below, natural_below;
gboolean have_baseline;
gint extra_space;
gint children_minimum_size = 0;
gint size_given_to_child;
gint n_extra_widgets = 0; /* Number of widgets that receive 1 extra px */
gint x = 0, y = 0, i;
gint child_size;
gint spacing;
count_expand_children (widget, self->orientation, &nvis_children, &nexpand_children);
/* If there is no visible child, simply return. */
if (nvis_children <= 0)
return;
direction = _gtk_widget_get_direction (widget);
sizes = g_newa (GtkRequestedSize, nvis_children);
spacing = get_spacing (self, _gtk_widget_get_style_context (widget));
if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
extra_space = width - (nvis_children - 1) * spacing;
else
extra_space = height - (nvis_children - 1) * spacing;
have_baseline = FALSE;
minimum_above = natural_above = 0;
minimum_below = natural_below = 0;
/* Retrieve desired size for visible children. */
for (i = 0, child = _gtk_widget_get_first_child (widget);
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
if (!_gtk_widget_get_visible (child))
continue;
gtk_widget_measure (child,
self->orientation,
self->orientation == GTK_ORIENTATION_HORIZONTAL ? height : width,
&sizes[i].minimum_size, &sizes[i].natural_size,
NULL, NULL);
children_minimum_size += sizes[i].minimum_size;
sizes[i].data = child;
i++;
}
if (self->homogeneous)
{
/* We still need to run the above loop to populate the minimum sizes for
* children that aren't going to fill.
*/
size_given_to_child = extra_space / nvis_children;
n_extra_widgets = extra_space % nvis_children;
}
else
{
/* Bring children up to size first */
extra_space -= children_minimum_size;
extra_space = MAX (0, extra_space);
extra_space = gtk_distribute_natural_allocation (extra_space, nvis_children, sizes);
/* Calculate space which hasn't distributed yet,
* and is available for expanding children.
*/
if (nexpand_children > 0)
{
size_given_to_child = extra_space / nexpand_children;
n_extra_widgets = extra_space % nexpand_children;
}
else
{
size_given_to_child = 0;
}
}
/* Allocate child sizes. */
for (i = 0, child = _gtk_widget_get_first_child (widget);
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
/* If widget is not visible, skip it. */
if (!_gtk_widget_get_visible (child))
continue;
/* Assign the child's size. */
if (self->homogeneous)
{
child_size = size_given_to_child;
if (n_extra_widgets > 0)
{
child_size++;
n_extra_widgets--;
}
}
else
{
child_size = sizes[i].minimum_size;
if (gtk_widget_compute_expand (child, self->orientation))
{
child_size += size_given_to_child;
if (n_extra_widgets > 0)
{
child_size++;
n_extra_widgets--;
}
}
}
sizes[i].natural_size = child_size;
if (self->orientation == GTK_ORIENTATION_HORIZONTAL &&
gtk_widget_get_valign (child) == GTK_ALIGN_BASELINE)
{
int child_allocation_width;
int child_minimum_height, child_natural_height;
child_allocation_width = child_size;
child_minimum_baseline = -1;
child_natural_baseline = -1;
gtk_widget_measure (child, GTK_ORIENTATION_VERTICAL,
child_allocation_width,
&child_minimum_height, &child_natural_height,
&child_minimum_baseline, &child_natural_baseline);
if (child_minimum_baseline >= 0)
{
have_baseline = TRUE;
minimum_below = MAX (minimum_below, child_minimum_height - child_minimum_baseline);
natural_below = MAX (natural_below, child_natural_height - child_natural_baseline);
minimum_above = MAX (minimum_above, child_minimum_baseline);
natural_above = MAX (natural_above, child_natural_baseline);
}
}
i++;
}
if (self->orientation == GTK_ORIENTATION_VERTICAL)
baseline = -1;
/* we only calculate our own baseline if we don't get one passed from the parent
* and any of the child widgets explicitly request one */
if (baseline == -1 && have_baseline)
{
/* TODO: This is purely based on the minimum baseline, when things fit we should
use the natural one? */
switch (self->baseline_position)
{
case GTK_BASELINE_POSITION_TOP:
baseline = minimum_above;
break;
case GTK_BASELINE_POSITION_CENTER:
baseline = minimum_above + (height - (minimum_above + minimum_below)) / 2;
break;
case GTK_BASELINE_POSITION_BOTTOM:
baseline = height - minimum_below;
break;
default:
break;
}
}
/* Allocate child positions. */
if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
{
child_allocation.y = 0;
child_allocation.height = height;
x = 0;
}
else
{
child_allocation.x = 0;
child_allocation.width = width;
y = 0;
}
for (i = 0, child = _gtk_widget_get_first_child (widget);
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
/* If widget is not visible, skip it. */
if (!_gtk_widget_get_visible (child))
continue;
child_size = sizes[i].natural_size;
/* Assign the child's position. */
if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
{
child_allocation.width = child_size;
child_allocation.x = x;
x += child_size + spacing;
if (direction == GTK_TEXT_DIR_RTL)
child_allocation.x = width - child_allocation.x - child_allocation.width;
}
else /* (self->orientation == GTK_ORIENTATION_VERTICAL) */
{
child_allocation.height = child_size;
child_allocation.y = y;
y += child_size + spacing;
}
gtk_widget_size_allocate (child, &child_allocation, baseline);
i++;
}
}
static void
gtk_box_layout_class_init (GtkBoxLayoutClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GtkLayoutManagerClass *layout_manager_class = GTK_LAYOUT_MANAGER_CLASS (klass);
gobject_class->set_property = gtk_box_layout_set_property;
gobject_class->get_property = gtk_box_layout_get_property;
layout_manager_class->get_request_mode = gtk_box_layout_get_request_mode;
layout_manager_class->measure = gtk_box_layout_measure;
layout_manager_class->allocate = gtk_box_layout_allocate;
/**
* GtkBoxLayout:homogeneous:
*
* Whether the box layout should distribute the available space
* homogeneously among the children of the widget using it as a
* layout manager.
*/
box_layout_props[PROP_HOMOGENEOUS] =
g_param_spec_boolean ("homogeneous",
P_("Homogeneous"),
P_("Distribute space homogeneously"),
FALSE,
GTK_PARAM_READWRITE |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkBoxLayout:spacing:
*
* The space between each child of the widget using the box
* layout as its layout manager.
*/
box_layout_props[PROP_SPACING] =
g_param_spec_int ("spacing",
P_("Spacing"),
P_("Spacing between widgets"),
0, G_MAXINT, 0,
GTK_PARAM_READWRITE |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkBoxLayout:baseline-position:
*
* The position of the allocated baseline within the extra space
* allocated to each child of the widget using a box layout
* manager.
*
* This property is only relevant for horizontal layouts containing
* at least one child with a baseline alignment.
*/
box_layout_props[PROP_BASELINE_POSITION] =
g_param_spec_enum ("baseline-position",
P_("Baseline position"),
P_("The position of the baseline aligned widgets if extra space is available"),
GTK_TYPE_BASELINE_POSITION,
GTK_BASELINE_POSITION_CENTER,
GTK_PARAM_READWRITE |
G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, N_PROPS, box_layout_props);
g_object_class_override_property (gobject_class, PROP_ORIENTATION, "orientation");
}
static void
gtk_box_layout_init (GtkBoxLayout *self)
{
self->homogeneous = FALSE;
self->spacing = 0;
self->orientation = GTK_ORIENTATION_HORIZONTAL;
self->baseline_position = GTK_BASELINE_POSITION_CENTER;
}
GtkLayoutManager *
gtk_box_layout_new (GtkOrientation orientation)
{
return g_object_new (GTK_TYPE_BOX_LAYOUT,
"orientation", orientation,
NULL);
}
void
gtk_box_layout_set_homogeneous (GtkBoxLayout *box_layout,
gboolean homogeneous)
{
g_return_if_fail (GTK_IS_BOX_LAYOUT (box_layout));
homogeneous = !!homogeneous;
if (box_layout->homogeneous == homogeneous)
return;
box_layout->homogeneous = homogeneous;
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (box_layout));
g_object_notify_by_pspec (G_OBJECT (box_layout), box_layout_props[PROP_HOMOGENEOUS]);
}
gboolean
gtk_box_layout_get_homogeneous (GtkBoxLayout *box_layout)
{
g_return_val_if_fail (GTK_IS_BOX_LAYOUT (box_layout), FALSE);
return box_layout->homogeneous;
}
void
gtk_box_layout_set_spacing (GtkBoxLayout *box_layout,
guint spacing)
{
g_return_if_fail (GTK_IS_BOX_LAYOUT (box_layout));
if (box_layout->spacing == spacing)
return;
box_layout->spacing = spacing;
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (box_layout));
g_object_notify_by_pspec (G_OBJECT (box_layout), box_layout_props[PROP_SPACING]);
}
guint
gtk_box_layout_get_spacing (GtkBoxLayout *box_layout)
{
g_return_val_if_fail (GTK_IS_BOX_LAYOUT (box_layout), 0);
return box_layout->spacing;
}
/**
* gtk_box_layout_set_baseline_position:
* @box_layout: a #GtkBoxLayout
* @position: a #GtkBaselinePosition
*
* Sets the baseline position of a box layout.
*
* The baseline position affects only horizontal boxes with at least one
* baseline aligned child. If there is more vertical space available than
* requested, and the baseline is not allocated by the parent then the
* given @position is used to allocate the baseline within the extra
* space available.
*/
void
gtk_box_layout_set_baseline_position (GtkBoxLayout *box_layout,
GtkBaselinePosition position)
{
g_return_if_fail (GTK_IS_BOX_LAYOUT (box_layout));
if (box_layout->baseline_position != position)
{
box_layout->baseline_position = position;
g_object_notify_by_pspec (G_OBJECT (box_layout), box_layout_props[PROP_BASELINE_POSITION]);
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (box_layout));
}
}
/**
* gtk_box_layout_get_baseline_position:
* @box_layout: a #GtkBoxLayout
*
* Gets the value set by gtk_box_layout_set_baseline_position().
*
* Returns: the baseline position
*/
GtkBaselinePosition
gtk_box_layout_get_baseline_position (GtkBoxLayout *box_layout)
{
g_return_val_if_fail (GTK_IS_BOX_LAYOUT (box_layout), GTK_BASELINE_POSITION_CENTER);
return box_layout->baseline_position;
}

54
gtk/gtkboxlayout.h Normal file
View File

@@ -0,0 +1,54 @@
/* gtkboxlayout.h: Box layout manager
*
* Copyright 2019 GNOME Foundation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtkenums.h>
#include <gtk/gtklayoutmanager.h>
G_BEGIN_DECLS
#define GTK_TYPE_BOX_LAYOUT (gtk_box_layout_get_type())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkBoxLayout, gtk_box_layout, GTK, BOX_LAYOUT, GtkLayoutManager)
GDK_AVAILABLE_IN_ALL
GtkLayoutManager * gtk_box_layout_new (GtkOrientation orientation);
GDK_AVAILABLE_IN_ALL
void gtk_box_layout_set_homogeneous (GtkBoxLayout *box_layout,
gboolean homogeneous);
GDK_AVAILABLE_IN_ALL
gboolean gtk_box_layout_get_homogeneous (GtkBoxLayout *box_layout);
GDK_AVAILABLE_IN_ALL
void gtk_box_layout_set_spacing (GtkBoxLayout *box_layout,
guint spacing);
GDK_AVAILABLE_IN_ALL
guint gtk_box_layout_get_spacing (GtkBoxLayout *box_layout);
GDK_AVAILABLE_IN_ALL
void gtk_box_layout_set_baseline_position (GtkBoxLayout *box_layout,
GtkBaselinePosition position);
GDK_AVAILABLE_IN_ALL
GtkBaselinePosition gtk_box_layout_get_baseline_position (GtkBoxLayout *box_layout);
G_END_DECLS

130
gtk/gtkcustomlayout.c Normal file
View File

@@ -0,0 +1,130 @@
/**
* SECTION:gtkcustomlayout
* @Title: GtkCustomLayout
* @Short_description: A convenience layout manager
*
* #GtkCustomLayout is a convenience type meant to be used as a transition
* mechanism between #GtkContainers implementing a layout policy, and
* #GtkLayoutManager classes.
*
* A #GtkCustomLayout uses closures matching to the old #GtkWidget virtual
* functions for size negotiation, as a convenience API to ease the porting
* towards the corresponding #GtkLayoutManager virtual functions.
*/
#include "config.h"
#include "gtkcustomlayout.h"
struct _GtkCustomLayout
{
GtkLayoutManager parent_instance;
GtkCustomRequestModeFunc request_mode_func;
GtkCustomMeasureFunc measure_func;
GtkCustomAllocateFunc allocate_func;
};
G_DEFINE_TYPE (GtkCustomLayout, gtk_custom_layout, GTK_TYPE_LAYOUT_MANAGER)
static GtkSizeRequestMode
gtk_custom_layout_get_request_mode (GtkLayoutManager *manager,
GtkWidget *widget)
{
GtkCustomLayout *self = GTK_CUSTOM_LAYOUT (manager);
if (self->request_mode_func != NULL)
return self->request_mode_func (widget);
return GTK_SIZE_REQUEST_CONSTANT_SIZE;
}
static void
gtk_custom_layout_measure (GtkLayoutManager *manager,
GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkCustomLayout *self = GTK_CUSTOM_LAYOUT (manager);
int min = 0, nat = 0;
int min_baseline = -1, nat_baseline = -1;
self->measure_func (widget, orientation, for_size,
&min, &nat,
&min_baseline, &nat_baseline);
if (minimum != NULL)
*minimum = min;
if (natural != NULL)
*natural = nat;
if (minimum_baseline != NULL)
*minimum_baseline = min_baseline;
if (natural_baseline != NULL)
*natural_baseline = nat_baseline;
}
static void
gtk_custom_layout_allocate (GtkLayoutManager *manager,
GtkWidget *widget,
int width,
int height,
int baseline)
{
GtkCustomLayout *self = GTK_CUSTOM_LAYOUT (manager);
self->allocate_func (widget, width, height, baseline);
}
static void
gtk_custom_layout_class_init (GtkCustomLayoutClass *klass)
{
GtkLayoutManagerClass *layout_class = GTK_LAYOUT_MANAGER_CLASS (klass);
layout_class->get_request_mode = gtk_custom_layout_get_request_mode;
layout_class->measure = gtk_custom_layout_measure;
layout_class->allocate = gtk_custom_layout_allocate;
}
static void
gtk_custom_layout_init (GtkCustomLayout *self)
{
}
/**
* gtk_custom_layout_new:
* @request_mode: (nullable): a function to retrieve
* the #GtkSizeRequestMode of the widget using the layout; the
* default request mode is %GTK_SIZE_REQUEST_CONSTANT_SIZE
* @measure: a function to measure the widget using the layout manager
* @allocate: a function to allocate the children of the widget using
* the layout manager
*
* Creates a new legacy layout manager.
*
* Legacy layout managers map to the old #GtkWidget size negotiation
* virtual functions, and are meant to be used during the transition
* from layout containers to layout manager delegates.
*
* Returns: (transfer full): the newly created #GtkCustomLayout
*/
GtkLayoutManager *
gtk_custom_layout_new (GtkCustomRequestModeFunc request_mode,
GtkCustomMeasureFunc measure,
GtkCustomAllocateFunc allocate)
{
GtkCustomLayout *self = g_object_new (GTK_TYPE_CUSTOM_LAYOUT, NULL);
g_return_val_if_fail (measure != NULL, NULL);
g_return_val_if_fail (allocate != NULL, NULL);
self->request_mode_func = request_mode;
self->measure_func = measure;
self->allocate_func = allocate;
return GTK_LAYOUT_MANAGER (self);
}

77
gtk/gtkcustomlayout.h Normal file
View File

@@ -0,0 +1,77 @@
/* gtkcustomlayout.h: Simple layout manager
* Copyright 2019 GNOME Foundation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <gtk/gtklayoutmanager.h>
G_BEGIN_DECLS
#define GTK_TYPE_CUSTOM_LAYOUT (gtk_custom_layout_get_type ())
/**
* GtkCustomRequestModeFunc:
* @widget: the widget to be queried
*
* Queries a widget for its preferred size request mode.
*
* Returns: the size request mode
*/
typedef GtkSizeRequestMode (* GtkCustomRequestModeFunc) (GtkWidget *widget);
/**
* GtkCustomMeasureFunc:
* @widget: the widget to be measured
* @orientation: the direction to be measured
* @for_size: the size to be measured for
* @minimum: (out): the measured minimum size of the widget
* @natural: (out): the measured natural size of the widget
* @minimum_baseline: (out): the measured minimum baseline of the widget
* @natural_baseline: (out): the measured natural baseline of the widget
*
* A function to be used by #GtkCustomLayout to measure a widget.
*/
typedef void (* GtkCustomMeasureFunc) (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline);
/**
* GtkCustomAllocateFunc:
* @widget: the widget to allocate
* @width: the new width of the widget
* @height: the new height of the widget
* @baseline: the new baseline of the widget, or -1
*
* A function to be used by #GtkCustomLayout to allocate a widget.
*/
typedef void (* GtkCustomAllocateFunc) (GtkWidget *widget,
int width,
int height,
int baseline);
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkCustomLayout, gtk_custom_layout, GTK, CUSTOM_LAYOUT, GtkLayoutManager)
GDK_AVAILABLE_IN_ALL
GtkLayoutManager * gtk_custom_layout_new (GtkCustomRequestModeFunc request_mode,
GtkCustomMeasureFunc measure,
GtkCustomAllocateFunc allocate);
G_END_DECLS

View File

@@ -57,7 +57,7 @@
*/
typedef struct _GtkLayoutChild GtkLayoutChild;
typedef struct _LayoutChild LayoutChild;
typedef struct
{
@@ -77,7 +77,7 @@ typedef struct
GList *children;
} GtkLayoutPrivate;
struct _GtkLayoutChild {
struct _LayoutChild {
GtkWidget *widget;
gint x;
gint y;
@@ -298,7 +298,7 @@ gtk_layout_set_vadjustment (GtkLayout *layout,
g_object_notify (G_OBJECT (layout), "vadjustment");
}
static GtkLayoutChild*
static LayoutChild *
get_child (GtkLayout *layout,
GtkWidget *widget)
{
@@ -308,7 +308,7 @@ get_child (GtkLayout *layout,
children = priv->children;
while (children)
{
GtkLayoutChild *child;
LayoutChild *child;
child = children->data;
children = children->next;
@@ -338,12 +338,12 @@ gtk_layout_put (GtkLayout *layout,
gint y)
{
GtkLayoutPrivate *priv = gtk_layout_get_instance_private (layout);
GtkLayoutChild *child;
LayoutChild *child;
g_return_if_fail (GTK_IS_LAYOUT (layout));
g_return_if_fail (GTK_IS_WIDGET (child_widget));
child = g_new (GtkLayoutChild, 1);
child = g_new (LayoutChild, 1);
child->widget = child_widget;
child->x = x;
@@ -362,7 +362,7 @@ gtk_layout_move_internal (GtkLayout *layout,
gboolean change_y,
gint y)
{
GtkLayoutChild *child;
LayoutChild *child;
child = get_child (layout, widget);
@@ -660,7 +660,7 @@ gtk_layout_get_child_property (GtkContainer *container,
GValue *value,
GParamSpec *pspec)
{
GtkLayoutChild *layout_child;
LayoutChild *layout_child;
layout_child = get_child (GTK_LAYOUT (container), child);
@@ -729,7 +729,7 @@ gtk_layout_size_allocate (GtkWidget *widget,
while (tmp_list)
{
GtkLayoutChild *child = tmp_list->data;
LayoutChild *child = tmp_list->data;
GtkAllocation allocation;
GtkRequisition requisition;
@@ -765,7 +765,7 @@ gtk_layout_remove (GtkContainer *container,
GtkLayout *layout = GTK_LAYOUT (container);
GtkLayoutPrivate *priv = gtk_layout_get_instance_private (layout);
GList *tmp_list;
GtkLayoutChild *child = NULL;
LayoutChild *child = NULL;
tmp_list = priv->children;
while (tmp_list)
@@ -793,7 +793,7 @@ gtk_layout_forall (GtkContainer *container,
{
GtkLayout *layout = GTK_LAYOUT (container);
GtkLayoutPrivate *priv = gtk_layout_get_instance_private (layout);
GtkLayoutChild *child;
LayoutChild *child;
GList *tmp_list;
tmp_list = priv->children;

189
gtk/gtklayoutchild.c Normal file
View File

@@ -0,0 +1,189 @@
#include "config.h"
#include "gtklayoutchild.h"
#include "gtklayoutmanager.h"
#include "gtkprivate.h"
/**
* SECTION:gtklayoutchild
* @Title: GtkLayoutChild
* @Short_description: An object containing layout properties
*
* #GtkLayoutChild is the base class for objects that are meant to hold
* layout properties. If a #GtkLayoutManager has per-child properties,
* like their packing type, or the horizontal and vertical span, or the
* icon name, then the layout manager should use a #GtkLayoutChild
* implementation to store those properties.
*
* A #GtkLayoutChild instance is only ever valid while a widget is part
* of a layout.
*/
typedef struct {
GtkLayoutManager *manager;
GtkWidget *widget;
} GtkLayoutChildPrivate;
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GtkLayoutChild, gtk_layout_child, G_TYPE_OBJECT)
enum {
PROP_LAYOUT_MANAGER = 1,
PROP_CHILD_WIDGET,
N_PROPS
};
static GParamSpec *layout_child_properties[N_PROPS];
static void
gtk_layout_child_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkLayoutChild *layout_child = GTK_LAYOUT_CHILD (gobject);
GtkLayoutChildPrivate *priv = gtk_layout_child_get_instance_private (layout_child);
switch (prop_id)
{
case PROP_LAYOUT_MANAGER:
priv->manager = g_value_get_object (value);
break;
case PROP_CHILD_WIDGET:
priv->widget = g_value_get_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
}
static void
gtk_layout_child_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkLayoutChild *layout_child = GTK_LAYOUT_CHILD (gobject);
GtkLayoutChildPrivate *priv = gtk_layout_child_get_instance_private (layout_child);
switch (prop_id)
{
case PROP_LAYOUT_MANAGER:
g_value_set_object (value, priv->manager);
break;
case PROP_CHILD_WIDGET:
g_value_set_object (value, priv->widget);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
}
static void
gtk_layout_child_constructed (GObject *gobject)
{
GtkLayoutChild *layout_child = GTK_LAYOUT_CHILD (gobject);
GtkLayoutChildPrivate *priv = gtk_layout_child_get_instance_private (layout_child);
G_OBJECT_CLASS (gtk_layout_child_parent_class)->constructed (gobject);
if (priv->manager == NULL)
{
g_critical ("The layout child of type %s does not have "
"the GtkLayoutChild:layout-manager property set",
G_OBJECT_TYPE_NAME (gobject));
return;
}
if (priv->widget == NULL)
{
g_critical ("The layout child of type %s does not have "
"the GtkLayoutChild:child-widget property set",
G_OBJECT_TYPE_NAME (gobject));
return;
}
}
static void
gtk_layout_child_class_init (GtkLayoutChildClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->set_property = gtk_layout_child_set_property;
gobject_class->get_property = gtk_layout_child_get_property;
gobject_class->constructed = gtk_layout_child_constructed;
/**
* GtkLayoutChild:layout-manager:
*
* The layout manager that created the #GtkLayoutChild instance.
*/
layout_child_properties[PROP_LAYOUT_MANAGER] =
g_param_spec_object ("layout-manager",
"Layout Manager",
"The layout manager that created this object",
GTK_TYPE_LAYOUT_MANAGER,
GTK_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY);
/**
* GtkLayoutChild:child-widget:
*
* The widget that is associated to the #GtkLayoutChild instance.
*/
layout_child_properties[PROP_CHILD_WIDGET] =
g_param_spec_object ("child-widget",
"Child Widget",
"The child widget that is associated to this object",
GTK_TYPE_WIDGET,
GTK_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_properties (gobject_class, N_PROPS, layout_child_properties);
}
static void
gtk_layout_child_init (GtkLayoutChild *self)
{
}
/**
* gtk_layout_child_get_layout_manager:
* @layout_child: a #GtkLayoutChild
*
* Retrieves the #GtkLayoutManager instance that created the
* given @layout_child.
*
* Returns: (transfer none): a #GtkLayoutManager
*/
GtkLayoutManager *
gtk_layout_child_get_layout_manager (GtkLayoutChild *layout_child)
{
GtkLayoutChildPrivate *priv = gtk_layout_child_get_instance_private (layout_child);
g_return_val_if_fail (GTK_IS_LAYOUT_CHILD (layout_child), NULL);
return priv->manager;
}
/**
* gtk_layout_child_get_child_widget:
* @layout_child: a #GtkLayoutChild
*
* Retrieves the #GtkWidget associated to the given @layout_child.
*
* Returns: (transfer none): a #GtkWidget
*/
GtkWidget *
gtk_layout_child_get_child_widget (GtkLayoutChild *layout_child)
{
GtkLayoutChildPrivate *priv = gtk_layout_child_get_instance_private (layout_child);
g_return_val_if_fail (GTK_IS_LAYOUT_CHILD (layout_child), NULL);
return priv->widget;
}

27
gtk/gtklayoutchild.h Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtktypes.h>
G_BEGIN_DECLS
#define GTK_TYPE_LAYOUT_CHILD (gtk_layout_child_get_type())
GDK_AVAILABLE_IN_ALL
G_DECLARE_DERIVABLE_TYPE (GtkLayoutChild, gtk_layout_child, GTK, LAYOUT_CHILD, GObject)
struct _GtkLayoutChildClass
{
/*< private >*/
GObjectClass parent_class;
};
GDK_AVAILABLE_IN_ALL
GtkLayoutManager * gtk_layout_child_get_layout_manager (GtkLayoutChild *layout_child);
GDK_AVAILABLE_IN_ALL
GtkWidget * gtk_layout_child_get_child_widget (GtkLayoutChild *layout_child);
G_END_DECLS

380
gtk/gtklayoutmanager.c Normal file
View File

@@ -0,0 +1,380 @@
/* gtklayoutmanager.c: Layout manager base class
* Copyright 2018 The GNOME Foundation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Author: Emmanuele Bassi
*/
/**
* SECTION:gtklayoutmanager
* @Title: GtkLayoutManager
* @Short_description: Base class for layout manager
*
* Layout managers are delegate classes that handle the preferred size
* and the allocation of a container widget.
*
* You typically subclass #GtkLayoutManager if you want to implement a
* layout policy for the children of a widget, or if you want to determine
* the size of a widget depending on its contents.
*
* Each #GtkWidget can only have a #GtkLayoutManager instance associated to it
* at any given time; it is possible, though, to replace the layout manager
* instance using gtk_widget_set_layout_manager().
*/
#include "config.h"
#include "gtklayoutmanagerprivate.h"
#include "gtklayoutchild.h"
#include "gtkwidget.h"
#ifdef G_ENABLE_DEBUG
#define LAYOUT_MANAGER_WARN_NOT_IMPLEMENTED(m,method) G_STMT_START { \
GObject *_obj = G_OBJECT (m); \
g_warning ("Layout managers of type %s do not implement " \
"the GtkLayoutManager::%s method", \
G_OBJECT_TYPE_NAME (_obj), \
#method); } G_STMT_END
#else
#define LAYOUT_MANAGER_WARN_NOT_IMPLEMENTED(m,method)
#endif
typedef struct {
GtkWidget *widget;
/* HashTable<Widget, LayoutChild> */
GHashTable *layout_children;
} GtkLayoutManagerPrivate;
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GtkLayoutManager, gtk_layout_manager, G_TYPE_OBJECT)
static GQuark quark_layout_child;
static GtkSizeRequestMode
gtk_layout_manager_real_get_request_mode (GtkLayoutManager *manager,
GtkWidget *widget)
{
LAYOUT_MANAGER_WARN_NOT_IMPLEMENTED (manager, get_request_mode);
return GTK_SIZE_REQUEST_CONSTANT_SIZE;
}
static void
gtk_layout_manager_real_measure (GtkLayoutManager *manager,
GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *baseline_minimum,
int *baseline_natural)
{
LAYOUT_MANAGER_WARN_NOT_IMPLEMENTED (manager, measure);
if (minimum != NULL)
*minimum = 0;
if (natural != NULL)
*natural = 0;
if (baseline_minimum != NULL)
*baseline_minimum = 0;
if (baseline_natural != NULL)
*baseline_natural = 0;
}
static void
gtk_layout_manager_real_allocate (GtkLayoutManager *manager,
GtkWidget *widget,
int width,
int height,
int baseline)
{
LAYOUT_MANAGER_WARN_NOT_IMPLEMENTED (manager, allocate);
}
static void
gtk_layout_manager_class_init (GtkLayoutManagerClass *klass)
{
klass->get_request_mode = gtk_layout_manager_real_get_request_mode;
klass->measure = gtk_layout_manager_real_measure;
klass->allocate = gtk_layout_manager_real_allocate;
quark_layout_child = g_quark_from_static_string ("-GtkLayoutManager-layout-child");
}
static void
gtk_layout_manager_init (GtkLayoutManager *self)
{
}
/*< private >
* gtk_layout_manager_set_widget:
* @layout_manager: a #GtkLayoutManager
* @widget: (nullable): a #GtkWidget
*
* Sets a back pointer from @widget to @layout_manager.
*/
void
gtk_layout_manager_set_widget (GtkLayoutManager *layout_manager,
GtkWidget *widget)
{
GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (layout_manager);
if (widget != NULL && priv->widget != NULL)
{
g_critical ("The layout manager %p of type %s is already in use "
"by widget %p '%s', and cannot be used by widget %p '%s'",
layout_manager, G_OBJECT_TYPE_NAME (layout_manager),
priv->widget, gtk_widget_get_name (priv->widget),
widget, gtk_widget_get_name (widget));
return;
}
priv->widget = widget;
}
/**
* gtk_layout_manager_measure:
* @manager: a #GtkLayoutManager
* @widget: the #GtkWidget using @manager
* @orientation: the orientation to measure
* @for_size: Size for the opposite of @orientation; for instance, if
* the @orientation is %GTK_ORIENTATION_HORIZONTAL, this is the height
* of the widget; if the @orientation is %GTK_ORIENTATION_VERTICAL, this
* is the width of the widget. This allows to measure the height for the
* given width, and the width for the given height. Use -1 if the size
* is not known
* @minimum: (out) (optional): the minimum size for the given size and
* orientation
* @natural: (out) (optional): the natural, or preferred size for the
* given size and orientation
* @minimum_baseline: (out) (optional): the baseline position for the
* minimum size
* @natural_baseline: (out) (optional): the baseline position for the
* natural size
*
* Measures the size of the @widget using @manager, for the
* given @orientation and size.
*
* See [GtkWidget's geometry management section][geometry-management] for
* more details.
*/
void
gtk_layout_manager_measure (GtkLayoutManager *manager,
GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkLayoutManagerClass *klass;
g_return_if_fail (GTK_IS_LAYOUT_MANAGER (manager));
g_return_if_fail (GTK_IS_WIDGET (widget));
klass = GTK_LAYOUT_MANAGER_GET_CLASS (manager);
klass->measure (manager, widget, orientation,
for_size,
minimum, natural,
minimum_baseline, natural_baseline);
}
/**
* gtk_layout_manager_allocate:
* @manager: a #GtkLayoutManager
* @widget: the #GtkWidget using @manager
* @width: the new width of the @widget
* @height: the new height of the @widget
* @baseline: the baseline position of the @widget, or -1
*
* This function assigns the given @width, @height, and @baseline to
* a @widget, and computes the position and sizes of the children of
* the @widget using the layout management policy of @manager.
*/
void
gtk_layout_manager_allocate (GtkLayoutManager *manager,
GtkWidget *widget,
int width,
int height,
int baseline)
{
GtkLayoutManagerClass *klass;
g_return_if_fail (GTK_IS_LAYOUT_MANAGER (manager));
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (baseline >= -1);
klass = GTK_LAYOUT_MANAGER_GET_CLASS (manager);
klass->allocate (manager, widget, width, height, baseline);
}
/**
* gtk_layout_manager_get_request_mode:
* @manager: a #GtkLayoutManager
*
* Retrieves the request mode of @manager.
*
* Returns: a #GtkSizeRequestMode
*/
GtkSizeRequestMode
gtk_layout_manager_get_request_mode (GtkLayoutManager *manager)
{
GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (manager);
GtkLayoutManagerClass *klass;
g_return_val_if_fail (GTK_IS_LAYOUT_MANAGER (manager), GTK_SIZE_REQUEST_CONSTANT_SIZE);
klass = GTK_LAYOUT_MANAGER_GET_CLASS (manager);
return klass->get_request_mode (manager, priv->widget);
}
/**
* gtk_layout_manager_get_widget:
* @manager: a #GtkLayoutManager
*
* Retrieves the #GtkWidget using the given #GtkLayoutManager.
*
* Returns: (transfer none) (nullable): a #GtkWidget
*/
GtkWidget *
gtk_layout_manager_get_widget (GtkLayoutManager *manager)
{
GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (manager);
g_return_val_if_fail (GTK_IS_LAYOUT_MANAGER (manager), NULL);
return priv->widget;
}
/**
* gtk_layout_manager_layout_changed:
* @manager: a #GtkLayoutManager
*
* Queues a resize on the #GtkWidget using @manager, if any.
*
* This function should be called by subclasses of #GtkLayoutManager in
* response to changes to their layout management policies.
*/
void
gtk_layout_manager_layout_changed (GtkLayoutManager *manager)
{
GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (manager);
g_return_if_fail (GTK_IS_LAYOUT_MANAGER (manager));
if (priv->widget != NULL)
gtk_widget_queue_resize (priv->widget);
}
static void
remove_layout_child (GtkWidget *widget,
GtkWidget *old_parent,
GtkLayoutManager *self)
{
GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (self);
if (priv->layout_children != NULL)
{
g_hash_table_remove (priv->layout_children, widget);
if (g_hash_table_size (priv->layout_children) == 0)
g_clear_pointer (&priv->layout_children, g_hash_table_unref);
}
g_signal_handlers_disconnect_by_func (widget, remove_layout_child, self);
}
/**
* gtk_layout_manager_get_layout_child:
* @manager: a #GtkLayoutManager
* @child: a #GtkWidget
*
* Retrieves a #GtkLayoutChild instance for the #GtkLayoutManager, creating
* one if necessary.
*
* The @child widget must be a child of the widget using @manager.
*
* The #GtkLayoutChild instance is owned by the #GtkLayoutManager, and is
* guaranteed to exist as long as @child is a child of the #GtkWidget using
* the given #GtkLayoutManager.
*
* Returns: (transfer none): a #GtkLayoutChild
*/
GtkLayoutChild *
gtk_layout_manager_get_layout_child (GtkLayoutManager *manager,
GtkWidget *child)
{
GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (manager);
GtkLayoutChild *res;
GtkWidget *parent;
g_return_val_if_fail (GTK_IS_LAYOUT_MANAGER (manager), NULL);
g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
parent = gtk_widget_get_parent (child);
g_return_val_if_fail (parent != NULL, NULL);
if (priv->widget != parent)
{
g_critical ("The parent %s %p of the widget %s %p does not "
"use the given layout manager of type %s %p",
gtk_widget_get_name (parent), parent,
gtk_widget_get_name (child), child,
G_OBJECT_TYPE_NAME (manager), manager);
return NULL;
}
if (GTK_LAYOUT_MANAGER_GET_CLASS (manager)->create_layout_child == NULL)
{
g_critical ("The layout manager of type %s %p does not create "
"GtkLayoutChild instances",
G_OBJECT_TYPE_NAME (manager), manager);
return NULL;
}
if (priv->layout_children == NULL)
{
priv->layout_children = g_hash_table_new_full (NULL, NULL,
NULL,
(GDestroyNotify) g_object_unref);
}
res = g_hash_table_lookup (priv->layout_children, child);
if (res != NULL)
{
/* If the LayoutChild instance is stale, and refers to another
* layout manager, then we simply ask the LayoutManager to
* replace it, as it means the layout manager for the parent
* widget was replaced
*/
if (gtk_layout_child_get_layout_manager (res) == manager)
return res;
}
res = GTK_LAYOUT_MANAGER_GET_CLASS (manager)->create_layout_child (manager, parent, child);
g_assert (res != NULL);
g_assert (g_type_is_a (G_OBJECT_TYPE (res), GTK_TYPE_LAYOUT_CHILD));
g_hash_table_insert (priv->layout_children, child, res);
g_signal_connect (child, "parent-set", G_CALLBACK (remove_layout_child), manager);
return res;
}

109
gtk/gtklayoutmanager.h Normal file
View File

@@ -0,0 +1,109 @@
/* gtklayoutmanager.h: Layout manager base class
* Copyright 2019 The GNOME Foundation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Author: Emmanuele Bassi
*/
#pragma once
#include <gsk/gsk.h>
#include <gtk/gtktypes.h>
#include <gtk/gtkwidget.h>
#include <gtk/gtklayoutchild.h>
G_BEGIN_DECLS
#define GTK_TYPE_LAYOUT_MANAGER (gtk_layout_manager_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_DERIVABLE_TYPE (GtkLayoutManager, gtk_layout_manager, GTK, LAYOUT_MANAGER, GObject)
/**
* GtkLayoutManagerClass:
* @get_request_mode: a virtual function, used to return the preferred
* request mode for the layout manager; for instance, "width for height"
* or "height for width"; see #GtkSizeRequestMode
* @measure: a virtual function, used to measure the minimum and preferred
* sizes of the widget using the layout manager for a given orientation
* @allocate: a virtual function, used to allocate the size of the widget
* using the layout manager
* @create_layout_child: a virtual function, used to create a #GtkLayoutChild
* meta object for the layout properties
*
* The `GtkLayoutManagerClass` structure contains only private data, and
* should only be accessed through the provided API, or when subclassing
* #GtkLayoutManager.
*/
struct _GtkLayoutManagerClass
{
/*< private >*/
GObjectClass parent_class;
/*< public >*/
GtkSizeRequestMode (* get_request_mode) (GtkLayoutManager *manager,
GtkWidget *widget);
void (* measure) (GtkLayoutManager *manager,
GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline);
void (* allocate) (GtkLayoutManager *manager,
GtkWidget *widget,
int width,
int height,
int baseline);
GtkLayoutChild * (* create_layout_child) (GtkLayoutManager *manager,
GtkWidget *widget,
GtkWidget *for_child);
/*< private >*/
gpointer _padding[16];
};
GDK_AVAILABLE_IN_ALL
void gtk_layout_manager_measure (GtkLayoutManager *manager,
GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline);
GDK_AVAILABLE_IN_ALL
void gtk_layout_manager_allocate (GtkLayoutManager *manager,
GtkWidget *widget,
int width,
int height,
int baseline);
GDK_AVAILABLE_IN_ALL
GtkSizeRequestMode gtk_layout_manager_get_request_mode (GtkLayoutManager *manager);
GDK_AVAILABLE_IN_ALL
GtkWidget * gtk_layout_manager_get_widget (GtkLayoutManager *manager);
GDK_AVAILABLE_IN_ALL
void gtk_layout_manager_layout_changed (GtkLayoutManager *manager);
GDK_AVAILABLE_IN_ALL
GtkLayoutChild * gtk_layout_manager_get_layout_child (GtkLayoutManager *manager,
GtkWidget *child);
G_END_DECLS

View File

@@ -0,0 +1,10 @@
#pragma once
#include "gtklayoutmanager.h"
G_BEGIN_DECLS
void gtk_layout_manager_set_widget (GtkLayoutManager *manager,
GtkWidget *widget);
G_END_DECLS

View File

@@ -31,6 +31,7 @@
#include "gtkwidgetprivate.h"
#include "gtkcssnodeprivate.h"
#include "gtkcssnumbervalueprivate.h"
#include "gtklayoutmanagerprivate.h"
#ifdef G_ENABLE_CONSISTENCY_CHECKS
@@ -195,45 +196,94 @@ gtk_widget_query_size_for_orientation (GtkWidget *widget,
css_min_for_size = get_number_ceil (style, GTK_CSS_PROPERTY_MIN_WIDTH);
}
if (for_size < 0)
GtkLayoutManager *layout_manager = gtk_widget_get_layout_manager (widget);
if (layout_manager != NULL)
{
push_recursion_check (widget, orientation);
widget_class->measure (widget, orientation, -1,
&reported_min_size, &reported_nat_size,
&min_baseline, &nat_baseline);
pop_recursion_check (widget, orientation);
if (for_size < 0)
{
push_recursion_check (widget, orientation);
gtk_layout_manager_measure (layout_manager, widget,
orientation, -1,
&reported_min_size, &reported_nat_size,
&min_baseline, &nat_baseline);
pop_recursion_check (widget, orientation);
}
else
{
int adjusted_for_size;
int minimum_for_size = 0;
int natural_for_size = 0;
int dummy = 0;
/* Pull the minimum for_size from the cache as it's needed to adjust
* the proposed 'for_size' */
gtk_layout_manager_measure (layout_manager, widget,
OPPOSITE_ORIENTATION (orientation), -1,
&minimum_for_size, &natural_for_size,
NULL, NULL);
if (for_size < MAX (minimum_for_size, css_min_for_size))
for_size = MAX (minimum_for_size, css_min_for_size);
adjusted_for_size = for_size;
gtk_widget_adjust_size_allocation (widget, OPPOSITE_ORIENTATION (orientation),
&for_size, &natural_for_size,
&dummy, &adjusted_for_size);
adjusted_for_size -= css_extra_for_size;
if (adjusted_for_size < 0)
adjusted_for_size = MAX (minimum_for_size, css_min_for_size);
push_recursion_check (widget, orientation);
gtk_layout_manager_measure (layout_manager, widget,
orientation,
adjusted_for_size,
&reported_min_size, &reported_nat_size,
&min_baseline, &nat_baseline);
pop_recursion_check (widget, orientation);
}
}
else
{
int adjusted_for_size;
int minimum_for_size = 0;
int natural_for_size = 0;
int dummy = 0;
if (for_size < 0)
{
push_recursion_check (widget, orientation);
widget_class->measure (widget, orientation, -1,
&reported_min_size, &reported_nat_size,
&min_baseline, &nat_baseline);
pop_recursion_check (widget, orientation);
}
else
{
int adjusted_for_size;
int minimum_for_size = 0;
int natural_for_size = 0;
int dummy = 0;
/* Pull the minimum for_size from the cache as it's needed to adjust
* the proposed 'for_size' */
gtk_widget_measure (widget, OPPOSITE_ORIENTATION (orientation), -1,
&minimum_for_size, &natural_for_size, NULL, NULL);
/* Pull the minimum for_size from the cache as it's needed to adjust
* the proposed 'for_size' */
gtk_widget_measure (widget, OPPOSITE_ORIENTATION (orientation), -1,
&minimum_for_size, &natural_for_size, NULL, NULL);
/* TODO: Warn if the given for_size is too small? */
if (for_size < MAX (minimum_for_size, css_min_for_size))
for_size = MAX (minimum_for_size, css_min_for_size);
/* TODO: Warn if the given for_size is too small? */
if (for_size < MAX (minimum_for_size, css_min_for_size))
for_size = MAX (minimum_for_size, css_min_for_size);
adjusted_for_size = for_size;
gtk_widget_adjust_size_allocation (widget, OPPOSITE_ORIENTATION (orientation),
&for_size, &natural_for_size,
&dummy, &adjusted_for_size);
adjusted_for_size = for_size;
gtk_widget_adjust_size_allocation (widget, OPPOSITE_ORIENTATION (orientation),
&for_size, &natural_for_size,
&dummy, &adjusted_for_size);
adjusted_for_size -= css_extra_for_size;
push_recursion_check (widget, orientation);
widget_class->measure (widget,
orientation,
adjusted_for_size,
&reported_min_size, &reported_nat_size,
&min_baseline, &nat_baseline);
pop_recursion_check (widget, orientation);
adjusted_for_size -= css_extra_for_size;
push_recursion_check (widget, orientation);
widget_class->measure (widget,
orientation,
adjusted_for_size,
&reported_min_size, &reported_nat_size,
&min_baseline, &nat_baseline);
pop_recursion_check (widget, orientation);
}
}
min_size = MAX (0, MAX (reported_min_size, css_min_size)) + css_extra_size;
@@ -512,7 +562,13 @@ gtk_widget_get_request_mode (GtkWidget *widget)
if (G_UNLIKELY (!cache->request_mode_valid))
{
cache->request_mode = GTK_WIDGET_GET_CLASS (widget)->get_request_mode (widget);
GtkLayoutManager *layout_manager = gtk_widget_get_layout_manager (widget);
if (layout_manager != NULL)
cache->request_mode = gtk_layout_manager_get_request_mode (layout_manager);
else
cache->request_mode = GTK_WIDGET_GET_CLASS (widget)->get_request_mode (widget);
cache->request_mode_valid = TRUE;
}

View File

@@ -61,6 +61,7 @@
#include "gtkgizmoprivate.h"
#include "gtkintl.h"
#include "gtkimage.h"
#include "gtkcustomlayout.h"
#include "gtkmarshalers.h"
#include "gtkprivate.h"
#include "gtkprogresstrackerprivate.h"
@@ -319,10 +320,10 @@ gtk_switch_measure (GtkWidget *widget,
}
static void
gtk_switch_size_allocate (GtkWidget *widget,
int width,
int height,
int baseline)
gtk_switch_allocate (GtkWidget *widget,
int width,
int height,
int baseline)
{
GtkSwitch *self = GTK_SWITCH (widget);
GtkSwitchPrivate *priv = gtk_switch_get_instance_private (self);
@@ -544,9 +545,6 @@ gtk_switch_class_init (GtkSwitchClass *klass)
g_object_class_install_properties (gobject_class, LAST_PROP, switch_props);
widget_class->measure = gtk_switch_measure;
widget_class->size_allocate = gtk_switch_size_allocate;
klass->activate = gtk_switch_activate;
klass->state_set = state_set;
@@ -613,6 +611,7 @@ static void
gtk_switch_init (GtkSwitch *self)
{
GtkSwitchPrivate *priv = gtk_switch_get_instance_private (self);
GtkLayoutManager *layout;
GtkGesture *gesture;
gtk_widget_set_has_surface (GTK_WIDGET (self), FALSE);
@@ -642,6 +641,10 @@ gtk_switch_init (GtkSwitch *self)
gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
priv->pan_gesture = gesture;
layout = gtk_custom_layout_new (NULL,
gtk_switch_measure,
gtk_switch_allocate);
gtk_widget_set_layout_manager (GTK_WIDGET (self), layout);
priv->on_image = gtk_image_new_from_icon_name ("switch-on-symbolic");
gtk_widget_set_parent (priv->on_image, GTK_WIDGET (self));

View File

@@ -38,6 +38,7 @@ typedef struct _GtkBuilder GtkBuilder;
typedef struct _GtkClipboard GtkClipboard;
typedef struct _GtkEventController GtkEventController;
typedef struct _GtkGesture GtkGesture;
typedef struct _GtkLayoutManager GtkLayoutManager;
typedef struct _GtkRequisition GtkRequisition;
typedef struct _GtkRoot GtkRoot;
typedef struct _GtkSelectionData GtkSelectionData;

View File

@@ -48,6 +48,7 @@
#include "gtkgesturesingle.h"
#include "gtkgestureswipe.h"
#include "gtkintl.h"
#include "gtklayoutmanagerprivate.h"
#include "gtkmain.h"
#include "gtkmarshalers.h"
#include "gtkmenu.h"
@@ -283,6 +284,35 @@
* </object>
* ]|
*
* If the parent widget uses a #GtkLayoutManager, #GtkWidget supports a
* custom <layout> element, used to define layout properties:
*
* |[
* <object class="MyGrid" id="grid1">
* <child>
* <object class="GtkLabel" id="label1">
* <property name="label">Description</property>
* <layout>
* <property name="left-attach">0</property>
* <property name="top-attach">0</property>
* <property name="row-span">1</property>
* <property name="col-span">1</property>
* </layout>
* </object>
* </child>
* <child>
* <object class="GtkEntry" id="description_entry">
* <layout>
* <property name="left-attach">1</property>
* <property name="top-attach">0</property>
* <property name="row-span">1</property>
* <property name="col-span">1</property>
* </layout>
* </object>
* </child>
* </object>
* ]|
*
* Finally, GtkWidget allows style information such as style classes to
* be associated with widgets, using the custom <style> element:
* |[
@@ -542,6 +572,7 @@ enum {
PROP_EXPAND,
PROP_SCALE_FACTOR,
PROP_CSS_NAME,
PROP_LAYOUT_MANAGER,
NUM_PROPERTIES
};
@@ -1402,6 +1433,19 @@ gtk_widget_class_init (GtkWidgetClass *klass)
NULL,
GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
/**
* GtkWidget:layout-manager:
*
* The #GtkLayoutManager instance to use to compute the preferred size
* of the widget, and allocate its children.
*/
widget_props[PROP_LAYOUT_MANAGER] =
g_param_spec_object ("layout-manager",
P_("Layout Manager"),
P_("The layout manager used to layout children of the widget"),
GTK_TYPE_LAYOUT_MANAGER,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, NUM_PROPERTIES, widget_props);
/**
@@ -2350,6 +2394,9 @@ gtk_widget_set_property (GObject *object,
else
gtk_css_node_set_name (priv->cssnode, GTK_WIDGET_GET_CLASS (widget)->priv->css_name);
break;
case PROP_LAYOUT_MANAGER:
gtk_widget_set_layout_manager (widget, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -2499,6 +2546,9 @@ gtk_widget_get_property (GObject *object,
case PROP_CSS_NAME:
g_value_set_string (value, gtk_css_node_get_name (priv->cssnode));
break;
case PROP_LAYOUT_MANAGER:
g_value_set_object (value, gtk_widget_get_layout_manager (widget));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -4346,16 +4396,26 @@ gtk_widget_allocate (GtkWidget *widget,
priv->height = adjusted.height;
priv->baseline = baseline;
if (g_signal_has_handler_pending (widget, widget_signals[SIZE_ALLOCATE], 0, FALSE))
g_signal_emit (widget, widget_signals[SIZE_ALLOCATE], 0,
priv->width,
priv->height,
baseline);
if (priv->layout_manager != NULL)
{
gtk_layout_manager_allocate (priv->layout_manager, widget,
priv->width,
priv->height,
baseline);
}
else
GTK_WIDGET_GET_CLASS (widget)->size_allocate (widget,
priv->width,
priv->height,
baseline);
{
if (g_signal_has_handler_pending (widget, widget_signals[SIZE_ALLOCATE], 0, FALSE))
g_signal_emit (widget, widget_signals[SIZE_ALLOCATE], 0,
priv->width,
priv->height,
baseline);
else
GTK_WIDGET_GET_CLASS (widget)->size_allocate (widget,
priv->width,
priv->height,
baseline);
}
/* Size allocation is god... after consulting god, no further requests or allocations are needed */
#ifdef G_ENABLE_DEBUG
@@ -8041,6 +8101,10 @@ gtk_widget_dispose (GObject *object)
while (priv->paintables)
gtk_widget_paintable_set_widget (priv->paintables->data, NULL);
if (priv->layout_manager != NULL)
gtk_layout_manager_set_widget (priv->layout_manager, NULL);
g_clear_object (&priv->layout_manager);
priv->visible = FALSE;
if (_gtk_widget_get_realized (widget))
gtk_widget_unrealize (widget);
@@ -9997,6 +10061,134 @@ static const GMarkupParser style_parser =
style_start_element,
};
typedef struct
{
char *name;
GString *value;
char *context;
gboolean translatable;
} LayoutPropertyInfo;
typedef struct
{
GObject *object;
GtkBuilder *builder;
LayoutPropertyInfo *cur_property;
/* SList<LayoutPropertyInfo> */
GSList *properties;
} LayoutParserData;
static void
layout_property_info_free (gpointer data)
{
LayoutPropertyInfo *pinfo = data;
if (pinfo == NULL)
return;
g_free (pinfo->name);
g_free (pinfo->context);
g_string_free (pinfo->value, TRUE);
}
static void
layout_start_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **names,
const gchar **values,
gpointer user_data,
GError **error)
{
LayoutParserData *layout_data = user_data;
if (strcmp (element_name, "property") == 0)
{
const char *name = NULL;
const char *ctx = NULL;
gboolean translatable = FALSE;
LayoutPropertyInfo *pinfo;
if (!_gtk_builder_check_parent (layout_data->builder, context, "layout", error))
return;
if (!g_markup_collect_attributes (element_name, names, values, error,
G_MARKUP_COLLECT_STRING, "name", &name,
G_MARKUP_COLLECT_BOOLEAN | G_MARKUP_COLLECT_OPTIONAL, "translatable", &translatable,
G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "context", &ctx,
G_MARKUP_COLLECT_INVALID))
{
_gtk_builder_prefix_error (layout_data->builder, context, error);
return;
}
pinfo = g_new0 (LayoutPropertyInfo, 1);
pinfo->name = g_strdup (name);
pinfo->translatable = translatable;
pinfo->context = g_strdup (ctx);
pinfo->value = g_string_new (NULL);
layout_data->cur_property = pinfo;
}
else
{
_gtk_builder_error_unhandled_tag (layout_data->builder, context,
"GtkWidget", element_name,
error);
}
}
static void
layout_text (GMarkupParseContext *context,
const gchar *text,
gsize text_len,
gpointer user_data,
GError **error)
{
LayoutParserData *layout_data = user_data;
if (layout_data->cur_property != NULL)
g_string_append_len (layout_data->cur_property->value, text, text_len);
}
static void
layout_end_element (GMarkupParseContext *context,
const char *element_name,
gpointer user_data,
GError **error)
{
LayoutParserData *layout_data = user_data;
if (layout_data->cur_property != NULL)
{
LayoutPropertyInfo *pinfo = g_steal_pointer (&layout_data->cur_property);
/* Translate the string, if needed */
if (pinfo->value->len != 0 && pinfo->translatable)
{
const char *translated;
const char *domain;
domain = gtk_builder_get_translation_domain (layout_data->builder);
translated = _gtk_builder_parser_translate (domain, pinfo->context, pinfo->value->str);
g_string_assign (pinfo->value, translated);
}
/* We assign all properties at the end of the `layout` section */
layout_data->properties = g_slist_prepend (layout_data->properties, pinfo);
}
}
static const GMarkupParser layout_parser =
{
layout_start_element,
layout_end_element,
layout_text,
};
static gboolean
gtk_widget_buildable_custom_tag_start (GtkBuildable *buildable,
GtkBuilder *builder,
@@ -10045,6 +10237,20 @@ gtk_widget_buildable_custom_tag_start (GtkBuildable *buildable,
return TRUE;
}
if (strcmp (tagname, "layout") == 0)
{
LayoutParserData *data;
data = g_slice_new0 (LayoutParserData);
data->builder = builder;
data->object = (GObject *) g_object_ref (buildable);
*parser = layout_parser;
*parser_data = data;
return TRUE;
}
return FALSE;
}
@@ -10086,6 +10292,72 @@ _gtk_widget_buildable_finish_accelerator (GtkWidget *widget,
g_slice_free (AccelGroupParserData, accel_data);
}
static void
gtk_widget_buildable_finish_layout_properties (GtkWidget *widget,
GtkWidget *parent,
gpointer data)
{
LayoutParserData *layout_data = data;
GtkLayoutManager *layout_manager;
GtkLayoutChild *layout_child;
GObject *gobject;
GObjectClass *gobject_class;
GSList *layout_properties, *l;
layout_manager = gtk_widget_get_layout_manager (parent);
if (layout_manager == NULL)
return;
layout_child = gtk_layout_manager_get_layout_child (layout_manager, widget);
if (layout_child == NULL)
return;
gobject = G_OBJECT (layout_child);
gobject_class = G_OBJECT_GET_CLASS (layout_child);
layout_properties = g_slist_reverse (layout_data->properties);
layout_data->properties = NULL;
for (l = layout_properties; l != NULL; l = l->next)
{
LayoutPropertyInfo *pinfo = l->data;
GParamSpec *pspec;
GValue value = G_VALUE_INIT;
GError *error = NULL;
pspec = g_object_class_find_property (gobject_class, pinfo->name);
if (pspec == NULL)
{
g_warning ("Unable to find layout property “%s” for children "
"of layout managers of type “%s”",
pinfo->name,
G_OBJECT_TYPE_NAME (layout_manager));
continue;
}
gtk_builder_value_from_string (layout_data->builder,
pspec,
pinfo->value->str,
&value,
&error);
if (error != NULL)
{
g_warning ("Failed to set property “%s.%s” to “%s”: %s",
G_OBJECT_TYPE_NAME (layout_child),
pinfo->name,
pinfo->value->str,
error->message);
g_error_free (error);
continue;
}
g_object_set_property (gobject, pinfo->name, &value);
g_value_unset (&value);
}
g_slist_free_full (layout_properties, layout_property_info_free);
}
static void
gtk_widget_buildable_custom_finished (GtkBuildable *buildable,
GtkBuilder *builder,
@@ -10183,6 +10455,19 @@ gtk_widget_buildable_custom_finished (GtkBuildable *buildable,
g_slist_free_full (style_data->classes, g_free);
g_slice_free (StyleParserData, style_data);
}
else if (strcmp (tagname, "layout") == 0)
{
LayoutParserData *layout_data = (LayoutParserData *) user_data;
GtkWidget *parent = _gtk_widget_get_parent (GTK_WIDGET (buildable));
if (parent != NULL)
gtk_widget_buildable_finish_layout_properties (GTK_WIDGET (buildable),
parent,
layout_data);
g_object_unref (layout_data->object);
g_slice_free (LayoutParserData, layout_data);
}
}
static GtkSizeRequestMode
@@ -13361,3 +13646,51 @@ gtk_widget_get_height (GtkWidget *widget)
return priv->height;
}
/**
* gtk_widget_set_layout_manager:
* @widget: a #GtkWidget
* @layout_manager: (nullable) (transfer full): a #GtkLayoutManager
*
* Sets the layout manager delegate instance that provides an implementation
* for measuring and allocating the children of @widget.
*/
void
gtk_widget_set_layout_manager (GtkWidget *widget,
GtkLayoutManager *layout_manager)
{
GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
g_return_if_fail (GTK_IS_WIDGET (widget));
g_return_if_fail (layout_manager == NULL || GTK_IS_LAYOUT_MANAGER (layout_manager));
g_return_if_fail (layout_manager == NULL || gtk_layout_manager_get_widget (layout_manager) == NULL);
if (priv->layout_manager == layout_manager)
return;
priv->layout_manager = layout_manager;
if (priv->layout_manager != NULL)
gtk_layout_manager_set_widget (priv->layout_manager, widget);
gtk_widget_queue_resize (widget);
g_object_notify_by_pspec (G_OBJECT (widget), widget_props[PROP_LAYOUT_MANAGER]);
}
/**
* gtk_widget_get_layout_manager:
* @widget: a #GtkWidget
*
* Retrieves the layout manager set using gtk_widget_set_layout_manager().
*
* Returns: (transfer none) (nullable): a #GtkLayoutManager
*/
GtkLayoutManager *
gtk_widget_get_layout_manager (GtkWidget *widget)
{
GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
return priv->layout_manager;
}

View File

@@ -424,6 +424,12 @@ void gtk_widget_get_preferred_size (GtkWidget *w
GtkRequisition *minimum_size,
GtkRequisition *natural_size);
GDK_AVAILABLE_IN_ALL
void gtk_widget_set_layout_manager (GtkWidget *widget,
GtkLayoutManager *layout_manager);
GDK_AVAILABLE_IN_ALL
GtkLayoutManager * gtk_widget_get_layout_manager (GtkWidget *widget);
GDK_AVAILABLE_IN_ALL
void gtk_widget_add_accelerator (GtkWidget *widget,
const gchar *accel_signal,

View File

@@ -160,6 +160,9 @@ struct _GtkWidgetPrivate
/* The render node we draw or %NULL if not yet created.*/
GskRenderNode *render_node;
/* The layout manager, or %NULL */
GtkLayoutManager *layout_manager;
GSList *paintables;
/* The widget's surface or its parent surface if it does

View File

@@ -98,6 +98,7 @@ gtk_private_sources = files([
'gtkcssunsetvalue.c',
'gtkcssvalue.c',
'gtkcsswidgetnode.c',
'gtkcustomlayout.c',
'gtkfilechooserembed.c',
'gtkfilechooserentry.c',
'gtkfilechoosererrorstack.c',
@@ -163,8 +164,10 @@ gtk_public_sources = files([
'gtkaspectframe.c',
'gtkassistant.c',
'gtkbin.c',
'gtkbinlayout.c',
'gtkbindings.c',
'gtkborder.c',
'gtkboxlayout.c',
'gtkbox.c',
'gtkbuildable.c',
'gtkbuilder.c',
@@ -256,6 +259,8 @@ gtk_public_sources = files([
'gtkinfobar.c',
'gtklabel.c',
'gtklayout.c',
'gtklayoutchild.c',
'gtklayoutmanager.c',
'gtklevelbar.c',
'gtklinkbutton.c',
'gtklistbox.c',
@@ -416,6 +421,7 @@ gtk_public_headers = files([
'gtkaspectframe.h',
'gtkassistant.h',
'gtkbin.h',
'gtkbinlayout.h',
'gtkbindings.h',
'gtkborder.h',
'gtkbox.h',
@@ -451,6 +457,7 @@ gtk_public_headers = files([
'gtkcontainer.h',
'gtkcssprovider.h',
'gtkcsssection.h',
'gtkcustomlayout.h',
'gtkdebug.h',
'gtkdialog.h',
'gtkdnd.h',
@@ -506,6 +513,8 @@ gtk_public_headers = files([
'gtkinfobar.h',
'gtklabel.h',
'gtklayout.h',
'gtklayoutchild.h',
'gtklayoutmanager.h',
'gtklevelbar.h',
'gtklinkbutton.h',
'gtklistbox.h',