/* 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 . * * 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 *prefix, const char *name) { variable->_id = gtk_constraint_variable_next_id++; variable->prefix = g_intern_string (prefix); 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, NULL, 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, NULL, 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, NULL, 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: * @prefix: (nullable): an optional prefix string for @name * @name: (nullable): an optional name for 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 *prefix, const char *name) { GtkConstraintVariable *res = g_rc_box_new (GtkConstraintVariable); gtk_constraint_variable_init (res, prefix, 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_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, ""); 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 { /* List, owns a reference */ GSequence *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_sequence_free (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_sequence_new ((GDestroyNotify) gtk_constraint_variable_unref); res->age = 0; return res; } static int sort_by_variable_id (gconstpointer a, gconstpointer b, gpointer data) { 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) { GSequenceIter *iter; iter = g_sequence_search (set->set, variable, sort_by_variable_id, NULL); if (!g_sequence_iter_is_end (iter)) { GtkConstraintVariable *v = g_sequence_get (iter); if (v->_id == variable->_id) return FALSE; } g_sequence_insert_before (iter, gtk_constraint_variable_ref (variable)); 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) { GSequenceIter *iter; iter = g_sequence_lookup (set->set, variable, sort_by_variable_id, NULL); if (iter != NULL) { g_sequence_remove (iter); 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_sequence_get_length (set->set); } gboolean gtk_constraint_variable_set_is_empty (GtkConstraintVariableSet *set) { return g_sequence_is_empty (set->set); } gboolean gtk_constraint_variable_set_is_singleton (GtkConstraintVariableSet *set) { return g_sequence_iter_next (g_sequence_get_begin_iter (set->set)) == g_sequence_get_end_iter (set->set); } /*< private > * GtkConstraintVariableSetIter: * * An iterator type for #GtkConstraintVariableSet. */ /* Keep in sync with GtkConstraintVariableSetIter */ typedef struct { GtkConstraintVariableSet *set; GSequenceIter *iter; 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->iter = g_sequence_get_begin_iter (set->set); 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 (g_sequence_iter_is_end (riter->iter)) return FALSE; *variable_p = g_sequence_get (riter->iter); riter->iter = g_sequence_iter_next (riter->iter); return TRUE; } /*< 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; 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); } 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 (""); 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: * * |[ * 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); } /* }}} */