diff options
Diffstat (limited to 'gtk/gtkconstraintexpression.c')
-rw-r--r-- | gtk/gtkconstraintexpression.c | 1842 |
1 files changed, 1842 insertions, 0 deletions
diff --git a/gtk/gtkconstraintexpression.c b/gtk/gtkconstraintexpression.c new file mode 100644 index 0000000000..0e5db96b62 --- /dev/null +++ b/gtk/gtkconstraintexpression.c @@ -0,0 +1,1842 @@ +/* gtkconstraintexpression.c: Constraint expressions and variables + * Copyright 2019 GNOME Foundation + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Author: Emmanuele Bassi + */ + +#include "config.h" + +#include "gtkconstraintexpressionprivate.h" +#include "gtkconstraintsolverprivate.h" + +/* {{{ Variables */ + +typedef enum { + GTK_CONSTRAINT_SYMBOL_DUMMY = 'd', + GTK_CONSTRAINT_SYMBOL_OBJECTIVE = 'o', + GTK_CONSTRAINT_SYMBOL_SLACK = 'S', + GTK_CONSTRAINT_SYMBOL_REGULAR = 'v' +} GtkConstraintSymbolType; + +struct _GtkConstraintVariable +{ + guint64 _id; + + GtkConstraintSymbolType _type; + + /* Interned strings */ + const char *name; + const char *prefix; + + double value; + + guint is_external : 1; + guint is_pivotable : 1; + guint is_restricted : 1; +}; + +/* Variables are sorted by a monotonic id */ +static guint64 gtk_constraint_variable_next_id; + +static void +gtk_constraint_variable_init (GtkConstraintVariable *variable, + const char *name) +{ + variable->_id = gtk_constraint_variable_next_id++; + variable->name = g_intern_string (name); + variable->prefix = NULL; + variable->value = 0.0; +} + +/*< private > + * gtk_constraint_variable_new_dummy: + * @name: the name of the variable + * + * Allocates and initializes a new #GtkConstraintVariable for a "dummy" + * symbol. Dummy symbols are typically used as markers inside a solver, + * and will not be factored in the solution when pivoting the tableau + * of the constraint equations. + * + * Only #GtkConstraintSolver should use this function. + * + * Returns: a newly allocated #GtkConstraintVariable + */ +GtkConstraintVariable * +gtk_constraint_variable_new_dummy (const char *name) +{ + GtkConstraintVariable *res = g_rc_box_new (GtkConstraintVariable); + + gtk_constraint_variable_init (res, name); + + res->_type = GTK_CONSTRAINT_SYMBOL_DUMMY; + res->is_external = FALSE; + res->is_pivotable = FALSE; + res->is_restricted = TRUE; + + return res; +} + +/*< private > + * gtk_constraint_variable_new_objective: + * @name: the name of the variable + * + * Allocates and initializes a new #GtkConstraintVariable for an objective + * symbol. This is the constant value we wish to find as the result of the + * simplex optimization. + * + * Only #GtkConstraintSolver should use this function. + * + * Returns: a newly allocated #GtkConstraintVariable + */ +GtkConstraintVariable * +gtk_constraint_variable_new_objective (const char *name) +{ + GtkConstraintVariable *res = g_rc_box_new (GtkConstraintVariable); + + gtk_constraint_variable_init (res, name); + + res->_type = GTK_CONSTRAINT_SYMBOL_OBJECTIVE; + res->is_external = FALSE; + res->is_pivotable = FALSE; + res->is_restricted = FALSE; + + return res; +} + +/*< private > + * gtk_constraint_variable_new_slack: + * @name: the name of the variable + * + * Allocates and initializes a new #GtkConstraintVariable for a "slack" + * symbol. Slack variables are introduced inside the tableau to turn + * inequalities, like: + * + * |[ + * expr ≥ 0 + * ]| + * + * Into equalities, like: + * + * |[ + * expr - slack = 0 + * ]| + * + * Only #GtkConstraintSolver should use this function. + * + * Returns: a newly allocated #GtkConstraintVariable + */ +GtkConstraintVariable * +gtk_constraint_variable_new_slack (const char *name) +{ + GtkConstraintVariable *res = g_rc_box_new (GtkConstraintVariable); + + gtk_constraint_variable_init (res, name); + + res->_type = GTK_CONSTRAINT_SYMBOL_SLACK; + res->is_external = FALSE; + res->is_pivotable = TRUE; + res->is_restricted = TRUE; + + return res; +} + +/*< private > + * gtk_constraint_variable_new: + * @name: the name of the variable + * + * Allocates and initializes a new #GtkConstraintVariable for a regular + * symbol. All variables introduced by constraints are regular variables. + * + * Only #GtkConstraintSolver should use this function; a constraint layout + * manager should ask the #GtkConstraintSolver to create a variable, using + * gtk_constraint_solver_create_variable(), which will insert the variable + * in the solver's tableau. + * + * Returns: a newly allocated #GtkConstraintVariable + */ +GtkConstraintVariable * +gtk_constraint_variable_new (const char *name) +{ + GtkConstraintVariable *res = g_rc_box_new (GtkConstraintVariable); + + gtk_constraint_variable_init (res, name); + + res->_type = GTK_CONSTRAINT_SYMBOL_REGULAR; + res->is_external = TRUE; + res->is_pivotable = FALSE; + res->is_restricted = FALSE; + + return res; +} + +/*< private > + * gtk_constraint_variable_set_prefix: + * @variable: a #GtkConstraintVariable + * @prefix: a prefix string + * + * Sets the prefix to the @variable's name. + * + * This function is useful when debugging the variable contents. + */ +void +gtk_constraint_variable_set_prefix (GtkConstraintVariable *variable, + const char *prefix) +{ + g_return_if_fail (variable != NULL); + + variable->prefix = g_intern_string (prefix); +} + +/*< private > + * gtk_constraint_variable_ref: + * @variable: a #GtkConstraintVariable + * + * Acquires a reference to @variable. + * + * Returns: (transfer full): the given #GtkConstraintVariable, with its reference + * count increased + */ +GtkConstraintVariable * +gtk_constraint_variable_ref (GtkConstraintVariable *variable) +{ + g_return_val_if_fail (variable != NULL, NULL); + + return g_rc_box_acquire (variable); +} + +/*< private > + * gtk_constraint_variable_unref: + * @variable: (transfer full): a #GtkConstraintVariable + * + * Releases a reference to @variable. + */ +void +gtk_constraint_variable_unref (GtkConstraintVariable *variable) +{ + g_return_if_fail (variable != NULL); + + g_rc_box_release (variable); +} + +/*< private > + * gtk_constraint_variable_set_value: + * @variable: a #GtkConstraintVariable + * + * Sets the current value of a #GtkConstraintVariable. + */ +void +gtk_constraint_variable_set_value (GtkConstraintVariable *variable, + double value) +{ + variable->value = value; +} + +/*< private > + * gtk_constraint_variable_get_value: + * @variable: a #GtkConstraintVariable + * + * Retrieves the current value of a #GtkConstraintVariable + * + * Returns: the value of the variable + */ +double +gtk_constraint_variable_get_value (const GtkConstraintVariable *variable) +{ + return variable->value; +} + +/*< private > + * gtk_constraint_variable_to_string: + * @variable: a #GtkConstraintVariable + * + * Turns @variable into a string, for debugging purposes. + * + * Returns: (transfer full): a string with the contents of @variable + */ +char * +gtk_constraint_variable_to_string (const GtkConstraintVariable *variable) +{ + GString *buf = g_string_new (NULL); + + if (variable == NULL) + g_string_append (buf, "<null>"); + else + { + switch (variable->_type) + { + case GTK_CONSTRAINT_SYMBOL_DUMMY: + g_string_append (buf, "(d)"); + break; + case GTK_CONSTRAINT_SYMBOL_OBJECTIVE: + g_string_append (buf, "(O)"); + break; + case GTK_CONSTRAINT_SYMBOL_SLACK: + g_string_append (buf, "(S)"); + break; + case GTK_CONSTRAINT_SYMBOL_REGULAR: + break; + + default: + g_assert_not_reached (); + } + + 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 == GTK_CONSTRAINT_SYMBOL_REGULAR) + { + char dbl_buf[G_ASCII_DTOSTR_BUF_SIZE]; + + g_ascii_dtostr (dbl_buf, G_ASCII_DTOSTR_BUF_SIZE, variable->value); + + g_string_append_c (buf, ':'); + g_string_append (buf, dbl_buf); + } + + g_string_append_c (buf, ']'); + } + + return g_string_free (buf, FALSE); +} + +/*< private > + * gtk_constraint_variable_is_external: + * @variable: a #GtkConstraintVariable + * + * Checks whether the @variable was introduced from outside the solver. + * + * Returns: %TRUE if the variable is external + */ +gboolean +gtk_constraint_variable_is_external (const GtkConstraintVariable *variable) +{ + return variable->is_external; +} + +/*< private > + * gtk_constraint_variable_is_pivotable: + * @variable: a #GtkConstraintVariable + * + * Checks whether the @variable can be used as a pivot. + * + * Returns: %TRUE if the variable is pivotable + */ +gboolean +gtk_constraint_variable_is_pivotable (const GtkConstraintVariable *variable) +{ + return variable->is_pivotable; +} + +/*< private > + * gtk_constraint_variable_is_restricted: + * @variable: a #GtkConstraintVariable + * + * Checks whether the @variable's use is restricted. + * + * Returns: %TRUE if the variable is restricted + */ +gboolean +gtk_constraint_variable_is_restricted (const GtkConstraintVariable *variable) +{ + return variable->is_restricted; +} + +/*< private > + * gtk_constraint_variable_is_dummy: + * @variable: a #GtkConstraintVariable + * + * Checks whether the @variable is a dummy symbol. + * + * Returns: %TRUE if the variable is a dummy symbol + */ +gboolean +gtk_constraint_variable_is_dummy (const GtkConstraintVariable *variable) +{ + return variable->_type == GTK_CONSTRAINT_SYMBOL_DUMMY; +} + +/*< private > + * GtkConstraintVariableSet: + * + * A set of variables. + */ +struct _GtkConstraintVariableSet { + /* HashSet<Variable>, owns a reference */ + GHashTable *set; + + /* List<Variable>, used for iterating */ + GList *ordered_set; + + /* Age of the set, to guard against mutations while iterating */ + gint64 age; +}; + +/*< private > + * gtk_constraint_variable_set_free: + * @set: a #GtkConstraintVariableSet + * + * Frees the resources associated to a #GtkConstraintVariableSet/ + */ +void +gtk_constraint_variable_set_free (GtkConstraintVariableSet *set) +{ + g_return_if_fail (set != NULL); + + g_list_free (set->ordered_set); + g_hash_table_unref (set->set); + + g_free (set); +} + +/*< private > + * gtk_constraint_variable_set_new: + * + * Creates a new #GtkConstraintVariableSet. + * + * Returns: the newly created variable set + */ +GtkConstraintVariableSet * +gtk_constraint_variable_set_new (void) +{ + GtkConstraintVariableSet *res = g_new (GtkConstraintVariableSet, 1); + + res->set = g_hash_table_new_full (NULL, NULL, + (GDestroyNotify) gtk_constraint_variable_unref, + NULL); + res->ordered_set = NULL; + + res->age = 0; + + return res; +} + +static int +sort_by_variable_id (gconstpointer a, + gconstpointer b) +{ + const GtkConstraintVariable *va = a, *vb = b; + + if (va == vb) + return 0; + + return va->_id - vb->_id; +} + +/*< private > + * gtk_constraint_variable_set_add: + * @set: a #GtkConstraintVariableSet + * @variable: a #GtkConstraintVariable + * + * Adds @variable to the given @set, if the @variable is not already + * in it. + * + * The @set will acquire a reference on the @variable, and will release + * it after calling gtk_constraint_variable_set_remove(), or when the @set + * is freed. + * + * Returns: %TRUE if the variable was added to the set, and %FALSE otherwise + */ +gboolean +gtk_constraint_variable_set_add (GtkConstraintVariableSet *set, + GtkConstraintVariable *variable) +{ + if (g_hash_table_contains (set->set, variable)) + return FALSE; + + g_hash_table_add (set->set, gtk_constraint_variable_ref (variable)); + + /* This is a tricky bit; the variables in the set must be ordered + * not by insertion, but by the incremental id of each variable, + * as that's the expected iteration order + */ + set->ordered_set = g_list_insert_sorted (set->ordered_set, variable, sort_by_variable_id); + + set->age += 1; + + return TRUE; +} + +/*< private > + * gtk_constraint_variable_set_remove: + * @set: a #GtkConstraintVariableSet + * @variable: a #GtkConstraintVariable + * + * Removes @variable from the @set. + * + * This function will release the reference on @variable held by the @set. + * + * Returns: %TRUE if the variable was removed from the set, and %FALSE + * otherwise + */ +gboolean +gtk_constraint_variable_set_remove (GtkConstraintVariableSet *set, + GtkConstraintVariable *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); + set->age += 1; + + return TRUE; + } + + return FALSE; +} + +/*< private > + * gtk_constraint_variable_set_size: + * @set: a #GtkConstraintVariableSet + * + * Retrieves the size of the @set. + * + * Returns: the number of variables in the set + */ +int +gtk_constraint_variable_set_size (GtkConstraintVariableSet *set) +{ + return g_hash_table_size (set->set); +} + +/*< private > + * GtkConstraintVariableSetIter: + * + * An iterator type for #GtkConstraintVariableSet. + */ +/* Keep in sync with GtkConstraintVariableSetIter */ +typedef struct { + GtkConstraintVariableSet *set; + GList *current; + gint64 age; +} RealVariableSetIter; + +#define REAL_VARIABLE_SET_ITER(i) ((RealVariableSetIter *) (i)) + +/*< private > + * gtk_constraint_variable_set_iter_init: + * @iter: a #GtkConstraintVariableSetIter + * @set: the #GtkConstraintVariableSet to iterate + * + * Initializes @iter for iterating over @set. + */ +void +gtk_constraint_variable_set_iter_init (GtkConstraintVariableSetIter *iter, + GtkConstraintVariableSet *set) +{ + RealVariableSetIter *riter = REAL_VARIABLE_SET_ITER (iter); + + g_return_if_fail (iter != NULL); + g_return_if_fail (set != NULL); + + riter->set = set; + riter->current = NULL; + riter->age = set->age; +} + +/*< private > + * gtk_constraint_variable_set_iter_next: + * @iter: a #GtkConstraintVariableSetIter + * @variable_p: (out): the next variable in the set + * + * Advances the @iter to the next variable in the #GtkConstraintVariableSet. + * + * Returns: %TRUE if the iterator was advanced, and %FALSE otherwise + */ +gboolean +gtk_constraint_variable_set_iter_next (GtkConstraintVariableSetIter *iter, + GtkConstraintVariable **variable_p) +{ + RealVariableSetIter *riter = REAL_VARIABLE_SET_ITER (iter); + + g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (variable_p != NULL, FALSE); + + g_assert (riter->age == riter->set->age); + + if (riter->current == NULL) + riter->current = riter->set->ordered_set; + else + riter->current = riter->current->next; + + if (riter->current != NULL) + *variable_p = riter->current->data; + + return riter->current != NULL; +} + +/*< private > + * gtk_constraint_variable_pair_new: + * @first: a #GtkConstraintVariable + * @second: a #GtkConstraintVariable + * + * Creates a new #GtkConstraintVariablePair, containint @first and @second. + * + * The #GtkConstraintVariablePair acquires a reference over the two + * given #GtkConstraintVariables. + * + * Returns: a new #GtkConstraintVariablePair + */ +GtkConstraintVariablePair * +gtk_constraint_variable_pair_new (GtkConstraintVariable *first, + GtkConstraintVariable *second) +{ + GtkConstraintVariablePair *res = g_new (GtkConstraintVariablePair, 1); + + res->first = gtk_constraint_variable_ref (first); + res->second = gtk_constraint_variable_ref (second); + + return res; +} + +/*< private > + * gtk_constraint_variable_pair_free: + * @pair: a #GtkConstraintVariablePair + * + * Frees the resources associated by @pair. + */ +void +gtk_constraint_variable_pair_free (GtkConstraintVariablePair *pair) +{ + g_clear_pointer (&pair->first, gtk_constraint_variable_unref); + g_clear_pointer (&pair->second, gtk_constraint_variable_unref); + + g_free (pair); +} + +/* }}} */ + +/* {{{ Expressions */ + +/*< private > + * Term: + * @variable: a #GtkConstraintVariable + * @coefficient: the coefficient applied to the @variable + * @next: the next term in the expression + * @prev: the previous term in the expression; + * + * A tuple of (@variable, @coefficient) in an equation. + * + * The term acquires a reference on the variable. + */ +typedef struct _Term Term; + +struct _Term { + GtkConstraintVariable *variable; + double coefficient; + + Term *next; + Term *prev; +}; + +static Term * +term_new (GtkConstraintVariable *variable, + double coefficient) +{ + Term *res = g_new (Term, 1); + + res->variable = gtk_constraint_variable_ref (variable); + res->coefficient = coefficient; + res->next = res->prev = NULL; + + return res; +} + +static void +term_free (gpointer data) +{ + Term *term = data; + + if (term == NULL) + return; + + gtk_constraint_variable_unref (term->variable); + + g_free (term); +} + +struct _GtkConstraintExpression +{ + double constant; + + /* HashTable<Variable, Term>; the key is the term's variable, + * and the value is owned by the hash table + */ + GHashTable *terms; + + /* List of terms, in insertion order */ + Term *first_term; + Term *last_term; + + /* Used by GtkConstraintExpressionIter to guard against changes + * in the expression while iterating + */ + gint64 age; +}; + +/*< private > + * gtk_constraint_expression_add_term: + * @self: a #GtkConstraintExpression + * @variable: a #GtkConstraintVariable + * @coefficient: a coefficient for @variable + * + * Adds a new term formed by (@variable, @coefficient) into a + * #GtkConstraintExpression. + * + * The @expression acquires a reference on @variable. + */ +static void +gtk_constraint_expression_add_term (GtkConstraintExpression *self, + GtkConstraintVariable *variable, + double coefficient) +{ + Term *term; + + if (self->terms == NULL) + { + g_assert (self->first_term == NULL && self->last_term == NULL); + self->terms = g_hash_table_new_full (NULL, NULL, + NULL, + term_free); + } + + term = term_new (variable, coefficient); + + g_hash_table_insert (self->terms, term->variable, term); + + if (self->first_term == NULL) + self->first_term = term; + + term->prev = self->last_term; + + if (self->last_term != NULL) + self->last_term->next = term; + + self->last_term = term; + + /* Increase the age of the expression, so that we can catch + * mutations from within an iteration over the terms + */ + self->age += 1; +} + +static void +gtk_constraint_expression_remove_term (GtkConstraintExpression *self, + GtkConstraintVariable *variable) +{ + Term *term, *iter; + + if (self->terms == NULL) + return; + + term = g_hash_table_lookup (self->terms, variable); + if (term == NULL) + return; + + /* Keep the variable alive for the duration of the function */ + gtk_constraint_variable_ref (variable); + + iter = self->first_term; + while (iter != NULL) + { + Term *next = iter->next; + Term *prev = iter->prev; + + if (iter == term) + { + if (prev != NULL) + prev->next = next; + if (next != NULL) + next->prev = prev; + + if (iter == self->first_term) + self->first_term = next; + if (iter == self->last_term) + self->last_term = prev; + + iter->next = NULL; + iter->prev = NULL; + + break; + } + + iter = next; + } + + g_hash_table_remove (self->terms, variable); + + gtk_constraint_variable_unref (variable); + + self->age += 1; +} + +/*< private > + * gtk_constraint_expression_new: + * @constant: a constant for the expression + * + * Creates a new #GtkConstraintExpression with the given @constant. + * + * Returns: (transfer full): the newly created expression + */ +GtkConstraintExpression * +gtk_constraint_expression_new (double constant) +{ + GtkConstraintExpression *res = g_rc_box_new (GtkConstraintExpression); + + res->age = 0; + res->terms = NULL; + res->first_term = NULL; + res->last_term = NULL; + res->constant = constant; + + return res; +} + +/*< private > + * gtk_constraint_expression_new_from_variable: + * @variable: a #GtkConstraintVariable + * + * Creates a new #GtkConstraintExpression with the given @variable. + * + * Returns: (transfer full): the newly created expression + */ +GtkConstraintExpression * +gtk_constraint_expression_new_from_variable (GtkConstraintVariable *variable) +{ + GtkConstraintExpression *res = gtk_constraint_expression_new (0.0); + + gtk_constraint_expression_add_term (res, variable, 1.0); + + return res; +} + +/*< private > + * gtk_constraint_expression_ref: + * @expression: a #GtkConstraintExpression + * + * Acquires a reference on @expression. + * + * Returns: (transfer full): the @expression, with its reference + * count increased + */ +GtkConstraintExpression * +gtk_constraint_expression_ref (GtkConstraintExpression *expression) +{ + g_return_val_if_fail (expression != NULL, NULL); + + return g_rc_box_acquire (expression); +} + +static void +gtk_constraint_expression_clear (gpointer data) +{ + GtkConstraintExpression *self = data; + + g_clear_pointer (&self->terms, g_hash_table_unref); + + self->age = 0; + self->constant = 0.0; + self->first_term = NULL; + self->last_term = NULL; +} + +/*< private > + * gtk_constraint_expression_unref: + * @expression: (transfer full): a #GtkConstraintExpression + * + * Releases a reference on @expression. + */ +void +gtk_constraint_expression_unref (GtkConstraintExpression *expression) +{ + g_rc_box_release_full (expression, gtk_constraint_expression_clear); +} + +/*< private > + * gtk_constraint_expression_is_constant: + * @expression: a #GtkConstraintExpression + * + * Checks whether @expression is a constant value, with no variable terms. + * + * Returns: %TRUE if the @expression is a constant + */ +gboolean +gtk_constraint_expression_is_constant (const GtkConstraintExpression *expression) +{ + return expression->terms == NULL; +} + +/*< private > + * gtk_constraint_expression_set_constant: + * @expression: a #GtkConstraintExpression + * @constant: the value of the constant + * + * Sets the value of the constant part of @expression. + */ +void +gtk_constraint_expression_set_constant (GtkConstraintExpression *expression, + double constant) +{ + g_return_if_fail (expression != NULL); + + expression->constant = constant; +} + +/*< private > + * gtk_constraint_expression_get_constant: + * @expression: a #GtkConstraintExpression + * + * Retrieves the constant value of @expression. + * + * Returns: the constant of @expression + */ +double +gtk_constraint_expression_get_constant (const GtkConstraintExpression *expression) +{ + g_return_val_if_fail (expression != NULL, 0.0); + + return expression->constant; +} + +GtkConstraintExpression * +gtk_constraint_expression_clone (GtkConstraintExpression *expression) +{ + GtkConstraintExpression *res; + Term *iter; + + res = gtk_constraint_expression_new (expression->constant); + + iter = expression->first_term; + while (iter != NULL) + { + gtk_constraint_expression_add_term (res, iter->variable, iter->coefficient); + + iter = iter->next; + } + + return res; +} + +/*< private > + * gtk_constraint_expression_add_variable: + * @expression: a #GtkConstraintExpression + * @variable: a #GtkConstraintVariable to add to @expression + * @coefficient: the coefficient of @variable + * @subject: (nullable): a #GtkConstraintVariable + * @solver: (nullable): a #GtkConstraintSolver + * + * Adds a `(@coefficient × @variable)` term to @expression. + * + * If @expression already contains a term for @variable, this function will + * update its coefficient. + * + * If @coefficient is 0 and @expression already contains a term for @variable, + * the term for @variable will be removed. + * + * This function will notify @solver if @variable is added or removed from + * the @expression. + */ +void +gtk_constraint_expression_add_variable (GtkConstraintExpression *expression, + GtkConstraintVariable *variable, + double coefficient, + GtkConstraintVariable *subject, + GtkConstraintSolver *solver) +{ + /* If the expression already contains the variable, update the coefficient */ + if (expression->terms != NULL) + { + Term *t = g_hash_table_lookup (expression->terms, variable); + + if (t != NULL) + { + double new_coefficient = t->coefficient + coefficient; + + /* Setting the coefficient to 0 will remove the variable */ + if (G_APPROX_VALUE (new_coefficient, 0.0, 0.001)) + { + /* Update the tableau if needed */ + if (solver != NULL) + gtk_constraint_solver_note_removed_variable (solver, variable, subject); + + gtk_constraint_expression_remove_term (expression, variable); + + if (subject != NULL) + gtk_constraint_variable_unref (subject); + } + else + { + t->coefficient = new_coefficient; + } + + return; + } + } + + /* Otherwise, add the variable if the coefficient is non-zero */ + if (!G_APPROX_VALUE (coefficient, 0.0, 0.001)) + { + gtk_constraint_expression_add_term (expression, variable, coefficient); + + if (solver != NULL) + gtk_constraint_solver_note_added_variable (solver, variable, subject); + } +} + +/*< private > + * gtk_constraint_expression_remove_variable: + * @expression: a #GtkConstraintExpression + * @variable: a #GtkConstraintVariable + * + * Removes @variable from @expression. + */ +void +gtk_constraint_expression_remove_variable (GtkConstraintExpression *expression, + GtkConstraintVariable *variable) +{ + g_return_if_fail (expression != NULL); + g_return_if_fail (variable != NULL); + + gtk_constraint_expression_remove_term (expression, variable); +} + +/*< private > + * gtk_constraint_expression_set_variable: + * @expression: a #GtkConstraintExpression + * @variable: a #GtkConstraintVariable + * @coefficient: a coefficient for @variable + * + * Sets the @coefficient for @variable inside an @expression. + * + * If the @expression does not contain a term for @variable, a new + * one will be added. + */ +void +gtk_constraint_expression_set_variable (GtkConstraintExpression *expression, + GtkConstraintVariable *variable, + double coefficient) +{ + if (expression->terms != NULL) + { + Term *t = g_hash_table_lookup (expression->terms, variable); + + if (t != NULL) + { + t->coefficient = coefficient; + return; + } + } + + gtk_constraint_expression_add_term (expression, variable, coefficient); +} + +/*< private > + * gtk_constraint_expression_add_expression: + * @a_expr: first operand + * @b_expr: second operand + * @n: the multiplication factor for @b_expr + * @subject: (nullable): a #GtkConstraintVariable + * @solver: (nullable): a #GtkConstraintSolver + * + * Adds `(@n × @b_expr)` to @a_expr. + * + * Typically, this function is used to turn two expressions in the + * form: + * + * |[ + * a.x + a.width = b.x + b.width + * ]| + * + * into a single expression: + * + * |[ + * a.x + a.width - b.x - b.width = 0 + * ]| + * + * If @solver is not %NULL, this function will notify a #GtkConstraintSolver + * of every variable that was added or removed from @a_expr. + */ +void +gtk_constraint_expression_add_expression (GtkConstraintExpression *a_expr, + GtkConstraintExpression *b_expr, + double n, + GtkConstraintVariable *subject, + GtkConstraintSolver *solver) +{ + Term *iter; + + a_expr->constant += (n * b_expr->constant); + + iter = b_expr->last_term; + while (iter != NULL) + { + Term *next = iter->prev; + + gtk_constraint_expression_add_variable (a_expr, + iter->variable, n * iter->coefficient, + subject, + solver); + + iter = next; + } +} + +/*< private > + * gtk_constraint_expression_plus_constant: + * @expression: a #GtkConstraintExpression + * @constant: a constant value + * + * Adds a @constant value to the @expression. + * + * This is the equivalent of creating a new #GtkConstraintExpression for + * the @constant and calling gtk_constraint_expression_add_expression(). + * + * Returns: the @expression + */ +GtkConstraintExpression * +gtk_constraint_expression_plus_constant (GtkConstraintExpression *expression, + double constant) +{ + GtkConstraintExpression *e; + + e = gtk_constraint_expression_new (constant); + gtk_constraint_expression_add_expression (expression, e, 1.0, NULL, NULL); + gtk_constraint_expression_unref (e); + + return expression; +} + +/*< private > + * gtk_constraint_expression_minus_constant: + * @expression: a #GtkConstraintExpression + * @constant: a constant value + * + * Removes a @constant value from the @expression. + * + * This is the equivalent of creating a new #GtkConstraintExpression for + * the inverse of @constant and calling gtk_constraint_expression_add_expression(). + * + * Returns: the @expression + */ +GtkConstraintExpression * +gtk_constraint_expression_minus_constant (GtkConstraintExpression *expression, + double constant) +{ + return gtk_constraint_expression_plus_constant (expression, constant * -1.0); +} + +/*< private > + * gtk_constraint_expression_plus_variable: + * @expression: a #GtkConstraintExpression + * @variable: a #GtkConstraintVariable + * + * Adds a @variable to the @expression. + * + * Returns: the @expression + */ +GtkConstraintExpression * +gtk_constraint_expression_plus_variable (GtkConstraintExpression *expression, + GtkConstraintVariable *variable) +{ + GtkConstraintExpression *e; + + e = gtk_constraint_expression_new_from_variable (variable); + gtk_constraint_expression_add_expression (expression, e, 1.0, NULL, NULL); + gtk_constraint_expression_unref (e); + + return expression; +} + +/*< private > + * gtk_constraint_expression_minus_variable: + * @expression: a #GtkConstraintExpression + * @variable: a #GtkConstraintVariable + * + * Subtracts a @variable from the @expression. + * + * Returns: the @expression + */ +GtkConstraintExpression * +gtk_constraint_expression_minus_variable (GtkConstraintExpression *expression, + GtkConstraintVariable *variable) +{ + GtkConstraintExpression *e; + + e = gtk_constraint_expression_new_from_variable (variable); + gtk_constraint_expression_add_expression (expression, e, -1.0, NULL, NULL); + gtk_constraint_expression_unref (e); + + return expression; +} + +/*< private > + * gtk_constraint_expression_multiply_by: + * @expression: a #GtkConstraintExpression + * @factor: the multiplication factor + * + * Multiplies the constant part and the coefficient of all terms + * in @expression with the given @factor. + * + * Returns: the @expression + */ +GtkConstraintExpression * +gtk_constraint_expression_multiply_by (GtkConstraintExpression *expression, + double factor) +{ + GHashTableIter iter; + gpointer value_p; + + expression->constant *= factor; + + 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 *= factor; + } + + return expression; +} + +/*< private > + * gtk_constraint_expression_divide_by: + * @expression: a #GtkConstraintExpression + * @factor: the division factor + * + * Divides the constant part and the coefficient of all terms + * in @expression by the given @factor. + * + * Returns: the @expression + */ +GtkConstraintExpression * +gtk_constraint_expression_divide_by (GtkConstraintExpression *expression, + double factor) +{ + if (G_APPROX_VALUE (factor, 0.0, 0.001)) + return expression; + + return gtk_constraint_expression_multiply_by (expression, 1.0 / factor); +} + +/*< private > + * gtk_constraint_expression_new_subject: + * @expression: a #GtkConstraintExpression + * @subject: a #GtkConstraintVariable part of @expression + * + * Modifies @expression to have a new @subject. + * + * A #GtkConstraintExpression is a linear expression in the form of + * `@expression = 0`. If @expression contains @subject, for instance: + * + * |[ + * c + (a × @subject) + (a1 × v1) + … + (an × vn) = 0 + * ]| + * + * this function will make @subject the new subject of the expression: + * + * |[ + * subject = - (c / a) - ((a1 / a) × v1) - … - ((an / a) × vn) = 0 + * ]| + * + * The term @subject is removed from the @expression. + * + * Returns: the reciprocal of the coefficient of @subject, so we + * can use this function in gtk_constraint_expression_change_subject() + */ +double +gtk_constraint_expression_new_subject (GtkConstraintExpression *expression, + GtkConstraintVariable *subject) +{ + double reciprocal = 1.0; + Term *term; + + g_assert (!gtk_constraint_expression_is_constant (expression)); + + term = g_hash_table_lookup (expression->terms, subject); + g_assert (term != NULL); + g_assert (!G_APPROX_VALUE (term->coefficient, 0.0, 0.001)); + + reciprocal = 1.0 / term->coefficient; + + gtk_constraint_expression_remove_term (expression, subject); + gtk_constraint_expression_multiply_by (expression, -reciprocal); + + return reciprocal; +} + +/*< private > + * gtk_constraint_expression_change_subject: + * @expression: a #GtkConstraintExpression + * @old_subject: the old subject #GtkConstraintVariable of @expression + * @new_subject: the new subject #GtkConstraintVariable of @expression + * + * Turns an @expression in the form of: + * + * |[ + * old_subject = c + (a × new_subject) + (a1 × v1) + … + (an × vn) + * ]| + * + * into the form of: + * + * |[ + * new_subject = -c / a + old_subject / a - ((a1 / a) × v1) - … - ((an / a) × vn) + * ]| + * + * Which means resolving @expression for @new_subject. + */ +void +gtk_constraint_expression_change_subject (GtkConstraintExpression *expression, + GtkConstraintVariable *old_subject, + GtkConstraintVariable *new_subject) +{ + double reciprocal; + + g_return_if_fail (expression != NULL); + g_return_if_fail (old_subject != NULL); + g_return_if_fail (new_subject != NULL); + + reciprocal = gtk_constraint_expression_new_subject (expression, new_subject); + gtk_constraint_expression_set_variable (expression, old_subject, reciprocal); +} + +/*< private > + * gtk_constraint_expression_get_coefficient: + * @expression: a #GtkConstraintExpression + * @variable: a #GtkConstraintVariable + * + * Retrieves the coefficient of the term for @variable inside @expression. + * + * Returns: the coefficient of @variable + */ +double +gtk_constraint_expression_get_coefficient (GtkConstraintExpression *expression, + GtkConstraintVariable *variable) +{ + const Term *term; + + g_return_val_if_fail (expression != NULL, 0.0); + g_return_val_if_fail (variable != NULL, 0.0); + + if (expression->terms == NULL) + return 0.0; + + term = g_hash_table_lookup (expression->terms, variable); + if (term == NULL) + return 0.0; + + return term->coefficient; +} + +/*< private > + * gtk_constraint_expression_substitute_out: + * @expression: a #GtkConstraintExpression + * @out_var: the variable to replace + * @expr: the expression used to replace @out_var + * @subject: (nullable): a #GtkConstraintVariable + * @solver: (nullable): a #GtkConstraintSolver + * + * Replaces every term containing @out_var inside @expression with @expr. + * + * If @solver is not %NULL, this function will notify the #GtkConstraintSolver + * for every variable added to or removed from @expression. + */ +void +gtk_constraint_expression_substitute_out (GtkConstraintExpression *expression, + GtkConstraintVariable *out_var, + GtkConstraintExpression *expr, + GtkConstraintVariable *subject, + GtkConstraintSolver *solver) +{ + double multiplier; + Term *iter; + + if (expression->terms == NULL) + return; + + multiplier = gtk_constraint_expression_get_coefficient (expression, out_var); + gtk_constraint_expression_remove_term (expression, out_var); + + expression->constant = expression->constant + multiplier * expr->constant; + + iter = expr->first_term; + while (iter != NULL) + { + GtkConstraintVariable *clv = iter->variable; + double coeff = iter->coefficient; + Term *next = iter->next; + + if (expression->terms != NULL && + g_hash_table_contains (expression->terms, clv)) + { + double old_coefficient = gtk_constraint_expression_get_coefficient (expression, clv); + double new_coefficient = old_coefficient + multiplier * coeff; + + if (G_APPROX_VALUE (new_coefficient, 0.0, 0.001)) + { + if (solver != NULL) + gtk_constraint_solver_note_removed_variable (solver, clv, subject); + + gtk_constraint_expression_remove_term (expression, clv); + } + else + gtk_constraint_expression_set_variable (expression, clv, new_coefficient); + } + else + { + gtk_constraint_expression_set_variable (expression, clv, multiplier * coeff); + + if (solver != NULL) + gtk_constraint_solver_note_added_variable (solver, clv, subject); + } + + iter = next; + } +} + +/*< private > + * gtk_constraint_expression_get_pivotable_variable: + * @expression: a #GtkConstraintExpression + * + * Retrieves the first #GtkConstraintVariable in @expression that + * is marked as pivotable. + * + * Returns: (transfer none) (nullable): a #GtkConstraintVariable + */ +GtkConstraintVariable * +gtk_constraint_expression_get_pivotable_variable (GtkConstraintExpression *expression) +{ + Term *iter; + + if (expression->terms == NULL) + { + g_critical ("Expression %p is a constant", expression); + return NULL; + } + + iter = expression->first_term; + while (iter != NULL) + { + Term *next = iter->next; + + if (gtk_constraint_variable_is_pivotable (iter->variable)) + return iter->variable; + + iter = next; + } + + return NULL; +} + +/*< private > + * gtk_constraint_expression_to_string: + * @expression: a #GtkConstraintExpression + * + * Creates a string containing @expression. + * + * This function is only useful for debugging. + * + * Returns: (transfer full): a string containing the given expression + */ +char * +gtk_constraint_expression_to_string (const GtkConstraintExpression *expression) +{ + gboolean needs_plus = FALSE; + GString *buf; + Term *iter; + + if (expression == NULL) + return g_strdup ("<null>"); + + buf = g_string_new (NULL); + + if (!G_APPROX_VALUE (expression->constant, 0.0, 0.001)) + { + g_string_append_printf (buf, "%g", expression->constant); + + if (expression->terms != NULL) + needs_plus = TRUE; + } + + if (expression->terms == NULL) + return g_string_free (buf, FALSE); + + iter = expression->first_term; + while (iter != NULL) + { + char *str = gtk_constraint_variable_to_string (iter->variable); + Term *next = iter->next; + + if (needs_plus) + g_string_append (buf, " + "); + + if (G_APPROX_VALUE (iter->coefficient, 1.0, 0.001)) + g_string_append_printf (buf, "%s", str); + else + g_string_append_printf (buf, "(%g * %s)", iter->coefficient, str); + + g_free (str); + + if (!needs_plus) + needs_plus = TRUE; + + iter = next; + } + + return g_string_free (buf, FALSE); +} + +/* Keep in sync with GtkConstraintExpressionIter */ +typedef struct { + GtkConstraintExpression *expression; + Term *current; + gint64 age; +} RealExpressionIter; + +#define REAL_EXPRESSION_ITER(i) ((RealExpressionIter *) (i)) + +/*< private > + * gtk_constraint_expression_iter_init: + * @iter: a #GtkConstraintExpressionIter + * @expression: a #GtkConstraintExpression + * + * Initializes an iterator over @expression. + */ +void +gtk_constraint_expression_iter_init (GtkConstraintExpressionIter *iter, + GtkConstraintExpression *expression) +{ + RealExpressionIter *riter = REAL_EXPRESSION_ITER (iter); + + riter->expression = expression; + riter->current = NULL; + riter->age = expression->age; +} + +/*< private > + * gtk_constraint_expression_iter_next: + * @iter: a valid #GtkConstraintExpressionIter + * @variable: (out): the variable of the next term + * @coefficient: (out): the coefficient of the next term + * + * Moves the given #GtkConstraintExpressionIter forwards to the next + * term in the expression, starting from the first term. + * + * Returns: %TRUE if the iterator was moved, and %FALSE if the iterator + * has reached the end of the terms of the expression + */ +gboolean +gtk_constraint_expression_iter_next (GtkConstraintExpressionIter *iter, + GtkConstraintVariable **variable, + double *coefficient) +{ + RealExpressionIter *riter = REAL_EXPRESSION_ITER (iter); + + g_assert (riter->age == riter->expression->age); + + if (riter->current == NULL) + riter->current = riter->expression->first_term; + else + riter->current = riter->current->next; + + if (riter->current != NULL) + { + *coefficient = riter->current->coefficient; + *variable = riter->current->variable; + } + + return riter->current != NULL; +} + +/*< private > + * gtk_constraint_expression_iter_prev: + * @iter: a valid #GtkConstraintExpressionIter + * @variable: (out): the variable of the previous term + * @coefficient: (out): the coefficient of the previous term + * + * Moves the given #GtkConstraintExpressionIter backwards to the previous + * term in the expression, starting from the last term. + * + * Returns: %TRUE if the iterator was moved, and %FALSE if the iterator + * has reached the beginning of the terms of the expression + */ +gboolean +gtk_constraint_expression_iter_prev (GtkConstraintExpressionIter *iter, + GtkConstraintVariable **variable, + double *coefficient) +{ + RealExpressionIter *riter = REAL_EXPRESSION_ITER (iter); + + g_assert (riter->age == riter->expression->age); + + if (riter->current == NULL) + riter->current = riter->expression->last_term; + else + riter->current = riter->current->prev; + + if (riter->current != NULL) + { + *coefficient = riter->current->coefficient; + *variable = riter->current->variable; + } + + return riter->current != NULL; +} + +typedef enum { + BUILDER_OP_NONE, + BUILDER_OP_PLUS, + BUILDER_OP_MINUS, + BUILDER_OP_MULTIPLY, + BUILDER_OP_DIVIDE +} BuilderOpType; + +typedef struct +{ + GtkConstraintExpression *expression; + GtkConstraintSolver *solver; + int op; +} RealExpressionBuilder; + +#define REAL_EXPRESSION_BUILDER(b) ((RealExpressionBuilder *) (b)) + +/*< private > + * gtk_constraint_expression_builder_init: + * @builder: a #GtkConstraintExpressionBuilder + * @solver: a #GtkConstraintSolver + * + * Initializes the given #GtkConstraintExpressionBuilder for the + * given #GtkConstraintSolver. + * + * You can use the @builder to construct expressions to be added to the + * @solver, in the form of constraints. + * + * A typical use is: + * + * |[<!-- language="C" --> + * GtkConstraintExpressionBuilder builder; + * + * // "solver" is set in another part of the code + * gtk_constraint_expression_builder_init (&builder, solver); + * + * // "width" is set in another part of the code + * gtk_constraint_expression_builder_term (&builder, width); + * gtk_constraint_expression_builder_divide_by (&builder); + * gtk_constraint_expression_builder_constant (&builder, 2.0); + * + * // "left" is set in another part of the code + * gtk_constraint_expression_builder_plus (&builder); + * gtk_constraint_expression_builder_term (&builder, left); + * + * // "expr" now contains the following expression: + * // width / 2.0 + left + * GtkConstraintExpression *expr = + * gtk_constraint_expression_builder_finish (&builder); + * + * // The builder is inert, and can be re-used by calling + * // gtk_constraint_expression_builder_init() again. + * ]| + */ +void +gtk_constraint_expression_builder_init (GtkConstraintExpressionBuilder *builder, + GtkConstraintSolver *solver) +{ + RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder); + + rbuilder->solver = solver; + rbuilder->expression = gtk_constraint_expression_new (0); + rbuilder->op = BUILDER_OP_NONE; +} + +/*< private > + * gtk_constraint_expression_builder_term: + * @builder: a #GtkConstraintExpressionBuilder + * @term: a #GtkConstraintVariable + * + * Adds a variable @term to the @builder. + */ +void +gtk_constraint_expression_builder_term (GtkConstraintExpressionBuilder *builder, + GtkConstraintVariable *term) +{ + RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder); + GtkConstraintExpression *expr; + + expr = gtk_constraint_expression_new_from_variable (term); + + switch (rbuilder->op) + { + case BUILDER_OP_NONE: + g_clear_pointer (&rbuilder->expression, gtk_constraint_expression_unref); + rbuilder->expression = g_steal_pointer (&expr); + break; + + case BUILDER_OP_PLUS: + gtk_constraint_expression_add_expression (rbuilder->expression, + expr, 1.0, + NULL, + NULL); + gtk_constraint_expression_unref (expr); + break; + + case BUILDER_OP_MINUS: + gtk_constraint_expression_add_expression (rbuilder->expression, + expr, -1.0, + NULL, + NULL); + gtk_constraint_expression_unref (expr); + break; + + default: + break; + } + + rbuilder->op = BUILDER_OP_NONE; +} + +/*< private > + * gtk_constraint_expression_builder_plus: + * @builder: a #GtkConstraintExpressionBuilder + * + * Adds a plus operator to the @builder. + */ +void +gtk_constraint_expression_builder_plus (GtkConstraintExpressionBuilder *builder) +{ + RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder); + + rbuilder->op = BUILDER_OP_PLUS; +} + +/*< private > + * gtk_constraint_expression_builder_minus: + * @builder: a #GtkConstraintExpressionBuilder + * + * Adds a minus operator to the @builder. + */ +void +gtk_constraint_expression_builder_minus (GtkConstraintExpressionBuilder *builder) +{ + RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder); + + rbuilder->op = BUILDER_OP_MINUS; +} + +/*< private > + * gtk_constraint_expression_builder_divide_by: + * @builder: a #GtkConstraintExpressionBuilder + * + * Adds a division operator to the @builder. + */ +void +gtk_constraint_expression_builder_divide_by (GtkConstraintExpressionBuilder *builder) +{ + RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder); + + rbuilder->op = BUILDER_OP_DIVIDE; +} + +/*< private > + * gtk_constraint_expression_builder_multiply_by: + * @builder: a #GtkConstraintExpressionBuilder + * + * Adds a multiplication operator to the @builder. + */ +void +gtk_constraint_expression_builder_multiply_by (GtkConstraintExpressionBuilder *builder) + +{ + RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder); + + rbuilder->op = BUILDER_OP_MULTIPLY; +} + +/*< private > + * gtk_constraint_expression_builder_constant: + * @builder: a #GtkConstraintExpressionBuilder + * @value: a constant value + * + * Adds a constant value to the @builder. + */ +void +gtk_constraint_expression_builder_constant (GtkConstraintExpressionBuilder *builder, + double value) +{ + RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder); + + switch (rbuilder->op) + { + case BUILDER_OP_NONE: + gtk_constraint_expression_set_constant (rbuilder->expression, value); + break; + + case BUILDER_OP_PLUS: + gtk_constraint_expression_plus_constant (rbuilder->expression, value); + break; + + case BUILDER_OP_MINUS: + gtk_constraint_expression_minus_constant (rbuilder->expression, value); + break; + + case BUILDER_OP_MULTIPLY: + gtk_constraint_expression_multiply_by (rbuilder->expression, value); + break; + + case BUILDER_OP_DIVIDE: + gtk_constraint_expression_divide_by (rbuilder->expression, value); + break; + + default: + break; + } + + rbuilder->op = BUILDER_OP_NONE; +} + +/*< private > + * gtk_constraint_expression_builder_finish: + * @builder: a #GtkConstraintExpressionBuilder + * + * Closes the given expression builder, and returns the expression. + * + * You can only call this function once. + * + * Returns: (transfer full): the built expression + */ +GtkConstraintExpression * +gtk_constraint_expression_builder_finish (GtkConstraintExpressionBuilder *builder) +{ + RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder); + + rbuilder->solver = NULL; + rbuilder->op = BUILDER_OP_NONE; + + return g_steal_pointer (&rbuilder->expression); +} + +/* }}} */ |