forked from AuroraMiddleware/gtk
bd2349c0a0
Since the LayoutManager owns the LayoutChild it creates, it's also responsible for mopping them up.
557 lines
18 KiB
C
557 lines
18 KiB
C
/* 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().
|
|
*
|
|
* ## Layout properties
|
|
*
|
|
* A layout manager can expose properties for controlling the layout of
|
|
* each child, by creating an object type derived from #GtkLayoutChild
|
|
* and installing the properties on it as normal GObject properties.
|
|
*
|
|
* Each #GtkLayoutChild instance storing the layout properties for a
|
|
* specific child is created through the gtk_layout_manager_get_layout_child()
|
|
* method; a #GtkLayoutManager controls the creation of its #GtkLayoutChild
|
|
* instances by overriding the GtkLayoutManagerClass.create_layout_child()
|
|
* virtual function. The typical implementation should look like:
|
|
*
|
|
* |[<!-- language="C" -->
|
|
* static GtkLayoutChild *
|
|
* create_layout_child (GtkLayoutManager *manager,
|
|
* GtkWidget *container,
|
|
* GtkWidget *child)
|
|
* {
|
|
* return g_object_new (your_layout_child_get_type (),
|
|
* "layout-manager", manager,
|
|
* "child-widget", child,
|
|
* NULL);
|
|
* }
|
|
* ]|
|
|
*
|
|
* The #GtkLayoutChild:layout-manager and #GtkLayoutChild:child-widget properties
|
|
* on the newly created #GtkLayoutChild instance are mandatory. The
|
|
* #GtkLayoutManager will cache the newly created #GtkLayoutChild instance until
|
|
* the widget is removed from its parent, or the parent removes the layout
|
|
* manager.
|
|
*
|
|
* Each #GtkLayoutManager instance creating a #GtkLayoutChild should use
|
|
* gtk_layout_manager_get_layout_child() every time it needs to query the
|
|
* layout properties; each #GtkLayoutChild instance should call
|
|
* gtk_layout_manager_layout_changed() every time a property is updated, in
|
|
* order to queue a new size measuring and allocation.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gtklayoutmanagerprivate.h"
|
|
#include "gtklayoutchild.h"
|
|
#include "gtkwidgetprivate.h"
|
|
#include "gtknative.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;
|
|
GtkRoot *root;
|
|
|
|
/* HashTable<Widget, LayoutChild> */
|
|
GHashTable *layout_children;
|
|
} GtkLayoutManagerPrivate;
|
|
|
|
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GtkLayoutManager, gtk_layout_manager, G_TYPE_OBJECT)
|
|
|
|
static void
|
|
gtk_layout_manager_real_root (GtkLayoutManager *manager)
|
|
{
|
|
}
|
|
|
|
static void
|
|
gtk_layout_manager_real_unroot (GtkLayoutManager *manager)
|
|
{
|
|
}
|
|
|
|
static GtkSizeRequestMode
|
|
gtk_layout_manager_real_get_request_mode (GtkLayoutManager *manager,
|
|
GtkWidget *widget)
|
|
{
|
|
int hfw = 0, wfh = 0;
|
|
GtkWidget *child;
|
|
|
|
for (child = _gtk_widget_get_first_child (widget);
|
|
child != NULL;
|
|
child = _gtk_widget_get_next_sibling (child))
|
|
{
|
|
GtkSizeRequestMode res = gtk_widget_get_request_mode (child);
|
|
|
|
switch (res)
|
|
{
|
|
case GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH:
|
|
hfw += 1;
|
|
break;
|
|
|
|
case GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT:
|
|
wfh += 1;
|
|
break;
|
|
|
|
case GTK_SIZE_REQUEST_CONSTANT_SIZE:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (hfw == 0 && wfh == 0)
|
|
return GTK_SIZE_REQUEST_CONSTANT_SIZE;
|
|
|
|
return hfw > wfh ? GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH
|
|
: GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT;
|
|
}
|
|
|
|
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 GtkLayoutChild *
|
|
gtk_layout_manager_real_create_layout_child (GtkLayoutManager *manager,
|
|
GtkWidget *widget,
|
|
GtkWidget *child)
|
|
{
|
|
GtkLayoutManagerClass *manager_class = GTK_LAYOUT_MANAGER_GET_CLASS (manager);
|
|
|
|
if (manager_class->layout_child_type == G_TYPE_INVALID)
|
|
{
|
|
LAYOUT_MANAGER_WARN_NOT_IMPLEMENTED (manager, create_layout_child);
|
|
return NULL;
|
|
}
|
|
|
|
return g_object_new (manager_class->layout_child_type,
|
|
"layout-manager", manager,
|
|
"child-widget", widget,
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
gtk_layout_manager_finalize (GObject *gobject)
|
|
{
|
|
GtkLayoutManager *self = GTK_LAYOUT_MANAGER (gobject);
|
|
GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (self);
|
|
|
|
g_clear_pointer (&priv->layout_children, g_hash_table_unref);
|
|
|
|
G_OBJECT_CLASS (gtk_layout_manager_parent_class)->finalize (gobject);
|
|
}
|
|
|
|
static void
|
|
gtk_layout_manager_class_init (GtkLayoutManagerClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
gobject_class->finalize = gtk_layout_manager_finalize;
|
|
|
|
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;
|
|
klass->create_layout_child = gtk_layout_manager_real_create_layout_child;
|
|
klass->root = gtk_layout_manager_real_root;
|
|
klass->unroot = gtk_layout_manager_real_unroot;
|
|
}
|
|
|
|
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;
|
|
|
|
if (widget != NULL)
|
|
gtk_layout_manager_set_root (layout_manager, gtk_widget_get_root (widget));
|
|
}
|
|
|
|
/*< private >
|
|
* gtk_layout_manager_set_root:
|
|
* @layout_manager: a #GtkLayoutManager
|
|
* @root: (nullable): a #GtkWidget implementing #GtkRoot
|
|
*
|
|
* Sets a back pointer from @root to @layout_manager.
|
|
*
|
|
* This function is called by #GtkWidget when getting rooted and unrooted,
|
|
* and will call #GtkLayoutManagerClass.root() or #GtkLayoutManagerClass.unroot()
|
|
* depending on whether @root is a #GtkWidget or %NULL.
|
|
*/
|
|
void
|
|
gtk_layout_manager_set_root (GtkLayoutManager *layout_manager,
|
|
GtkRoot *root)
|
|
{
|
|
GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (layout_manager);
|
|
GtkRoot *old_root = priv->root;
|
|
|
|
priv->root = root;
|
|
|
|
if (old_root != root)
|
|
{
|
|
if (priv->root != NULL)
|
|
GTK_LAYOUT_MANAGER_GET_CLASS (layout_manager)->root (layout_manager);
|
|
else
|
|
GTK_LAYOUT_MANAGER_GET_CLASS (layout_manager)->unroot (layout_manager);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
int min_size = 0;
|
|
int nat_size = 0;
|
|
int min_baseline = -1;
|
|
int nat_baseline = -1;
|
|
|
|
|
|
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,
|
|
&min_size, &nat_size,
|
|
&min_baseline, &nat_baseline);
|
|
|
|
if (minimum)
|
|
*minimum = min_size;
|
|
|
|
if (natural)
|
|
*natural = nat_size;
|
|
|
|
if (minimum_baseline)
|
|
*minimum_baseline = min_baseline;
|
|
|
|
if (natural_baseline)
|
|
*natural_baseline = nat_baseline;
|
|
}
|
|
|
|
static void
|
|
allocate_native_children (GtkWidget *widget)
|
|
{
|
|
GtkWidget *child;
|
|
|
|
for (child = _gtk_widget_get_first_child (widget);
|
|
child != NULL;
|
|
child = _gtk_widget_get_next_sibling (child))
|
|
{
|
|
if (GTK_IS_NATIVE (child))
|
|
gtk_native_check_resize (GTK_NATIVE (child));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
|
|
allocate_native_children (widget);
|
|
|
|
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);
|
|
}
|
|
|
|
/*< private >
|
|
* gtk_layout_manager_remove_layout_child:
|
|
* @manager: a #GtkLayoutManager
|
|
* @widget: a #GtkWidget
|
|
*
|
|
* Removes the #GtkLayoutChild associated with @widget from the
|
|
* given #GtkLayoutManager, if any is set.
|
|
*/
|
|
void
|
|
gtk_layout_manager_remove_layout_child (GtkLayoutManager *manager,
|
|
GtkWidget *widget)
|
|
{
|
|
GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (manager);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 (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);
|
|
if (res == NULL)
|
|
{
|
|
g_critical ("The layout manager of type %s %p does not create "
|
|
"GtkLayoutChild instances",
|
|
G_OBJECT_TYPE_NAME (manager), manager);
|
|
return NULL;
|
|
}
|
|
|
|
g_assert (g_type_is_a (G_OBJECT_TYPE (res), GTK_TYPE_LAYOUT_CHILD));
|
|
g_hash_table_insert (priv->layout_children, child, res);
|
|
|
|
return res;
|
|
}
|