From ae75958f99bb8c3967053110b94957b0e7bfcc79 Mon Sep 17 00:00:00 2001 From: Cecil Date: Mon, 29 Oct 2018 21:30:13 -0600 Subject: [PATCH] for #410 - add emeus cassowary C code. * compiles and links linux, osx, mxe (not xwin7 - needs newer glib) * doesn't run --- Tests/layout/l3.rb | 27 +- Tests/layout/vfl3.rb | 2 +- shoes/layout/config.h | 35 + shoes/layout/emeus-constraint-private.h | 60 + shoes/layout/emeus-constraint.c | 952 +++++++++ shoes/layout/emeus-constraint.h | 84 + shoes/layout/emeus-expression-private.h | 190 ++ shoes/layout/emeus-expression.c | 747 +++++++ shoes/layout/emeus-macros-private.h | 41 + shoes/layout/emeus-simplex-solver-private.h | 108 + shoes/layout/emeus-simplex-solver.c | 1982 +++++++++++++++++++ shoes/layout/emeus-test-utils.h | 19 + shoes/layout/emeus-types-private.h | 175 ++ shoes/layout/emeus-types.c | 48 + shoes/layout/emeus-types.h | 123 ++ shoes/layout/emeus-utils-private.h | 42 + shoes/layout/emeus-utils.c | 184 ++ shoes/layout/emeus-utils.h | 34 + shoes/layout/emeus-version-macros.h | 197 ++ shoes/layout/emeus-version.h | 31 + shoes/layout/emeus.h | 32 + shoes/layout/layouts.h | 41 +- shoes/layout/shoes-vfl-parser.c | 14 +- shoes/layout/shoes-vfl.c | 174 ++ shoes/types/layout.c | 47 +- shoes/types/layout.h | 8 +- 26 files changed, 5355 insertions(+), 42 deletions(-) create mode 100644 shoes/layout/config.h create mode 100644 shoes/layout/emeus-constraint-private.h create mode 100644 shoes/layout/emeus-constraint.c create mode 100644 shoes/layout/emeus-constraint.h create mode 100644 shoes/layout/emeus-expression-private.h create mode 100644 shoes/layout/emeus-expression.c create mode 100644 shoes/layout/emeus-macros-private.h create mode 100644 shoes/layout/emeus-simplex-solver-private.h create mode 100644 shoes/layout/emeus-simplex-solver.c create mode 100644 shoes/layout/emeus-test-utils.h create mode 100644 shoes/layout/emeus-types-private.h create mode 100644 shoes/layout/emeus-types.c create mode 100644 shoes/layout/emeus-types.h create mode 100644 shoes/layout/emeus-utils-private.h create mode 100644 shoes/layout/emeus-utils.c create mode 100644 shoes/layout/emeus-utils.h create mode 100644 shoes/layout/emeus-version-macros.h create mode 100644 shoes/layout/emeus-version.h create mode 100644 shoes/layout/emeus.h create mode 100644 shoes/layout/shoes-vfl.c diff --git a/Tests/layout/l3.rb b/Tests/layout/l3.rb index c7e1b981..f3279c28 100644 --- a/Tests/layout/l3.rb +++ b/Tests/layout/l3.rb @@ -5,7 +5,7 @@ include Cassowary class CassowaryLayout attr_accessor :canvas, :widgets, :solver, :attrs, :left_limit, - :right_limit, :top_limit, :height_limit, :right_limit_stay, + :right_limit, :top_limit, :height_limit, :rl_stay, :hl_stay, :canvas_set, :canvas_w, :canvas_h def initialize() @@ -40,8 +40,11 @@ def setup(canvas, attr) @right_limit = Variable.new(name: 'width', value: wid) @top_limit = Variable.new(name: "top", value: 0.0) @height_limit = Variable.new(name: "height", value: hgt) - @solver.add_stay(@left_limit) - @solver.add_stay(@right_limit, Strength::WeakStrength) + + @solver.add_stay(@left_limit, Strength::RequiredStrength) + @rl_stay = @solver.add_stay(@right_limit) + @solver.add_stay(@top_limit, Strength::RequiredStrength) + @hl_stay = @solver.add_stay(@height_limit) $stderr.puts "callback: setup #{wid} X #{hgt}" end @@ -80,11 +83,17 @@ def size(canvas, pass) return else $stderr.puts "callback: size Change! w: #{canvas.width} h:#{canvas.height}" + # reset stays for new window size + #@solver.remove_constraint(@rl_stay) + @right_limit.value = canvas.width + #@rl_stay = @solver.add_constraint(@right_limit, Strength::RequiredStrength) + #@solver.add_constraint(@rl_stay) + @solver.solve + self.finish end end def widget_defaults - @canvas.show @widgets.each_pair do |id, ele| vh = @attrs[id]['height'] vh.value = ele.height @@ -95,13 +104,7 @@ def widget_defaults end def resize(w, h) - @right_limit.value = w - @height_limit.value = h - #@solver.add_stay(@right_limit, Strength::RequiredStrength) - - #right_limit_stay = solver.add_constraint(right_limit, strength=REQUIRED) - #solver.add_constraint(right_limit_stay) - end + end def clear() $stderr.puts "callback: clear" @@ -184,7 +187,7 @@ def method_missing(meth, *args, &block) @ml.add_constraint(@ml.var('b2-width').cn_equal 113, Strength::StrongStrength) # string b3 is at the bottom ? - @ml.add_constraint(@ml.height_limit.cn_equal @ml.var('b3-top')) + @ml.add_constraint(@ml.var('b3-top').cn_equal 100) @lay.finish @p.text = @lay.inspect diff --git a/Tests/layout/vfl3.rb b/Tests/layout/vfl3.rb index 39a04eb3..4ee5a83f 100644 --- a/Tests/layout/vfl3.rb +++ b/Tests/layout/vfl3.rb @@ -11,7 +11,7 @@ end @lay.start { metrics = { - 'el1' => 80 # what does this mean or do or should be? + 'padding' => 10 } lines = [ "H:|-[para1(but1)]-[but1]-|", diff --git a/shoes/layout/config.h b/shoes/layout/config.h new file mode 100644 index 00000000..562ae621 --- /dev/null +++ b/shoes/layout/config.h @@ -0,0 +1,35 @@ +/* + * Autogenerated by the Meson build system. + * Do not edit, your changes will be lost. + */ + +#pragma once + +#define ENABLE_NLS 1 + +#define GETTEXT_PACKAGE PACKAGE_NAME + +#define HAVE_STDBOOL_H + +#define HAVE_STDINT_H + +#define HAVE_STDLIB_H + +#define LOCALEDIR PACKAGE_LOCALE_DIR + +#define PACKAGE_DATADIR "/usr/local/share" + +#define PACKAGE_LIBDIR "/usr/local/lib/x86_64-linux-gnu" + +#define PACKAGE_LIBEXECDIR "/usr/local/libexec" + +#define PACKAGE_LOCALE_DIR "/usr/local/share/locale" + +#define PACKAGE_NAME "emeus" + +#define PACKAGE_STRING "emeus-0.99.1" + +#define PACKAGE_VERSION "0.99.1" + +#define _EMEUS_PUBLIC __attribute__((visibility("default"))) + diff --git a/shoes/layout/emeus-constraint-private.h b/shoes/layout/emeus-constraint-private.h new file mode 100644 index 00000000..f4b00f3d --- /dev/null +++ b/shoes/layout/emeus-constraint-private.h @@ -0,0 +1,60 @@ +/* emeus-constraint-private.h: The base constraint object + * + * Copyright 2016 Endless + * + * 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 . + */ + +#pragma once + +#include "emeus-constraint.h" +//#include "emeus-constraint-layout-private.h" +#include "emeus-types-private.h" + +G_BEGIN_DECLS + +struct _EmeusConstraint +{ + GInitiallyUnowned parent_instance; + + gpointer target_object; + EmeusConstraintAttribute target_attribute; + + EmeusConstraintRelation relation; + + gpointer source_object; + EmeusConstraintAttribute source_attribute; + + double multiplier; + double constant; + + int strength; + + gboolean is_active; + + char *description; + SimplexSolver *solver; + Constraint *constraint; + EmeusConstraintLayout *layout; +}; + +gboolean emeus_constraint_attach (EmeusConstraint *constraint, + EmeusConstraintLayout *layout); +void emeus_constraint_detach (EmeusConstraint *constraint); + +Constraint * emeus_constraint_get_real_constraint (EmeusConstraint *constraint); + +const char * emeus_constraint_to_string (EmeusConstraint *constraint); + +G_END_DECLS diff --git a/shoes/layout/emeus-constraint.c b/shoes/layout/emeus-constraint.c new file mode 100644 index 00000000..25731d45 --- /dev/null +++ b/shoes/layout/emeus-constraint.c @@ -0,0 +1,952 @@ +/* emeus-constraint.c: The base constraint object + * + * Copyright 2016 Endless + * + * 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 . + */ + +/** + * SECTION:emeus-constraint + * @Title: EmeusConstraint + * @Short_Description: The representation of a single constraint + * + * #EmeusConstraint is a type that describes a constraint between two widget + * attributes which must be satisfied by a #GtkConstraintLayout. + * + * Each constraint is a linear equation in the form: + * + * |[ + * target.attribute1 = source.attribute2 × multiplier + constant + * ]| + * + * The #EmeusConstraintLayout will take all the constraints associated to + * each of its children and solve the value of `attribute1` that satisfy the + * system of equations. + * + * For instance, if a #EmeusConstraintLayout has two children, `button1` and + * `button2`, and you wish for `button2` to follow `button1` with 8 pixels + * of spacing between the two, using the direction of the text, you can + * express this relationship as this constraint: + * + * |[ + * button2.start = button1.end × 1.0 + 8.0 + * ]| + * + * If you also wish `button1` to have a minimum width of 120 pixels, and + * `button2` to have the same size, you can use the following constraints: + * + * |[ + * button1.width ≥ 120.0 + * button2.width = button1.width × 1.0 + 0.0 + * ]| + * + * Each #EmeusConstraint instance references a target attribute; the target + * object to which the attribute applies is a child of a #EmeusConstraintLayout, + * and it's associated with the #EmeusConstraint instance once the constraint + * is added to a child #GtkWidget of the layout. + */ + +#include "config.h" +#include "shoes/layout/layouts.h" +#include "emeus-constraint-private.h" + +#include "emeus-expression-private.h" +#include "emeus-simplex-solver-private.h" +#include "emeus-utils-private.h" +#include "emeus-utils.h" +#include "emeus-vfl-parser-private.h" +#include +#include +//#include + +enum { + PROP_TARGET_OBJECT = 1, + PROP_TARGET_ATTRIBUTE, + PROP_RELATION, + PROP_SOURCE_OBJECT, + PROP_SOURCE_ATTRIBUTE, + PROP_MULTIPLIER, + PROP_CONSTANT, + PROP_STRENGTH, + PROP_ACTIVE, + + N_PROPERTIES +}; + +static GParamSpec *emeus_constraint_properties[N_PROPERTIES]; + +G_DEFINE_TYPE (EmeusConstraint, emeus_constraint, G_TYPE_INITIALLY_UNOWNED) + +static void +emeus_constraint_finalize (GObject *gobject) +{ + EmeusConstraint *self = EMEUS_CONSTRAINT (gobject); + + g_free (self->description); + + G_OBJECT_CLASS (emeus_constraint_parent_class)->finalize (gobject); +} + +static void +emeus_constraint_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + EmeusConstraint *self = EMEUS_CONSTRAINT (gobject); + + switch (prop_id) + { + case PROP_TARGET_OBJECT: + self->target_object = g_value_get_object (value); + break; + + case PROP_TARGET_ATTRIBUTE: + self->target_attribute = g_value_get_enum (value); + break; + + case PROP_RELATION: + self->relation = g_value_get_enum (value); + break; + + case PROP_SOURCE_OBJECT: + self->source_object = g_value_get_object (value); + break; + + case PROP_SOURCE_ATTRIBUTE: + self->source_attribute = g_value_get_enum (value); + break; + + case PROP_MULTIPLIER: + self->multiplier = g_value_get_double (value); + break; + + case PROP_CONSTANT: + self->constant = g_value_get_double (value); + break; + + case PROP_STRENGTH: + self->strength = g_value_get_int (value); + break; + + case PROP_ACTIVE: + emeus_constraint_set_active (self, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + } +} + +static void +emeus_constraint_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + EmeusConstraint *self = EMEUS_CONSTRAINT (gobject); + + switch (prop_id) + { + case PROP_TARGET_OBJECT: + g_value_set_object (value, self->target_object); + break; + + case PROP_TARGET_ATTRIBUTE: + g_value_set_enum (value, self->target_attribute); + break; + + case PROP_RELATION: + g_value_set_enum (value, self->relation); + break; + + case PROP_SOURCE_OBJECT: + g_value_set_object (value, self->source_object); + break; + + case PROP_SOURCE_ATTRIBUTE: + g_value_set_enum (value, self->source_attribute); + break; + + case PROP_MULTIPLIER: + g_value_set_double (value, self->multiplier); + break; + + case PROP_CONSTANT: + g_value_set_double (value, self->constant); + break; + + case PROP_STRENGTH: + g_value_set_int (value, self->strength); + break; + + case PROP_ACTIVE: + g_value_set_boolean (value, self->is_active); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + } +} + +static void +emeus_constraint_class_init (EmeusConstraintClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->set_property = emeus_constraint_set_property; + gobject_class->get_property = emeus_constraint_get_property; + gobject_class->finalize = emeus_constraint_finalize; + + /** + * EmeusConstraint:target-object: + * + * The target object in the constraint. + * + * Since: 1.0 + */ + emeus_constraint_properties[PROP_TARGET_OBJECT] = + g_param_spec_object ("target-object", "Target Object", NULL, + GTK_TYPE_WIDGET, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + /** + * EmeusConstraint:target-attribute: + * + * The attribute on the #EmeusConstraint:target-object in + * the constraint. + * + * Since: 1.0 + */ + emeus_constraint_properties[PROP_TARGET_ATTRIBUTE] = + g_param_spec_enum ("target-attribute", "Target Attribute", NULL, + EMEUS_TYPE_CONSTRAINT_ATTRIBUTE, + EMEUS_CONSTRAINT_ATTRIBUTE_INVALID, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + /** + * EmeusConstraint:relation: + * + * The relation between target and source attributes in + * the constraint. + * + * Since: 1.0 + */ + emeus_constraint_properties[PROP_RELATION] = + g_param_spec_enum ("relation", "Relation", NULL, + EMEUS_TYPE_CONSTRAINT_RELATION, + EMEUS_CONSTRAINT_RELATION_EQ, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + /** + * EmeusConstraint:source-object: + * + * The source object in the constraint. + * + * Since: 1.0 + */ + emeus_constraint_properties[PROP_SOURCE_OBJECT] = + g_param_spec_object ("source-object", "Source Object", NULL, + GTK_TYPE_WIDGET, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + /** + * EmeusConstraint:source-attribute: + * + * The attribute on the #EmeusConstraint:source-object in the + * constraint. + * + * Since: 1.0 + */ + emeus_constraint_properties[PROP_SOURCE_ATTRIBUTE] = + g_param_spec_enum ("source-attribute", "Source Attribute", NULL, + EMEUS_TYPE_CONSTRAINT_ATTRIBUTE, + EMEUS_CONSTRAINT_ATTRIBUTE_INVALID, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + /** + * EmeusConstraint:multiplier: + * + * The multiplication factor to be applied to the value of + * the #EmeusConstraint:source-attribute in the constraint. + * + * Since: 1.0 + */ + emeus_constraint_properties[PROP_MULTIPLIER] = + g_param_spec_double ("multiplier", "Multiplier", NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, + 1.0, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + /** + * EmeusConstraint:constant: + * + * The constant factor to be applied to the value of + * the #EmeusConstraint:source-attribute in the constraint. + * + * Since: 1.0 + */ + emeus_constraint_properties[PROP_CONSTANT] = + g_param_spec_double ("constant", "Constant", NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, + 0.0, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + /** + * EmeusConstraint:strength: + * + * The strength, or priority of the constraint. + * + * You can use any positive integer for custom priorities, or use + * the values of the #EmeusConstraintStrength enumeration for common + * strength values. + * + * Since: 1.0 + */ + emeus_constraint_properties[PROP_STRENGTH] = + g_param_spec_int ("strength", "Strength", NULL, + G_MININT, G_MAXINT, + EMEUS_CONSTRAINT_STRENGTH_REQUIRED, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + /** + * EmeusConstraint:active: + * + * Whether a #EmeusConstraint participates in the layout. + * + * Since: 1.0 + */ + emeus_constraint_properties[PROP_ACTIVE] = + g_param_spec_boolean ("active", "Active", NULL, + TRUE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (gobject_class, N_PROPERTIES, emeus_constraint_properties); +} + +static void +emeus_constraint_init (EmeusConstraint *self) +{ + self->target_attribute = EMEUS_CONSTRAINT_ATTRIBUTE_INVALID; + self->relation = EMEUS_CONSTRAINT_RELATION_EQ; + self->source_attribute = EMEUS_CONSTRAINT_ATTRIBUTE_INVALID; + self->multiplier = 1.0; + self->constant = 0.0; + self->strength = EMEUS_CONSTRAINT_STRENGTH_REQUIRED; + self->is_active = TRUE; +} + +/** + * emeus_constraint_new: (constructor) + * @target_object: (type Gtk.Widget) (nullable): the target widget, or + * %NULL for the parent layout + * @target_attribute: the attribute to set on the target widget + * @relation: the relation between the target and source attributes + * @source_object: (type Gtk.Widget) (nullable): the source widget, or + * %NULL for the parent layout + * @source_attribute: the attribute to get from the source widget + * @multiplier: the multiplication coefficient to apply to the source + * attribute + * @constant: the constant to add to the source attribute + * @strength: the priority of the constraint + * + * Creates a new constraint using a value from the source widget's attribute + * and applying it to the target widget's attribute. + * + * Returns: (transfer full): the newly created constraint + * + * Since: 1.0 + */ +EmeusConstraint * +emeus_constraint_new (gpointer target_object, + EmeusConstraintAttribute target_attribute, + EmeusConstraintRelation relation, + gpointer source_object, + EmeusConstraintAttribute source_attribute, + double multiplier, + double constant, + int strength) +{ + g_return_val_if_fail (target_object == NULL || GTK_IS_WIDGET (target_object), NULL); + g_return_val_if_fail (source_object == NULL || GTK_IS_WIDGET (source_object), NULL); + + return g_object_new (EMEUS_TYPE_CONSTRAINT, + "target-object", target_object, + "target-attribute", target_attribute, + "relation", relation, + "source-object", source_object, + "source-attribute", source_attribute, + "multiplier", multiplier, + "constant", constant, + "strength", strength, + NULL); +} + +/** + * emeus_constraint_new_constant: (constructor) + * @target_object: (type Gtk.Widget) (nullable): the target widget, + * or %NULL for the parent layout + * @target_attribute: the attribute to set on the target widget + * @relation: the relation between the target and the constant + * @constant: the constant value of the constraint + * @strength: the priority of the constraint + * + * Creates a new constant constraint. + * + * This function is the equivalent of creating a new #EmeusConstraint with: + * + * - #EmeusConstraint:source_object set to %NULL + * - #EmeusConstraint:source_attribute set to %EMEUS_CONSTRAINT_ATTRIBUTE_INVALID + * - #EmeusConstraint:multiplier set to 1.0 + * + * Returns: (transfer full): the newly created constraint + * + * Since: 1.0 + */ +EmeusConstraint * +emeus_constraint_new_constant (gpointer target_object, + EmeusConstraintAttribute target_attribute, + EmeusConstraintRelation relation, + double constant, + int strength) +{ + g_return_val_if_fail (target_object == NULL || GTK_IS_WIDGET (target_object), NULL); + + return g_object_new (EMEUS_TYPE_CONSTRAINT, + "target-object", target_object, + "target-attribute", target_attribute, + "relation", relation, + "source-object", NULL, + "source-attribute", EMEUS_CONSTRAINT_ATTRIBUTE_INVALID, + "multiplier", 1.0, + "constant", constant, + "strength", strength, + NULL); +} + +/** + * emeus_constraint_get_target_object: + * @constraint: a #EmeusConstraint + * + * Retrieves the target object of the constraint. + * + * This function may return %NULL if the @constraint is not attached + * to a #EmeusConstraintLayout. Use emeus_constraint_is_attached() to + * before calling this function. + * + * Returns: (transfer none) (type Gtk.Widget) (nullable): the target + * object + * + * Since: 1.0 + */ +gpointer +emeus_constraint_get_target_object (EmeusConstraint *constraint) +{ + g_return_val_if_fail (EMEUS_IS_CONSTRAINT (constraint), NULL); + + return constraint->target_object; +} + +/** + * emeus_constraint_get_target_attribute: + * @constraint: a #EmeusConstraint + * + * Retrieves the attribute of the target object bound by this @constraint. + * + * Returns: a constraint attribute + * + * Since: 1.0 + */ +EmeusConstraintAttribute +emeus_constraint_get_target_attribute (EmeusConstraint *constraint) +{ + g_return_val_if_fail (EMEUS_IS_CONSTRAINT (constraint), EMEUS_CONSTRAINT_ATTRIBUTE_INVALID); + + return constraint->target_attribute; +} + +/** + * emeus_constraint_get_relation: + * @constraint: a #EmeusConstraint + * + * Retrieves the relation between the target and source attributes. + * + * Returns: a constraint relation + * + * Since: 1.0 + */ +EmeusConstraintRelation +emeus_constraint_get_relation (EmeusConstraint *constraint) +{ + g_return_val_if_fail (EMEUS_IS_CONSTRAINT (constraint), EMEUS_CONSTRAINT_RELATION_EQ); + + return constraint->relation; +} + +/** + * emeus_constraint_get_source_object: + * @constraint: a #EmeusConstraint + * + * Retrieves the source object of the @constraint. + * + * Returns: (transfer none) (nullable) (type Gtk.Widget): the source object + */ +gpointer +emeus_constraint_get_source_object (EmeusConstraint *constraint) +{ + g_return_val_if_fail (EMEUS_IS_CONSTRAINT (constraint), NULL); + + return constraint->source_object; +} + +/** + * emeus_constraint_get_source_attribute: + * @constraint: a #EmeusConstraint + * + * Retrieves the attribute of the source object bound by this @constraint. + * + * Returns: a constraint attribute + * + * Since: 1.0 + */ +EmeusConstraintAttribute +emeus_constraint_get_source_attribute (EmeusConstraint *constraint) +{ + g_return_val_if_fail (EMEUS_IS_CONSTRAINT (constraint), EMEUS_CONSTRAINT_ATTRIBUTE_INVALID); + + return constraint->source_attribute; +} + +/** + * emeus_constraint_get_multiplier: + * @constraint: a #EmeusConstraint + * + * Retrieves the multiplication factor of the @constraint. + * + * Returns: a factor + * + * Since: 1.0 + */ +double +emeus_constraint_get_multiplier (EmeusConstraint *constraint) +{ + g_return_val_if_fail (EMEUS_IS_CONSTRAINT (constraint), 1.0); + + return constraint->multiplier; +} + +/** + * emeus_constraint_get_constant: + * @constraint: a #EmeusConstraint + * + * Retrieves the additional constant of the @constraint. + * + * Returns: a constant + * + * Since: 1.0 + */ +double +emeus_constraint_get_constant (EmeusConstraint *constraint) +{ + g_return_val_if_fail (EMEUS_IS_CONSTRAINT (constraint), 0.0); + + return constraint->constant; +} + +/** + * emeus_constraint_get_strength: + * @constraint: a #EmeusConstraint + * + * Retrieves the strength of the @constraint. + * + * Returns: a constraint strength + * + * Since: 1.0 + */ +int +emeus_constraint_get_strength (EmeusConstraint *constraint) +{ + g_return_val_if_fail (EMEUS_IS_CONSTRAINT (constraint), EMEUS_CONSTRAINT_STRENGTH_REQUIRED); + + return constraint->strength; +} + +/** + * emeus_constraint_is_required: + * @constraint: a #EmeusConstraint + * + * Checks whether a @constraint is marked as required. + * + * Returns: %TRUE if the constraint is required + * + * Since: 1.0 + */ +gboolean +emeus_constraint_is_required (EmeusConstraint *constraint) +{ + g_return_val_if_fail (EMEUS_IS_CONSTRAINT (constraint), FALSE); + + return constraint->strength == EMEUS_CONSTRAINT_STRENGTH_REQUIRED; +} + +const char * +emeus_constraint_to_string (EmeusConstraint *constraint) +{ + GString *buf; + + if (constraint->description != NULL) + return constraint->description; + + buf = g_string_new (NULL); + + if (constraint->target_object != NULL) + { + g_string_append (buf, G_OBJECT_TYPE_NAME (constraint->target_object)); + g_string_append (buf, "."); + } + + g_string_append (buf, get_attribute_name (constraint->target_attribute)); + g_string_append (buf, " "); + g_string_append (buf, get_relation_symbol (constraint->relation)); + g_string_append (buf, " "); + + if (constraint->target_attribute != EMEUS_CONSTRAINT_ATTRIBUTE_INVALID) + { + if (constraint->source_object != NULL) + g_string_append (buf, G_OBJECT_TYPE_NAME (constraint->source_object)); + else + g_string_append (buf, "parent"); + + g_string_append (buf, "."); + g_string_append (buf, G_OBJECT_TYPE_NAME (constraint->source_attribute)); + + if (fabs (constraint->multiplier - 1.0) > DBL_EPSILON) + g_string_append_printf (buf, " * %g", constraint->multiplier); + + if (fabs (constraint->constant - 0.0) > DBL_EPSILON) + g_string_append (buf, " + "); + } + + g_string_append_printf (buf, "%g", constraint->constant); + + g_string_append (buf, " "); + + double strength = strength_to_value (constraint->strength); + g_string_append_printf (buf, "[%s (%g)]", + strength_to_string (strength), + strength); + + constraint->description = g_string_free (buf, FALSE); + + return constraint->description; +} + +gboolean +emeus_constraint_attach (EmeusConstraint *constraint, + EmeusConstraintLayout *layout) +{ + constraint->layout = layout; + constraint->solver = emeus_constraint_layout_get_solver (layout); + + return TRUE; +} + +void +emeus_constraint_detach (EmeusConstraint *constraint) +{ + if (constraint->constraint != NULL) + simplex_solver_remove_constraint (constraint->solver, constraint->constraint); + + constraint->constraint = NULL; + constraint->layout = NULL; + constraint->solver = NULL; +} + +/** + * emeus_constraint_is_attached: + * @constraint: a #EmeusConstraint + * + * Checks whether the @constraint is attached to a child of a + * #EmeusConstraintLayout. + * + * Returns: %TRUE if the constraint is attached + * + * Since: 1.0 + */ +gboolean +emeus_constraint_is_attached (EmeusConstraint *constraint) +{ + g_return_val_if_fail (EMEUS_IS_CONSTRAINT (constraint), FALSE); + + return constraint->solver != NULL; +} + +/** + * emeus_constraint_set_active: + * @constraint: a #EmeusConstraint + * @active: whether the @constraint should be active + * + * Sets whether a @constraint should be actively used by + * a #EmeusConstraintLayout or not. + * + * Since: 1.0 + */ +void +emeus_constraint_set_active (EmeusConstraint *constraint, + gboolean active) +{ + g_return_if_fail (EMEUS_IS_CONSTRAINT (constraint)); + + active = !!active; + + if (constraint->is_active == active) + return; + + constraint->is_active = active; + + if (constraint->layout != NULL) + { + if (constraint->is_active) + emeus_constraint_layout_activate_constraint (constraint->layout, constraint); + else + emeus_constraint_layout_deactivate_constraint (constraint->layout, constraint); + } + + g_object_notify_by_pspec (G_OBJECT (constraint), emeus_constraint_properties[PROP_ACTIVE]); +} + +/** + * emeus_constraint_get_active: + * @constraint: a #EmeusConstraint + * + * Checks whether a @constraint is active. + * + * Returns: %TRUE if the constraint is active + * + * Since: 1.0 + */ +gboolean +emeus_constraint_get_active (EmeusConstraint *constraint) +{ + g_return_val_if_fail (EMEUS_IS_CONSTRAINT (constraint), FALSE); + + return constraint->is_active; +} + +/** + * emeus_create_constraints_from_description: + * @lines: (array length=n_lines): an array of Visual Format Language lines + * defining a set of constraints + * @n_lines: the number of lines + * @hspacing: default horizontal spacing value, or -1 for the fallback value + * @vspacing: default vertical spacing value, or -1 for the fallback value + * @views: (element-type utf8 Gtk.Widget): a dictionary of [ name, widget ] + * pairs; the `name` keys map to the view names in the VFL lines, while + * the `widget` values map to the children of a #EmeusConstraintLayout + * @metrics: (element-type utf8 double) (nullable): a dictionary of + * [ name, value ] pairs; the `name` keys map to the metric names in the + * VFL lines, while the `value` values maps to its numeric value + * + * Creates a list of constraints they formal description using the + * [Visual Format Language](https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage.html) + * syntax. + * + * The @views dictionary is used to match widgets to the symbolic view name + * inside the VFL; the @metrics dictionary is used to substitute symbolic + * names with numeric values, to avoid hard coding constants inside the + * VFL strings. + * + * The VFL grammar is: + * + * |[ + * = ()? + * ()? + * ()* + * ()? + * = 'H' | 'V' + * = '|' + * = '' | '-' '-' | '-' + * = | + * = | + * = '(' (',' )* ')' + * = ()? ()? ('@' )? + * = '==' | '<=' | '>=' + * = | | ('.' )? + * = | 'required' | 'strong' | 'medium' | 'weak' + * = + * = ()? ()? + * = [ '*' | '/' ] + * = [ '+' | '-' ] + * = [A-Za-z_]([A-Za-z0-9_]*) // A C identifier + * = [A-Za-z_]([A-Za-z0-9_]*) // A C identifier + * = 'top' | 'bottom' | 'left' | 'right' | 'width' | 'height' | + * 'start' | 'end' | 'centerX' | 'centerY' | 'baseline' + * // A positive real number parseable by g_ascii_strtod() + * // A real number parseable by g_ascii_strtod() + * ]| + * + * **Note**: The VFL grammar is slightly different than the one defined by Apple, + * as it uses symbolic values for the constraint's priority instead of numeric + * values. + * + * Examples of VFL descriptions are: + * + * |[ + * // Default spacing + * [button]-[textField] + * + * // Width constraint + * [button(>=50)] + * + * // Connection to super view + * |-50-[purpleBox]-50-| + * + * // Vertical layout + * V:[topField]-10-[bottomField] + * + * // Flush views + * [maroonView][blueView] + * + * // Priority + * [button(100@strong)] + * + * // Equal widths + * [button1(==button2)] + * + * // Multiple predicates + * [flexibleButton(>=70,<=100)] + * + * // A complete line of layout + * |-[find]-[findNext]-[findField(>=20)]-| + * + * // Operators + * [button1(button2 / 3 + 50)] + * + * // Named attributes + * [button1(==button2.height)] + * ]| + * + * Returns: (transfer container) (element-type Emeus.Constraint): a list of #EmeusConstraint + * instances, to be used with an #EmeusConstraintLayout + * + * Since: 1.0 + */ +GList * +emeus_create_constraints_from_description (const char * const lines[], + guint n_lines, + int hspacing, + int vspacing, + GHashTable *views, + GHashTable *metrics) +{ + g_return_val_if_fail (lines != NULL && n_lines != 0, NULL); + + VflParser *parser = vfl_parser_new (hspacing, vspacing, metrics, views); + + GList *res = NULL; + + for (guint i = 0; i < n_lines; i++) + { + const char *line = lines[i]; + GError *error = NULL; + + vfl_parser_parse_line (parser, line, -1, &error); + if (error != NULL) + { + int offset = vfl_parser_get_error_offset (parser); + int range = vfl_parser_get_error_range (parser); + char *squiggly = NULL; + + if (range > 0) + { + squiggly = g_new (char, range + 1); + + for (int r = 0; r < range; i++) + squiggly[r] = '~'; + + squiggly[range] = '\0'; + } + + g_critical ("VFL parsing error:%d:%d: %s\n" + "%s\n" + "%*s^%s", + i, offset + 1, + error->message, + line, + offset, " ", squiggly != NULL ? squiggly : ""); + + g_free (squiggly); + g_error_free (error); + vfl_parser_free (parser); + continue; + } + + int n_constraints = 0; + VflConstraint *constraints = vfl_parser_get_constraints (parser, &n_constraints); + for (int j = 0; j < n_constraints; j++) + { + const VflConstraint *c = &constraints[j]; + gpointer source, target; + EmeusConstraintAttribute source_attr, target_attr; + EmeusConstraintRelation relation; + + target = g_hash_table_lookup (views, c->view1); + target_attr = attribute_from_name (c->attr1); + + if (c->view2 != NULL) + source = g_hash_table_lookup (views, c->view2); + else + source = NULL; + + if (c->attr2 != NULL) + source_attr = attribute_from_name (c->attr2); + else + source_attr = EMEUS_CONSTRAINT_ATTRIBUTE_INVALID; + + relation = operator_to_relation (c->relation); + + EmeusConstraint *constraint = + emeus_constraint_new (target, target_attr, + relation, + source, source_attr, + c->multiplier, + c->constant, + c->strength); + + res = g_list_prepend (res, constraint); + } + + g_free (constraints); + } + + return g_list_reverse (res); +} diff --git a/shoes/layout/emeus-constraint.h b/shoes/layout/emeus-constraint.h new file mode 100644 index 00000000..a231483d --- /dev/null +++ b/shoes/layout/emeus-constraint.h @@ -0,0 +1,84 @@ +/* emeus-constraint.h: The base constraint object + * + * Copyright 2016 Endless + * + * 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 . + */ + +#pragma once + +#include "shoes/layout/emeus-types.h" + +G_BEGIN_DECLS + +#define EMEUS_TYPE_CONSTRAINT (emeus_constraint_get_type()) + +/** + * EmeusConstraint: + * + * The representation of a constraint inside an #EmeusConstraintLayout. + * + * The contents of the `EmeusConstraint` structure are private and should + * never be accessed directly. + * + * Since: 1.0 + */ +extern +G_DECLARE_FINAL_TYPE (EmeusConstraint, emeus_constraint, EMEUS, CONSTRAINT, GInitiallyUnowned) + +extern +EmeusConstraint *emeus_constraint_new (gpointer target_object, + EmeusConstraintAttribute target_attribute, + EmeusConstraintRelation relation, + gpointer source_object, + EmeusConstraintAttribute source_attribute, + double multiplier, + double constant, + int strength); +extern +EmeusConstraint *emeus_constraint_new_constant (gpointer target_object, + EmeusConstraintAttribute target_attribute, + EmeusConstraintRelation relation, + double constant, + int strength); + +extern +gpointer emeus_constraint_get_target_object(EmeusConstraint *constraint); +extern +EmeusConstraintAttribute emeus_constraint_get_target_attribute (EmeusConstraint *constraint); +extern +EmeusConstraintRelation emeus_constraint_get_relation (EmeusConstraint *constraint); +extern +gpointer emeus_constraint_get_source_object (EmeusConstraint *constraint); +extern +EmeusConstraintAttribute emeus_constraint_get_source_attribute (EmeusConstraint *constraint); +extern +double emeus_constraint_get_multiplier (EmeusConstraint *constraint); +extern +double emeus_constraint_get_constant (EmeusConstraint *constraint); +extern +int emeus_constraint_get_strength (EmeusConstraint *constraint); +extern +gboolean emeus_constraint_is_required (EmeusConstraint *constraint); + +extern +gboolean emeus_constraint_is_attached (EmeusConstraint *constraint); + +extern +void emeus_constraint_set_active (EmeusConstraint *constraint, + gboolean active); +extern +gboolean emeus_constraint_get_active(EmeusConstraint *constraint); + +G_END_DECLS diff --git a/shoes/layout/emeus-expression-private.h b/shoes/layout/emeus-expression-private.h new file mode 100644 index 00000000..3beb9832 --- /dev/null +++ b/shoes/layout/emeus-expression-private.h @@ -0,0 +1,190 @@ +/* emeus-expression-private.h: A set of terms and a constant + * + * Copyright 2016 Endless + * + * 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 . + */ + +#pragma once + +#include "emeus-types-private.h" + +G_BEGIN_DECLS + +static inline bool +variable_is_dummy (const Variable *variable) +{ + return variable->type == VARIABLE_DUMMY; +} + +static inline bool +variable_is_objective (const Variable *variable) +{ + return variable->type == VARIABLE_OBJECTIVE; +} + +static inline bool +variable_is_slack (const Variable *variable) +{ + return variable->type == VARIABLE_SLACK; +} + +static inline bool +variable_is_external (const Variable *variable) +{ + return variable->is_external; +} + +static inline bool +variable_is_pivotable (const Variable *variable) +{ + return variable->is_pivotable; +} + +static inline gboolean +variable_is_restricted (const Variable *variable) +{ + return variable->is_restricted; +} + +static inline double +variable_get_value (const Variable *variable) +{ + if (variable_is_dummy (variable) || + variable_is_objective (variable) || + variable_is_slack (variable)) + return 0.0; + + return variable->value; +} + +Variable *variable_new (SimplexSolver *solver, + VariableType type); +Variable *variable_ref (Variable *variable); +void variable_unref (Variable *variable); + +void variable_set_value (Variable *variable, + double value); + +char *variable_to_string (const Variable *variable); + +void variable_set_name (Variable *variable, + const char *name); + +void variable_set_prefix (Variable *variable, + const char *prefix); + +static inline Variable * +term_get_variable (const Term *term) +{ + return term->variable; +} + +static inline double +term_get_coefficient (const Term *term) +{ + return term->coefficient; +} + +static inline bool +expression_is_constant (const Expression *expression) +{ + return expression->terms == NULL; +} + +static inline double +expression_get_constant (const Expression *expression) +{ + return expression->constant; +} + +Expression *expression_new (SimplexSolver *solver, + double constant); + +Expression *expression_new_from_constant (double constant); +Expression *expression_new_from_variable (Variable *variable); + +Expression *expression_clone (Expression *expression); + +Expression *expression_ref (Expression *expression); +void expression_unref (Expression *expression); + +void expression_set_constant (Expression *expression, + double constant); + +void expression_set_variable (Expression *expression, + Variable *variable, + double coefficient); + +void expression_add_variable (Expression *expression, + Variable *variable, + double value, + Variable *subject); + +void expression_remove_variable (Expression *expression, + Variable *variable, + Variable *subject); + +bool expression_has_variable (Expression *expression, + Variable *variable); + +void expression_add_expression (Expression *a, + Expression *b, + double n, + Variable *subject); + +Expression *expression_plus (Expression *expression, + double constant); +Expression *expression_times (Expression *expression, + double multiplier); +Expression *expression_minus (Expression *expression, + double constant); +Expression *expression_divide (Expression *expression, + double constant); + +Expression *expression_plus_variable (Expression *expression, + Variable *variable); +Expression *expression_minus_variable (Expression *expression, + Variable *variable); + +double expression_get_coefficient (const Expression *expression, + Variable *variable); + +double expression_get_value (const Expression *expression); + +typedef bool (* ExpressionForeachTermFunc) (Term *term, gpointer data); + +void expression_terms_foreach (Expression *expression, + ExpressionForeachTermFunc func, + gpointer data); + +GList *expression_get_terms (Expression *expression); + +void expression_change_subject (Expression *expression, + Variable *old_subject, + Variable *new_subject); + +double expression_new_subject (Expression *expression, + Variable *subject); + +void expression_substitute_out (Expression *expression, + Variable *out_var, + Expression *expr, + Variable *subject); + +Variable *expression_get_pivotable_variable (Expression *expression); + +char *expression_to_string (const Expression *expression); + +G_END_DECLS diff --git a/shoes/layout/emeus-expression.c b/shoes/layout/emeus-expression.c new file mode 100644 index 00000000..aea2d579 --- /dev/null +++ b/shoes/layout/emeus-expression.c @@ -0,0 +1,747 @@ +/* emeus-expression-private.c: A set of terms and a constant + * + * Copyright 2016 Endless + * + * 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 . + */ + +#include "config.h" + +#include "emeus-expression-private.h" +#include "emeus-simplex-solver-private.h" +#include "emeus-utils-private.h" + +#include +#include +#include + +/* Monotonic counter for variables; we use this when comparing + * variables because the solver relies on ordering when pivoting + * and optimizing the tableau + */ +static unsigned long variable_id; + +static void +dummy_variable_init (Variable *v) +{ + v->is_external = FALSE; + v->is_pivotable = FALSE; + v->is_restricted = TRUE; +} + +static void +objective_variable_init (Variable *v) +{ + v->is_external = FALSE; + v->is_pivotable = FALSE; + v->is_restricted = FALSE; +} + +static void +slack_variable_init (Variable *v) +{ + v->is_external = FALSE; + v->is_pivotable = TRUE; + v->is_restricted = TRUE; +} + +static void +regular_variable_init (Variable *v) +{ + v->is_external = TRUE; + v->is_pivotable = FALSE; + v->is_restricted = FALSE; +} + +Variable * +variable_new (SimplexSolver *solver, + VariableType type) +{ + Variable *res = g_slice_new0 (Variable); + + res->solver = solver; + res->id_ = ++variable_id; + res->type = type; + res->ref_count = 1; + res->name = NULL; + res->prefix = NULL; + + switch (type) + { + case VARIABLE_DUMMY: + dummy_variable_init (res); + break; + + case VARIABLE_OBJECTIVE: + objective_variable_init (res); + break; + + case VARIABLE_SLACK: + slack_variable_init (res); + break; + + case VARIABLE_REGULAR: + regular_variable_init (res); + break; + } + + return res; +} + +static void +variable_free (Variable *variable) +{ + if (variable == NULL) + return; + + g_slice_free (Variable, variable); +} + +Variable * +variable_ref (Variable *variable) +{ + variable->ref_count += 1; + + return variable; +} + +void +variable_unref (Variable *variable) +{ + variable->ref_count -= 1; + if (variable->ref_count == 0) + variable_free (variable); +} + +void +variable_set_value (Variable *variable, + double value) +{ + variable->value = value; +} + +void +variable_set_name (Variable *variable, + const char *name) +{ + variable->name = name; +} + +void +variable_set_prefix (Variable *variable, + const char *prefix) +{ + variable->prefix = prefix; +} + +char * +variable_to_string (const Variable *variable) +{ + GString *buf = g_string_new (NULL); + + if (variable == NULL) + g_string_append (buf, ""); + else + { + switch (variable->type) + { + case VARIABLE_DUMMY: + g_string_append (buf, "dummy"); + break; + case VARIABLE_OBJECTIVE: + g_string_append (buf, "objective"); + break; + case VARIABLE_SLACK: + g_string_append (buf, "slack"); + break; + case VARIABLE_REGULAR: + break; + } + + g_string_append_c (buf, '['); + + if (variable->prefix != NULL) + { + g_string_append (buf, variable->prefix); + g_string_append_c (buf, '.'); + } + + if (variable->name != NULL) + g_string_append (buf, variable->name); + + if (variable->type == VARIABLE_REGULAR) + { + g_string_append_c (buf, ':'); + g_string_append_printf (buf, "%g", variable->value); + } + + g_string_append_c (buf, ']'); + } + + return g_string_free (buf, FALSE); +} + +static Term * +term_new (Variable *variable, + double coefficient) +{ + Term *t = g_slice_new (Term); + + t->variable = variable_ref (variable); + t->coefficient = coefficient; + + return t; +} + +static void +term_free (Term *term) +{ + if (term == NULL) + return; + + variable_unref (term->variable); + + g_slice_free (Term, term); +} + +static void +expression_add_term (Expression *expression, + Term *term) +{ + if (expression->terms == NULL) + expression->terms = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) term_free); + + expression->ordered_terms = g_list_prepend (expression->ordered_terms, term); + g_hash_table_insert (expression->terms, term->variable, term); +} + +static Expression * +expression_new_full (SimplexSolver *solver, + Variable *variable, + double coefficient, + double constant) +{ + Expression *res = g_slice_new (Expression); + + res->solver = solver; + res->constant = constant; + res->terms = NULL; + res->ordered_terms = NULL; + res->ref_count = 1; + + if (variable != NULL) + expression_add_term (res, term_new (variable, coefficient)); + + return res; +} + +Expression * +expression_new (SimplexSolver *solver, + double constant) +{ + return expression_new_full (solver, NULL, 0.0, constant); +} + +Expression * +expression_new_from_variable (Variable *variable) +{ + return expression_new_full (variable->solver, variable, 1.0, 0.0); +} + +Expression * +expression_new_from_constant (double constant) +{ + return expression_new_full (NULL, NULL, 0.0, constant); +} + +Expression * +expression_clone (Expression *expression) +{ + Expression *clone = expression_new_full (expression->solver, + NULL, 0.0, + expression->constant); + GHashTableIter iter; + gpointer value_p; + + if (expression->terms == NULL) + return clone; + + g_hash_table_iter_init (&iter, expression->terms); + while (g_hash_table_iter_next (&iter, NULL, &value_p)) + { + const Term *t = value_p; + + expression_add_term (clone, term_new (t->variable, t->coefficient)); + } + + return clone; +} + +Expression * +expression_ref (Expression *expression) +{ + if (expression == NULL) + return NULL; + + expression->ref_count += 1; + + return expression; +} + +void +expression_unref (Expression *expression) +{ + if (expression == NULL) + return; + + expression->ref_count -= 1; + + if (expression->ref_count == 0) + { + if (expression->terms != NULL) + { + g_list_free (expression->ordered_terms); + g_hash_table_unref (expression->terms); + } + + g_slice_free (Expression, expression); + } +} + +void +expression_set_constant (Expression *expression, + double constant) +{ + expression->constant = constant; +} + +void +expression_add_variable (Expression *expression, + Variable *variable, + double coefficient, + Variable *subject) +{ + if (expression->terms != NULL) + { + Term *t = g_hash_table_lookup (expression->terms, variable); + + if (t != NULL) + { + double new_coefficient = term_get_coefficient (t) + coefficient; + + if (approx_val (new_coefficient, 0.0)) + { + if (expression->solver != NULL) + simplex_solver_note_removed_variable (expression->solver, variable, subject); + + expression_remove_variable (expression, variable, subject); + } + else + t->coefficient = new_coefficient; + + return; + } + } + + if (!approx_val (coefficient, 0.0)) + { + expression_add_term (expression, term_new (variable, coefficient)); + + if (expression->solver != NULL) + simplex_solver_note_added_variable (expression->solver, variable, subject); + } +} + +void +expression_remove_variable (Expression *expression, + Variable *variable, + Variable *subject) +{ + Term *term; + + if (expression->terms == NULL) + return; + + term = g_hash_table_lookup (expression->terms, variable); + if (term == NULL) + return; + + variable_ref (variable); + + if (subject != NULL) + variable_ref (subject); + + expression->ordered_terms = g_list_remove (expression->ordered_terms, term); + g_hash_table_remove (expression->terms, variable); + + if (subject != NULL) + variable_unref (subject); + + variable_unref (variable); +} + +bool +expression_has_variable (Expression *expression, + Variable *variable) +{ + if (expression->terms == NULL) + return false; + + return g_hash_table_lookup (expression->terms, variable) != NULL; +} + +void +expression_set_variable (Expression *expression, + Variable *variable, + double coefficient) +{ + if (expression->terms != NULL) + { + Term *t = g_hash_table_lookup (expression->terms, variable); + + if (t != NULL) + { + t->coefficient = coefficient; + return; + } + } + + expression_add_term (expression, term_new (variable, coefficient)); +} + +void +expression_add_expression (Expression *a, + Expression *b, + double n, + Variable *subject) +{ + GList *l; + + a->constant += (n * b->constant); + + for (l = g_list_last (b->ordered_terms); l != NULL; l = l->prev) + { + Term *t = l->data; + + expression_add_variable (a, t->variable, n * t->coefficient, subject); + } +} + +double +expression_get_coefficient (const Expression *expression, + Variable *variable) +{ + Term *t; + + if (expression->terms == NULL) + return 0.0; + + t = g_hash_table_lookup (expression->terms, variable); + if (t == NULL) + return 0.0; + + return term_get_coefficient (t); +} + +double +expression_get_value (const Expression *expression) +{ + double res = expression->constant; + const GList *l; + + for (l = g_list_last (expression->ordered_terms); l != NULL; l = l->prev) + { + const Term *t = l->data; + + res += (t->coefficient * variable_get_value (t->variable)); + } + + return res; +} + +GList * +expression_get_terms (Expression *expression) +{ + if (expression->terms == NULL) + return NULL; + + return g_list_reverse (g_list_copy (expression->ordered_terms)); +} + +void +expression_terms_foreach (Expression *expression, + ExpressionForeachTermFunc func, + gpointer data) +{ + GList *l; + + if (expression->terms == NULL) + return; + + for (l = g_list_last (expression->ordered_terms); l != NULL; l = l->prev) + { + Term *t = l->data; + + g_assert (t != NULL && t->variable != NULL); + + if (!func (t, data)) + break; + } +} + +Expression * +expression_plus (Expression *expression, + double constant) +{ + Expression *e = expression_new (expression->solver, constant); + + expression_add_expression (expression, e, 1.0, NULL); + + expression_unref (e); + + return expression; +} + +Expression * +expression_minus (Expression *expression, + double constant) +{ + return expression_plus (expression, constant * -1.0); +} + +Expression * +expression_plus_variable (Expression *expression, + Variable *variable) +{ + Expression *e = expression_new_from_variable (variable); + + expression_add_expression (expression, e, 1.0, NULL); + + expression_unref (e); + + return expression; +} + +Expression * +expression_minus_variable (Expression *expression, + Variable *variable) +{ + Expression *e = expression_new_from_variable (variable); + + expression_add_expression (expression, e, -1.0, NULL); + + expression_unref (e); + + return expression; +} + +Expression * +expression_times (Expression *expression, + double multiplier) +{ + GHashTableIter iter; + gpointer value_p; + + expression->constant *= multiplier; + + if (expression->terms == NULL) + return expression; + + g_hash_table_iter_init (&iter, expression->terms); + while (g_hash_table_iter_next (&iter, NULL, &value_p)) + { + Term *t = value_p; + + t->coefficient *= multiplier; + } + + return expression; +} + +Expression * +expression_divide (Expression *expression, + double factor) +{ + if (approx_val (factor, 0.0)) + return expression; + + return expression_times (expression, 1.0 / factor); +} + +void +expression_change_subject (Expression *expression, + Variable *old_subject, + Variable *new_subject) +{ + expression_set_variable (expression, + old_subject, + expression_new_subject (expression, new_subject)); +} + +double +expression_new_subject (Expression *expression, + Variable *subject) +{ + double reciprocal = 1.0; + Term *term; + + g_assert (!expression_is_constant (expression)); + + term = g_hash_table_lookup (expression->terms, subject); + g_assert (term != NULL); + g_assert (term->coefficient != 0.0); + + reciprocal = 1.0 / term->coefficient; + + expression->ordered_terms = g_list_remove (expression->ordered_terms, term); + g_hash_table_remove (expression->terms, subject); + + expression_times (expression, -reciprocal); + + return reciprocal; +} + +void +expression_substitute_out (Expression *expression, + Variable *out_var, + Expression *expr, + Variable *subject) +{ + if (expression->terms == NULL) + return; + + double multiplier = expression_get_coefficient (expression, out_var); + + expression_remove_variable (expression, out_var, NULL); + + expression->constant = expression->constant + multiplier * expr->constant; + + GHashTableIter iter; + gpointer value_p; + g_hash_table_iter_init (&iter, expr->terms); + while (g_hash_table_iter_next (&iter, NULL, &value_p)) + { + Variable *clv = term_get_variable (value_p); + double coeff = term_get_coefficient (value_p); + + double old_coefficient = expression_get_coefficient (expression, clv); + + if (expression_has_variable (expression, clv)) + { + double new_coefficient = old_coefficient + multiplier * coeff; + + if (approx_val (new_coefficient, 0.0)) + { + if (expression->solver) + simplex_solver_note_removed_variable (expression->solver, clv, subject); + + expression_remove_variable (expression, clv, subject); + } + else + expression_set_variable (expression, clv, new_coefficient); + } + else + { + expression_set_variable (expression, clv, multiplier * coeff); + + if (expression->solver) + simplex_solver_note_added_variable (expression->solver, clv, subject); + } + } +} + +Variable * +expression_get_pivotable_variable (Expression *expression) +{ + GHashTableIter iter; + gpointer key_p; + + if (expression->terms == NULL) + { + g_critical ("Expression %p is a constant", expression); + return NULL; + } + + g_hash_table_iter_init (&iter, expression->terms); + while (g_hash_table_iter_next (&iter, &key_p, NULL)) + { + if (variable_is_pivotable (key_p)) + return key_p; + } + + return NULL; +} + +static int +sort_by_variable_name (gconstpointer a, + gconstpointer b) +{ + const Variable *va = a; + const Variable *vb = b; + + if (va == vb) + return 0; + + return g_strcmp0 (va->name, vb->name); +} + +char * +expression_to_string (const Expression *expression) +{ + GString *buf; + GList *keys, *l; + bool needs_plus = false; + + if (expression == NULL) + return g_strdup (""); + + buf = g_string_new (NULL); + + if (!approx_val (expression->constant, 0.0) || expression->terms == NULL) + { + g_string_append_printf (buf, "%g", expression->constant); + needs_plus = true; + } + + if (expression->terms == NULL) + return g_string_free (buf, FALSE); + + keys = g_hash_table_get_keys (expression->terms); + keys = g_list_sort (keys, sort_by_variable_name); + + for (l = keys; l != NULL; l = l->next) + { + Term *t = g_hash_table_lookup (expression->terms, l->data); + Variable *clv = term_get_variable (t); + double coeff = term_get_coefficient (t); + char *str = variable_to_string (clv); + + if (needs_plus) + g_string_append (buf, " + "); + + if (approx_val (coeff, 1.0)) + g_string_append_printf (buf, "%s", str); + else + g_string_append_printf (buf, "%g * %s", coeff, str); + + g_free (str); + + if (!needs_plus) + needs_plus = true; + } + + g_list_free (keys); + + return g_string_free (buf, FALSE); +} diff --git a/shoes/layout/emeus-macros-private.h b/shoes/layout/emeus-macros-private.h new file mode 100644 index 00000000..bd2506c6 --- /dev/null +++ b/shoes/layout/emeus-macros-private.h @@ -0,0 +1,41 @@ +/* emeus-macros-private.h: Private macros + * + * Copyright 2016 Endless + * + * 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 . + */ + +#pragma once + +#include + +#define EMEUS_ENUM_VALUE(EnumValue,EnumNick) { EnumValue, #EnumValue, EnumNick }, + +#define EMEUS_DEFINE_ENUM_TYPE(TypeName,type_name,values) \ +GType \ +type_name ## _get_type (void) \ +{ \ + static volatile gsize emeus_define_id__volatile = 0; \ + if (g_once_init_enter (&emeus_define_id__volatile)) \ + { \ + static const GEnumValue v[] = { \ + values \ + { 0, NULL, NULL }, \ + }; \ + GType emeus_define_id = \ + g_enum_register_static (g_intern_static_string (#TypeName), v); \ + g_once_init_leave (&emeus_define_id__volatile, emeus_define_id); \ + } \ + return emeus_define_id__volatile; \ +} diff --git a/shoes/layout/emeus-simplex-solver-private.h b/shoes/layout/emeus-simplex-solver-private.h new file mode 100644 index 00000000..62833d40 --- /dev/null +++ b/shoes/layout/emeus-simplex-solver-private.h @@ -0,0 +1,108 @@ +/* emeus-simplex-solver-private.h: The constraint solver + * + * Copyright 2016 Endless + * + * 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 . + */ + +#pragma once + +#include "emeus-types-private.h" + +G_BEGIN_DECLS + +static inline bool +constraint_is_inequality (const Constraint *constraint) +{ + return constraint->op_type != OPERATOR_TYPE_EQ; +} + +static inline bool +constraint_is_required (const Constraint *constraint) +{ + return constraint->strength >= STRENGTH_REQUIRED; +} + +static inline bool +constraint_is_stay (const Constraint *constraint) +{ + return constraint->is_stay; +} + +static inline bool +constraint_is_edit (const Constraint *constraint) +{ + return constraint->is_edit; +} + +void simplex_solver_init (SimplexSolver *solver); +void simplex_solver_clear (SimplexSolver *solver); +void simplex_solver_reset (SimplexSolver *solver); + +void simplex_solver_freeze (SimplexSolver *solver); +void simplex_solver_thaw (SimplexSolver *solver); + +Variable *simplex_solver_create_variable (SimplexSolver *solver, + const char *name, + double value); +Expression *simplex_solver_create_expression (SimplexSolver *solver, + double constant); + +Constraint *simplex_solver_add_constraint (SimplexSolver *solver, + Variable *variable, + OperatorType op, + Expression *expression, + double strength); + +Constraint *simplex_solver_add_stay_variable (SimplexSolver *solver, + Variable *variable, + double strength); + +Constraint *simplex_solver_add_edit_variable (SimplexSolver *solver, + Variable *variable, + double strength); + +bool simplex_solver_has_edit_variable (SimplexSolver *solver, + Variable *variable); + +bool simplex_solver_has_stay_variable (SimplexSolver *solver, + Variable *variable); + +void simplex_solver_remove_constraint (SimplexSolver *solver, + Constraint *constraint); + +void simplex_solver_remove_edit_variable (SimplexSolver *solver, + Variable *variable); + +void simplex_solver_remove_stay_variable (SimplexSolver *solver, + Variable *variable); + +void simplex_solver_suggest_value (SimplexSolver *solver, + Variable *variable, + double value); + +void simplex_solver_resolve (SimplexSolver *solver); + +void simplex_solver_begin_edit (SimplexSolver *solver); +void simplex_solver_end_edit (SimplexSolver *solver); + +/* Internal */ +void simplex_solver_note_added_variable (SimplexSolver *solver, + Variable *variable, + Variable *subject); +void simplex_solver_note_removed_variable (SimplexSolver *solver, + Variable *variable, + Variable *subject); + +G_END_DECLS diff --git a/shoes/layout/emeus-simplex-solver.c b/shoes/layout/emeus-simplex-solver.c new file mode 100644 index 00000000..ff557828 --- /dev/null +++ b/shoes/layout/emeus-simplex-solver.c @@ -0,0 +1,1982 @@ +/* emeus-simplex-solver.c: The constraint solver + * + * Copyright 2016 Endless + * + * 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 . + */ + +#include "config.h" + +#include "emeus-simplex-solver-private.h" + +#include "emeus-expression-private.h" +#include "emeus-types-private.h" +#include "emeus-utils-private.h" + +#include +#include +#include +#include + +typedef struct { + Constraint *constraint; + + Variable *eplus; + Variable *eminus; + + double prev_constant; +} EditInfo; + +typedef struct { + Constraint *constraint; +} StayInfo; + +typedef struct { + /* HashSet, owns a reference */ + GHashTable *set; + GList *ordered_set; +} VariableSet; + +typedef struct { + VariableSet *set; + GList *current; + GList *next; +} VariableSetIter; + +typedef struct { + Variable *first; + Variable *second; +} VariablePair; + +static void +constraint_free (gpointer data) +{ + Constraint *constraint = data; + + if (data == NULL) + return; + + expression_unref (constraint->expression); + + if (constraint->is_edit || constraint->is_stay) + { + g_assert (constraint->variable != NULL); + variable_unref (constraint->variable); + } + + g_slice_free (Constraint, constraint); +} + +static char * +constraint_to_string (const Constraint *constraint) +{ + GString *buf = g_string_new (NULL); + char *str; + + if (constraint->is_stay) + g_string_append (buf, "[stay]"); + + if (constraint->is_edit) + g_string_append (buf, "[edit]"); + + str = expression_to_string (constraint->expression); + g_string_append (buf, str); + g_free (str); + + g_string_append (buf, " "); + g_string_append (buf, operator_to_string (constraint->op_type)); + g_string_append (buf, " 0.0 "); + + g_string_append_printf (buf, "[strength:%s]", strength_to_string (constraint->strength)); + + return g_string_free (buf, FALSE); +} + +static void +edit_info_free (gpointer data) +{ + g_slice_free (EditInfo, data); +} + +static void +stay_info_free (gpointer data) +{ + g_slice_free (StayInfo, data); +} + +static void +variable_set_free (gpointer data) +{ + VariableSet *set = data; + + if (data == NULL) + return; + + g_list_free (set->ordered_set); + g_hash_table_unref (set->set); + g_slice_free (VariableSet, set); +} + +static VariableSet * +variable_set_new (void) +{ + VariableSet *res = g_slice_new (VariableSet); + + res->set = g_hash_table_new_full (NULL, NULL, (GDestroyNotify) variable_unref, NULL); + res->ordered_set = NULL; + + return res; +} + +static int +sort_by_variable_id (gconstpointer a, + gconstpointer b) +{ + const Variable *va = a, *vb = b; + + if (va == vb) + return 0; + + return va->id_ - vb->id_; +} + +static void +variable_set_add_variable (VariableSet *set, + Variable *variable) +{ + if (g_hash_table_contains (set->set, variable)) + return; + + g_hash_table_add (set->set, variable_ref (variable)); + set->ordered_set = g_list_insert_sorted (set->ordered_set, variable, sort_by_variable_id); +} + +static bool +variable_set_remove_variable (VariableSet *set, + Variable *variable) +{ + if (g_hash_table_contains (set->set, variable)) + { + set->ordered_set = g_list_remove (set->ordered_set, variable); + g_hash_table_remove (set->set, variable); + return true; + } + + return false; +} + +static void +variable_set_iter_init (VariableSet *set, + VariableSetIter *iter) +{ + iter->set = set; + iter->current = NULL; + iter->next = NULL; +} + +static bool +variable_set_iter_next (VariableSetIter *iter, + Variable **variable_p) +{ + if (iter->current == NULL) + iter->current = iter->set->ordered_set; + else + iter->current = iter->current->next; + + if (iter->current != NULL) + *variable_p = iter->current->data; + + return iter->current != NULL; +} + +static int +variable_set_get_size (VariableSet *set) +{ + return g_hash_table_size (set->set); +} + +static VariablePair * +variable_pair_new (Variable *first, + Variable *second) +{ + VariablePair *res = g_slice_new (VariablePair); + + res->first = variable_ref (first); + res->second = variable_ref (second); + + return res; +} + +static void +variable_pair_free (gpointer data) +{ + VariablePair *pair = data; + + if (data == NULL) + return; + + if (pair->first != NULL) + variable_unref (pair->first); + if (pair->second != NULL) + variable_unref (pair->second); + + g_slice_free (VariablePair, pair); +} + +void +simplex_solver_init (SimplexSolver *solver) +{ + if (solver->initialized) + { + g_critical ("The SimplexSolver %p has already been initialized", solver); + return; + } + + memset (solver, 0, sizeof (SimplexSolver)); + + /* HashTable; owns keys and values */ + solver->columns = g_hash_table_new_full (NULL, NULL, + (GDestroyNotify) variable_unref, + variable_set_free); + + /* HashTable; owns keys and values */ + solver->rows = g_hash_table_new_full (NULL, NULL, + (GDestroyNotify) variable_unref, + (GDestroyNotify) expression_unref); + + /* HashSet; owns keys */ + solver->external_rows = g_hash_table_new_full (NULL, NULL, + (GDestroyNotify) variable_unref, + NULL); + + /* Vec */ + solver->infeasible_rows = g_ptr_array_new (); + + /* HashSet; owns keys */ + solver->external_parametric_vars = g_hash_table_new_full (NULL, NULL, + (GDestroyNotify) variable_unref, + NULL); + + /* Vec */ + solver->stay_error_vars = g_ptr_array_new_with_free_func (variable_pair_free); + + /* HashTable */ + solver->error_vars = g_hash_table_new_full (NULL, NULL, + NULL, + variable_set_free); + + /* HashTable */ + solver->marker_vars = g_hash_table_new (NULL, NULL); + + /* HashTable; does not own keys, but owns values */ + solver->edit_var_map = g_hash_table_new_full (NULL, NULL, + NULL, + edit_info_free); + + /* HashTable; does not own keys, but owns values */ + solver->stay_var_map = g_hash_table_new_full (NULL, NULL, + NULL, + stay_info_free); + + /* The rows table owns the objective variable */ + solver->objective = variable_new (solver, VARIABLE_OBJECTIVE); + variable_set_name (solver->objective, "Z"); + g_hash_table_insert (solver->rows, solver->objective, expression_new (solver, 0.0)); + + /* HashSet */ + solver->constraints = g_hash_table_new_full (NULL, NULL, constraint_free, NULL); + + solver->slack_counter = 0; + solver->dummy_counter = 0; + solver->artificial_counter = 0; + solver->freeze_count = 0; + + solver->needs_solving = false; + solver->auto_solve = true; + solver->initialized = true; +} + +void +simplex_solver_reset (SimplexSolver *solver) +{ + if (!solver->initialized) + { + g_critical ("SimplexSolver %p is not initialized.", solver); + return; + } + + solver->needs_solving = false; + solver->auto_solve = true; + + solver->freeze_count = 0; + solver->slack_counter = 0; + solver->dummy_counter = 0; + solver->artificial_counter = 0; + + g_ptr_array_set_size (solver->stay_error_vars, 0); + g_ptr_array_set_size (solver->infeasible_rows, 0); + + g_hash_table_remove_all (solver->external_rows); + g_hash_table_remove_all (solver->external_parametric_vars); + g_hash_table_remove_all (solver->error_vars); + g_hash_table_remove_all (solver->marker_vars); + g_hash_table_remove_all (solver->edit_var_map); + g_hash_table_remove_all (solver->stay_var_map); + g_hash_table_remove_all (solver->constraints); + + g_hash_table_remove_all (solver->rows); + g_hash_table_remove_all (solver->columns); + + solver->objective = variable_new (solver, VARIABLE_OBJECTIVE); + variable_set_name (solver->objective, "Z"); + g_hash_table_insert (solver->rows, solver->objective, expression_new (solver, 0.0)); +} + +void +simplex_solver_clear (SimplexSolver *solver) +{ + if (!solver->initialized) + return; + + solver->initialized = false; + +#ifdef EMEUS_ENABLE_DEBUG + { + g_debug ("Solver [%p]:\n" + "- Rows: %d, Columns: %d\n" + "- Slack variables: %d\n" + "- Error variables: %d (pairs: %d)\n" + "- Marker variables: %d\n" + "- Infeasible rows: %d\n" + "- External rows: %d\n" + "- Edit: %d, Stay: %d", + solver, + g_hash_table_size (solver->rows), + g_hash_table_size (solver->columns), + solver->slack_counter, + g_hash_table_size (solver->error_vars), + solver->stay_error_vars->len, + g_hash_table_size (solver->marker_vars), + solver->infeasible_rows->len, + g_hash_table_size (solver->external_rows), + g_hash_table_size (solver->edit_var_map), + g_hash_table_size (solver->stay_var_map)); + } +#endif + + solver->objective = NULL; + + solver->needs_solving = false; + solver->auto_solve = true; + + solver->freeze_count = 0; + solver->slack_counter = 0; + solver->dummy_counter = 0; + solver->artificial_counter = 0; + + g_clear_pointer (&solver->stay_error_vars, g_ptr_array_unref); + g_clear_pointer (&solver->infeasible_rows, g_ptr_array_unref); + + g_clear_pointer (&solver->external_rows, g_hash_table_unref); + g_clear_pointer (&solver->external_parametric_vars, g_hash_table_unref); + g_clear_pointer (&solver->error_vars, g_hash_table_unref); + g_clear_pointer (&solver->marker_vars, g_hash_table_unref); + g_clear_pointer (&solver->edit_var_map, g_hash_table_unref); + g_clear_pointer (&solver->stay_var_map, g_hash_table_unref); + g_clear_pointer (&solver->constraints, g_hash_table_unref); + + /* The columns need to be deleted last, for reference counting */ + g_clear_pointer (&solver->rows, g_hash_table_unref); + g_clear_pointer (&solver->columns, g_hash_table_unref); +} + +void +simplex_solver_freeze (SimplexSolver *solver) +{ + solver->freeze_count += 1; + + if (solver->freeze_count > 0) + solver->auto_solve = false; +} + +void +simplex_solver_thaw (SimplexSolver *solver) +{ + if (solver->freeze_count == 0) + { + g_critical ("Unbalanced thaw; did you forger to call simplex_solver_freeze()?"); + return; + } + + solver->freeze_count -= 1; + + if (solver->freeze_count == 0) + solver->auto_solve = true; +} + +static char * +simplex_solver_to_string (SimplexSolver *solver) +{ + GString *buf = g_string_new (NULL); + + g_string_append (buf, "Tableau info:\n"); + g_string_append_printf (buf, "Rows: %d (= %d constraints)\n", + g_hash_table_size (solver->rows), + g_hash_table_size (solver->rows) - 1); + g_string_append_printf (buf, "Columns: %d\n", + g_hash_table_size (solver->columns)); + g_string_append_printf (buf, "Infeasible rows: %d\n", + solver->infeasible_rows->len); + g_string_append_printf (buf, "External basic variables: %d\n", + g_hash_table_size (solver->external_rows)); + g_string_append_printf (buf, "External parametric variables: %d\n", + g_hash_table_size (solver->external_parametric_vars)); + + g_string_append (buf, "Stay error vars:"); + if (solver->stay_error_vars->len == 0) + g_string_append (buf, " \n"); + else + { + g_string_append (buf, "\n"); + + for (int i = 0; i < solver->stay_error_vars->len; i++) + { + const VariablePair *pair = g_ptr_array_index (solver->stay_error_vars, i); + char *first_s = variable_to_string (pair->first); + char *second_s = variable_to_string (pair->second); + + g_string_append_printf (buf, " (%s, %s)\n", first_s, second_s); + + g_free (first_s); + g_free (second_s); + } + } + + g_string_append (buf, "Edit var map:"); + if (g_hash_table_size (solver->edit_var_map) == 0) + g_string_append (buf, " \n"); + else + { + GHashTableIter iter; + gpointer key_p, value_p; + + g_string_append (buf, "\n"); + + g_hash_table_iter_init (&iter, solver->edit_var_map); + while (g_hash_table_iter_next (&iter, &key_p, &value_p)) + { + char *var = variable_to_string (key_p); + const EditInfo *ei = value_p; + char *c = constraint_to_string (ei->constraint); + + g_string_append_printf (buf, " %s => %s\n", var, c); + + g_free (var); + g_free (c); + } + } + + return g_string_free (buf, FALSE); +} + +static VariableSet * +simplex_solver_get_column_set (SimplexSolver *solver, + Variable *param_var) +{ + if (!solver->initialized) + return NULL; + + return g_hash_table_lookup (solver->columns, param_var); +} + +static bool +simplex_solver_column_has_key (SimplexSolver *solver, + Variable *subject) +{ + if (!solver->initialized) + return false; + + return g_hash_table_contains (solver->columns, subject); +} + +static void +simplex_solver_insert_column_variable (SimplexSolver *solver, + Variable *param_var, + Variable *row_var) +{ + if (!solver->initialized) + return; + + VariableSet *cset = simplex_solver_get_column_set (solver, param_var); + if (cset == NULL) + { + cset = variable_set_new (); + g_hash_table_insert (solver->columns, variable_ref (param_var), cset); + } + + if (row_var != NULL) + variable_set_add_variable (cset, row_var); +} + +static void +simplex_solver_insert_error_variable (SimplexSolver *solver, + Constraint *constraint, + Variable *variable) +{ + if (!solver->initialized) + return; + + VariableSet *cset = g_hash_table_lookup (solver->error_vars, constraint); + if (cset == NULL) + { + cset = variable_set_new (); + g_hash_table_insert (solver->error_vars, constraint, cset); + } + + variable_set_add_variable (cset, variable); +} + +static void +simplex_solver_reset_stay_constants (SimplexSolver *solver) +{ + for (int i = 0; i < solver->stay_error_vars->len; i++) + { + VariablePair *pair = g_ptr_array_index (solver->stay_error_vars, i); + Expression *expression; + + expression = g_hash_table_lookup (solver->rows, pair->first); + if (expression == NULL) + expression = g_hash_table_lookup (solver->rows, pair->second); + + if (expression != NULL) + expression_set_constant (expression, 0.0); + } +} + +static void +simplex_solver_set_external_variables (SimplexSolver *solver) +{ + GHashTableIter iter; + gpointer key_p; + + g_hash_table_iter_init (&iter, solver->external_parametric_vars); + while (g_hash_table_iter_next (&iter, &key_p, NULL)) + { + Variable *variable = key_p; + + if (g_hash_table_contains (solver->rows, variable)) + continue; + + variable_set_value (variable, 0.0); + } + + g_hash_table_iter_init (&iter, solver->external_rows); + while (g_hash_table_iter_next (&iter, &key_p, NULL)) + { + Variable *variable = key_p; + Expression *expression; + + expression = g_hash_table_lookup (solver->rows, variable); + + variable_set_value (variable, expression_get_constant (expression)); + } + + solver->needs_solving = false; +} + +typedef struct { + SimplexSolver *solver; + Variable *subject; +} ForeachClosure; + +static bool +insert_expression_columns (Term *term, + gpointer data_) +{ + Variable *variable = term_get_variable (term); + ForeachClosure *data = data_; + + simplex_solver_insert_column_variable (data->solver, + variable, + data->subject); + + if (variable_is_external (variable)) + g_hash_table_add (data->solver->external_parametric_vars, variable_ref (variable)); + + return true; +} + +static void +simplex_solver_add_row (SimplexSolver *solver, + Variable *variable, + Expression *expression) +{ + if (!solver->initialized) + return; + + g_hash_table_insert (solver->rows, variable_ref (variable), expression_ref (expression)); + + ForeachClosure data = { + .subject = variable, + .solver = solver, + }; + + expression_terms_foreach (expression, + insert_expression_columns, + &data); + + if (variable_is_external (variable)) + g_hash_table_add (solver->external_rows, variable_ref (variable)); +} + +static void +simplex_solver_remove_column (SimplexSolver *solver, + Variable *variable) +{ + VariableSet *set = g_hash_table_lookup (solver->columns, variable); + if (set == NULL) + goto out; + + variable_ref (variable); + + VariableSetIter iter; + Variable *v; + + variable_set_iter_init (set, &iter); + while (variable_set_iter_next (&iter, &v)) + { + Expression *e = g_hash_table_lookup (solver->rows, v); + + expression_remove_variable (e, variable, NULL); + } + + g_hash_table_remove (solver->columns, variable); + +out: + if (variable_is_external (variable)) + { + g_hash_table_remove (solver->external_rows, variable); + g_hash_table_remove (solver->external_parametric_vars, variable); + } + + variable_unref (variable); +} + +static bool +remove_expression_columns (Term *term, + gpointer data_) +{ + ForeachClosure *data = data_; + VariableSet *set = g_hash_table_lookup (data->solver->columns, term_get_variable (term)); + + if (set != NULL) + variable_set_remove_variable (set, data->subject); + + return true; +} + +static Expression * +simplex_solver_remove_row (SimplexSolver *solver, + Variable *variable, + gboolean free_res) +{ + if (!solver->initialized) + return NULL; + + Expression *e = g_hash_table_lookup (solver->rows, variable); + g_assert (e != NULL); + + expression_ref (e); + + ForeachClosure data = { + .solver = solver, + .subject = variable, + }; + + expression_terms_foreach (e, + remove_expression_columns, + &data); + + g_ptr_array_remove (solver->infeasible_rows, variable); + + if (variable_is_external (variable)) + g_hash_table_remove (solver->external_rows, variable); + + g_hash_table_remove (solver->rows, variable); + + if (free_res) + { + expression_unref (e); + return NULL; + } + + return e; +} + +static void +simplex_solver_substitute_out (SimplexSolver *solver, + Variable *old_variable, + Expression *expression) +{ + if (!solver->initialized) + return; + + VariableSet *set = g_hash_table_lookup (solver->columns, old_variable); + if (set != NULL) + { + VariableSetIter iter; + Variable *v; + + variable_set_iter_init (set, &iter); + while (variable_set_iter_next (&iter, &v)) + { + Expression *row = g_hash_table_lookup (solver->rows, v); + + expression_substitute_out (row, old_variable, expression, v); + + if (variable_is_restricted (v) && expression_get_constant (row) < 0) + g_ptr_array_add (solver->infeasible_rows, v); + } + } + + if (variable_is_external (old_variable)) + { + g_hash_table_add (solver->external_rows, variable_ref (old_variable)); + g_hash_table_remove (solver->external_parametric_vars, old_variable); + } + + g_hash_table_remove (solver->columns, old_variable); +} + +static void +simplex_solver_pivot (SimplexSolver *solver, + Variable *entry_var, + Variable *exit_var) +{ + if (!solver->initialized) + return; + + if (entry_var == NULL) + g_critical ("INTERNAL: No entry variable for pivot"); + else + variable_ref (entry_var); + + if (exit_var == NULL) + g_critical ("INTERNAL: No exit variable for pivot"); + else + variable_ref (exit_var); + + Expression *expr = simplex_solver_remove_row (solver, exit_var, FALSE); + expression_change_subject (expr, exit_var, entry_var); + + simplex_solver_substitute_out (solver, entry_var, expr); + simplex_solver_add_row (solver, entry_var, expr); + + variable_unref (entry_var); + variable_unref (exit_var); + expression_unref (expr); +} + +static void +simplex_solver_optimize (SimplexSolver *solver, + Variable *z) +{ + if (!solver->initialized) + return; + + Expression *z_row = g_hash_table_lookup (solver->rows, z); + g_assert (z_row != NULL); + + Variable *entry = NULL, *exit = NULL; + + solver->optimize_count += 1; + +#ifdef EMEUS_ENABLE_DEBUG + gint64 start_time = g_get_monotonic_time (); +#endif + +#ifdef EMEUS_ENABLE_DEBUG + { + char *str; + + str = variable_to_string (z); + g_debug ("optimize: %s\n", str); + g_free (str); + + str = simplex_solver_to_string (solver); + g_debug ("%s", str); + g_free (str); + } +#endif + + while (true) + { + GList *l; + VariableSet *column_vars; + VariableSetIter iter; + Variable *v; + double objective_coefficient = 0.0; + double min_ratio; + double r; + + for (l = g_list_last (z_row->ordered_terms); l != NULL; l = l->prev) + { + const Term *t = l->data; + + if (variable_is_pivotable (t->variable) && t->coefficient < objective_coefficient) + { + entry = t->variable; + objective_coefficient = t->coefficient; + break; + } + } + + if (objective_coefficient >= -DBL_EPSILON) + break; + + min_ratio = DBL_MAX; + r = 0; + + column_vars = simplex_solver_get_column_set (solver, entry); + variable_set_iter_init (column_vars, &iter); + while (variable_set_iter_next (&iter, &v)) + { + if (variable_is_pivotable (v)) + { + Expression *expr = g_hash_table_lookup (solver->rows, v); + double coeff = expression_get_coefficient (expr, entry); + + if (coeff < 0.0) + { + r = -1.0 * expression_get_constant (expr) / coeff; + if (r < min_ratio) + { + min_ratio = r; + exit = v; + } + } + } + } + + if (min_ratio == DBL_MAX) + { + g_debug ("Unbounded objective variable during optimization"); + break; + } + + simplex_solver_pivot (solver, entry, exit); + +#ifdef EMEUS_ENABLE_DEBUG + { + char *str = simplex_solver_to_string (solver); + g_debug ("%s", str); + g_free (str); + } +#endif + } + +#ifdef EMEUS_ENABLE_DEBUG + g_debug ("optimize.time := %.3f ms (pass:%d)", + (float) (g_get_monotonic_time () - start_time) / 1000.f, + solver->optimize_count); +#endif +} + +typedef struct { + SimplexSolver *solver; + Expression *expr; +} ReplaceClosure; + +static bool +replace_terms (Term *term, + gpointer data_) +{ + Variable *v = term_get_variable (term); + double c = term_get_coefficient (term); + ReplaceClosure *data = data_; + + Expression *e = g_hash_table_lookup (data->solver->rows, v); + + if (e == NULL) + expression_add_variable (data->expr, v, c, NULL); + else + expression_add_expression (data->expr, e, c, NULL); + + return true; +} + +static Expression * +simplex_solver_new_expression (SimplexSolver *solver, + Constraint *constraint, + Variable **eplus_p, + Variable **eminus_p, + double *prev_constant_p) +{ + Expression *cn_expr = constraint->expression; + Expression *expr; + Variable *slack_var, *dummy_var; + Variable *eplus, *eminus; + ReplaceClosure data; + + if (eplus_p != NULL) + *eplus_p = NULL; + if (eminus_p != NULL) + *eminus_p = NULL; + if (prev_constant_p != NULL) + *prev_constant_p = 0.0; + + expr = expression_new (solver, expression_get_constant (cn_expr)); + + data.solver = solver; + data.expr = expr; + expression_terms_foreach (cn_expr, replace_terms, &data); + + if (constraint_is_inequality (constraint)) + { + /* If the constraint is an inequality, we add a slack variable to + * turn it into an equality, e.g. from + * + * expr >= 0 + * + * to + * + * expr - slack = 0 + * + * Additionally, if the constraint is not required we add an + * error variable: + * + * expr - slack + error = 0 + */ + solver->slack_counter += 1; + + slack_var = variable_new (solver, VARIABLE_SLACK); + variable_set_prefix (slack_var, "s"); + expression_set_variable (expr, slack_var, -1.0); + variable_unref (slack_var); + + g_hash_table_insert (solver->marker_vars, constraint, slack_var); + + if (!constraint_is_required (constraint)) + { + Expression *z_row; + + solver->slack_counter += 1; + + eminus = variable_new (solver, VARIABLE_SLACK); + variable_set_name (eminus, "em"); + expression_set_variable (expr, eminus, 1.0); + variable_unref (eminus); + + z_row = g_hash_table_lookup (solver->rows, solver->objective); + expression_set_variable (z_row, eminus, constraint->strength); + + simplex_solver_insert_error_variable (solver, constraint, eminus); + simplex_solver_note_added_variable (solver, eminus, solver->objective); + } + } + else + { + if (constraint_is_required (constraint)) + { + /* If the constraint is required, we use a dummy marker variable; + * the dummy won't be allowed to enter the basis of the tableau + * when pivoting. + */ + solver->dummy_counter += 1; + + dummy_var = variable_new (solver, VARIABLE_DUMMY); + + if (eplus_p != NULL) + *eplus_p = dummy_var; + if (eminus_p != NULL) + *eminus_p = dummy_var; + if (prev_constant_p != NULL) + *prev_constant_p = expression_get_constant (cn_expr); + + expression_set_variable (expr, dummy_var, 1.0); + g_hash_table_insert (solver->marker_vars, constraint, dummy_var); + + variable_unref (dummy_var); + } + else + { + Expression *z_row; + + /* Since the constraint is a non-required equality, we need to + * add error variables around it, i.e. turn it from: + * + * expr = 0 + * + * to: + * + * expr - eplus + eminus = 0 + */ + solver->slack_counter += 1; + + eplus = variable_new (solver, VARIABLE_SLACK); + variable_set_name (eplus, "ep"); + eminus = variable_new (solver, VARIABLE_SLACK); + variable_set_name (eminus, "em"); + + expression_set_variable (expr, eplus, -1.0); + expression_set_variable (expr, eminus, 1.0); + + g_hash_table_insert (solver->marker_vars, constraint, eplus); + + z_row = g_hash_table_lookup (solver->rows, solver->objective); + + expression_set_variable (z_row, eplus, constraint->strength); + expression_set_variable (z_row, eminus, constraint->strength); + simplex_solver_note_added_variable (solver, eplus, solver->objective); + simplex_solver_note_added_variable (solver, eminus, solver->objective); + + simplex_solver_insert_error_variable (solver, constraint, eplus); + simplex_solver_insert_error_variable (solver, constraint, eminus); + + if (constraint_is_stay (constraint)) + { + g_ptr_array_add (solver->stay_error_vars, variable_pair_new (eplus, eminus)); + } + else if (constraint_is_edit (constraint)) + { + if (eplus_p != NULL) + *eplus_p = eplus; + if (eminus_p != NULL) + *eminus_p = eminus; + if (prev_constant_p != NULL) + *prev_constant_p = expression_get_constant (cn_expr); + } + + variable_unref (eplus); + variable_unref (eminus); + } + } + + if (expression_get_constant (expr) < 0.0) + expression_times (expr, -1.0); + + return expr; +} + +static void +simplex_solver_dual_optimize (SimplexSolver *solver) +{ + Expression *z_row = g_hash_table_lookup (solver->rows, solver->objective); + +#ifdef EMEUS_ENABLE_DEBUG + gint64 start_time = g_get_monotonic_time (); +#endif + + /* We iterate until we don't have any more infeasible rows; the pivot() + * at the end of the loop iteration may add or remove infeasible rows + * as well + */ + while (solver->infeasible_rows->len != 0) + { + Variable *entry_var, *exit_var; + Expression *expr; + double ratio; + GList *l; + + /* Pop the last element of the array */ + exit_var = + g_ptr_array_index (solver->infeasible_rows, solver->infeasible_rows->len - 1); + g_ptr_array_set_size (solver->infeasible_rows, solver->infeasible_rows->len - 1); + + expr = g_hash_table_lookup (solver->rows, exit_var); + if (expr == NULL) + continue; + + if (expression_get_constant (expr) >= 0.0) + continue; + + ratio = DBL_MAX; + entry_var = NULL; + for (l = g_list_last (expr->ordered_terms); l != NULL; l = l->prev) + { + Term *term = l->data; + Variable *v = term_get_variable (term); + double cd = term_get_coefficient (term); + + if (cd > 0.0 && variable_is_pivotable (v)) + { + double zc = expression_get_coefficient (z_row, v); + double r = zc / cd; + + if (r < ratio) + { + entry_var = v; + ratio = r; + } + } + } + + if (ratio == DBL_MAX) + g_critical ("INTERNAL: ratio == DBL_MAX in dual_optimize"); + + if (entry_var != NULL) + simplex_solver_pivot (solver, entry_var, exit_var); + } + +#ifdef EMEUS_ENABLE_DEBUG + g_debug ("dual_optimize.time := %.3f ms", + (float) (g_get_monotonic_time () - start_time) / 1000.f); +#endif +} + +static void +simplex_solver_delta_edit_constant (SimplexSolver *solver, + double delta, + Variable *plus_error_var, + Variable *minus_error_var) +{ + Expression *plus_expr, *minus_expr; + VariableSet *column_set; + VariableSetIter iter; + Variable *basic_var; + + if (!solver->initialized) + return; + + plus_expr = g_hash_table_lookup (solver->rows, plus_error_var); + if (plus_expr != NULL) + { + double new_constant = expression_get_constant (plus_expr) + delta; + + expression_set_constant (plus_expr, new_constant); + + if (new_constant < 0.0) + g_ptr_array_add (solver->infeasible_rows, plus_error_var); + + return; + } + + minus_expr = g_hash_table_lookup (solver->rows, minus_error_var); + if (minus_expr != NULL) + { + double new_constant = expression_get_constant (minus_expr) - delta; + + expression_set_constant (minus_expr, new_constant); + + if (new_constant < 0.0) + g_ptr_array_add (solver->infeasible_rows, minus_error_var); + + return; + } + + column_set = g_hash_table_lookup (solver->columns, minus_error_var); + if (column_set == NULL) + { + g_critical ("INTERNAL: Columns are unset during delta edit"); + return; + } + + variable_set_iter_init (column_set, &iter); + while (variable_set_iter_next (&iter, &basic_var)) + { + Expression *expr; + double c, new_constant; + + expr = g_hash_table_lookup (solver->rows, basic_var); + c = expression_get_coefficient (expr, minus_error_var); + + new_constant = expression_get_constant (expr) + (c * delta); + expression_set_constant (expr, new_constant); + + if (variable_is_restricted (basic_var) && new_constant < 0.0) + g_ptr_array_add (solver->infeasible_rows, basic_var); + } +} + +static Variable * +simplex_solver_choose_subject (SimplexSolver *solver, + Expression *expression) +{ + Variable *subject = NULL; + Variable *retval = NULL; + bool found_unrestricted = false; + bool found_new_restricted = false; + bool retval_found = false; + double coeff = 0.0; + GList *iter; + + iter = g_list_last (expression->ordered_terms); + while (iter != NULL) + { + Term *t = iter->data; + Variable *v = term_get_variable (t); + double c = term_get_coefficient (t); + + iter = iter->prev; + + if (found_unrestricted) + { + if (!variable_is_restricted (v)) + { + if (!g_hash_table_contains (solver->columns, v)) + { + retval_found = true; + retval = v; + break; + } + } + } + else + { + if (variable_is_restricted (v)) + { + if (!found_new_restricted && !variable_is_dummy (v) && c < 0.0) + { + VariableSet *cset = g_hash_table_lookup (solver->columns, v); + + if (cset == NULL || + (variable_set_get_size (cset) == 1 && g_hash_table_contains (solver->columns, solver->objective))) + { + subject = v; + found_new_restricted = true; + } + } + } + else + { + subject = v; + found_unrestricted = true; + } + } + } + + if (retval_found) + return retval; + + if (subject != NULL) + return subject; + + iter = g_list_last (expression->ordered_terms); + while (iter != NULL) + { + Term *t = iter->data; + Variable *v = term_get_variable (t); + double c = term_get_coefficient (t); + + iter = iter->prev; + + if (!variable_is_dummy (v)) + { + retval_found = true; + retval = NULL; + break; + } + + if (!g_hash_table_contains (solver->columns, v)) + { + subject = v; + coeff = c; + } + } + + if (retval_found) + return retval; + + if (!approx_val (expression->constant, 0.0)) + { + g_debug ("Unable to satisfy required constraint (choose_subject)"); + return NULL; + } + + if (coeff > 0) + expression_times (expression, -1.0); + + return subject; +} + +static bool +simplex_solver_try_adding_directly (SimplexSolver *solver, + Expression *expression) +{ + Variable *subject; + + if (!solver->initialized) + return false; + + subject = simplex_solver_choose_subject (solver, expression); + if (subject == NULL) + return false; + + variable_ref (subject); + + expression_new_subject (expression, subject); + if (simplex_solver_column_has_key (solver, subject)) + simplex_solver_substitute_out (solver, subject, expression); + + simplex_solver_add_row (solver, subject, expression); + + variable_unref (subject); + + return true; +} + +static void +simplex_solver_add_with_artificial_variable (SimplexSolver *solver, + Expression *expression) +{ + Variable *av, *az; + Expression *az_row; + Expression *az_tableau_row; + Expression *e; + + if (!solver->initialized) + return; + + av = variable_new (solver, VARIABLE_SLACK); + variable_set_prefix (av, "a"); + solver->artificial_counter += 1; + + az = variable_new (solver, VARIABLE_OBJECTIVE); + variable_set_name (az, "az"); + + az_row = expression_clone (expression); + + simplex_solver_add_row (solver, az, az_row); + simplex_solver_add_row (solver, av, expression); + + expression_unref (az_row); + variable_unref (av); + variable_unref (az); + + simplex_solver_optimize (solver, az); + + az_tableau_row = g_hash_table_lookup (solver->rows, az); + if (!approx_val (expression_get_constant (az_tableau_row), 0.0)) + { + char *str = expression_to_string (expression); + + simplex_solver_remove_column (solver, av); + simplex_solver_remove_row (solver, az, TRUE); + + g_debug ("Unable to satisfy a required constraint (add): %s", str); + + g_free (str); + + return; + } + + e = g_hash_table_lookup (solver->rows, av); + if (e != NULL) + { + Variable *entry_var; + + if (expression_is_constant (e)) + { + simplex_solver_remove_row (solver, av, TRUE); + simplex_solver_remove_row (solver, az, TRUE); + + return; + } + + entry_var = expression_get_pivotable_variable (e); + simplex_solver_pivot (solver, entry_var, av); + } + + g_assert (!g_hash_table_contains (solver->rows, av)); + + simplex_solver_remove_column (solver, av); + simplex_solver_remove_row (solver, az, TRUE); +} + +void +simplex_solver_note_added_variable (SimplexSolver *solver, + Variable *variable, + Variable *subject) +{ + if (!solver->initialized) + return; + + if (subject != NULL) + simplex_solver_insert_column_variable (solver, variable, subject); +} + +void +simplex_solver_note_removed_variable (SimplexSolver *solver, + Variable *variable, + Variable *subject) +{ + VariableSet *set; + + if (!solver->initialized) + return; + + set = g_hash_table_lookup (solver->columns, variable); + if (set != NULL && subject != NULL) + variable_set_remove_variable (set, subject); +} + +Variable * +simplex_solver_create_variable (SimplexSolver *solver, + const char *name, + double value) +{ + Variable *res; + + if (!solver->initialized) + { + g_critical ("SimplexSolver %p is not initialized.", solver); + return NULL; + } + + res = variable_new (solver, VARIABLE_REGULAR); + variable_set_name (res, name); + variable_set_value (res, value); + + return res; +} + +Expression * +simplex_solver_create_expression (SimplexSolver *solver, + double constant) +{ + if (!solver->initialized) + { + g_critical ("SimplexSolver %p is not initialized.", solver); + return NULL; + } + + return expression_new (solver, constant); +} + +static void +simplex_solver_add_constraint_internal (SimplexSolver *solver, + Constraint *constraint) +{ + Expression *expr; + Variable *eplus; + Variable *eminus; + double prev_constant; + + expr = simplex_solver_new_expression (solver, constraint, + &eplus, + &eminus, + &prev_constant); + +#ifdef EMEUS_ENABLE_DEBUG + { + char *str1 = constraint_to_string (constraint); + char *str2 = expression_to_string (expr); + + g_debug ("Adding constraint: %s (normalized expression: %s)", str1, str2); + + g_free (str1); + g_free (str2); + } +#endif + + if (constraint_is_stay (constraint)) + { + StayInfo *si = g_slice_new (StayInfo); + + si->constraint = constraint; + + g_hash_table_insert (solver->stay_var_map, constraint->variable, si); + } + + if (constraint_is_edit (constraint)) + { + EditInfo *ei = g_slice_new (EditInfo); + + ei->constraint = constraint; + ei->eplus = eplus; + ei->eminus = eminus; + ei->prev_constant = prev_constant; + + g_hash_table_insert (solver->edit_var_map, constraint->variable, ei); + } + + if (!simplex_solver_try_adding_directly (solver, expr)) + simplex_solver_add_with_artificial_variable (solver, expr); + + solver->needs_solving = true; + + if (solver->auto_solve) + { + simplex_solver_optimize (solver, solver->objective); + simplex_solver_set_external_variables (solver); + } + + expression_unref (expr); + + g_hash_table_add (solver->constraints, constraint); +} + +Constraint * +simplex_solver_add_constraint (SimplexSolver *solver, + Variable *variable, + OperatorType op, + Expression *expression, + double strength) +{ + if (!solver->initialized) + { + g_critical ("SimplexSolver %p has not been initialized.", solver); + return NULL; + } + + Constraint *res = g_slice_new0 (Constraint); + res->solver = solver; + res->strength = strength; + res->is_edit = false; + res->is_stay = false; + res->op_type = op; + + if (expression == NULL) + res->expression = expression_new_from_variable (variable); + else + { + res->expression = expression_ref (expression); + if (res->expression->solver == NULL) + res->expression->solver = solver; + + if (variable != NULL) + { + switch (res->op_type) + { + case OPERATOR_TYPE_EQ: + expression_add_variable (res->expression, variable, -1.0, NULL); + break; + + case OPERATOR_TYPE_LE: + expression_add_variable (res->expression, variable, -1.0, NULL); + break; + + case OPERATOR_TYPE_GE: + expression_times (res->expression, -1.0); + expression_add_variable (res->expression, variable, 1.0, NULL); + break; + } + } + } + + simplex_solver_add_constraint_internal (solver, res); + + return res; +} + +Constraint * +simplex_solver_add_stay_variable (SimplexSolver *solver, + Variable *variable, + double strength) +{ + if (!solver->initialized) + { + g_critical ("SimplexSolver %p has not been initialized.", solver); + return NULL; + } + + Constraint *res = g_slice_new0 (Constraint); + res->solver = solver; + res->variable = variable_ref (variable); + res->op_type = OPERATOR_TYPE_EQ; + res->strength = strength; + res->is_stay = true; + res->is_edit = false; + + res->expression = expression_new (solver, variable_get_value (res->variable)); + expression_add_variable (res->expression, res->variable, -1.0, NULL); + + simplex_solver_add_constraint_internal (solver, res); + + return res; +} + +bool +simplex_solver_has_stay_variable (SimplexSolver *solver, + Variable *variable) +{ + if (!solver->initialized) + return false; + + return g_hash_table_contains (solver->stay_var_map, variable); +} + +void +simplex_solver_remove_stay_variable (SimplexSolver *solver, + Variable *variable) +{ + if (!solver->initialized) + { + g_critical ("Solver %p is not initialized.", solver); + return; + } + + StayInfo *si = g_hash_table_lookup (solver->stay_var_map, variable); + if (si == NULL) + { + char *str = variable_to_string (variable); + + g_critical ("Unknown stay variable '%s'", str); + + g_free (str); + + return; + } + + simplex_solver_remove_constraint (solver, si->constraint); +} + +Constraint * +simplex_solver_add_edit_variable (SimplexSolver *solver, + Variable *variable, + double strength) +{ + if (!solver->initialized) + return NULL; + + Constraint *res = g_slice_new (Constraint); + res->solver = solver; + res->variable = variable_ref (variable); + res->op_type = OPERATOR_TYPE_EQ; + res->strength = strength; + res->is_stay = false; + res->is_edit = true; + + res->expression = expression_new (solver, variable_get_value (variable)); + expression_add_variable (res->expression, variable, -1.0, NULL); + + simplex_solver_add_constraint_internal (solver, res); + + return res; +} + +bool +simplex_solver_has_edit_variable (SimplexSolver *solver, + Variable *variable) +{ + if (!solver->initialized) + return false; + + return g_hash_table_contains (solver->edit_var_map, variable); +} + +void +simplex_solver_remove_edit_variable (SimplexSolver *solver, + Variable *variable) +{ + if (!solver->initialized) + { + g_critical ("Solver %p is not initialized.", solver); + return; + } + + EditInfo *ei = g_hash_table_lookup (solver->edit_var_map, variable); + if (ei == NULL) + { + char *str = variable_to_string (variable); + + g_critical ("Unknown edit variable '%s'", str); + + g_free (str); + + return; + } + + simplex_solver_remove_constraint (solver, ei->constraint); +} + +void +simplex_solver_remove_constraint (SimplexSolver *solver, + Constraint *constraint) +{ + Expression *z_row; + VariableSet *error_vars; + VariableSetIter iter; + Variable *marker; + + if (!solver->initialized) + return; + + if (!g_hash_table_contains (solver->constraints, constraint)) + { + char *str = constraint_to_string (constraint); + + g_critical ("Unknown constraint '%s', unable to remove it from solver", str); + + g_free (str); + } + + solver->needs_solving = true; + + simplex_solver_reset_stay_constants (solver); + + z_row = g_hash_table_lookup (solver->rows, solver->objective); + error_vars = g_hash_table_lookup (solver->error_vars, constraint); + + if (error_vars != NULL) + { + Variable *v; + + variable_set_iter_init (error_vars, &iter); + while (variable_set_iter_next (&iter, &v)) + { + Expression *e; + + e = g_hash_table_lookup (solver->rows, v); + + if (e == NULL) + { + expression_add_variable (z_row, + v, + constraint->strength, + solver->objective); + } + else + { + expression_add_expression (z_row, + e, + constraint->strength, + solver->objective); + } + } + } + + marker = g_hash_table_lookup (solver->marker_vars, constraint); + if (marker == NULL) + { + g_critical ("Constraint %p not found", constraint); + return; + } + + g_hash_table_remove (solver->marker_vars, constraint); + + if (g_hash_table_lookup (solver->rows, marker) == NULL) + { + VariableSet *set = g_hash_table_lookup (solver->columns, marker); + Variable *exit_var = NULL; + Variable *v; + double min_ratio = 0; + + if (set == NULL) + goto no_columns; + + variable_set_iter_init (set, &iter); + while (variable_set_iter_next (&iter, &v)) + { + if (variable_is_restricted (v)) + { + Expression *e = g_hash_table_lookup (solver->rows, v); + double coeff = expression_get_coefficient (e, marker); + + if (coeff < 0.0) + { + double r = -expression_get_constant (e) / coeff; + + if (exit_var == NULL || + r < min_ratio || + approx_val (r, min_ratio)) + { + min_ratio = r; + exit_var = v; + } + } + } + } + + if (exit_var == NULL) + { + variable_set_iter_init (set, &iter); + while (variable_set_iter_next (&iter, &v)) + { + if (variable_is_restricted (v)) + { + Expression *e = g_hash_table_lookup (solver->rows, v); + double coeff = expression_get_coefficient (e, marker); + double r = 0.0; + + if (!approx_val (coeff, 0.0)) + r = expression_get_constant (e) / coeff; + + if (exit_var == NULL || r < min_ratio) + { + min_ratio = r; + exit_var = v; + } + } + } + } + + if (exit_var == NULL) + { + if (variable_set_get_size (set) == 0) + simplex_solver_remove_column (solver, marker); + else + { + variable_set_iter_init (set, &iter); + while (variable_set_iter_next (&iter, &v)) + { + if (v != solver->objective) + { + exit_var = v; + break; + } + } + } + } + + if (exit_var != NULL) + simplex_solver_pivot (solver, marker, exit_var); + } + +no_columns: + if (g_hash_table_lookup (solver->rows, marker) != NULL) + simplex_solver_remove_row (solver, marker, TRUE); + else + variable_unref (marker); + + if (error_vars != NULL) + { + Variable *v; + + variable_set_iter_init (error_vars, &iter); + while (variable_set_iter_next (&iter, &v)) + { + if (v != marker) + simplex_solver_remove_column (solver, v); + } + } + + if (constraint_is_stay (constraint)) + { + if (error_vars != NULL) + { + GPtrArray *remaining = g_ptr_array_new_with_free_func (variable_pair_free); + int i = 0; + + for (i = 0; i < solver->stay_error_vars->len; i++) + { + VariablePair *pair = g_ptr_array_index (solver->stay_error_vars, i); + bool found = false; + + if (variable_set_remove_variable (error_vars, pair->first)) + found = true; + + if (variable_set_remove_variable (error_vars, pair->second)) + found = true; + + if (!found) + g_ptr_array_add (remaining, variable_pair_new (pair->first, pair->second)); + } + + g_clear_pointer (&solver->stay_error_vars, g_ptr_array_unref); + solver->stay_error_vars = remaining; + } + + g_hash_table_remove (solver->stay_var_map, constraint->variable); + } + else if (constraint_is_edit (constraint)) + { + EditInfo *ei = g_hash_table_lookup (solver->edit_var_map, constraint->variable); + + simplex_solver_remove_column (solver, ei->eminus); + + g_hash_table_remove (solver->edit_var_map, constraint->variable); + } + + if (error_vars != NULL) + g_hash_table_remove (solver->error_vars, constraint); + + if (solver->auto_solve) + { + simplex_solver_optimize (solver, solver->objective); + simplex_solver_set_external_variables (solver); + } + + g_hash_table_remove (solver->constraints, constraint); +} + +void +simplex_solver_suggest_value (SimplexSolver *solver, + Variable *variable, + double value) +{ + if (!solver->initialized) + { + char *str = variable_to_string (variable); + + g_critical ("Unable to suggest value '%g' for variable '%s': the " + "SimplexSolver %p is not initialized.", + value, str, solver); + + g_free (str); + return; + } + + EditInfo *ei = g_hash_table_lookup (solver->edit_var_map, variable); + if (ei == NULL) + { + g_critical ("Suggesting value '%g' but variable %p is not editable", + value, variable); + return; + } + + ei->prev_constant = value - ei->prev_constant; + + simplex_solver_delta_edit_constant (solver, ei->prev_constant, ei->eplus, ei->eminus); +} + +void +simplex_solver_resolve (SimplexSolver *solver) +{ + if (!solver->initialized) + { + g_critical ("Unable to resolve the simplex: the SimplexSolver %p " + "is not initialized", + solver); + return; + } + +#ifdef EMEUS_ENABLE_DEBUG + gint64 start_time = g_get_monotonic_time (); +#endif + + simplex_solver_dual_optimize (solver); + simplex_solver_set_external_variables (solver); + + g_ptr_array_set_size (solver->infeasible_rows, 0); + + simplex_solver_reset_stay_constants (solver); + +#ifdef EMEUS_ENABLE_DEBUG + g_debug ("resolve.time := %.3f ms", + (float) (g_get_monotonic_time () - start_time) / 1000.f); +#endif + + solver->needs_solving = false; +} + +void +simplex_solver_begin_edit (SimplexSolver *solver) +{ + if (g_hash_table_size (solver->edit_var_map) == 0) + { + g_critical ("Solver %p does not have editable variables.", solver); + return; + } + + g_ptr_array_set_size (solver->infeasible_rows, 0); + simplex_solver_reset_stay_constants (solver); +} + + +void +simplex_solver_end_edit (SimplexSolver *solver) +{ + simplex_solver_resolve (solver); +} diff --git a/shoes/layout/emeus-test-utils.h b/shoes/layout/emeus-test-utils.h new file mode 100644 index 00000000..f2061a7b --- /dev/null +++ b/shoes/layout/emeus-test-utils.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include +#include + +#define emeus_fuzzy_equals(n1,n2,epsilon) \ + (((n1) > (n2) ? ((n1) - (n2)) : ((n2) - (n1))) < (epsilon)) + +#define emeus_assert_almost_equals(n1,n2) \ + G_STMT_START { \ + double __n1 = (n1), __n2 = (n2); \ + if (emeus_fuzzy_equals (__n1, __n2, DBL_EPSILON)) ; else { \ + g_assertion_message_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #n1 " == " #n2 " (+/- 1e-9)", \ + __n1, "==", __n2, 'f'); \ + } \ + } G_STMT_END diff --git a/shoes/layout/emeus-types-private.h b/shoes/layout/emeus-types-private.h new file mode 100644 index 00000000..60516811 --- /dev/null +++ b/shoes/layout/emeus-types-private.h @@ -0,0 +1,175 @@ +/* emeus-types-private.h: Shared private types + * + * Copyright 2016 Endless + * + * 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 . + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +typedef struct _SimplexSolver SimplexSolver; + +typedef enum { + VARIABLE_DUMMY = 'd', + VARIABLE_OBJECTIVE = 'o', + VARIABLE_SLACK = 's', + VARIABLE_REGULAR = 'v' +} VariableType; + +typedef struct { + int ref_count; + + unsigned long id_; + + VariableType type; + + const char *prefix; + const char *name; + + double value; + + bool is_external; + bool is_pivotable; + bool is_restricted; + + SimplexSolver *solver; +} Variable; + +typedef struct { + double coefficient; + + Variable *variable; +} Term; + +typedef struct { + int ref_count; + + double constant; + + /* HashTable */ + GHashTable *terms; + + GList *ordered_terms; + + SimplexSolver *solver; +} Expression; + +typedef enum { + OPERATOR_TYPE_LE = -1, + OPERATOR_TYPE_EQ = 0, + OPERATOR_TYPE_GE = 1 +} OperatorType; + +G_GNUC_PURE +static inline double +create_strength (double a, + double b, + double c, + double w) +{ + double res = 0.0; + + res += CLAMP (a * w, 0.0, 1000.0) * 1000000.0; + res += CLAMP (b * w, 0.0, 1000.0) * 1000.0; + res += CLAMP (c * w, 0.0, 1000.0); + + return res; +} + +#define STRENGTH_REQUIRED (create_strength (1000.0, 1000.0, 1000.0, 1.0)) +#define STRENGTH_STRONG (create_strength ( 1.0, 0.0, 0.0, 1.0)) +#define STRENGTH_MEDIUM (create_strength ( 0.0, 1.0, 0.0, 1.0)) +#define STRENGTH_WEAK (create_strength ( 0.0, 0.0, 1.0, 1.0)) + +typedef struct { + /* While the Constraint's normal form is expressed in terms of a linear + * equation between two terms, e.g.: + * + * x = y * coefficient + constant + * + * we use a single expression and compare with zero: + * + * x - (y * coefficient + constant) = 0 + * + * as this simplifies tableau we set up for the simplex solver + */ + Expression *expression; + OperatorType op_type; + + /* The variable used by edit and stay constraints */ + Variable *variable; + + double strength; + + bool is_edit; + bool is_stay; + + SimplexSolver *solver; +} Constraint; + +#define SIMPLEX_SOLVER_INIT \ + { false, \ + NULL, NULL, \ + NULL, NULL, NULL, \ + NULL, \ + NULL, NULL, \ + NULL, NULL, \ + NULL, \ + NULL, \ + 0, 0, 0, 0, 0, \ + false, false, \ + } + +struct _SimplexSolver { + bool initialized; + + /* HashTable> */ + GHashTable *columns; + + /* HashTable */ + GHashTable *rows; + + /* Sets */ + GHashTable *external_rows; + GHashTable *external_parametric_vars; + + GPtrArray *infeasible_rows; + GPtrArray *stay_error_vars; + + GHashTable *error_vars; + GHashTable *marker_vars; + + GHashTable *edit_var_map; + GHashTable *stay_var_map; + + Variable *objective; + + GHashTable *constraints; + + int slack_counter; + int artificial_counter; + int dummy_counter; + int optimize_count; + int freeze_count; + + bool auto_solve; + bool needs_solving; +}; + +G_END_DECLS diff --git a/shoes/layout/emeus-types.c b/shoes/layout/emeus-types.c new file mode 100644 index 00000000..c57a367a --- /dev/null +++ b/shoes/layout/emeus-types.c @@ -0,0 +1,48 @@ +/* emeus-types.c: Shared public types + * + * Copyright 2016 Endless + * + * 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 . + */ + +#include "config.h" + +#include "emeus-macros-private.h" + +#include "emeus-types.h" + +EMEUS_DEFINE_ENUM_TYPE (EmeusConstraintAttribute, emeus_constraint_attribute, + EMEUS_ENUM_VALUE (EMEUS_CONSTRAINT_ATTRIBUTE_INVALID, "invalid") + EMEUS_ENUM_VALUE (EMEUS_CONSTRAINT_ATTRIBUTE_LEFT, "left") + EMEUS_ENUM_VALUE (EMEUS_CONSTRAINT_ATTRIBUTE_RIGHT, "right") + EMEUS_ENUM_VALUE (EMEUS_CONSTRAINT_ATTRIBUTE_TOP, "top") + EMEUS_ENUM_VALUE (EMEUS_CONSTRAINT_ATTRIBUTE_BOTTOM, "bottom") + EMEUS_ENUM_VALUE (EMEUS_CONSTRAINT_ATTRIBUTE_START, "start") + EMEUS_ENUM_VALUE (EMEUS_CONSTRAINT_ATTRIBUTE_END, "end") + EMEUS_ENUM_VALUE (EMEUS_CONSTRAINT_ATTRIBUTE_WIDTH, "width") + EMEUS_ENUM_VALUE (EMEUS_CONSTRAINT_ATTRIBUTE_HEIGHT, "height") + EMEUS_ENUM_VALUE (EMEUS_CONSTRAINT_ATTRIBUTE_CENTER_X, "center-x") + EMEUS_ENUM_VALUE (EMEUS_CONSTRAINT_ATTRIBUTE_CENTER_Y, "center-y") + EMEUS_ENUM_VALUE (EMEUS_CONSTRAINT_ATTRIBUTE_BASELINE, "baseline")) + +EMEUS_DEFINE_ENUM_TYPE (EmeusConstraintRelation, emeus_constraint_relation, + EMEUS_ENUM_VALUE (EMEUS_CONSTRAINT_RELATION_GE, "le") + EMEUS_ENUM_VALUE (EMEUS_CONSTRAINT_RELATION_EQ, "eq") + EMEUS_ENUM_VALUE (EMEUS_CONSTRAINT_RELATION_LE, "ge")) + +EMEUS_DEFINE_ENUM_TYPE (EmeusConstraintStrength, emeus_constraint_strength, + EMEUS_ENUM_VALUE (EMEUS_CONSTRAINT_STRENGTH_WEAK, "weak") + EMEUS_ENUM_VALUE (EMEUS_CONSTRAINT_STRENGTH_MEDIUM, "medium") + EMEUS_ENUM_VALUE (EMEUS_CONSTRAINT_STRENGTH_STRONG, "strong") + EMEUS_ENUM_VALUE (EMEUS_CONSTRAINT_STRENGTH_REQUIRED, "required")) diff --git a/shoes/layout/emeus-types.h b/shoes/layout/emeus-types.h new file mode 100644 index 00000000..fef3eeb8 --- /dev/null +++ b/shoes/layout/emeus-types.h @@ -0,0 +1,123 @@ +/* emeus-types.h: Shared public types + * + * Copyright 2016 Endless + * + * 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 . + */ + +#pragma once + +#include + +#include "shoes/layout/emeus-version-macros.h" + +G_BEGIN_DECLS + +/** + * EmeusConstraintStrength: + * @EMEUS_CONSTRAINT_STRENGTH_WEAK: Weak constraint + * @EMEUS_CONSTRAINT_STRENGTH_MEDIUM: Medium constraint + * @EMEUS_CONSTRAINT_STRENGTH_STRONG: Strong constraint + * @EMEUS_CONSTRAINT_STRENGTH_REQUIRED: Requires constraint + * + * The possible strengths for a constraint. + * + * The order is: + * + * |[ + * weak < medium < strong < required + * ]| + * + * Since: 1.0 + */ +typedef enum { + EMEUS_CONSTRAINT_STRENGTH_REQUIRED = 0, + EMEUS_CONSTRAINT_STRENGTH_WEAK = -1, + EMEUS_CONSTRAINT_STRENGTH_MEDIUM = -2, + EMEUS_CONSTRAINT_STRENGTH_STRONG = -3 +} EmeusConstraintStrength; + +#define EMEUS_TYPE_CONSTRAINT_STRENGTH (emeus_constraint_strength_get_type()) + +EMEUS_AVAILABLE_IN_1_0 +GType emeus_constraint_strength_get_type (void) G_GNUC_CONST; + +/** + * EmeusConstraintRelation: + * @EMEUS_CONSTRAINT_RELATION_LE: Less than, or equal + * @EMEUS_CONSTRAINT_RELATION_EQ: Equal + * @EMEUS_CONSTRAINT_RELATION_GE: Greater than, or equal + * + * The relation between the two terms of the constraint. + * + * Since: 1.0 + */ +typedef enum +{ + EMEUS_CONSTRAINT_RELATION_LE, + EMEUS_CONSTRAINT_RELATION_EQ, + EMEUS_CONSTRAINT_RELATION_GE +} EmeusConstraintRelation; + +#define EMEUS_TYPE_CONSTRAINT_RELATION (emeus_constraint_relation_get_type()) + +EMEUS_AVAILABLE_IN_1_0 +GType emeus_constraint_relation_get_type (void) G_GNUC_CONST; + +/** + * EmeusConstraintAttribute: + * @EMEUS_CONSTRAINT_ATTRIBUTE_INVALID: Used for constants + * @EMEUS_CONSTRAINT_ATTRIBUTE_LEFT: The left edge of a widget + * @EMEUS_CONSTRAINT_ATTRIBUTE_RIGHT: The right edge of a widget + * @EMEUS_CONSTRAINT_ATTRIBUTE_TOP: The top edge of a widget + * @EMEUS_CONSTRAINT_ATTRIBUTE_BOTTOM: The bottom edge of a widget + * @EMEUS_CONSTRAINT_ATTRIBUTE_START: The leading edge of a widget, depending + * on the text direction (left for LTR languages, right for RTL ones) + * @EMEUS_CONSTRAINT_ATTRIBUTE_END: The trailing edge of a widget, depending + * on the text direction (right for LTR languages, left for RTL ones) + * @EMEUS_CONSTRAINT_ATTRIBUTE_WIDTH: The width of a widget + * @EMEUS_CONSTRAINT_ATTRIBUTE_HEIGHT: The height of a widget + * @EMEUS_CONSTRAINT_ATTRIBUTE_CENTER_X: The center of a widget, on the horizontal + * axis + * @EMEUS_CONSTRAINT_ATTRIBUTE_CENTER_Y: The center of a widget, on the vertical + * axis + * @EMEUS_CONSTRAINT_ATTRIBUTE_BASELINE: The baseline of a widget + * + * The attributes that can be used to build a constraint. + * + * Since: 1.0 + */ +typedef enum +{ + EMEUS_CONSTRAINT_ATTRIBUTE_INVALID, + + EMEUS_CONSTRAINT_ATTRIBUTE_LEFT, + EMEUS_CONSTRAINT_ATTRIBUTE_RIGHT, + EMEUS_CONSTRAINT_ATTRIBUTE_TOP, + EMEUS_CONSTRAINT_ATTRIBUTE_BOTTOM, + EMEUS_CONSTRAINT_ATTRIBUTE_START, + EMEUS_CONSTRAINT_ATTRIBUTE_END, + EMEUS_CONSTRAINT_ATTRIBUTE_WIDTH, + EMEUS_CONSTRAINT_ATTRIBUTE_HEIGHT, + EMEUS_CONSTRAINT_ATTRIBUTE_CENTER_X, + EMEUS_CONSTRAINT_ATTRIBUTE_CENTER_Y, + EMEUS_CONSTRAINT_ATTRIBUTE_BASELINE +} EmeusConstraintAttribute; + +#define EMEUS_TYPE_CONSTRAINT_ATTRIBUTE (emeus_constraint_attribute_get_type()) + +EMEUS_AVAILABLE_IN_1_0 +GType emeus_constraint_attribute_get_type (void) G_GNUC_CONST; + +G_END_DECLS diff --git a/shoes/layout/emeus-utils-private.h b/shoes/layout/emeus-utils-private.h new file mode 100644 index 00000000..50205073 --- /dev/null +++ b/shoes/layout/emeus-utils-private.h @@ -0,0 +1,42 @@ +/* emeus-utils-private.h: Internal utility functions + * + * Copyright 2016 Endless + * + * 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 . + */ + +#pragma once + +#include "emeus-types.h" +#include "emeus-types-private.h" + +G_BEGIN_DECLS + +const char *get_attribute_name (EmeusConstraintAttribute attr); +const char *get_relation_symbol (EmeusConstraintRelation rel); + +EmeusConstraintAttribute attribute_from_name (const char *name); + +OperatorType relation_to_operator (EmeusConstraintRelation rel); +double strength_to_value (EmeusConstraintStrength strength); + +EmeusConstraintRelation operator_to_relation (OperatorType op); +EmeusConstraintStrength value_to_strength (double strength); + +const char *operator_to_string (OperatorType op); +const char *strength_to_string (double strength); + +bool approx_val (double v1, double v2); + +G_END_DECLS diff --git a/shoes/layout/emeus-utils.c b/shoes/layout/emeus-utils.c new file mode 100644 index 00000000..7ae33421 --- /dev/null +++ b/shoes/layout/emeus-utils.c @@ -0,0 +1,184 @@ +/* emeus-utils.c: Internal utility functions + * + * Copyright 2016 Endless + * + * 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 . + */ + +#include "config.h" + +#include "emeus-utils-private.h" + +#include +#include +#include + +static const char *attribute_names[] = { + [EMEUS_CONSTRAINT_ATTRIBUTE_INVALID] = "invalid", + [EMEUS_CONSTRAINT_ATTRIBUTE_LEFT] = "left", + [EMEUS_CONSTRAINT_ATTRIBUTE_RIGHT] = "right", + [EMEUS_CONSTRAINT_ATTRIBUTE_TOP] = "top", + [EMEUS_CONSTRAINT_ATTRIBUTE_BOTTOM] = "bottom", + [EMEUS_CONSTRAINT_ATTRIBUTE_START] = "start", + [EMEUS_CONSTRAINT_ATTRIBUTE_END] = "end", + [EMEUS_CONSTRAINT_ATTRIBUTE_WIDTH] = "width", + [EMEUS_CONSTRAINT_ATTRIBUTE_HEIGHT] = "height", + [EMEUS_CONSTRAINT_ATTRIBUTE_CENTER_X] = "center-x", + [EMEUS_CONSTRAINT_ATTRIBUTE_CENTER_Y] = "center-y", + [EMEUS_CONSTRAINT_ATTRIBUTE_BASELINE] = "baseline", +}; + +static const char *relation_symbols[] = { + [EMEUS_CONSTRAINT_RELATION_EQ] = "==", + [EMEUS_CONSTRAINT_RELATION_LE] = "<=", + [EMEUS_CONSTRAINT_RELATION_GE] = ">=", +}; + +const char * +get_attribute_name (EmeusConstraintAttribute attr) +{ + return attribute_names[attr]; +} + +const char * +get_relation_symbol (EmeusConstraintRelation rel) +{ + return relation_symbols[rel]; +} + +EmeusConstraintAttribute +attribute_from_name (const char *name) +{ + if (name == NULL || *name == '\0') + return EMEUS_CONSTRAINT_ATTRIBUTE_INVALID; + + /* We sadly need to special case these two because the name does + * not match the VFL grammar rules + */ + if (strcmp (name, "centerX") == 0) + return EMEUS_CONSTRAINT_ATTRIBUTE_CENTER_X; + + if (strcmp (name, "centerY") == 0) + return EMEUS_CONSTRAINT_ATTRIBUTE_CENTER_Y; + + for (int i = 0; i < G_N_ELEMENTS (attribute_names); i++) + { + if (strcmp (attribute_names[i], name) == 0) + return i; + } + + return EMEUS_CONSTRAINT_ATTRIBUTE_INVALID; +} + +OperatorType +relation_to_operator (EmeusConstraintRelation rel) +{ + switch (rel) + { + case EMEUS_CONSTRAINT_RELATION_EQ: + return OPERATOR_TYPE_EQ; + case EMEUS_CONSTRAINT_RELATION_LE: + return OPERATOR_TYPE_LE; + case EMEUS_CONSTRAINT_RELATION_GE: + return OPERATOR_TYPE_GE; + } + + return OPERATOR_TYPE_EQ; +} + +EmeusConstraintRelation +operator_to_relation (OperatorType op) +{ + switch (op) + { + case OPERATOR_TYPE_EQ: + return EMEUS_CONSTRAINT_RELATION_EQ; + case OPERATOR_TYPE_LE: + return EMEUS_CONSTRAINT_RELATION_LE; + case OPERATOR_TYPE_GE: + return EMEUS_CONSTRAINT_RELATION_GE; + } + + return EMEUS_CONSTRAINT_RELATION_EQ; +} + +double +strength_to_value (int strength) +{ + switch (strength) + { + case EMEUS_CONSTRAINT_STRENGTH_REQUIRED: + return STRENGTH_REQUIRED; + + case EMEUS_CONSTRAINT_STRENGTH_WEAK: + return STRENGTH_WEAK; + + case EMEUS_CONSTRAINT_STRENGTH_MEDIUM: + return STRENGTH_MEDIUM; + + case EMEUS_CONSTRAINT_STRENGTH_STRONG: + return STRENGTH_STRONG; + } + + return strength; +} + +EmeusConstraintStrength +value_to_strength (double strength) +{ + if (strength >= STRENGTH_REQUIRED) + return EMEUS_CONSTRAINT_STRENGTH_REQUIRED; + + if (strength >= STRENGTH_STRONG) + return EMEUS_CONSTRAINT_STRENGTH_STRONG; + + if (strength >= STRENGTH_MEDIUM) + return EMEUS_CONSTRAINT_STRENGTH_MEDIUM; + + return EMEUS_CONSTRAINT_STRENGTH_WEAK; +} + +static const char *operators[] = { + "<=", + "==", + ">=", +}; + +const char * +operator_to_string (OperatorType o) +{ + return operators[o + 1]; +} + +const char * +strength_to_string (double s) +{ + if (s >= STRENGTH_REQUIRED) + return "required"; + + if (s >= STRENGTH_STRONG) + return "strong"; + + if (s >= STRENGTH_MEDIUM) + return "medium"; + + return "weak"; +} + +bool +approx_val (double v1, + double v2) +{ + return fabs (v1 - v2) < DBL_EPSILON; +} diff --git a/shoes/layout/emeus-utils.h b/shoes/layout/emeus-utils.h new file mode 100644 index 00000000..24305a37 --- /dev/null +++ b/shoes/layout/emeus-utils.h @@ -0,0 +1,34 @@ +/* emeus-utils.h: Utility functions + * + * Copyright 2016 Endless + * + * 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 . + */ + +#pragma once + +#include "emeus-types.h" +#include "emeus-constraint.h" + +G_BEGIN_DECLS + +EMEUS_AVAILABLE_IN_1_0 +GList * emeus_create_constraints_from_description (const char * const lines[], + guint n_lines, + int hspacing, + int vspacing, + GHashTable *views, + GHashTable *metrics); + +G_END_DECLS diff --git a/shoes/layout/emeus-version-macros.h b/shoes/layout/emeus-version-macros.h new file mode 100644 index 00000000..f623c5a6 --- /dev/null +++ b/shoes/layout/emeus-version-macros.h @@ -0,0 +1,197 @@ +/* emeus-version-macros.h + * + * Copyright 2016 Endless + * + * 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 . + */ + +#pragma once + +#include "shoes/layout/emeus-version.h" + +/** + * SECTION: emeus-version + * @Title: Version macros + * @Short_Description: Check the version at compile time + * + * Emeus allows checking the version of the API at compile time; this can be + * used to write code that can be used when built against a specific version + * of the library. + */ + +/** + * EMEUS_MAJOR_VERSION: + * + * The major version of Emeus, or 1 in `1.2.3` + * + * Since: 1.0 + */ + +/** + * EMEUS_MINOR_VERSION: + * + * The minor version of Emeus, or 2 in `1.2.3` + * + * Since: 1.0 + */ + +/** + * EMEUS_MICRO_VERSION: + * + * The micro version of Emeus, or 3 in `1.2.3` + * + * Since: 1.0 + */ + +/** + * EMEUS_CHECK_VERSION: + * @major: the major version, or 1 in `1.2.3` + * @minor: the minor version, or 2 in `1.2.3` + * @micro: the micro version, or 3 in `1.2.3` + * + * Compile time check for a specific version of Emeus. + * + * This macro evaluates to %TRUE if the version of Emeus is greater than + * or equal to @major.@minor.@micro. + * + * Since: 1.0 + */ +#define EMEUS_CHECK_VERSION(major,minor,micro) \ + (((major) > EMEUS_MAJOR_VERSION) || \ + ((major) == EMEUS_MAJOR_VERSION && (minor) > EMEUS_MINOR_VERSION) || \ + ((major) == EMEUS_MAJOR_VERSION && (minor) == EMEUS_MINOR_VERSION && (micro) >= EMEUS_MICRO_VERSION)) + +#ifndef _EMEUS_PUBLIC +# define _EMEUS_PUBLIC extern +#endif + +#ifdef EMEUS_DISABLE_DEPRECATION_WARNINGS +# define EMEUS_DEPRECATED _EMEUS_PUBLIC +# define EMEUS_DEPRECATED_FOR(f) _EMEUS_PUBLIC +# define EMEUS_UNAVAILABLE(maj,min) _EMEUS_PUBLIC +#else +# define EMEUS_DEPRECATED G_DEPRECATED _EMEUS_PUBLIC +# define EMEUS_DEPRECATED_FOR(f) G_DEPRECATED_FOR(f) _EMEUS_PUBLIC +# define EMEUS_UNAVAILABLE(maj,min) G_UNAVAILABLE(maj,min) _EMEUS_PUBLIC +#endif + +/** + * EMEUS_VERSION_1_0: + * + * A pre-processor symbol that expands to the 1.0 version of Emeus. + * + * You should use this macro with %EMEUS_VERSION_MIN_REQUIRED and + * %EMEUS_VERSION_MAX_ALLOWED. + * + * Since: 1.0 + */ +#define EMEUS_VERSION_1_0 (G_ENCODE_VERSION (1, 0)) + +#if EMEUS_MINOR_VERSION >= 90 +# define EMEUS_VERSION_CUR_STABLE (G_ENCODE_VERSION (EMEUS_MAJOR_VERSION + 1, 0)) +#else +# if (EMEUS_MINOR_VERSION % 2) +# define EMEUS_VERSION_CUR_STABLE (G_ENCODE_VERSION (EMEUS_MAJOR_VERSION, EMEUS_MINOR_VERSION + 1)) +# else +# define EMEUS_VERSION_CUR_STABLE (G_ENCODE_VERSION (EMEUS_MAJOR_VERSION, EMEUS_MINOR_VERSION)) +# endif +#endif + +#if EMEUS_MINOR_VERSION >= 90 +# define EMEUS_VERSION_PREV_STABLE (G_ENCODE_VERSION (EMEUS_MAJOR_VERSION + 1, 0)) +#else +# if (EMEUS_MINOR_VERSION % 2) +# define EMEUS_VERSION_PREV_STABLE (G_ENCODE_VERSION (EMEUS_MAJOR_VERSION, EMEUS_MINOR_VERSION - 1)) +# else +# define EMEUS_VERSION_PREV_STABLE (G_ENCODE_VERSION (EMEUS_MAJOR_VERSION, EMEUS_MINOR_VERSION - 2)) +# endif +#endif + +#ifndef EMEUS_VERSION_MIN_REQUIRED +# define EMEUS_VERSION_MIN_REQUIRED (EMEUS_VERSION_CUR_STABLE) +#endif + +#ifndef EMEUS_VERSION_MAX_ALLOWED +# if EMEUS_VERSION_MIN_REQUIRED > EMEUS_VERSION_PREV_STABLE +# define EMEUS_VERSION_MAX_ALLOWED EMEUS_VERSION_MIN_REQUIRED +# else +# define EMEUS_VERSION_MAX_ALLOWED EMEUS_VERSION_CUR_STABLE +# endif +#endif + +/** + * EMEUS_VERSION_MIN_REQUIRED: + * + * Defines the lower bound of the API of Emeus that can be used. + * + * This macro can only be defined prior to including `emeus.h`. + * + * The allowed values for this macro are one of the encoded Emeus version symbols, + * like %EMEUS_VERSION_1_0. + * + * If this pre-processor symbol is defined, Emeus will only emit compiler warnings + * when attempting to use functions that have been deprecated in newer versions + * of the library; for instance, if %EMEUS_VERSION_MIN_REQUIRED is defined to be + * %EMEUS_VERSION_1_4, you'll be able to use API that has been deprecated in version + * 1.0 and 1.2 without any warning; any API deprecated in 1.4 and above will emit a + * compiler warning. + * + * See also: %EMEUS_VERSION_MAX_ALLOWED + * + * Since: 1.0 + */ + +/** + * EMEUS_VERSION_MAX_ALLOWED: + * + * Defines the upper bound of the API of Emeus that can be used. + * + * This macro can only be defined prior to including `emeus.h`. + * + * The allowed values for this macro are one of the encoded Emeus version symbols, + * like %EMEUS_VERSION_1_0. + * + * If this pre-processor symbol is defined, Emeus will emit a compiler warning when + * attempting to use functions that have been introduced in newer versions of the + * library; for instance, if %EMEUS_VERSION_MAX_ALLOWED is defined to be + * %EMEUS_VERSION_1_2, you'll be able to use API that has been introduced in version + * 1.0 and 1.2 without any warning; any API introduced in 1.4 and above will emit + * a compiler warning. + * + * See also: %EMEUS_VERSION_MIN_REQUIRED + * + * Since: 1.0 + */ + +/* Sanity checks */ +#if EMEUS_VERSION_MAX_ALLOWED < EMEUS_VERSION_MIN_REQUIRED +# error "EMEUS_VERSION_MAX_ALLOWED must be greater than EMEUS_VERSION_MIN_REQUIRED" +#endif +#if EMEUS_VERSION_MIN_REQUIRED < EMEUS_VERSION_1_0 +# error "EMEUS_VERSION_MIN_REQUIRED must be greater than EMEUS_VERSION_1_0" +#endif + +#if EMEUS_VERSION_MIN_REQUIRED >= EMEUS_VERSION_1_0 +# define EMEUS_DEPRECATED_IN_1_0 EMEUS_DEPRECATED +# define EMEUS_DEPRECATED_IN_1_0_FOR(f) EMEUS_DEPRECATED_FOR(f) +#else +# define EMEUS_DEPRECATED_IN_1_0 _EMEUS_PUBLIC +# define EMEUS_DEPRECATED_IN_1_0_FOR(f) _EMEUS_PUBLIC +#endif + +#if EMEUS_VERSION_MAX_ALLOWED < EMEUS_VERSION_1_0 +# define EMEUS_AVAILABLE_IN_1_0 EMEUS_UNAVAILABLE(1, 0) +#else +# define EMEUS_AVAILABLE_IN_1_0 _EMEUS_PUBLIC +#endif diff --git a/shoes/layout/emeus-version.h b/shoes/layout/emeus-version.h new file mode 100644 index 00000000..ad4ac25c --- /dev/null +++ b/shoes/layout/emeus-version.h @@ -0,0 +1,31 @@ +/* emeus-version.h + * + * Copyright 2016 Endless + * + * 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 . + */ + +#pragma once + +#if !defined(EMEUS_H_INSIDE) && !defined(EMEUS_COMPILATION) +//#error "Only emeus.h can be included directly." +#endif + +/* This file is auto-generated; do not modify */ + +#define EMEUS_MAJOR_VERSION 0 + +#define EMEUS_MINOR_VERSION 99 + +#define EMEUS_MICRO_VERSION 1 diff --git a/shoes/layout/emeus.h b/shoes/layout/emeus.h new file mode 100644 index 00000000..b220fcc3 --- /dev/null +++ b/shoes/layout/emeus.h @@ -0,0 +1,32 @@ +/* emeus.h + * + * Copyright 2016 Endless + * + * 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 . + */ + +#pragma once + +#define EMEUS_H_INSIDE + +#include "emeus-types.h" + +#include "emeus-version.h" +#include "emeus-version-macros.h" + +#include "emeus-constraint.h" +//#include "emeus-constraint-layout.h" // CJC has all the gtk3 stuff. +#include "emeus-utils.h" + +#undef EMEUS_H_INSIDE diff --git a/shoes/layout/layouts.h b/shoes/layout/layouts.h index d7abd69e..16ef95a5 100644 --- a/shoes/layout/layouts.h +++ b/shoes/layout/layouts.h @@ -1,7 +1,46 @@ #ifndef SHOES_LAYOUTS_H #define SHOES_LAYOUTS_H +#include "shoes/app.h" +#include "shoes/canvas.h" +#include "shoes/ruby.h" +#include "shoes/internal.h" +#include "shoes/world.h" +#include "shoes/native/native.h" +#include "shoes/version.h" +#include "shoes/types/types.h" +#include "shoes/types/settings.h" +#include "shoes/types/layout.h" -#include "shoes/layout/emeus-vfl-parser-private.h" +#include "shoes/layout/emeus-simplex-solver-private.h" +#include "shoes/layout/emeus.h" +// TODO: just to compile and link - temporary +#define GtkWidet void +#ifdef GTK_TYPE_WIDGET +#undef GTK_TYPE_WIDGET +#define GTK_TYPE_WIDGET NULL +#endif +#ifdef GTK_IS_WIDGET +#undef GTK_IS_WIDGET +#define GTK_IS_WIDGET(ptr) shoes_is_element(ptr) +#endif +// This struct is mostly glib - not ruby +typedef struct { + shoes_layout *shoes_contents; + SimplexSolver *solver; +} EmeusConstraintLayout; + +// in shoes-vfl-parser.c: VALUE shoes_vfl_rules(shoes_layout *lay, shoes_canvas *canvas, VALUE args); +// in shoes-vfl.c: +void shoes_vfl_setup(shoes_layout *lay, shoes_canvas *canvas, VALUE attr); void shoes_vfl_add_ele(shoes_canvas *canvas, VALUE ele); +void shoes_vfl_delete_at(shoes_layout *lay, shoes_canvas *canvas, VALUE ele, + int pos); +void shoes_vfl_clear(shoes_layout *lay, shoes_canvas *canvas); +void shoes_vfl_size(shoes_layout *lay, shoes_canvas *canvas, int pass); +void shoes_vfl_finish(shoes_layout *lay, shoes_canvas *canvas); +void shoes_vfl_add_contraints(shoes_layout *lay, shoes_canvas *canvas, VALUE arg); +VALUE shoes_vfl_parse(shoes_layout *lay, shoes_canvas *canvas, VALUE arg); +VALUE shoes_vfl_get_constraints(shoes_layout *lay, shoes_canvas *canvas); + #endif diff --git a/shoes/layout/shoes-vfl-parser.c b/shoes/layout/shoes-vfl-parser.c index ffe58b90..729ca74e 100644 --- a/shoes/layout/shoes-vfl-parser.c +++ b/shoes/layout/shoes-vfl-parser.c @@ -10,6 +10,7 @@ #include "shoes/types/layout.h" #include "shoes/layout/layouts.h" +#include "shoes/layout/emeus-vfl-parser-private.h" // Test data from emeus/examples/simple-grid.c @@ -135,15 +136,6 @@ static VALUE wrap_constraint(VflConstraint *c) { } -void shoes_vfl_add_ele(shoes_canvas *canvas, VALUE ele) { - shoes_layout *lay; - Data_Get_Struct(canvas->layout_mgr, shoes_layout, lay); - shoes_abstract *widget; - Data_Get_Struct(ele, shoes_abstract, widget); - VALUE name = ATTR(widget->attr, name); - char *str = RSTRING_PTR(name); - rb_hash_aset(lay->views, name, ele); -} // args is a verified Ruby hash. VALUE shoes_vfl_rules(shoes_layout *lay, shoes_canvas *canvas, VALUE args) { @@ -227,7 +219,7 @@ VALUE shoes_vfl_rules(shoes_layout *lay, shoes_canvas *canvas, VALUE args) { rb_raise(rb_eArgError, "missing lines: array?"); // feed lines to parser - lay->constraints = rb_ary_new(); + lay->rbconstraints = rb_ary_new(); for (int i = 0; i < RARRAY_LEN(lnary); i++) { VALUE ln = rb_ary_entry(lnary, i); char *line = RSTRING_PTR(ln); @@ -242,7 +234,7 @@ VALUE shoes_vfl_rules(shoes_layout *lay, shoes_canvas *canvas, VALUE args) { //fprintf(stderr, "n_constraints: %d\n", n_constraints); for (int i = 0; i < n_constraints; i++) { VflConstraint *c = &constraints[i]; - rb_ary_push(lay->constraints, wrap_constraint(c)); + rb_ary_push(lay->rbconstraints, wrap_constraint(c)); } } } diff --git a/shoes/layout/shoes-vfl.c b/shoes/layout/shoes-vfl.c new file mode 100644 index 00000000..e1226d84 --- /dev/null +++ b/shoes/layout/shoes-vfl.c @@ -0,0 +1,174 @@ +/* + * Interface between Shoes (internal layout) and emeus cassowary solver. +*/ +#include "shoes/app.h" +#include "shoes/canvas.h" +#include "shoes/ruby.h" +#include "shoes/internal.h" +#include "shoes/world.h" +#include "shoes/native/native.h" +#include "shoes/version.h" +#include "shoes/types/types.h" +#include "shoes/types/settings.h" +#include "shoes/types/layout.h" +#include "shoes/layout/layouts.h" + +#include "shoes/layout/emeus-expression-private.h" +#include "shoes/layout/emeus-simplex-solver-private.h" +#include "shoes/layout/emeus-constraint-private.h" +#include "shoes/layout/emeus-utils-private.h" + +typedef struct { + SimplexSolver *solver; + GHashTable *bound_attributes; + GHashTable *constraints; + struct { + Constraint *top, *left, *width, *height; + } stays; +} shoes_vfl_layout; + +extern ID s_name; + +shoes_vfl_layout *vfl_layout; // TODO: this should be in a shoes_layout? + +// forward declares in this file +void shoes_vfl_add_layout_stays (shoes_vfl_layout *self); + + +// Temporary - just enough to link on non gtk platforms. + +SimplexSolver * emeus_constraint_layout_get_solver(EmeusConstraintLayout *layout) +{ + return NULL; +} + +void emeus_constraint_layout_activate_constraint (EmeusConstraintLayout *layout, + EmeusConstraint *constraint) { +} + +void emeus_constraint_layout_deactivate_constraint(EmeusConstraintLayout *layout, + EmeusConstraint *constraint) { +} + +// --------- implement shoes usr layout protocol for vfl/emeus ----------- + + +void shoes_vfl_setup(shoes_layout *lay, shoes_canvas *canvas, VALUE attr) { + fprintf(stderr, "shoes_vfl_setup called\n"); + // create OUR layout struct - different from the shoes_layout, sort of. + vfl_layout = malloc(sizeof(shoes_vfl_layout)); + // make a pointer to the solver (aka context, tableau) + SimplexSolver *solver = malloc(sizeof (SimplexSolver)); + solver->initialized = 0; + simplex_solver_init (solver); + vfl_layout->solver = solver; + + // get height and width from attr + VALUE hgtobj, widobj; + int wid, hgt = 0; + widobj = ATTR(attr, width); + if (! NIL_P(widobj)) + wid = NUM2INT(widobj); + hgtobj = ATTR(attr, height); + if (! NIL_P(hgtobj)) + hgt = NUM2INT(hgtobj); + + // Create Variables and stays for the Layout's canvas. + vfl_layout->bound_attributes = g_hash_table_new_full (NULL, NULL, + NULL, + (GDestroyNotify) variable_unref); + vfl_layout->constraints = g_hash_table_new_full (NULL, NULL, + g_object_unref, + NULL); + + shoes_vfl_add_layout_stays (vfl_layout); +} + +void shoes_vfl_add_ele(shoes_canvas *canvas, VALUE ele) { + shoes_layout *lay; + Data_Get_Struct(canvas->layout_mgr, shoes_layout, lay); + shoes_abstract *widget; + Data_Get_Struct(ele, shoes_abstract, widget); + VALUE name = ATTR(widget->attr, name); + if (NIL_P(name)) + rb_raise(rb_eArgError, "please supply a name: "); + char *str = RSTRING_PTR(name); + rb_hash_aset(lay->views, name, ele); + /* + * create Variables/constraints for 'ele'. At this point in time + * we don't have w or h for the elements + */ +} + +void shoes_vfl_delete_at(shoes_layout *lay, shoes_canvas *canvas, VALUE ele, + int pos) +{ + fprintf(stderr, "shoes_vfl_delete_at called\n"); +} + +void shoes_vfl_clear(shoes_layout *lay, shoes_canvas *canvas) +{ + fprintf(stderr, "shoes_vfl_clear called\n"); +} + +void shoes_vfl_size(shoes_layout *lay, shoes_canvas *canvas, int pass) { + fprintf(stderr, "shoes_vfl_size pass: %d called\n", pass); +} + +void shoes_vfl_finish(shoes_layout *lay, shoes_canvas *canvas) { + fprintf(stderr,"shoes_vfl_finish called\n"); +} + +void shoes_vfl_add_contraints(shoes_layout *lay, shoes_canvas *canvas, VALUE arg) +{ + fprintf(stderr, "shoes_vfl_add_contraints called\n"); +} + +VALUE shoes_vfl_parse(shoes_layout *lay, shoes_canvas *canvas, VALUE arg) +{ + fprintf(stderr, "shoes_vfl_parse called\n"); +} + +VALUE shoes_vfl_get_constraints(shoes_layout *lay, shoes_canvas *canvas) +{ + fprintf(stderr, "shoes_vfl_get_constraints called\n"); + return Qnil; +} +// -------------- internels - no Ruby api into these functions --------- + +void shoes_vfl_add_layout_stays(shoes_vfl_layout *self) { + Variable *var; + + /* Add two required stay constraints for the top left corner */ + var = simplex_solver_create_variable (self->solver, "top", 0.0); + variable_set_prefix (var, "super"); + g_hash_table_insert (self->bound_attributes, + (gpointer) get_attribute_name (EMEUS_CONSTRAINT_ATTRIBUTE_TOP), + var); + self->stays.top = + simplex_solver_add_stay_variable (self->solver, var, STRENGTH_WEAK); + + var = simplex_solver_create_variable (self->solver, "left", 0.0); + variable_set_prefix (var, "super"); + g_hash_table_insert (self->bound_attributes, + (gpointer) get_attribute_name (EMEUS_CONSTRAINT_ATTRIBUTE_LEFT), + var); + self->stays.left = + simplex_solver_add_stay_variable (self->solver, var, STRENGTH_WEAK); + + var = simplex_solver_create_variable (self->solver, "width", 0.0); + variable_set_prefix (var, "super"); + g_hash_table_insert (self->bound_attributes, + (gpointer) get_attribute_name (EMEUS_CONSTRAINT_ATTRIBUTE_WIDTH), + var); + self->stays.width = + simplex_solver_add_stay_variable (self->solver, var, STRENGTH_WEAK); + + var = simplex_solver_create_variable (self->solver, "height", 0.0); + variable_set_prefix (var, "super"); + g_hash_table_insert (self->bound_attributes, + (gpointer) get_attribute_name (EMEUS_CONSTRAINT_ATTRIBUTE_HEIGHT), + var); + self->stays.height = + simplex_solver_add_stay_variable (self->solver, var, STRENGTH_WEAK); +} diff --git a/shoes/types/layout.c b/shoes/types/layout.c index 699ea565..166a138f 100644 --- a/shoes/types/layout.c +++ b/shoes/types/layout.c @@ -42,8 +42,6 @@ void shoes_layout_init() { rb_define_method(cLayout, "style", CASTHOOK(shoes_layout_style), -1); // VFL parser is not tied to a particular Shoes internal layout. rb_define_method(cLayout, "vfl_parse", CASTHOOK(shoes_layout_parse_vfl), -1); - //rb_define_method(cLayout, "vfl_metrics", CASTHOOK(shoes_layout_get_metrics), -1); - //rb_define_method(cLayout, "vft_views", CASTHOOK(shoes_layout_get_views), -1); rb_define_method(cLayout, "vfl_constraints", CASTHOOK(shoes_layout_get_constraints), -1); @@ -211,7 +209,7 @@ VALUE shoes_layout_finish(int argc, VALUE *argv, VALUE self) { } } // here if no delgate or no manager object - shoes_layout_internal_finish(canvas); + shoes_layout_internal_finish(lay, canvas); return Qtrue; } @@ -274,14 +272,23 @@ VALUE shoes_layout_parse_vfl(int argc, VALUE *argv, VALUE self) { Data_Get_Struct(self, shoes_layout, lay); shoes_canvas *canvas; Data_Get_Struct(lay->canvas, shoes_canvas, canvas); - VALUE rtn = shoes_vfl_rules(lay, canvas, arg); + VALUE rtn; + if (lay->mgr == Layout_VFL) + rtn = shoes_vfl_parse(lay, canvas, arg); + else + rtn = shoes_vfl_rules(lay, canvas, arg); // standalone parser, creates ruby hash return rtn; } VALUE shoes_layout_get_constraints(int argc, VALUE *argv, VALUE self) { shoes_layout *lay; Data_Get_Struct(self, shoes_layout, lay); - return lay->constraints; + if (lay->mgr == Layout_VFL) { + shoes_canvas *canvas; + Data_Get_Struct(lay->canvas, shoes_canvas, canvas); + return shoes_vfl_get_constraints(lay, canvas); + } + return lay->rbconstraints; } @@ -363,7 +370,7 @@ void shoes_layout_cleared(shoes_canvas *canvas) { void shoes_layout_internal_setup(shoes_layout *lay, shoes_canvas *canvas, VALUE attr) { if (lay->mgr == Layout_VFL) - fprintf(stderr, "Vfl internal layout setup called\n"); + shoes_vfl_setup(lay, canvas, attr); else fprintf(stderr, "shoes_layout_internal_setup called\n"); } @@ -378,27 +385,43 @@ void shoes_layout_internal_add(shoes_canvas *canvas, VALUE ele) { } void shoes_layout_internal_clear(shoes_canvas *canvas) { - fprintf(stderr, "shoes_layout_internal_clear called\n"); + shoes_layout *lay; + Data_Get_Struct(canvas->layout_mgr, shoes_layout, lay); + if (lay->mgr == Layout_VFL) { + shoes_vfl_clear(lay, canvas); + } else { + fprintf(stderr, "shoes_layout_internal_clear called\n"); + } } +// This is for adding constraints, not parsing VALUE shoes_layout_internal_rules(shoes_layout *lay, shoes_canvas *canvas, VALUE arg) { if (lay->mgr == Layout_VFL) { - shoes_vfl_rules(lay, canvas, arg); + shoes_vfl_add_contraints(lay, canvas, arg); } else fprintf(stderr, "shoes_layout_internal_rules called\n"); return Qnil; } -void shoes_layout_internal_finish(shoes_canvas *canvas) { - fprintf(stderr,"shoes_layout_internal_finish called\n"); +void shoes_layout_internal_finish(shoes_layout *lay, shoes_canvas *canvas) { + if (lay->mgr == Layout_VFL) + shoes_vfl_finish(lay, canvas); + else + fprintf(stderr,"shoes_layout_internal_finish called\n"); } void shoes_layout_internal_size(shoes_layout *lay, shoes_canvas *canvas, int pass) { - fprintf(stderr,"shoes_layout_internal_size called pass: %d\n", pass); + if (lay->mgr == Layout_VFL) + shoes_vfl_size(lay, canvas, pass); + else + fprintf(stderr,"shoes_layout_internal_size called pass: %d\n", pass); } VALUE shoes_layout_internal_delete_at(shoes_layout *lay, shoes_canvas *canvas, VALUE ele, int pos) { - fprintf(stderr, "shoes_layout_internal_delete_at called\n"); + if (lay->mgr == Layout_VFL) + shoes_vfl_delete_at(lay, canvas, ele, pos); + else + fprintf(stderr, "shoes_layout_internal_delete_at called\n"); return Qtrue; } diff --git a/shoes/types/layout.h b/shoes/types/layout.h index 272a27b8..1cccaf08 100644 --- a/shoes/types/layout.h +++ b/shoes/types/layout.h @@ -19,10 +19,8 @@ typedef struct { Layout_Types mgr; VALUE views; // VFL speak. Aka Widgets, Shoes elements VALUE metrics; // VFL speak. - VALUE constraints; // VFL parse results, ruby-ized. not used ? - /* emeus - * emeus_create_constraints_from_description uses the parser - */ + VALUE rbconstraints; // VFL parse results, ruby-ized. not used ? + void *root; // whatever the layout wants. } shoes_layout; // all drawables do/should implement this at the top - slightly safer @@ -63,6 +61,6 @@ VALUE shoes_layout_internal_delete_at(shoes_layout *lay, shoes_canvas *canvas, VALUE ele, int pos); void shoes_layout_internal_clear(shoes_canvas *canvas); void shoes_layout_internal_size(shoes_layout *lay, shoes_canvas *canvas, int pass); -void shoes_layout_internal_finish(shoes_canvas *canvas); +void shoes_layout_internal_finish(shoes_layout *lay, shoes_canvas *canvas); VALUE shoes_layout_internal_rules(shoes_layout *lay, shoes_canvas *canvas, VALUE arg); #endif