1
/* HomeBank -- Free, easy, personal accounting for everyone.
2
* Copyright (C) 1995-2023 Maxime DOYEN
4
* This file is part of HomeBank.
6
* HomeBank is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation; either version 2 of the License, or
9
* (at your option) any later version.
11
* HomeBank is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program. If not, see <http://www.gnu.org/licenses/>.
22
#include "dsp-mainwindow.h"
24
#include "hub-reptotal.h"
25
#include "gtk-chart.h"
26
#include "list-report.h"
29
/****************************************************************************/
31
/****************************************************************************/
41
#define SHOW_TREE_VIEW 0
44
/* our global datas */
45
extern struct HomeBank *GLOBALS;
46
extern struct Preferences *PREFS;
49
static gint list_topspending_compare_func (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer userdata)
51
gint sortcol = GPOINTER_TO_INT(userdata);
52
gint pos1, pos2, retval = 0;
55
gtk_tree_model_get(model, a,
56
LST_TOPSPEND_POS, &pos1,
57
LST_TOPSPEND_AMOUNT, &val1,
59
gtk_tree_model_get(model, b,
60
LST_TOPSPEND_POS, &pos2,
61
LST_TOPSPEND_AMOUNT, &val2,
64
//#1933164 should return
65
// > 0 if a sorts before b
66
// = 0 if a sorts with b
67
// < 0 if a sorts after b
71
case LST_TOPSPEND_POS:
73
//DB( g_print(" sort %3d = %3d :: %d\n", pos1, pos2, retval) );
75
case LST_TOPSPEND_AMOUNT:
76
//retval = (ABS(val1) - ABS(val2)) > 0 ? -1 : 1;
77
retval = (val1 - val2) > 0 ? -1 : 1;
78
//DB( g_print(" sort %.2f = %.2f :: %d\n", val1, val2, retval) );
86
static GtkWidget *create_list_topspending(void)
91
/* create list store */
92
store = lst_report_new();
95
view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
96
g_object_unref(store);
99
gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(store), LST_TOPSPEND_POS , list_topspending_compare_func, GINT_TO_POINTER(LST_TOPSPEND_POS), NULL);
100
gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(store), LST_TOPSPEND_AMOUNT , list_topspending_compare_func, GINT_TO_POINTER(LST_TOPSPEND_AMOUNT), NULL);
101
//gtk_tree_sortable_set_default_sort_func(GTK_TREE_SORTABLE(store), list_topspending_compare_func, NULL, NULL);
107
/* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
110
void ui_hub_reptotal_update(GtkWidget *widget, gpointer user_data)
112
struct hbfile_data *data;
115
gchar strbuffer[G_ASCII_DTOSTR_BUF_SIZE];
117
DB( g_print("\n[hub-total] update\n") );
119
data = g_object_get_data(G_OBJECT(gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW)), "inst_data");
122
hb_strfmon(strbuffer, G_ASCII_DTOSTR_BUF_SIZE-1, data->hubtot_total, GLOBALS->kcur, GLOBALS->minor);
124
switch( PREFS->hub_tot_view )
126
case HUB_TOT_VIEW_CATEGORY:
127
title = g_strdup_printf(_("Top %d Spending / Category"), PREFS->rep_maxspenditems);
129
case HUB_TOT_VIEW_PAYEE:
130
title = g_strdup_printf(_("Top %d Spending / Payee"), PREFS->rep_maxspenditems);
132
case HUB_TOT_VIEW_ACCOUNT:
133
title = g_strdup_printf(_("Top %d Spending / Account"), PREFS->rep_maxspenditems);
135
case HUB_TOT_VIEW_BALANCE:
136
title = g_strdup_printf(_("Account Balance"));
140
gtk_chart_set_color_scheme(GTK_CHART(data->RE_hubtot_chart), PREFS->report_color_scheme);
141
gtk_chart_set_smallfont (GTK_CHART(data->RE_hubtot_chart), PREFS->rep_smallfont);
142
gtk_chart_set_currency(GTK_CHART(data->RE_hubtot_chart), GLOBALS->kcur);
144
//set column1 != column2 will dual display
145
model = gtk_tree_view_get_model(GTK_TREE_VIEW(data->LV_hubtot));
146
gtk_chart_set_datas_total(GTK_CHART(data->RE_hubtot_chart), model, LST_TOPSPEND_AMOUNT, LST_TOPSPEND_AMOUNT, title, strbuffer);
152
void ui_hub_reptotal_clear(GtkWidget *widget, gpointer user_data)
154
struct hbfile_data *data;
157
DB( g_print("\n[hub-total] clear\n") );
159
data = g_object_get_data(G_OBJECT(gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW)), "inst_data");
161
model = gtk_tree_view_get_model(GTK_TREE_VIEW(data->LV_hubtot));
162
gtk_tree_store_clear (GTK_TREE_STORE(model));
167
void ui_hub_reptotal_populate(GtkWidget *widget, gpointer user_data)
169
struct hbfile_data *data;
171
GtkTreeIter iter, parent, *tmpparent;
176
gdouble total, other;
177
gboolean tmpaccbal, valid;
179
DB( g_print("\n[hub-total] populate\n") );
181
data = g_object_get_data(G_OBJECT(gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW)), "inst_data");
183
tmpsrc = REPORT_SRC_CATEGORY;
184
//tmptype = REPORT_TYPE_EXPENSE;
187
switch( PREFS->hub_tot_view )
189
case HUB_TOT_VIEW_CATEGORY: tmpsrc = REPORT_SRC_CATEGORY; break;
190
case HUB_TOT_VIEW_PAYEE : tmpsrc = REPORT_SRC_PAYEE; break;
191
case HUB_TOT_VIEW_ACCOUNT : tmpsrc = REPORT_SRC_ACCOUNT; break;
192
case HUB_TOT_VIEW_BALANCE :
193
tmpsrc = REPORT_SRC_ACCOUNT;
198
//type = hbtk_radio_button_get_active(GTK_CONTAINER(data->RA_type));
199
range = hbtk_combo_box_get_active_id(GTK_COMBO_BOX_TEXT(data->CY_hubtot_range));
200
PREFS->hub_tot_range = range;
202
DB( g_print(" range=%d\n", range) );
204
//if(range == FLT_RANGE_MISC_CUSTOM)
207
filter_preset_daterange_set(data->hubtot_filter, range, 0);
208
//#1989211 option to include xfer by default
209
if(PREFS->stat_includexfer == FALSE)
210
filter_preset_type_set(data->hubtot_filter, FLT_TYPE_INTXFER, FLT_EXCLUDE);
212
filter_preset_type_set(data->hubtot_filter, FLT_TYPE_ALL, FLT_INCLUDE);
215
DB( hb_print_date(data->hubtot_filter->mindate, "min:") );
216
DB( hb_print_date(data->hubtot_filter->maxdate, "max:") );
220
GQueue *txn_queue = hbfile_transaction_get_partial(data->hubtot_filter->mindate, data->hubtot_filter->maxdate);
223
dt = report_compute(tmpsrc, REPORT_INTVL_NONE, data->hubtot_filter, txn_queue, FALSE, FALSE);
226
dt = report_compute(tmpsrc, REPORT_INTVL_NONE, data->hubtot_filter, txn_queue, FALSE, TRUE);
227
//dt = report_compute_balance(tmpsrc, REPORT_INTVL_NONE, data->hubtot_filter, TRUE);
228
g_queue_free (txn_queue);
232
//todo: should use clear func
233
model = gtk_tree_view_get_model(GTK_TREE_VIEW(data->LV_hubtot));
234
gtk_tree_store_clear (GTK_TREE_STORE(model));
236
g_object_ref(model); /* Make sure the model stays with us after the tree view unrefs it */
237
gtk_tree_view_set_model(GTK_TREE_VIEW(data->LV_hubtot), NULL); /* Detach model from view */
239
DB( g_print("\n -- populate total listview %d rows --\n", dt->nbrows) );
241
// insert into the treeview
242
for(i=0 ; i<dt->nbrows ; i++)
248
//since 5.7 we use the dt-keylst here to insert cat before subcat
249
reskey = dt->keylist[i];
250
dr = report_data_get_row(dt, reskey);
252
//if( tmptype == REPORT_TYPE_EXPENSE && !dr->expense[0] ) continue;
253
//if( tmptype == REPORT_TYPE_INCOME && !dr->income[1] ) continue;
254
if( !dr->rowexp && !dr->rowinc )
256
DB( g_print(" - %d : %s k='%d' %.2f %.2f, skipped no data\n", i, dr->label, reskey, dr->rowexp, dr->rowinc) );
260
//if( tmpsrc == REPORT_SRC_ACCOUNT && (i == 0) )
264
if( tmpaccbal == TRUE )
265
value = dr->rowexp + dr->rowinc;
269
// manage the toplevel for category
271
if( tmpsrc == REPORT_SRC_CATEGORY )
273
Category *tmpcat = da_cat_get(reskey);
276
//if( list_topspending_get_top_level (GTK_TREE_MODEL(model), tmpcat->parent, &parent) == TRUE )
277
if( hbtk_tree_store_get_top_level(GTK_TREE_MODEL(model), LST_TOPSPEND_KEY, tmpcat->parent, &parent) )
283
if( tmpcat->parent == 0 )
292
if(value < 0.0 || tmpaccbal == TRUE )
296
if( value < 0.0 || tmpaccbal == TRUE )
299
gtk_tree_store_append (GTK_TREE_STORE(model), &iter, tmpparent);
300
gtk_tree_store_set (GTK_TREE_STORE(model), &iter,
302
LST_TOPSPEND_KEY, reskey,
303
LST_TOPSPEND_NAME, dr->label,
304
LST_TOPSPEND_AMOUNT, value,
305
//LST_TOPSPEND_RATE, (gint)(((ABS(value)*100)/ABS(total)) + 0.5),
308
DB( g_print(" - %d : %s k='%d' v='%f' %f %f, added\n", i, dr->label, reskey, value, dr->rowexp, dr->rowinc) );
312
DB( g_print(" - %d : %s k='%d' v='%f' %f %f, skipped\n", i, dr->label, reskey, value, dr->rowexp, dr->rowinc) );
317
//sort by expense descending
318
gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), LST_TOPSPEND_AMOUNT, GTK_SORT_DESCENDING);
320
//5.7 order & limitation moved here
321
if( tmpaccbal == FALSE )
325
max_items = (guint)PREFS->rep_maxspenditems;
332
DB( g_print(" aggregate items\n") );
333
valid = gtk_tree_model_get_iter_first(model, &iter);
336
gtk_tree_store_set(GTK_TREE_STORE(model), &iter,
337
LST_TOPSPEND_POS, i++,
340
do_remove = (i > max_items ) ? TRUE : FALSE;
343
valid = gtk_tree_model_iter_next(model, &iter);
347
gtk_tree_model_get (GTK_TREE_MODEL(model), &remiter,
348
LST_TOPSPEND_AMOUNT, &othamt,
354
DB( g_print(" other += %.2f\n", othamt) );
355
hbtk_tree_store_remove_iter_with_child(model, &remiter);
362
DB( g_print(" - %d : %s k='%d' v='%f'\n", max_items+1, "Other", 0, other) );
364
gtk_tree_store_append (GTK_TREE_STORE(model), &iter, NULL);
365
gtk_tree_store_set (GTK_TREE_STORE(model), &iter,
366
LST_TOPSPEND_POS, max_items+1,
368
LST_TOPSPEND_NAME, _("Other"),
369
LST_TOPSPEND_AMOUNT, other,
370
//LST_TOPSPEND_RATE, (gint)(((ABS(other)*100)/ABS(total)) + 0.5),
376
gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), LST_TOPSPEND_POS, GTK_SORT_ASCENDING);
379
/* Re-attach model to view */
380
gtk_tree_view_set_model(GTK_TREE_VIEW(data->LV_hubtot), model);
381
g_object_unref(model);
383
// update chart and widgets
387
data->hubtot_total = total;
388
ui_hub_reptotal_update(widget, data);
390
daterange = filter_daterange_text_get(data->hubtot_filter);
391
gtk_widget_set_tooltip_markup(GTK_WIDGET(data->CY_hubtot_range), daterange);
395
//TODO: later needs to keep this until dispose LV
396
da_datatable_free (dt);
405
ui_hub_reptotal_activate_radio (GSimpleAction *action, GVariant *parameter, gpointer user_data)
407
GVariant *old_state, *new_state;
409
old_state = g_action_get_state (G_ACTION (action));
410
new_state = g_variant_new_string (g_variant_get_string (parameter, NULL));
412
DB( g_print ("Radio action %s activated, state changes from %s to %s\n",
413
g_action_get_name (G_ACTION (action)),
414
g_variant_get_string (old_state, NULL),
415
g_variant_get_string (new_state, NULL)) );
417
PREFS->hub_tot_view = HUB_TOT_VIEW_NONE;
419
if( !strcmp("cat", g_variant_get_string(new_state, NULL)) )
420
PREFS->hub_tot_view = HUB_TOT_VIEW_CATEGORY;
422
if( !strcmp("pay", g_variant_get_string(new_state, NULL)) )
423
PREFS->hub_tot_view = HUB_TOT_VIEW_PAYEE;
425
if( !strcmp("acc", g_variant_get_string(new_state, NULL)) )
426
PREFS->hub_tot_view = HUB_TOT_VIEW_ACCOUNT;
428
if( !strcmp("bal", g_variant_get_string(new_state, NULL)) )
429
PREFS->hub_tot_view = HUB_TOT_VIEW_BALANCE;
431
g_simple_action_set_state (action, new_state);
432
g_variant_unref (old_state);
434
ui_hub_reptotal_populate(GLOBALS->mainwindow, NULL);
438
static const GActionEntry actions[] = {
439
// name, function(), type, state,
440
{ "view", ui_hub_reptotal_activate_radio , "s", "'cat'", NULL, {0,0,0} }
444
void ui_hub_reptotal_setup(struct hbfile_data *data)
449
data->hubtot_filter = da_flt_malloc();
450
filter_reset(data->hubtot_filter);
452
hbtk_combo_box_set_active_id(GTK_COMBO_BOX_TEXT(data->CY_hubtot_range), PREFS->hub_tot_range);
454
//#1989211 option to include xfer by default
455
if(PREFS->stat_includexfer == FALSE)
456
filter_preset_type_set(data->hubtot_filter, FLT_TYPE_INTXFER, FLT_EXCLUDE);
458
if( !G_IS_SIMPLE_ACTION_GROUP(data->hubtot_action_group) )
461
action = g_action_map_lookup_action (G_ACTION_MAP (data->hubtot_action_group), "view");
464
const gchar *value = "cat";
466
switch( PREFS->hub_tot_view )
468
case HUB_TOT_VIEW_CATEGORY: value = "cat"; break;
469
case HUB_TOT_VIEW_PAYEE: value = "pay"; break;
470
case HUB_TOT_VIEW_ACCOUNT: value = "acc"; break;
471
case HUB_TOT_VIEW_BALANCE: value = "bal"; break;
474
new_state = g_variant_new_string (value);
475
g_simple_action_set_state (G_SIMPLE_ACTION (action), new_state);
480
void ui_hub_reptotal_dispose(struct hbfile_data *data)
482
gtk_chart_set_datas_none(GTK_CHART(data->RE_hubtot_chart));
483
da_flt_free(data->hubtot_filter);
484
data->hubtot_filter = NULL;
489
GtkWidget *ui_hub_reptotal_create(struct hbfile_data *data)
491
GtkWidget *hub, *hbox, *bbox, *tbar;
492
GtkWidget *label, *widget, *image;
494
DB( g_print("\n[hub-total] create\n") );
496
// /!\ this widget has to be freed
497
widget = (GtkWidget *)create_list_topspending();
498
data->LV_hubtot = widget;
500
hub = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
501
hb_widget_set_margins(GTK_WIDGET(hub), 0, SPACING_SMALL, SPACING_SMALL, SPACING_SMALL);
502
data->GR_hubtot = hub;
504
#if SHOW_TREE_VIEW == 1
505
GtkWidget *scrollwin = gtk_scrolled_window_new (NULL, NULL);
506
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrollwin), GTK_SHADOW_ETCHED_IN);
507
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrollwin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
508
gtk_box_pack_start (GTK_BOX(hub), scrollwin, TRUE, TRUE, 0);
509
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW(scrollwin), data->LV_hubtot);
510
gtk_tree_view_set_grid_lines (GTK_TREE_VIEW (data->LV_hubtot), GTK_TREE_VIEW_GRID_LINES_BOTH);
514
/* chart + listview */
515
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
516
gtk_box_pack_start (GTK_BOX (hub), hbox, TRUE, TRUE, 0);
518
widget = gtk_chart_new(CHART_TYPE_PIE);
519
data->RE_hubtot_chart = widget;
520
gtk_chart_set_minor_prefs(GTK_CHART(widget), PREFS->euro_value, PREFS->minor_cur.symbol);
521
gtk_chart_set_currency(GTK_CHART(widget), GLOBALS->kcur);
522
gtk_chart_show_legend(GTK_CHART(widget), TRUE, TRUE);
523
gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 0);
526
tbar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, SPACING_MEDIUM);
527
gtk_style_context_add_class (gtk_widget_get_style_context (tbar), GTK_STYLE_CLASS_INLINE_TOOLBAR);
528
gtk_box_pack_start (GTK_BOX (hub), tbar, FALSE, FALSE, 0);
530
label = make_label_group(_("Total chart"));
531
data->LB_hubtot = label;
532
gtk_box_pack_start (GTK_BOX (tbar), label, FALSE, FALSE, 0);
534
/* total + date range */
535
bbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
536
gtk_box_pack_end (GTK_BOX (tbar), bbox, FALSE, FALSE, 0);
538
widget = gtk_menu_button_new();
539
gtk_box_pack_start (GTK_BOX (bbox), widget, FALSE, FALSE, 0);
541
gtk_menu_button_set_direction (GTK_MENU_BUTTON(widget), GTK_ARROW_UP);
542
gtk_widget_set_halign (widget, GTK_ALIGN_END);
543
image = gtk_image_new_from_icon_name (ICONNAME_EMBLEM_SYSTEM, GTK_ICON_SIZE_MENU);
544
g_object_set (widget, "image", image, NULL);
546
GSimpleActionGroup *group = g_simple_action_group_new ();
547
data->hubtot_action_group = group;
548
g_action_map_add_action_entries (G_ACTION_MAP (group), actions, G_N_ELEMENTS (actions), data);
549
gtk_widget_insert_action_group (widget, "actions", G_ACTION_GROUP(group));
551
//gmenu test (see test folder into gtk)
552
GMenu *menu, *section;
554
menu = g_menu_new ();
555
section = g_menu_new ();
556
g_menu_append (section, _("Category") , "actions.view::cat");
557
g_menu_append (section, _("Payee") , "actions.view::pay");
558
g_menu_append (section, _("Account") , "actions.view::acc");
559
g_menu_append (section, _("Balance") , "actions.view::bal");
560
g_menu_append_section(menu, NULL, G_MENU_MODEL(section));
561
g_object_unref (section);
563
gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (widget), G_MENU_MODEL (menu));
566
data->CY_hubtot_range = make_daterange(NULL, DATE_RANGE_CUSTOM_HIDDEN);
567
gtk_box_pack_end (GTK_BOX (tbar), data->CY_hubtot_range, FALSE, FALSE, 0);
569
//hbtk_radio_button_connect (GTK_CONTAINER(data->RA_type), "toggled", G_CALLBACK (ui_hub_reptotal_populate), NULL);
571
g_signal_connect (data->CY_hubtot_range, "changed", G_CALLBACK (ui_hub_reptotal_populate), NULL);