/* * 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, const gchar * column_name) { g_return_val_if_fail (DB_IS_MODEL (model), NULL); return g_object_new (DB_TYPE_CALC ,"model", model ,"type", type ,"column-name", column_name ,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 * self, DbIter * iter, GValue * out, gpointer data) { const GValue * value; if (self->column_index != -1) { value = db_model_get_value (self->model, iter, self->column_index, NULL); g_value_copy (value, g_value_init (out, G_VALUE_TYPE (value))); } else g_value_init (out, GVN_TYPE_NULL); } static void db_calc_reset (DbCalc * self) { g_free (self->column_name); self->column_name = NULL; self->column_index = -1; self->func = db_calc_internal_func; } static void db_calc_refresh (DbCalc * self) { DbIter iter; gdouble current = 0; GValue val = G_VALUE_INIT; DbModel * model = self->model; self->count = 0; if (model) { DbModelStatus status = db_model_get_status (model); if (status == DB_MODEL_STATUS_READY && self->column_name) self->column_index = db_model_get_column_index (model, self->column_name); if (status == DB_MODEL_STATUS_READY && self->column_index != -1 && db_model_get_iter_first (model, &iter)) { do { self->func (self, &iter, &val, self->data); if (!gvn_value_is_null (&val)) { current += db_calc_value_to_double (&val); self->count++; } g_value_unset (&val); } while (db_model_iter_next (model, &iter)); if (self->type == DB_CALC_TYPE_AVG) current = (self->count > 0) ? current / (gdouble) self->count : 0; } } if (self->count > 0) g_value_set_double (g_value_init (&val, G_TYPE_DOUBLE), current); else g_value_init (&val, GVN_TYPE_NULL); GVN_PARAM_GET_CLASS (self)->put_value (GVN_PARAM (self), &val); g_value_unset (&val); } static void db_calc_on_model_refresh (DbModel * model, DbModelStatus status, DbCalc * self) { db_calc_refresh (self); } static void db_calc_on_update (DbModel * model, DbIter * iter, DbCalc * self) { GValue value = G_VALUE_INIT; self->func (self, iter, &value, self->data); if (!gvn_value_is_null (&value)) { gdouble current, old; current = db_calc_value_to_double (gvn_param_get_value (GVN_PARAM (self))); old = db_calc_value_to_double (&value); if (self->type == DB_CALC_TYPE_AVG) { if (self->count != 1) current = (current * (gdouble) self->count - old) / (gdouble) (self->count - 1); else current = 0; } else if (self->type == DB_CALC_TYPE_SUM) current -= old; self->count--; g_value_unset (&value); g_value_set_double (g_value_init (&value, G_TYPE_DOUBLE), current); GVN_PARAM_GET_CLASS (self)->put_value (GVN_PARAM (self), &value); } g_value_unset (&value); } static void db_calc_on_update_after (DbModel * model, DbIter * iter, DbCalc * self) { GValue value = G_VALUE_INIT; self->func (self, iter, &value, self->data); if (!gvn_value_is_null (&value)) { gdouble current, new; current = db_calc_value_to_double (gvn_param_get_value (GVN_PARAM (self))); new = db_calc_value_to_double (&value); if (self->type == DB_CALC_TYPE_AVG) current = (current * (gdouble) self->count + new) / (gdouble) (self->count + 1); else if (self->type == DB_CALC_TYPE_SUM) current += new; self->count++; g_value_unset (&value); g_value_set_double (g_value_init (&value, G_TYPE_DOUBLE), current); GVN_PARAM_GET_CLASS (self)->put_value (GVN_PARAM (self), &value); } g_value_unset (&value); } static void db_calc_on_delete (DbModel * model, gint path, DbCalc * self) { DbIter iter; db_model_get_iter (model, &iter, path); db_calc_on_update (model, &iter, self); } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Public void db_calc_set_column_name (DbCalc * self, const gchar * name) { g_return_if_fail (DB_IS_CALC (self)); db_calc_reset (self); self->column_name = g_strdup (name); db_calc_refresh (self); } void db_calc_set_column_index (DbCalc * self, gint index) { g_return_if_fail (DB_IS_CALC (self)); db_calc_reset (self); self->column_index = index; db_calc_refresh (self); } void db_calc_set_model (DbCalc * self, DbModel * model) { g_return_if_fail (DB_IS_CALC (self)); g_return_if_fail (DB_IS_MODEL (model) || !model); if (self->model) { g_object_disconnect (self->model ,"any_signal::line-updated", db_calc_on_update, self ,"any_signal::line-updated", db_calc_on_update_after, self ,"any_signal::line-deleted", db_calc_on_delete, self ,"any_signal::status-changed", db_calc_on_model_refresh, self ,NULL ); g_clear_object (&self->model); } if (model) { self->model = g_object_ref (model); g_object_connect (self->model ,"signal::line-updated", db_calc_on_update, self ,"signal-after::line-updated", db_calc_on_update_after, self ,"signal::line-deleted", db_calc_on_delete, self ,"signal::status-changed", db_calc_on_model_refresh, self ,NULL ); } db_calc_refresh (self); } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Class enum { PROP_MODEL = 1 ,PROP_TYPE ,PROP_FUNC ,PROP_DATA ,PROP_COLUMN_INDEX ,PROP_COLUMN_NAME }; static void db_calc_set_property (DbCalc * self, guint id, const GValue * value, GParamSpec * pspec) { switch (id) { case PROP_MODEL: db_calc_set_model (self, g_value_get_object (value)); break; case PROP_TYPE: self->type = g_value_get_enum (value); db_calc_refresh (self); break; case PROP_FUNC: db_calc_reset (self); if (g_value_get_pointer (value)) self->func = g_value_get_pointer (value); db_calc_refresh (self); break; case PROP_DATA: self->data = g_value_get_pointer (value); break; case PROP_COLUMN_INDEX: db_calc_set_column_index (self, g_value_get_int (value)); break; case PROP_COLUMN_NAME: db_calc_set_column_name (self, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (self, id, pspec); } } static void db_calc_get_property (DbCalc * self, guint id, GValue * value, GParamSpec * pspec) { switch (id) { case PROP_MODEL: g_value_set_object (value, self->model); break; case PROP_TYPE: g_value_set_enum (value, self->type); break; case PROP_FUNC: g_value_set_pointer (value, self->func); break; case PROP_DATA: g_value_set_pointer (value, self->data); break; case PROP_COLUMN_INDEX: g_value_set_int (value, self->column_index); break; case PROP_COLUMN_NAME: g_value_set_string (value, self->column_name); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (self, id, pspec); } } static void db_calc_init (DbCalc * self) { GvnParamSpec * spec; self->model = NULL; self->type = 0; self->func = NULL; self->data = NULL; self->column_index = -1; self->column_name = NULL; self->count = 0; spec = gvn_param_spec_new_with_attrs (G_TYPE_DOUBLE, FALSE, TRUE, NULL); GVN_PARAM_GET_CLASS (self)->set_spec (GVN_PARAM (self), spec); } static void db_calc_finalize (DbCalc * self) { db_calc_set_model (self, NULL); g_free (self->column_name); G_OBJECT_CLASS (db_calc_parent_class)->finalize (G_OBJECT (self)); } 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_COLUMN_INDEX ,g_param_spec_int ("column-index" ,_("Column index") ,_("A column to apply the operations over it") ,-1 ,G_MAXINT ,-1 ,G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE) ); g_object_class_install_property (klass, PROP_COLUMN_NAME ,g_param_spec_string ("column-name" ,_("Column name") ,_("A column name to apply the operations over it") ,NULL ,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; }