summaryrefslogtreecommitdiff
path: root/gtk/gtkconstraintlayout.c
diff options
context:
space:
mode:
authorEmmanuele Bassi <ebassi@gnome.org>2019-07-01 18:03:20 +0100
committerEmmanuele Bassi <ebassi@gnome.org>2019-07-01 18:03:20 +0100
commit6bc156c2378322cab5092fa36ef57dc7fa26a702 (patch)
tree32ed3d1d2796c4c533d35eeef33c9975453a1e14 /gtk/gtkconstraintlayout.c
parentb435dc436698c89cfc2a487a28b2fb6208628941 (diff)
downloadgtk+-6bc156c2378322cab5092fa36ef57dc7fa26a702.tar.gz
Implement GtkBuildable for GtkConstraintLayout
Using GtkBuildable we can provide a custom parser for reading constraints defined in a GtkBuilder UI file.
Diffstat (limited to 'gtk/gtkconstraintlayout.c')
-rw-r--r--gtk/gtkconstraintlayout.c440
1 files changed, 438 insertions, 2 deletions
diff --git a/gtk/gtkconstraintlayout.c b/gtk/gtkconstraintlayout.c
index d8c0bdc76c..ccca990f93 100644
--- a/gtk/gtkconstraintlayout.c
+++ b/gtk/gtkconstraintlayout.c
@@ -53,8 +53,113 @@
* is undefined.
*
* A constraint-based layout with conflicting constraints may be unsolvable,
- * and lead to an unstable layout.
+ * and lead to an unstable layout. You can use the #GtkConstraint:strength
+ * property of #GtkConstraint to "nudge" the layout towards a solution.
*
+ * # GtkConstraintLayout as GtkBuildable
+ *
+ * GtkConstraintLayout implements the #GtkBuildable interface and has a
+ * custom "constraints" element which allows describing constraints in a
+ * GtkBuilder UI file.
+ *
+ * An example of a UI definition fragment specifying a constraint:
+ *
+ * |[
+ * <object class="GtkConstraintLayout">
+ * <constraints>
+ * <constraint target="button" target-attribute="start"
+ * relation="eq"
+ * source="super" source-attribute="start"
+ * constant="12"
+ * strength="required" />
+ * <constraint target="button" target-attribute="width"
+ * relation="ge"
+ * constant="250"
+ * strength="strong" />
+ * </constraints>
+ * </object>
+ * ]|
+ *
+ * The definition above will add two constraints to the GtkConstraintLayout:
+ *
+ * - a required constraint between the leading edge of "button" and
+ * the leading edge of the widget using the constraint layout, plus
+ * 12 pixels
+ * - a strong, constant constraint making the width of "button" greater
+ * than, or equal to 250 pixels
+ *
+ * The "target" and "target-attribute" attributes are required.
+ *
+ * The "source" and "source-attribute" attributes of the "constraint"
+ * element are optional; if they are not specified, the constraint is
+ * assumed to be a constant.
+ *
+ * The "relation" attribute is optional; if not specified, the constraint
+ * is assumed to be an equality.
+ *
+ * The "strength" attribute is optional; if not specified, the constraint
+ * is assumed to be required.
+ *
+ * The "source" and "target" attributes can be set to "super" to indicate
+ * that the constraint target is the widget using the GtkConstraintLayout.
+ *
+ * # Using the Visual Format Language
+ *
+ * Complex constraints can be described using a compact syntax called VFL,
+ * or *Visual Format Language*.
+ *
+ * The Visual Format Language describes all the constraints on a row or
+ * column, typically starting from the leading edge towards the trailing
+ * one. Each element of the layout is composed by "views", which identify
+ * a #GtkConstraintTarget.
+ *
+ * For instance:
+ *
+ * |[
+ * [button]-[textField]
+ * ]|
+ *
+ * Describes a constraint that binds the trailing edge of "button" to the
+ * leading edge of "textField", leaving a default space between the two.
+ *
+ * Using VFL is also possible to specify predicates that describe constraints
+ * on attributes like width and height:
+ *
+ * |[
+ * // Width must be greater than, or equal to 50
+ * [button(>=50)]
+ *
+ * // Width of button1 must be equal to width of button2
+ * [button1(==button2)]
+ * ]|
+ *
+ * The default orientation for a VFL description is horizontal, unless
+ * otherwise specified:
+ *
+ * |[
+ * // horizontal orientation, default attribute: width
+ * H:[button(>=150)]
+ *
+ * // vertical orientation, default attribute: height
+ * V:[button1(==button2)]
+ * ]|
+ *
+ * It's also possible to specify multiple predicates, as well as their
+ * strength:
+ *
+ * |[
+ * // minimum width of button must be 150
+ * // natural width of button can be 250
+ * [button(>=150@required, ==250@medium)]
+ * ]|
+ *
+ * Finally, it's also possible to use simple arithmetic operators:
+ *
+ * |[
+ * // width of button1 must be equal to width of button2
+ * // divided by 2 plus 12
+ * [button1(button2 / 2 + 12)]
+ * ]|
*/
#include "config.h"
@@ -68,6 +173,8 @@
#include "gtkconstraintsolverprivate.h"
#include "gtkconstraintvflparserprivate.h"
+#include "gtkbuildable.h"
+#include "gtkbuilderprivate.h"
#include "gtkdebug.h"
#include "gtklayoutchild.h"
#include "gtkintl.h"
@@ -75,6 +182,9 @@
#include "gtksizerequest.h"
#include "gtkwidgetprivate.h"
+#include <string.h>
+#include <errno.h>
+
enum {
MIN_WIDTH,
MIN_HEIGHT,
@@ -393,7 +503,10 @@ gtk_constraint_layout_child_init (GtkConstraintLayoutChild *self)
(GDestroyNotify) gtk_constraint_variable_unref);
}
-G_DEFINE_TYPE (GtkConstraintLayout, gtk_constraint_layout, GTK_TYPE_LAYOUT_MANAGER)
+static void gtk_buildable_interface_init (GtkBuildableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GtkConstraintLayout, gtk_constraint_layout, GTK_TYPE_LAYOUT_MANAGER,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_buildable_interface_init))
static void
gtk_constraint_layout_finalize (GObject *gobject)
@@ -1070,6 +1183,329 @@ gtk_constraint_layout_init (GtkConstraintLayout *self)
NULL);
}
+typedef struct {
+ GtkConstraintLayout *layout;
+ GtkBuilder *builder;
+ GList *constraints;
+} ConstraintsParserData;
+
+typedef struct {
+ char *source_name;
+ char *source_attr;
+ char *target_name;
+ char *target_attr;
+ char *relation;
+ char *strength;
+ double constant;
+ double multiplier;
+} ConstraintData;
+
+static void
+constraint_data_free (gpointer _data)
+{
+ ConstraintData *data = _data;
+
+ g_free (data->source_name);
+ g_free (data->source_attr);
+ g_free (data->target_name);
+ g_free (data->target_attr);
+ g_free (data->relation);
+ g_free (data->strength);
+
+ g_free (data);
+}
+
+static void
+parse_double (const char *string,
+ double *value_p,
+ double default_value)
+{
+ double value;
+ char *endptr;
+ int saved_errno;
+
+ if (string == NULL || string[0] == '\0')
+ {
+ *value_p = default_value;
+ return;
+ }
+
+ saved_errno = errno;
+ errno = 0;
+ value = g_ascii_strtod (string, &endptr);
+ if (errno == 0 && endptr != string)
+ *value_p = value;
+ else
+ *value_p = default_value;
+
+ errno = saved_errno;
+}
+
+static GtkConstraint *
+constraint_data_to_constraint (const ConstraintData *data,
+ GtkBuilder *builder,
+ GError **error)
+{
+ gpointer source, target;
+ int source_attr, target_attr;
+ int relation, strength;
+ gboolean res;
+
+ if (g_strcmp0 (data->source_name, "super") == 0)
+ source = NULL;
+ else if (data->source_name == NULL)
+ {
+ if (data->source_attr != NULL)
+ {
+ g_set_error (error, GTK_BUILDER_ERROR,
+ GTK_BUILDER_ERROR_INVALID_VALUE,
+ "Constraints without 'source' must also not "
+ "have a 'source-attribute' attribute");
+ return NULL;
+ }
+
+ source = NULL;
+ }
+ else
+ source = gtk_builder_get_object (builder, data->source_name);
+
+ if (g_strcmp0 (data->target_name, "super") == 0)
+ target = NULL;
+ else
+ {
+ target = gtk_builder_get_object (builder, data->target_name);
+
+ if (target == NULL)
+ {
+ g_set_error (error, GTK_BUILDER_ERROR,
+ GTK_BUILDER_ERROR_INVALID_VALUE,
+ "Unable to find target '%s' for constraint",
+ data->target_name);
+ return NULL;
+ }
+ }
+
+ if (data->source_attr != NULL)
+ {
+ res = _gtk_builder_enum_from_string (GTK_TYPE_CONSTRAINT_ATTRIBUTE,
+ data->source_attr,
+ &source_attr,
+ error);
+ if (!res)
+ return NULL;
+ }
+ else
+ source_attr = GTK_CONSTRAINT_ATTRIBUTE_NONE;
+
+ res = _gtk_builder_enum_from_string (GTK_TYPE_CONSTRAINT_ATTRIBUTE,
+ data->target_attr,
+ &target_attr,
+ error);
+ if (!res)
+ return NULL;
+
+ if (data->relation != NULL)
+ {
+ res = _gtk_builder_enum_from_string (GTK_TYPE_CONSTRAINT_RELATION,
+ data->relation,
+ &relation,
+ error);
+ if (!res)
+ return NULL;
+ }
+ else
+ relation = GTK_CONSTRAINT_RELATION_EQ;
+
+ if (data->strength != NULL)
+ {
+ res = _gtk_builder_enum_from_string (GTK_TYPE_CONSTRAINT_STRENGTH,
+ data->strength,
+ &strength,
+ error);
+ }
+ else
+ strength = GTK_CONSTRAINT_STRENGTH_REQUIRED;
+
+ if (source != NULL && source_attr != GTK_CONSTRAINT_ATTRIBUTE_NONE)
+ return gtk_constraint_new (target, target_attr,
+ relation,
+ source, source_attr,
+ data->multiplier,
+ data->constant,
+ strength);
+
+ return gtk_constraint_new_constant (target, target_attr,
+ relation,
+ data->constant,
+ strength);
+}
+
+static void
+constraints_start_element (GMarkupParseContext *context,
+ const char *element_name,
+ const char **attr_names,
+ const char **attr_values,
+ gpointer user_data,
+ GError **error)
+{
+ ConstraintsParserData *data = user_data;
+
+ if (strcmp (element_name, "constraints") == 0)
+ {
+ if (!_gtk_builder_check_parent (data->builder, context, "object", error))
+ return;
+
+ if (!g_markup_collect_attributes (element_name, attr_names, attr_values, error,
+ G_MARKUP_COLLECT_INVALID, NULL, NULL,
+ G_MARKUP_COLLECT_INVALID))
+ _gtk_builder_prefix_error (data->builder, context, error);
+ }
+ else if (strcmp (element_name, "constraint") == 0)
+ {
+ const char *target_name, *target_attribute;
+ const char *relation_str = NULL;
+ const char *source_name = NULL, *source_attribute = NULL;
+ const char *multiplier_str = NULL, *constant_str = NULL;
+ const char *strength_str = NULL;
+ ConstraintData *cdata;
+
+ if (!_gtk_builder_check_parent (data->builder, context, "constraints", error))
+ return;
+
+ if (!g_markup_collect_attributes (element_name, attr_names, attr_values, error,
+ G_MARKUP_COLLECT_STRING, "target", &target_name,
+ G_MARKUP_COLLECT_STRING, "target-attribute", &target_attribute,
+ G_MARKUP_COLLECT_STRING, "relation", &relation_str,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "source", &source_name,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "source-attribute", &source_attribute,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "multiplier", &multiplier_str,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "constant", &constant_str,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "strength", &strength_str,
+ G_MARKUP_COLLECT_INVALID))
+ {
+ _gtk_builder_prefix_error (data->builder, context, error);
+ return;
+ }
+
+ cdata = g_new0 (ConstraintData, 1);
+ cdata->target_name = g_strdup (target_name);
+ cdata->target_attr = g_strdup (target_attribute);
+ cdata->relation = g_strdup (relation_str);
+ cdata->source_name = g_strdup (source_name);
+ cdata->source_attr = g_strdup (source_attribute);
+ parse_double (multiplier_str, &cdata->multiplier, 1.0);
+ parse_double (constant_str, &cdata->constant, 0.0);
+ cdata->strength = g_strdup (strength_str);
+
+ data->constraints = g_list_prepend (data->constraints, cdata);
+ }
+ else
+ {
+ _gtk_builder_error_unhandled_tag (data->builder, context,
+ "GtkConstraintLayout", element_name,
+ error);
+ }
+}
+
+static void
+constraints_end_element (GMarkupParseContext *context,
+ const char *element_name,
+ gpointer user_data,
+ GError **error)
+{
+}
+
+static const GMarkupParser constraints_parser = {
+ constraints_start_element,
+ constraints_end_element,
+ NULL,
+};
+
+static gboolean
+gtk_constraint_layout_custom_tag_start (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const char *element_name,
+ GMarkupParser *parser,
+ gpointer *parser_data)
+{
+ if (strcmp (element_name, "constraints") == 0)
+ {
+ ConstraintsParserData *data = g_new (ConstraintsParserData, 1);
+
+ data->layout = g_object_ref (GTK_CONSTRAINT_LAYOUT (buildable));
+ data->builder = builder;
+ data->constraints = NULL;
+
+ *parser = constraints_parser;
+ *parser_data = data;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+gtk_constraint_layout_custom_tag_end (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const char *element_name,
+ gpointer data)
+{
+}
+
+static void
+gtk_constraint_layout_custom_finished (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const char *element_name,
+ gpointer user_data)
+{
+ ConstraintsParserData *data = user_data;
+
+ if (strcmp (element_name, "constraints") == 0)
+ {
+ GList *l;
+
+ data->constraints = g_list_reverse (data->constraints);
+ for (l = data->constraints; l != NULL; l = l->next)
+ {
+ const ConstraintData *cdata = l->data;
+ GtkConstraint *c;
+ GError *error = NULL;
+
+ c = constraint_data_to_constraint (cdata, builder, &error);
+ if (error != NULL)
+ {
+ g_critical ("Unable to parse constraint definition '%s.%s [%s] %s.%s * %g + %g': %s",
+ cdata->target_name, cdata->target_attr,
+ cdata->relation,
+ cdata->source_name, cdata->source_attr,
+ cdata->multiplier,
+ cdata->constant,
+ error->message);
+ g_error_free (error);
+ continue;
+ }
+
+ gtk_constraint_layout_add_constraint (data->layout, c);
+ }
+
+ g_list_free_full (data->constraints, constraint_data_free);
+ g_object_unref (data->layout);
+ g_free (data);
+ }
+}
+
+static void
+gtk_buildable_interface_init (GtkBuildableIface *iface)
+{
+ iface->custom_tag_start = gtk_constraint_layout_custom_tag_start;
+ iface->custom_tag_end = gtk_constraint_layout_custom_tag_end;
+ iface->custom_finished = gtk_constraint_layout_custom_finished;
+}
+
/**
* gtk_constraint_layout_new:
*