472 lines
12 KiB
C
472 lines
12 KiB
C
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#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:
|
|
*
|
|
* Creates a new #DbCalc.
|
|
*
|
|
* Return value: a new #DbCalc
|
|
**/
|
|
DbCalc * db_calc_new ()
|
|
{
|
|
return g_object_new (DB_TYPE_CALC, 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_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_READWRITE)
|
|
);
|
|
|
|
g_object_class_install_property (klass, PROP_FUNC
|
|
,g_param_spec_pointer ("function"
|
|
,_("Function")
|
|
,_("The function to execute")
|
|
,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_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_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_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;
|
|
}
|