/* HomeBank -- Free, easy, personal accounting for everyone.
* Copyright (C) 1995-2019 Maxime DOYEN
*
* This file is part of HomeBank.
*
* HomeBank 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 2 of the License, or
* (at your option) any later version.
*
* HomeBank 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 "homebank.h"
#include "dsp-mainwindow.h"
#include "hub-spending.h"
#include "gtk-chart.h"
/****************************************************************************/
/* Debug macros */
/****************************************************************************/
#define MYDEBUG 0
#if MYDEBUG
#define DB(x) (x);
#else
#define DB(x);
#endif
/* our global datas */
extern struct HomeBank *GLOBALS;
extern struct Preferences *PREFS;
extern gchar *CYA_CATSUBCAT[];
static GtkWidget *create_list_topspending(void)
{
GtkListStore *store;
GtkWidget *view;
/* create list store */
store = gtk_list_store_new(
NUM_LST_TOPSPEND,
G_TYPE_INT,
G_TYPE_INT,
G_TYPE_STRING, //category
G_TYPE_DOUBLE, //amount
G_TYPE_INT //rate
);
//treeview
view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
g_object_unref(store);
return(view);
}
static gint tmptop_compare_func(struct tmptop *tt1, struct tmptop *tt2)
{
return tt1->value > tt2->value ? 1 : -1;
}
void ui_hub_spending_update(GtkWidget *widget, gpointer user_data)
{
struct hbfile_data *data;
GtkTreeModel *model;
gchar *title;
gchar strbuffer[G_ASCII_DTOSTR_BUF_SIZE];
DB( g_print("\n[hub-spendings] update\n") );
data = g_object_get_data(G_OBJECT(gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW)), "inst_data");
hb_strfmon(strbuffer, G_ASCII_DTOSTR_BUF_SIZE-1, data->toptotal, GLOBALS->kcur, GLOBALS->minor);
//hb_label_set_amount(GTK_LABEL(data->TX_topamount), total, GLOBALS->kcur, GLOBALS->minor);
title = g_strdup_printf("%s %s", _("Top spending"), strbuffer);
model = gtk_tree_view_get_model(GTK_TREE_VIEW(data->LV_top));
gtk_chart_set_color_scheme(GTK_CHART(data->RE_pie), PREFS->report_color_scheme);
gtk_chart_set_currency(GTK_CHART(data->RE_pie), GLOBALS->kcur);
gtk_chart_set_datas(GTK_CHART(data->RE_pie), model, LST_TOPSPEND_AMOUNT, title, NULL);
g_free(title);
//future usage
gchar *fu = _("Top %d spending"); title = fu;
}
void ui_hub_spending_populate(GtkWidget *widget, gpointer user_data)
{
struct hbfile_data *data;
GtkTreeModel *model;
GtkTreeIter iter;
GList *list;
gint type, range;
guint n_result, i, n_items;
GArray *garray;
gdouble total, other;
Account *acc;
DB( g_print("\n[hub-spendings] populate\n") );
data = g_object_get_data(G_OBJECT(gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW)), "inst_data");
type = hbtk_radio_button_get_active(GTK_CONTAINER(data->RA_type));
range = hbtk_combo_box_get_active_id(GTK_COMBO_BOX_TEXT(data->CY_range));
DB( g_print(" - type=%d, range=%d\n", type, range) );
DB( g_print(" - pref range=%d\n", PREFS->date_range_wal) );
if(range == FLT_RANGE_OTHER)
return;
filter_preset_daterange_set(data->filter, range, 0);
n_result = da_cat_get_max_key() + 1;
total = 0.0;
DB( g_print(" - max key is %d\n", n_result) );
/* allocate some memory */
garray = g_array_sized_new(FALSE, FALSE, sizeof(struct tmptop), n_result);
if(garray)
{
struct tmptop zero = { .key=0, .value=0.0 };
GQueue *txn_queue;
//DB( g_print(" - array length=%d\n", garray->len) );
for(i=0 ; ikey, tt->value) );
}
//DB( g_print("\n - end array length=%d\n", garray->len) );
//todo: not ideal, has ot force to get_acc for each txn below
txn_queue = hbfile_transaction_get_partial(data->filter->mindate, data->filter->maxdate);
/* compute the results */
list = g_queue_peek_head_link(txn_queue);
while (list != NULL)
{
Transaction *ope = list->data;
//DB( g_print(" - eval txn: '%s', cat=%d ==> flt-test=%d\n", ope->memo, ope->kcat, filter_txn_match(data->filter, ope)) );
if( !(ope->paymode == PAYMODE_INTXFER) )
{
guint32 pos = 0;
gdouble trn_amount;
//todo: optimize here
trn_amount = ope->amount;
acc = da_acc_get(ope->kacc);
if(acc)
trn_amount = hb_amount_base(ope->amount, acc->kcur);
if( ope->flags & OF_SPLIT )
{
guint nbsplit = da_splits_length(ope->splits);
Split *split;
struct tmptop *item;
for(i=0;isplits, i);
pos = category_report_id(split->kcat, type);
if( pos <= garray->len )
{
trn_amount = hb_amount_base(split->amount, acc->kcur);
//trn_amount = split->amount;
//#1297054 if( trn_amount < 0 ) {
item = &g_array_index (garray, struct tmptop, pos);
item->key = pos;
item->value += trn_amount;
//DB( g_print(" - stored %.2f to item %d\n", trn_amount, pos) );
//}
}
}
}
else
{
struct tmptop *item;
pos = category_report_id(ope->kcat, type);
if( pos <= garray->len )
{
//#1297054 if( trn_amount < 0 ) {
item = &g_array_index (garray, struct tmptop, pos);
item->key = pos;
item->value += trn_amount;
//DB( g_print(" - stored %.2f to item %d\n", trn_amount, pos) );
//}
}
}
}
list = g_list_next(list);
}
g_queue_free (txn_queue);
// we need to sort this and limit before
g_array_sort(garray, (GCompareFunc)tmptop_compare_func);
n_items = MIN(garray->len,MAX_TOPSPENDING);
other = 0;
for(i=0 ; ilen ; i++)
{
struct tmptop *item;
item = &g_array_index (garray, struct tmptop, i);
if(item->value < 0)
{
total += item->value;
if(i >= n_items)
other += item->value;
DB( g_print(" - %d : k='%d' v='%f' t='%f'\n", i, item->key, item->value, total) );
}
}
model = gtk_tree_view_get_model(GTK_TREE_VIEW(data->LV_top));
gtk_list_store_clear (GTK_LIST_STORE(model));
g_object_ref(model); /* Make sure the model stays with us after the tree view unrefs it */
gtk_tree_view_set_model(GTK_TREE_VIEW(data->LV_top), NULL); /* Detach model from view */
/* insert into the treeview */
for(i=0 ; ilen,MAX_TOPSPENDING) ; i++)
{
gchar *name;
Category *entry;
struct tmptop *item;
gdouble value;
item = &g_array_index (garray, struct tmptop, i);
if(!item->value) continue;
//#1767659 top spending should restrict to... spending
if(item->value < 0)
{
value = hb_amount_round(item->value, 2);
entry = da_cat_get(item->key);
if(entry == NULL) continue;
name = da_cat_get_name (entry);
// append test
gtk_list_store_append (GTK_LIST_STORE(model), &iter);
gtk_list_store_set (GTK_LIST_STORE(model), &iter,
LST_TOPSPEND_ID, i,
LST_TOPSPEND_KEY, 0,
LST_TOPSPEND_NAME, name,
LST_TOPSPEND_AMOUNT, value,
//LST_TOPSPEND_RATE, (gint)(((ABS(value)*100)/ABS(total)) + 0.5),
-1);
}
}
// append test
if(ABS(other) > 0)
{
gtk_list_store_append (GTK_LIST_STORE(model), &iter);
gtk_list_store_set (GTK_LIST_STORE(model), &iter,
LST_TOPSPEND_ID, n_items,
LST_TOPSPEND_KEY, 0,
LST_TOPSPEND_NAME, _("Other"),
LST_TOPSPEND_AMOUNT, other,
//LST_TOPSPEND_RATE, (gint)(((ABS(other)*100)/ABS(total)) + 0.5),
-1);
}
/* Re-attach model to view */
gtk_tree_view_set_model(GTK_TREE_VIEW(data->LV_top), model);
g_object_unref(model);
// update chart and widgets
{
gchar *daterange;
data->toptotal = total;
ui_hub_spending_update(widget, data);
daterange = filter_daterange_text_get(data->filter);
gtk_widget_set_tooltip_markup(GTK_WIDGET(data->CY_range), daterange);
g_free(daterange);
}
}
/* free our memory */
g_array_free (garray, TRUE);
}
GtkWidget *ui_hub_spending_create(struct hbfile_data *data)
{
GtkWidget *hub, *hbox, *tbar;
GtkWidget *label, *widget;
GtkToolItem *toolitem;
DB( g_print("\n[hub-spendings] create\n") );
widget = (GtkWidget *)create_list_topspending();
data->LV_top = widget;
hub = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_container_set_border_width(GTK_CONTAINER(hub), SPACING_SMALL);
data->GR_top = hub;
/* chart + listview */
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_box_pack_start (GTK_BOX (hub), hbox, TRUE, TRUE, 0);
widget = gtk_chart_new(CHART_TYPE_PIE);
data->RE_pie = widget;
gtk_chart_set_minor_prefs(GTK_CHART(widget), PREFS->euro_value, PREFS->minor_cur.symbol);
gtk_chart_show_legend(GTK_CHART(data->RE_pie), TRUE, TRUE);
gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 0);
//list toolbar
tbar = gtk_toolbar_new();
gtk_toolbar_set_icon_size (GTK_TOOLBAR(tbar), GTK_ICON_SIZE_MENU);
gtk_toolbar_set_style(GTK_TOOLBAR(tbar), GTK_TOOLBAR_ICONS);
gtk_style_context_add_class (gtk_widget_get_style_context (tbar), GTK_STYLE_CLASS_INLINE_TOOLBAR);
gtk_box_pack_start (GTK_BOX (hub), tbar, FALSE, FALSE, 0);
label = make_label_group(_("Where your money goes"));
toolitem = gtk_tool_item_new();
gtk_container_add (GTK_CONTAINER(toolitem), label);
gtk_toolbar_insert(GTK_TOOLBAR(tbar), GTK_TOOL_ITEM(toolitem), -1);
toolitem = gtk_separator_tool_item_new ();
gtk_tool_item_set_expand (toolitem, TRUE);
gtk_separator_tool_item_set_draw(GTK_SEPARATOR_TOOL_ITEM(toolitem), FALSE);
gtk_toolbar_insert(GTK_TOOLBAR(tbar), GTK_TOOL_ITEM(toolitem), -1);
/* total + date range */
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, SPACING_SMALL);
toolitem = gtk_tool_item_new();
gtk_container_add (GTK_CONTAINER(toolitem), hbox);
gtk_toolbar_insert(GTK_TOOLBAR(tbar), GTK_TOOL_ITEM(toolitem), -1);
data->CY_range = make_daterange(label, DATE_RANGE_CUSTOM_HIDE);
gtk_box_pack_end (GTK_BOX (hbox), data->CY_range, FALSE, FALSE, 0);
widget = hbtk_radio_button_new(CYA_CATSUBCAT, TRUE);
data->RA_type = widget;
gtk_box_pack_end (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
hbtk_radio_button_connect (GTK_CONTAINER(data->RA_type), "toggled", G_CALLBACK (ui_hub_spending_populate), &data);
g_signal_connect (data->CY_range, "changed", G_CALLBACK (ui_hub_spending_populate), NULL);
return hub;
}