/* dia-constraint.c * Copyright (C) 2001 Arjan Molenaar * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "dia-constraint.h" #include "diamarshal.h" #include "dia-canvas-i18n.h" ///#define D(msg) G_STMT_START { g_print (G_STRLOC": "); g_print msg; g_print ("\n"); } G_STMT_END #define D(msg) enum { NEED_RESOLVE, LAST_SIGNAL }; static void dia_constraint_init (DiaConstraint *constraint); static void dia_constraint_class_init (DiaConstraintClass *klass); static void dia_constraint_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void dia_constraint_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void dia_constraint_finalize (GObject *object); static void changed_internal_cb (DiaVariable *var, DiaConstraint *con); static GObjectClass *parent_class = NULL; static guint signals[LAST_SIGNAL] = { 0 }; GType dia_constraint_get_type (void) { static GType object_type = 0; if (!object_type) { static const GTypeInfo object_info = { sizeof (DiaConstraintClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) dia_constraint_class_init, (GClassFinalizeFunc) NULL, (gconstpointer) NULL, /* class_data */ sizeof (DiaConstraint), (guint16) 0, /* n_preallocs */ (GInstanceInitFunc) dia_constraint_init, }; object_type = g_type_register_static (G_TYPE_OBJECT, "DiaConstraint", &object_info, 0); } return object_type; } static void dia_constraint_class_init (DiaConstraintClass *klass) { GObjectClass *object_class; object_class = (GObjectClass*) klass; parent_class = g_type_class_peek_parent (klass); object_class->finalize = dia_constraint_finalize; object_class->set_property = dia_constraint_set_property; object_class->get_property = dia_constraint_get_property; signals[NEED_RESOLVE] = g_signal_new ("need_resolve", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (DiaConstraintClass, need_resolve), NULL, NULL, dia_marshal_VOID__OBJECT, G_TYPE_NONE, 1, DIA_TYPE_VARIABLE); } static void dia_constraint_init (DiaConstraint *constraint) { constraint->immutable = 0; constraint->expr = NULL; } static void dia_constraint_finalize (GObject *object) { DiaConstraint *con = (DiaConstraint*) object; guint i; if (con->expr) { for (i = 0; i < con->expr->len; i++) if (con->expr->elem[i].variable) g_signal_handlers_disconnect_by_func (con->expr->elem[i].variable, G_CALLBACK (changed_internal_cb), con); dia_expression_free (con->expr); } parent_class->finalize (object); } static void dia_constraint_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { //DiaConstraint *var = DIA_CONSTRAINT (object); switch (property_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void dia_constraint_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { //DiaConstraint *var = DIA_CONSTRAINT (object); switch (property_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void changed_internal_cb (DiaVariable *var, DiaConstraint *con) { g_signal_emit (con, signals[NEED_RESOLVE], 0, var); } /** * dia_constraint_new: * * Create an empty constraint. * * Return value: A new constraint object. **/ DiaConstraint* dia_constraint_new (void) { return g_object_new (DIA_TYPE_CONSTRAINT, NULL); } /** * dia_constraint_add: * @constraint: * @var: * @c: * * Add a @var * @c pair to the constraint. * * @constraint = @constraint + @var * @c. **/ void dia_constraint_add (DiaConstraint *constraint, DiaVariable *var, gdouble c) { g_return_if_fail (DIA_IS_CONSTRAINT (constraint)); g_return_if_fail ((var == NULL) || DIA_IS_VARIABLE (var)); g_return_if_fail (constraint->immutable == 0); dia_expression_add (&constraint->expr, var, c); if (var) g_signal_connect (var, "changed_internal", G_CALLBACK (changed_internal_cb), constraint); } /** * dia_constraint_add_expression: * @constraint: * @expr: * * Add a #DiaExpression to @constraint. A #DiaExpression containts one or more * variable-constant pairs. **/ void dia_constraint_add_expression (DiaConstraint *constraint, DiaExpression *expr) { guint i; g_return_if_fail (DIA_IS_CONSTRAINT (constraint)); g_return_if_fail (expr != NULL); g_return_if_fail (constraint->immutable == 0); dia_expression_add_expression (&constraint->expr, expr); for (i = 0; i < expr->len; i++) if (expr->elem[i].variable) g_signal_connect (expr->elem[i].variable, "changed_internal", G_CALLBACK (changed_internal_cb), constraint); } /** * dia_constraint_times: * @constraint: * @c: * * Multiply all constants in the constraint with @c. * * @constraint = (@constraint) * @c. **/ void dia_constraint_times (DiaConstraint *constraint, gdouble c) { g_return_if_fail (DIA_IS_CONSTRAINT (constraint)); g_return_if_fail (constraint->immutable == 0); dia_expression_times (constraint->expr, c); } /** * dia_constraint_has_variables: * @constraint: * * Determine if @constraint has any variables in its equation. * * Return value: TRUE if the constraint contains variables, FALSE otherwise. **/ gboolean dia_constraint_has_variables (DiaConstraint *constraint) { guint i; DiaExpression *expr; g_return_val_if_fail (DIA_IS_CONSTRAINT (constraint), FALSE); expr = constraint->expr; if (expr) { for (i = 0; i < expr->len; i++) { if (expr->elem[i].variable && DIA_IS_VARIABLE (expr->elem[i].variable)) return TRUE; } } return FALSE; } /** * dia_constraint_optimize: * @constraint: * * Optimeze the constraint. This is done by merging all variable-constant pairs * whose variable is the same and removing all pairs with a constant of 0.0. **/ void dia_constraint_optimize (DiaConstraint *constraint) { DiaExpression *expr; guint i; guint len; g_return_if_fail (DIA_IS_CONSTRAINT (constraint)); expr = constraint->expr; len = expr->len; /* Unite elements with the same variable. */ for (i = 0; i < len; i++) { guint k; for (k = i + 1; k < expr->len; k++) { if (expr->elem[k].variable == expr->elem[i].variable) { expr->elem[i].constant += expr->elem[k].constant; expr->elem[k].constant = 0.0; if (expr->elem[k].variable) { g_object_unref (expr->elem[k].variable); expr->elem[k].variable = NULL; } } } } /* Remove elements whose constant == 0.0. */ for (i = 0; i < len; i++) { if (expr->elem[i].constant == 0.0) { guint k = i; /* Skip all elements with constant == 0.0. We decrement * expr->len also. */ while ((i < len) && (expr->elem[i].constant == 0.0)) { i++; expr->len--; } if (i < len) { expr->elem[k].constant = expr->elem[i].constant; expr->elem[k].variable = expr->elem[i].variable; expr->elem[i].constant = 0.0; expr->elem[i].variable = NULL; } } } } /** * dia_constraint_solve: * @constraint: * @var: * * Solve a constraint with @var being the variable that should be changed. * * Return value: G_MAXDOUBLE on error, otherwise the value that @var should * be assigned too. **/ gdouble dia_constraint_solve (DiaConstraint *constraint, DiaVariable *var) { gdouble sum = 0.0; gdouble cvar = 0.0; /* constant belonging to @var. */ DiaExpression *expr; guint i; g_return_val_if_fail (DIA_IS_CONSTRAINT (constraint), G_MAXDOUBLE); g_return_val_if_fail (DIA_IS_VARIABLE (var), G_MAXDOUBLE); expr = constraint->expr; for (i = 0; i < expr->len; i++) { if (expr->elem[i].variable == var) { cvar += expr->elem[i].constant; } else { if (expr->elem[i].variable) sum += expr->elem[i].constant * dia_variable_get_value (expr->elem[i].variable); else sum += expr->elem[i].constant; } } if (cvar == 0.0) return G_MAXDOUBLE; return -sum / cvar; } /** * dia_constraint_freeze: * @constraint: * * Make the constraint immutable. As a result no more variable-constant pairs * can be added. This function is typically used by the #DiaSolver to make * sure a constraint does not change after it has been added to the solver. **/ void dia_constraint_freeze (DiaConstraint *constraint) { g_return_if_fail (DIA_IS_CONSTRAINT (constraint)); constraint->immutable++; } /** * dia_constraint_thaw: * @constraint: * * Inverse function of dia_constraint_freeze(). **/ void dia_constraint_thaw (DiaConstraint *constraint) { g_return_if_fail (DIA_IS_CONSTRAINT (constraint)); if (constraint->immutable > 0) constraint->immutable--; } /** * dia_constraint_foreach: * @constraint: * @func: * @user_data: * * Call @func for every variable-constant pair in the constraint. **/ void dia_constraint_foreach (DiaConstraint *constraint, DiaConstraintFunc func, gpointer user_data) { guint i; for (i = 0; i < constraint->expr->len; i++) { func (constraint, constraint->expr->elem[i].variable, constraint->expr->elem[i].constant, user_data); } } /** * dia_expression_add: * @expr: * @var: * @c: * * Add "@c * @var" to @expr (@expr = @expr + (@c * @var)). **/ void dia_expression_add (DiaExpression **expr, DiaVariable *var, gdouble c) { DiaExpression e; e.len = 1; e.elem[0].variable = var; e.elem[0].constant = c; dia_expression_add_expression (expr, &e); } /** * dia_expression_add_expression: * @expr: * @expr2: * * Add @expr2 to @expr (@expr = @expr + @expr2). **/ void dia_expression_add_expression (DiaExpression **expr, DiaExpression *expr2) { guint i; if (*expr == NULL) { *expr = g_new (DiaExpression, expr2->len); (*expr)->len = 0; } else { *expr = g_realloc (*expr, sizeof (DiaExpression) + (sizeof ((*expr)->elem[0]) * ((*expr)->len + expr2->len - 1))); } for (i = 0; i < expr2->len; i++) { (*expr)->elem[(*expr)->len].variable = expr2->elem[i].variable; if (expr2->elem[i].variable) g_object_ref (expr2->elem[i].variable); (*expr)->elem[(*expr)->len].constant = expr2->elem[i].constant; (*expr)->len++; } } /** * dia_expression_times: * @expr: * @c: * * Multiply @expr with value @c (@expr = @expr * c). **/ void dia_expression_times (DiaExpression *expr, gdouble c) { guint i; for (i = 0; i < expr->len; i++) { expr->elem[i].constant *= c; } } /** * dia_expression_free: * @expr: * * Free an expression. **/ void dia_expression_free (DiaExpression *expr) { guint i; for (i = 0; i < expr->len; i++) { if (expr->elem[i].variable) g_object_unref (expr->elem[i].variable); } g_free (expr); }