Quelle gtkconstraintexpression.c
Sprache: C
/* 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 *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, "<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 {
/* List<Variable>, 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 ` GtkConstraintVariable ` Set
*
* Frees the resources associated to a ` GtkConstraintVariable ` Set /
*/
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 ` GtkConstraintVariable ` Set .
*
* 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 ` GtkConstraintVariable ` Set
* @ 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 ` GtkConstraintVariable ` Set
* @ 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 ` GtkConstraintVariable ` Set
*
* 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 ` GtkConstraintVariable ` Set .
*/
/* 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 ` GtkConstraintVariable ` SetIter
* @ set : the ` GtkConstraintVariable ` Set 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 ` GtkConstraintVariable ` SetIter
* @ variable_p : ( out ) : the next variable in the set
*
* Advances the @ iter to the next variable in the ` GtkConstraintVariable ` Set .
*
* 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 ` GtkConstraintVariable ` Pair , containing @ first and @ second .
*
* The ` GtkConstraintVariable ` Pair acquires a reference over the two
* given ` GtkConstraintVariable ` s .
*
* Returns : a new ` GtkConstraintVariable ` Pair
*/
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 ` GtkConstraintVariable ` Pair
*
* 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);
}
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 ` GtkConstraintExpression ` Iter
* @ 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 ` GtkConstraintExpression ` Iter
* @ variable : ( out ) : the variable of the next term
* @ coefficient : ( out ) : the coefficient of the next term
*
* Moves the given ` GtkConstraintExpression ` Iter 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 ` GtkConstraintExpression ` Iter
* @ variable : ( out ) : the variable of the previous term
* @ coefficient : ( out ) : the coefficient of the previous term
*
* Moves the given ` GtkConstraintExpression ` Iter 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 ` GtkConstraintExpression ` Builder
* @ solver : a ` GtkConstraintSolver `
*
* Initializes the given ` GtkConstraintExpression ` Builder 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 :
*
* ` ` ` 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 ` GtkConstraintExpression ` Builder
* @ 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 ` GtkConstraintExpression ` Builder
*
* 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 ` GtkConstraintExpression ` Builder
*
* 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 ` GtkConstraintExpression ` Builder
*
* 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 ` GtkConstraintExpression ` Builder
*
* 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 ` GtkConstraintExpression ` Builder
* @ 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 ` GtkConstraintExpression ` Builder
*
* 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);
}
/* }}} */
Messung V0.5 in Prozent C=97 H=96 G=96
¤ Dauer der Verarbeitung: 0.32 Sekunden
(vorverarbeitet am 2026-07-02)
¤
*© Formatika GbR, Deutschland
2026-07-09