/* * Copyright (C) 2012 - Juan Ferrer Toribio * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "db-calc.h" #include "db-row.h" /** * SECTION: db-calc * @short_description: the result of an operation made over a column or between * columns * @title: DbCalc * * A #GvnParam that holds the result of the user-given operation applied over * a given column of a #DbModel. When any of these values is modified, #DbCalc * updates its value according to the changes. * * #DbCalc has as built-in operations the sumatory and average that can be * selected passing #DB_CALC_TYPE_SUM OR #DB_CALC_TYPE_AVG, respectively. * * While creating a #DbCalc it's possible to specify either a single column * number, to calculate using only this column and no additional operations, or * a #DbCalcFunc function, to use more than one column or do any operation * needed with the #DbModel fields, in the latter case, custom data can also be * passed. **/ G_DEFINE_TYPE (DbCalc, db_calc, GVN_TYPE_PARAM); /** * db_calc_new: * @model: the #DbModel in which the operations will apply * @type: the #DbCalcOperationType * @col: the number of a column (starting with 0) * * Creates a new #DbCalc using @col to select the only column to be processed * in each row of @model. * * Return value: a new #DbCalc **/ DbCalc * db_calc_new (DbModel * model, DbCalcType type, gint col) { g_return_val_if_fail (DB_IS_MODEL (model), NULL); g_return_val_if_fail (0 <= col || col < db_model_get_ncols (model), NULL); return g_object_new (DB_TYPE_CALC, "model", model, "type", type, "col", col, NULL); } /** * db_calc_new_with_func: * @model: the #DbModel in which the operations will apply * @type: the #DbCalcOperationType * @func: (scope call): a #DbCalcFunc * @data: additional data that will be passed to @func * * Creates a new #DbCalc using @func as the operation that will apply to each * row of @model. * * Return value: a new #DbCalc **/ DbCalc * db_calc_new_with_func (DbModel * model, DbCalcType type, DbCalcFunc func, gpointer data) { g_return_val_if_fail (DB_IS_MODEL (model), NULL); g_return_val_if_fail (func, NULL); return g_object_new (DB_TYPE_CALC, "model", model, "type", type, "function", func, "data", data, NULL); } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Private static gdouble db_calc_value_to_double (const GValue * v) { switch (G_VALUE_TYPE (v)) { case G_TYPE_UINT: return (gdouble) g_value_get_uint (v); case G_TYPE_UINT64: return (gdouble) g_value_get_uint64 (v); case G_TYPE_ULONG: return (gdouble) g_value_get_ulong (v); case G_TYPE_INT: return (gdouble) g_value_get_int (v); case G_TYPE_INT64: return (gdouble) g_value_get_int64 (v); case G_TYPE_LONG: return (gdouble) g_value_get_long (v); case G_TYPE_FLOAT: return (gdouble) g_value_get_float (v); case G_TYPE_DOUBLE: return g_value_get_double (v); case G_TYPE_STRING: return g_strtod (g_value_get_string (v), NULL); default: return 0; } } static void db_calc_internal_func (DbCalc * obj, DbIter * iter, GValue * out, gpointer data) { const GValue * value = db_model_get_value (obj->model, iter, obj->col, NULL); g_value_copy (value, g_value_init (out, G_VALUE_TYPE (value))); } static void db_calc_before_unset (DbModel * model, DbIter * iter, DbCalc * obj) { GValue value = G_VALUE_INIT; obj->func (obj, iter, &value, obj->data); if (!gvn_value_is_null (&value)) { gdouble current, old; current = db_calc_value_to_double (gvn_param_get_value (GVN_PARAM (obj))); old = db_calc_value_to_double (&value); if (obj->type == DB_CALC_TYPE_AVG) { if (obj->count != 1) current = (current * (gdouble) obj->count - old) / (gdouble) (obj->count - 1); else current = 0; } else if (obj->type == DB_CALC_TYPE_SUM) current -= old; obj->count--; g_value_unset (&value); g_value_set_double (g_value_init (&value, G_TYPE_DOUBLE), current); GVN_PARAM_GET_CLASS (obj)->put_value (GVN_PARAM (obj), &value); } g_value_unset (&value); } static void db_calc_before_delete (DbModel * model, gint path, DbCalc * obj) { DbIter iter; db_model_get_iter (model, &iter, path); db_calc_before_unset (model, &iter, obj); } static void db_calc_after_set (DbModel * model, DbIter * iter, DbCalc * obj) { GValue value = G_VALUE_INIT; obj->func (obj, iter, &value, obj->data); if (!gvn_value_is_null (&value)) { gdouble current, new; current = db_calc_value_to_double (gvn_param_get_value (GVN_PARAM (obj))); new = db_calc_value_to_double (&value); if (obj->type == DB_CALC_TYPE_AVG) current = (current * (gdouble) obj->count + new) / (gdouble) (obj->count + 1); else if (obj->type == DB_CALC_TYPE_SUM) current += new; obj->count++; g_value_unset (&value); g_value_set_double (g_value_init (&value, G_TYPE_DOUBLE), current); GVN_PARAM_GET_CLASS (obj)->put_value (GVN_PARAM (obj), &value); } g_value_unset (&value); } static void db_calc_refresh (DbModel * model, DbModelStatus status, DbCalc * obj) { DbIter iter; gdouble current = 0; GValue val = G_VALUE_INIT; if (status != DB_MODEL_STATUS_READY) return; if (!db_model_get_iter_first (model, &iter)) return; obj->count = 0; do { obj->func (obj, &iter, &val, obj->data); if (!gvn_value_is_null (&val)) { current += db_calc_value_to_double (&val); obj->count++; } g_value_unset (&val); } while (db_model_iter_next (model, &iter)); if (obj->type == DB_CALC_TYPE_AVG) current = (obj->count > 0) ? current / (gdouble) obj->count : 0; g_value_set_double (g_value_init (&val, G_TYPE_DOUBLE), current); GVN_PARAM_GET_CLASS (obj)->put_value (GVN_PARAM (obj), &val); g_value_unset (&val); } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Class enum { PROP_MODEL = 1 ,PROP_TYPE ,PROP_FUNC ,PROP_DATA ,PROP_COL }; static void db_calc_set_property (DbCalc * obj, guint id, const GValue * value, GParamSpec * pspec) { switch (id) { case PROP_MODEL: obj->model = g_value_dup_object (value); if (obj->model) g_object_connect (obj->model ,"signal::line-updated", db_calc_before_unset, obj ,"signal-after::line-updated", db_calc_after_set, obj ,"signal::line-deleted", db_calc_before_delete, obj ,"signal::status-changed", db_calc_refresh, obj , NULL); break; case PROP_TYPE: obj->type = g_value_get_int (value); break; case PROP_FUNC: if (g_value_get_pointer (value)) obj->func = g_value_get_pointer (value); break; case PROP_DATA: obj->data = g_value_get_pointer (value); break; case PROP_COL: obj->col = g_value_get_int (value); if (!obj->func) obj->func = db_calc_internal_func; break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, pspec); } } static void db_calc_get_property (DbCalc * obj, guint id, GValue * value, GParamSpec * pspec) { switch (id) { case PROP_MODEL: g_value_set_object (value, obj->model); break; case PROP_TYPE: g_value_set_int (value, obj->type); break; case PROP_FUNC: g_value_set_pointer (value, obj->func); break; case PROP_DATA: g_value_set_pointer (value, obj->data); break; case PROP_COL: g_value_set_int (value, obj->col); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, pspec); } } static void db_calc_init (DbCalc * obj) { GvnParamSpec * spec; obj->model = NULL; obj->type = 0; obj->func = NULL; obj->data = NULL; obj->col = 0; obj->count = 0; spec = gvn_param_spec_new_with_attrs (G_TYPE_DOUBLE, FALSE, TRUE, NULL); GVN_PARAM_GET_CLASS (obj)->set_spec (GVN_PARAM (obj), spec); } static void db_calc_finalize (DbCalc * obj) { if (obj->model) { g_object_disconnect (obj->model ,"any_signal::line-updated", db_calc_before_unset, obj ,"any_signal::line-updated", db_calc_after_set, obj ,"any_signal::line-deleted", db_calc_before_delete, obj ,"any_signal::status-changed", db_calc_refresh, obj ,NULL ); g_clear_object (&obj->model); } } static void db_calc_class_init (DbCalcClass * k) { GObjectClass * klass = G_OBJECT_CLASS (k); klass->finalize = (GObjectFinalizeFunc) db_calc_finalize; klass->set_property = (GObjectSetPropertyFunc) db_calc_set_property; klass->get_property = (GObjectGetPropertyFunc) db_calc_get_property; g_object_class_install_property (klass, PROP_MODEL ,g_param_spec_object ("model" ,_("Model") ,_("The model where the operations will be applied") ,DB_TYPE_MODEL ,G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE) ); g_object_class_install_property (klass, PROP_TYPE ,g_param_spec_enum ("type" ,_("Operation type") ,_("The type of the operation applied over the function") ,DB_TYPE_CALC_TYPE ,DB_CALC_TYPE_SUM ,G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE) ); g_object_class_install_property (klass, PROP_FUNC ,g_param_spec_pointer ("function" ,_("Function") ,_("The function to execute") ,G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE) ); g_object_class_install_property (klass, PROP_DATA ,g_param_spec_pointer ("data" ,_("Data") ,_("The user provided data for the function") ,G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE) ); g_object_class_install_property (klass, PROP_COL ,g_param_spec_int ("col" ,_("Column") ,_("A column to apply the operations over it") ,0 ,G_MAXINT32 ,0 ,G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE) ); } GType db_calc_type_get_type () { static GType type = 0; if (type == 0) { static const GEnumValue values[] = { {DB_CALC_TYPE_SUM, "DB_CALC_TYPE_SUM", "sum"} ,{DB_CALC_TYPE_AVG, "DB_CALC_TYPE_AVG", "avg"} ,{0, NULL, NULL} }; type = g_enum_register_static (g_intern_static_string ("DbCalcType"), values); } return type; }