1 /*  HomeBank -- Free, easy, personal accounting for everyone.
2  *  Copyright (C) 2018-2019 Adrien Dorsaz <adrien@adorsaz.ch>
3  *
4  *  This file is part of HomeBank.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19 
20 
21 #include "homebank.h"
22 #include "hb-misc.h"
23 #include "dsp-mainwindow.h"
24 #include "hb-category.h"
25 #include "ui-budget-tabview.h"
26 
27 /****************************************************************************/
28 /* Implementation notes */
29 /****************************************************************************/
30 /*
31  * This dialog allows user to manage its budget within a GtkTreeView.
32  *
33  * The view rows are separated in three main tree roots:
34  *   - Income: contains all Homebank categories of income type (see GF_INCOME)
35  *   - Expense: contains all Homebank categories of expense type
36  *   - Total: contains 3 sub-rows:
37  *      - Income: sum all amounts of the Income root
38  *      - Expense: sum all amounts of the Expense root
39  *      - Summary: difference between the two above sub-rows
40  *
41  * The view columns contain:
42  *   - Category: Homebank categories organised in hierarchy
43  *     according to the main tree roots above and the categories hierarchy
44  *
45  *   - Annual Total: sum all amounts of the year for the category
46  *
47  *   - Monthly Average: average of the amounts for the category
48  *
49  *   - Monthly: set the monthly amount when the Same flag is active
50  *     - That column contains a toggle check box to enable or not monthly values
51  *       Check it to disable the GF_CUSTOM flag of Homebank categories
52  *       "Does this category has same amount planned every month ?"
53  *
54  *   - 12 columns for each month of the year containing their specific amount
55  *
56  * The dialog shows 3 radio buttons on top to choose between 3 viewing modes:
57  *   - Summary: show Homebank categories with budget set or set with GF_FORCED
58   *  - Expense: show all available Homebank categories of expense type
59  *   - Income: show all available Homebank categories of income type
60  *
61  */
62 
63 /****************************************************************************/
64 /* Debug macros */
65 /****************************************************************************/
66 #define MYDEBUG 0
67 
68 #if MYDEBUG
69 #define DB(x) (x);
70 #else
71 #define DB(x);
72 #endif
73 
74 /* Global data */
75 extern struct HomeBank *GLOBALS;
76 extern struct Preferences *PREFS;
77 
78 static gchar *UI_BUD_TABVIEW_MONTHS[] = {
79 	N_("Jan"), N_("Feb"), N_("Mar"),
80 	N_("Apr"), N_("May"), N_("Jun"),
81 	N_("Jul"), N_("Aug"), N_("Sept"),
82 	N_("Oct"), N_("Nov"), N_("Dec"),
83 	NULL};
84 
85 /* The different view mode available */
86 static gchar *UI_BUD_TABVIEW_VIEW_MODE[] = {
87 	N_("Summary"),
88 	N_("Expense"),
89 	N_("Income"),
90 	NULL
91 };
92 
93 /* These values has to correspond to UI_BUD_TABVIEW_VIEW_MODE[] */
94 enum ui_bud_tabview_view_mode
95 {
96 	UI_BUD_TABVIEW_VIEW_SUMMARY = 0,
97 	UI_BUD_TABVIEW_VIEW_EXPENSE,
98 	UI_BUD_TABVIEW_VIEW_INCOME
99 };
100 typedef enum ui_bud_tabview_view_mode ui_bud_tabview_view_mode_t;
101 
102 /* These values corresponds to the return of category_type_get from hb-category */
103 enum ui_bud_tabview_cat_type
104 {
105 	UI_BUD_TABVIEW_CAT_TYPE_EXPENSE = -1,
106 	UI_BUD_TABVIEW_CAT_TYPE_NONE = 0, // Not real category type: used to retrieve tree roots
107 	UI_BUD_TABVIEW_CAT_TYPE_INCOME = 1
108 };
109 typedef enum ui_bud_tabview_cat_type ui_bud_tabview_cat_type_t;
110 
111 /* enum for the Budget Tree Store model */
112 enum ui_bud_tabview_store
113 {
114 	UI_BUD_TABVIEW_CATEGORY_KEY = 0,
115 	UI_BUD_TABVIEW_CATEGORY_NAME,
116 	UI_BUD_TABVIEW_CATEGORY_FULLNAME,
117 	UI_BUD_TABVIEW_CATEGORY_TYPE,
118 	UI_BUD_TABVIEW_IS_ROOT, // To retrieve easier the 3 main tree roots
119 	UI_BUD_TABVIEW_IS_TOTAL, // To retrieve rows inside the Total root
120 	UI_BUD_TABVIEW_IS_CHILD_HEADER, // The row corresponds to the head child which is shown before the separator
121 	UI_BUD_TABVIEW_IS_SEPARATOR, // Row to just display a separator in Tree View
122 	UI_BUD_TABVIEW_IS_MONITORING_FORCED,
123 	UI_BUD_TABVIEW_IS_SAME_AMOUNT,
124 	UI_BUD_TABVIEW_IS_SUB_CATEGORY,
125 	UI_BUD_TABVIEW_HAS_BUDGET,
126 	UI_BUD_TABVIEW_SAME_AMOUNT,
127 	UI_BUD_TABVIEW_JANUARY,
128 	UI_BUD_TABVIEW_FEBRUARY,
129 	UI_BUD_TABVIEW_MARCH,
130 	UI_BUD_TABVIEW_APRIL,
131 	UI_BUD_TABVIEW_MAY,
132 	UI_BUD_TABVIEW_JUNE,
133 	UI_BUD_TABVIEW_JULY,
134 	UI_BUD_TABVIEW_AUGUST,
135 	UI_BUD_TABVIEW_SEPTEMBER,
136 	UI_BUD_TABVIEW_OCTOBER,
137 	UI_BUD_TABVIEW_NOVEMBER,
138 	UI_BUD_TABVIEW_DECEMBER,
139 	UI_BUD_TABVIEW_NUMBER_COLOMNS
140 };
141 typedef enum ui_bud_tabview_store ui_bud_tabview_store_t;
142 
143 // Retrieve a row iterator according to specific criterias
144 const struct ui_bud_tabview_search_criteria
145 {
146 	// Search by non-zero category key
147 	guint32 row_category_key;
148 	// Search by other criterias
149 	ui_bud_tabview_cat_type_t row_category_type;
150 	gboolean row_is_root;
151 	gboolean row_is_total;
152 	// Found iterator, NULL if not found
153 	GtkTreeIter *iterator;
154 } ui_bud_tabview_search_criteria_default = {0, UI_BUD_TABVIEW_CAT_TYPE_NONE, FALSE, FALSE, NULL} ;
155 typedef struct ui_bud_tabview_search_criteria ui_bud_tabview_search_criteria_t;
156 
157 /*
158  * Local headers
159  **/
160 
161 // GtkTreeStore model
162 static gboolean ui_bud_tabview_model_search_iterator (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, ui_bud_tabview_search_criteria_t *search);
163 static void ui_bud_tabview_model_add_category_with_lineage(GtkTreeStore *budget, GtkTreeIter *balanceIter, guint32 *key_category);
164 static void ui_bud_tabview_model_collapse (GtkTreeView *view);
165 static void ui_bud_tabview_model_insert_roots(GtkTreeStore* budget);
166 static void ui_bud_tabview_model_update_monthly_total(GtkTreeStore* budget);
167 static gboolean ui_bud_tabview_model_row_filter (GtkTreeModel *model, GtkTreeIter *iter, gpointer data);
168 #if HB_BUD_TABVIEW_EDIT_ENABLE
169 static gboolean ui_bud_tabview_model_row_merge_filter_with_headers (GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data);
170 static gboolean ui_bud_tabview_model_row_filter_parents (GtkTreeModel *model, GtkTreeIter *iter, gpointer data);
171 #endif
172 static gint ui_bud_tabview_model_row_sort (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data);
173 static GtkTreeModel * ui_bud_tabview_model_new ();
174 
175 // GtkTreeView widget
176 static void ui_bud_tabview_view_display_category_name (GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data);
177 static void ui_bud_tabview_view_display_amount (GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data);
178 static void ui_bud_tabview_view_display_is_same_amount (GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data);
179 static void ui_bud_tabview_view_display_annual_total (GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data);
180 static void ui_bud_tabview_view_display_monthly_average(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data);
181 static void ui_bud_tabview_view_toggle (gpointer user_data, ui_bud_tabview_view_mode_t view_mode);
182 static gboolean ui_bud_tabview_view_search (GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer data);
183 #if HB_BUD_TABVIEW_EDIT_ENABLE
184 static gboolean ui_bud_tabview_view_separator (GtkTreeModel *model, GtkTreeIter *iter, gpointer data);
185 #endif
186 static void ui_bud_tabview_view_on_select(GtkTreeSelection *treeselection, gpointer user_data);
187 static GtkWidget *ui_bud_tabview_view_new (gpointer user_data);
188 
189 // UI actions
190 #if HB_BUD_TABVIEW_EDIT_ENABLE
191 static void ui_bud_tabview_cell_update_category(GtkCellRendererText *renderer, gchar *filter_path, gchar *new_text, gpointer user_data);
192 #endif
193 static void ui_bud_tabview_cell_update_amount(GtkCellRendererText *renderer, gchar *filter_path, gchar *new_text, gpointer user_data);
194 static void ui_bud_tabview_cell_update_is_same_amount(GtkCellRendererText *renderer, gchar *filter_path, gpointer user_data);
195 static void ui_bud_tabview_view_update_mode (GtkToggleButton *button, gpointer user_data);
196 static void ui_bud_tabview_view_expand (GtkButton *button, gpointer user_data);
197 static void ui_bud_tabview_view_collapse (GtkButton *button, gpointer user_data);
198 static gboolean ui_bud_tabview_get_selected_category (GtkTreeModel **budget, GtkTreeIter *iter, Category **category, ui_bud_tabview_data_t *data);
199 #if HB_BUD_TABVIEW_EDIT_ENABLE
200 static gboolean ui_bud_tabview_get_selected_root_iter (GtkTreeModel **budget, GtkTreeIter *iter, ui_bud_tabview_data_t *data);
201 static void ui_bud_tabview_category_add_full_filled (GtkWidget *source, gpointer user_data);
202 static void ui_bud_tabview_category_add (GtkButton *button, gpointer user_data);
203 static void ui_bud_tabview_category_delete (GtkButton *button, gpointer user_data);
204 static void ui_bud_tabview_category_merge_full_filled (GtkWidget *source, gpointer user_data);
205 static void ui_bud_tabview_category_merge (GtkButton *button, gpointer user_data);
206 #endif
207 static void ui_bud_tabview_category_reset (GtkButton *button, gpointer user_data);
208 static gboolean ui_bud_tabview_on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data);
209 static void ui_bud_tabview_dialog_close(ui_bud_tabview_data_t *data, gint response);
210 
211 /**
212  * GtkTreeStore model
213  **/
214 
215 // Look for category by deterministic characteristics
216 // Only categories with specific characteristics can be easily found
217 // like roots, total rows and categories with real key id
218 // You are responsible to g_free iterator
ui_bud_tabview_model_search_iterator(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,ui_bud_tabview_search_criteria_t * search)219 static gboolean ui_bud_tabview_model_search_iterator (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, ui_bud_tabview_search_criteria_t *search)
220 {
221 guint32 category_key;
222 ui_bud_tabview_cat_type_t category_type;
223 gboolean is_found = FALSE, is_root, is_total, is_separator;
224 
225 	search->iterator = NULL;
226 
227 	gtk_tree_model_get (model, iter,
228 		UI_BUD_TABVIEW_CATEGORY_KEY, &category_key,
229 		UI_BUD_TABVIEW_CATEGORY_TYPE, &category_type,
230 		UI_BUD_TABVIEW_IS_ROOT, &is_root,
231 		UI_BUD_TABVIEW_IS_TOTAL, &is_total,
232 		UI_BUD_TABVIEW_IS_SEPARATOR, &is_separator,
233 		-1);
234 
235 	if (search->row_category_key > 0 // Look for iter of real category row
236 		&& category_key == search->row_category_key
237 		&& !(is_total)
238 		&& !(is_root)
239 	)
240 	{
241 		DB(g_print("\tFound row with key %d\n", category_key));
242 		is_found = TRUE;
243 	}
244 	else if (search->row_category_key == 0 // Look for iter of fake category row
245 		&& is_root == search->row_is_root
246 		&& is_total == search->row_is_total
247 		&& category_type == search->row_category_type
248 		&& !is_separator
249 	)
250 	{
251 		DB(g_print("\tFound row with is_root = %d, is_total %d, type = %d\n", is_root, is_total, category_type));
252 		is_found = TRUE;
253 	}
254 
255 	// If found, save result to struct
256 	if (is_found)
257 	{
258 		search->iterator = g_malloc0(sizeof(GtkTreeIter));
259 		*search->iterator = *iter;
260 	}
261 
262 	return is_found;
263 }
264 
265 /* Recursive function which add a new row in the budget model with all its ancestors */
ui_bud_tabview_model_add_category_with_lineage(GtkTreeStore * budget,GtkTreeIter * balanceIter,guint32 * key_category)266 static void ui_bud_tabview_model_add_category_with_lineage(GtkTreeStore *budget, GtkTreeIter *balanceIter, guint32 *key_category)
267 {
268 GtkTreeIter child;
269 GtkTreeIter *parent;
270 Category *bdg_category;
271 gboolean cat_is_same_amount;
272 ui_bud_tabview_search_criteria_t parent_search = ui_bud_tabview_search_criteria_default;
273 
274 	bdg_category = da_cat_get(*key_category);
275 
276 	if (bdg_category == NULL)
277 	{
278 		return;
279 	}
280 
281 	cat_is_same_amount = (! (bdg_category->flags & GF_CUSTOM));
282 
283 	/* Check if parent category already exists */
284 	parent_search.row_category_key = bdg_category->parent;
285 
286 	gtk_tree_model_foreach(GTK_TREE_MODEL(budget),
287 		(GtkTreeModelForeachFunc) ui_bud_tabview_model_search_iterator,
288 		&parent_search);
289 
290 	if (bdg_category->parent == 0)
291 	{
292 		// If we are one of the oldest parent, stop recursion
293 		gtk_tree_store_insert (
294 			budget,
295 			&child,
296 			balanceIter,
297 			-1);
298 	}
299 	else
300 	{
301 		if (parent_search.iterator)
302 		{
303 			DB(g_print("\tRecursion optimisation: parent key %d already exists\n", parent_search.row_category_key));
304 			// If parent already exists, stop recursion
305 			parent = parent_search.iterator;
306 		}
307 		else
308 		{
309 			// Parent has not been found, ask to create it first
310 			ui_bud_tabview_model_add_category_with_lineage(budget, balanceIter, &(bdg_category->parent));
311 
312 			// Now, we are sure parent exists, look for it again
313 			gtk_tree_model_foreach(GTK_TREE_MODEL(budget),
314 				(GtkTreeModelForeachFunc) ui_bud_tabview_model_search_iterator,
315 				&parent_search);
316 
317 			parent = parent_search.iterator;
318 		}
319 
320 		gtk_tree_store_insert (
321 			budget,
322 			&child,
323 			parent,
324 			-1);
325 	}
326 
327 	DB(g_print("insert new category %s (key: %d, type: %d)\n",
328 		bdg_category->name, bdg_category->key, category_type_get (bdg_category)));
329 
330 	gtk_tree_store_set(
331 		budget,
332 		&child,
333 		UI_BUD_TABVIEW_CATEGORY_KEY, bdg_category->key,
334 		UI_BUD_TABVIEW_CATEGORY_NAME, bdg_category->name,
335 		UI_BUD_TABVIEW_CATEGORY_FULLNAME, bdg_category->fullname,
336 		UI_BUD_TABVIEW_CATEGORY_TYPE, category_type_get (bdg_category),
337 		UI_BUD_TABVIEW_IS_MONITORING_FORCED, (bdg_category->flags & GF_FORCED),
338 		UI_BUD_TABVIEW_IS_ROOT, FALSE,
339 		UI_BUD_TABVIEW_IS_SAME_AMOUNT, cat_is_same_amount,
340 		UI_BUD_TABVIEW_IS_TOTAL, FALSE,
341 		UI_BUD_TABVIEW_IS_SUB_CATEGORY, bdg_category->parent != 0,
342 		UI_BUD_TABVIEW_HAS_BUDGET, (bdg_category->flags & GF_BUDGET),
343 		UI_BUD_TABVIEW_SAME_AMOUNT, bdg_category->budget[0],
344 		UI_BUD_TABVIEW_JANUARY, bdg_category->budget[1],
345 		UI_BUD_TABVIEW_FEBRUARY, bdg_category->budget[2],
346 		UI_BUD_TABVIEW_MARCH, bdg_category->budget[3],
347 		UI_BUD_TABVIEW_APRIL, bdg_category->budget[4],
348 		UI_BUD_TABVIEW_MAY, bdg_category->budget[5],
349 		UI_BUD_TABVIEW_JUNE, bdg_category->budget[6],
350 		UI_BUD_TABVIEW_JULY, bdg_category->budget[7],
351 		UI_BUD_TABVIEW_AUGUST, bdg_category->budget[8],
352 		UI_BUD_TABVIEW_SEPTEMBER, bdg_category->budget[9],
353 		UI_BUD_TABVIEW_OCTOBER, bdg_category->budget[10],
354 		UI_BUD_TABVIEW_NOVEMBER, bdg_category->budget[11],
355 		UI_BUD_TABVIEW_DECEMBER, bdg_category->budget[12],
356 		-1);
357 
358 	// Always add child header and separator
359 	parent = gtk_tree_iter_copy(&child);
360 	gtk_tree_store_insert_with_values(
361 		budget,
362 		&child,
363 		parent,
364 		-1,
365 		UI_BUD_TABVIEW_CATEGORY_KEY, bdg_category->key,
366 		UI_BUD_TABVIEW_CATEGORY_NAME, bdg_category->name,
367 		UI_BUD_TABVIEW_CATEGORY_FULLNAME, bdg_category->fullname,
368 		UI_BUD_TABVIEW_CATEGORY_TYPE, category_type_get (bdg_category),
369 		UI_BUD_TABVIEW_IS_CHILD_HEADER, TRUE,
370 		-1);
371 
372 	gtk_tree_store_insert_with_values(
373 		budget,
374 		&child,
375 		parent,
376 		-1,
377 		UI_BUD_TABVIEW_CATEGORY_KEY, bdg_category->key,
378 		UI_BUD_TABVIEW_CATEGORY_TYPE, category_type_get (bdg_category),
379 		UI_BUD_TABVIEW_IS_SEPARATOR, TRUE,
380 		-1);
381 
382 	gtk_tree_iter_free(parent);
383 
384 	g_free(parent_search.iterator);
385 
386 	return;
387 }
388 
389 // Collapse all categories except root
ui_bud_tabview_model_collapse(GtkTreeView * view)390 static void ui_bud_tabview_model_collapse (GtkTreeView *view)
391 {
392 GtkTreeModel *budget;
393 GtkTreePath *path;
394 ui_bud_tabview_search_criteria_t root_search = ui_bud_tabview_search_criteria_default;
395 
396 	budget = gtk_tree_view_get_model (view);
397 
398 	gtk_tree_view_collapse_all(view);
399 
400 	// Keep root categories expanded
401 
402 	// Retrieve income root
403 	root_search.row_is_root = TRUE;
404 	root_search.row_is_total = FALSE;
405 	root_search.row_category_type = UI_BUD_TABVIEW_CAT_TYPE_INCOME;
406 	gtk_tree_model_foreach(GTK_TREE_MODEL(budget),
407 		(GtkTreeModelForeachFunc) ui_bud_tabview_model_search_iterator,
408 		&root_search);
409 
410 	if (root_search.iterator != NULL)
411 	{
412 		path = gtk_tree_model_get_path(budget, root_search.iterator);
413 		gtk_tree_view_expand_row(view, path, FALSE);
414 	}
415 
416 	// Retrieve expense root
417 	root_search.row_is_root = TRUE;
418 	root_search.row_is_total = FALSE;
419 	root_search.row_category_type = UI_BUD_TABVIEW_CAT_TYPE_EXPENSE;
420 	gtk_tree_model_foreach(GTK_TREE_MODEL(budget),
421 		(GtkTreeModelForeachFunc) ui_bud_tabview_model_search_iterator,
422 		&root_search);
423 
424 	if (root_search.iterator != NULL)
425 	{
426 		path = gtk_tree_model_get_path(budget, root_search.iterator);
427 		gtk_tree_view_expand_row(view, path, FALSE);
428 	}
429 
430 	// Retrieve total root
431 	root_search.row_is_root = TRUE;
432 	root_search.row_is_total = FALSE;
433 	root_search.row_category_type = UI_BUD_TABVIEW_CAT_TYPE_NONE;
434 	gtk_tree_model_foreach(GTK_TREE_MODEL(budget),
435 		(GtkTreeModelForeachFunc) ui_bud_tabview_model_search_iterator,
436 		&root_search);
437 
438 	if (root_search.iterator != NULL)
439 	{
440 		path = gtk_tree_model_get_path(budget, root_search.iterator);
441 		gtk_tree_view_expand_row(view, path, FALSE);
442 	}
443 
444 	g_free(root_search.iterator);
445 
446 	return;
447 }
448 
449 // Create tree roots for the store
ui_bud_tabview_model_insert_roots(GtkTreeStore * budget)450 static void ui_bud_tabview_model_insert_roots(GtkTreeStore* budget)
451 {
452 GtkTreeIter iter, root;
453 
454 	gtk_tree_store_insert_with_values (
455 		budget,
456 		&root,
457 		NULL,
458 		-1,
459 		UI_BUD_TABVIEW_CATEGORY_NAME, _(UI_BUD_TABVIEW_VIEW_MODE[UI_BUD_TABVIEW_VIEW_INCOME]),
460 		UI_BUD_TABVIEW_CATEGORY_FULLNAME, _(UI_BUD_TABVIEW_VIEW_MODE[UI_BUD_TABVIEW_VIEW_INCOME]),
461 		UI_BUD_TABVIEW_CATEGORY_TYPE, UI_BUD_TABVIEW_CAT_TYPE_INCOME,
462 		UI_BUD_TABVIEW_IS_ROOT, TRUE,
463 		UI_BUD_TABVIEW_IS_TOTAL, FALSE,
464 		-1);
465 
466 	// For add category dialog: copy of the root to be able to select it
467 	gtk_tree_store_insert_with_values (
468 		budget,
469 		&iter,
470 		&root,
471 		-1,
472 		UI_BUD_TABVIEW_CATEGORY_NAME, _(UI_BUD_TABVIEW_VIEW_MODE[UI_BUD_TABVIEW_VIEW_INCOME]),
473 		UI_BUD_TABVIEW_CATEGORY_FULLNAME, _(UI_BUD_TABVIEW_VIEW_MODE[UI_BUD_TABVIEW_VIEW_INCOME]),
474 		UI_BUD_TABVIEW_CATEGORY_TYPE, UI_BUD_TABVIEW_CAT_TYPE_INCOME,
475 		UI_BUD_TABVIEW_CATEGORY_KEY, 0,
476 		UI_BUD_TABVIEW_IS_ROOT, FALSE,
477 		UI_BUD_TABVIEW_IS_TOTAL, FALSE,
478 		UI_BUD_TABVIEW_IS_CHILD_HEADER, TRUE,
479 		-1);
480 
481 	// For add category dialog: add a separator to distinguish root with children
482 	gtk_tree_store_insert_with_values (
483 		budget,
484 		&iter,
485 		&root,
486 		-1,
487 		UI_BUD_TABVIEW_IS_SEPARATOR, TRUE,
488 		UI_BUD_TABVIEW_CATEGORY_TYPE, UI_BUD_TABVIEW_CAT_TYPE_INCOME,
489 		-1);
490 
491 	gtk_tree_store_insert_with_values (
492 		budget,
493 		&root,
494 		NULL,
495 		-1,
496 		UI_BUD_TABVIEW_CATEGORY_NAME, _(UI_BUD_TABVIEW_VIEW_MODE[UI_BUD_TABVIEW_VIEW_EXPENSE]),
497 		UI_BUD_TABVIEW_CATEGORY_FULLNAME, _(UI_BUD_TABVIEW_VIEW_MODE[UI_BUD_TABVIEW_VIEW_EXPENSE]),
498 		UI_BUD_TABVIEW_CATEGORY_TYPE, UI_BUD_TABVIEW_CAT_TYPE_EXPENSE,
499 		UI_BUD_TABVIEW_IS_ROOT, TRUE,
500 		UI_BUD_TABVIEW_IS_TOTAL, FALSE,
501 		-1);
502 
503 	// For add category dialog: copy of the root to be able to select it
504 	gtk_tree_store_insert_with_values (
505 		budget,
506 		&iter,
507 		&root,
508 		-1,
509 		UI_BUD_TABVIEW_CATEGORY_NAME, _(UI_BUD_TABVIEW_VIEW_MODE[UI_BUD_TABVIEW_VIEW_EXPENSE]),
510 		UI_BUD_TABVIEW_CATEGORY_FULLNAME, _(UI_BUD_TABVIEW_VIEW_MODE[UI_BUD_TABVIEW_VIEW_EXPENSE]),
511 		UI_BUD_TABVIEW_CATEGORY_TYPE, UI_BUD_TABVIEW_CAT_TYPE_EXPENSE,
512 		UI_BUD_TABVIEW_CATEGORY_KEY, 0,
513 		UI_BUD_TABVIEW_IS_ROOT, FALSE,
514 		UI_BUD_TABVIEW_IS_TOTAL, FALSE,
515 		UI_BUD_TABVIEW_IS_CHILD_HEADER, TRUE,
516 		-1);
517 
518 	// For add category dialog: add a separator to distinguish root with children
519 	gtk_tree_store_insert_with_values (
520 		budget,
521 		&iter,
522 		&root,
523 		-1,
524 		UI_BUD_TABVIEW_IS_SEPARATOR, TRUE,
525 		UI_BUD_TABVIEW_CATEGORY_TYPE, UI_BUD_TABVIEW_CAT_TYPE_EXPENSE,
526 		-1);
527 
528 	gtk_tree_store_insert_with_values (
529 		budget,
530 		&root,
531 		NULL,
532 		-1,
533 		UI_BUD_TABVIEW_CATEGORY_NAME, _("Totals"),
534 		UI_BUD_TABVIEW_CATEGORY_FULLNAME, _("Totals"),
535 		UI_BUD_TABVIEW_CATEGORY_TYPE, UI_BUD_TABVIEW_CAT_TYPE_NONE,
536 		UI_BUD_TABVIEW_IS_ROOT, TRUE,
537 		UI_BUD_TABVIEW_IS_TOTAL, FALSE,
538 		-1);
539 
540 	return;
541 }
542 
543 // Update (or insert) total rows for a budget according to the view mode
544 // This function will is used to initiate model and to refresh it after change by user
ui_bud_tabview_model_update_monthly_total(GtkTreeStore * budget)545 static void ui_bud_tabview_model_update_monthly_total(GtkTreeStore* budget)
546 {
547 ui_bud_tabview_search_criteria_t root_search = ui_bud_tabview_search_criteria_default;
548 GtkTreeIter total_root, child;
549 double total_income[12] = {0}, total_expense[12] = {0};
550 gboolean cat_is_same_amount;
551 guint32 n_category;
552 
553 	// Go through all categories to compute totals
554 	n_category = da_cat_get_max_key();
555 
556 	for(guint32 i=1; i<=n_category; ++i)
557 	{
558 	Category *bdg_category;
559 	gboolean cat_is_income;
560 
561 		bdg_category = da_cat_get(i);
562 
563 		if (bdg_category == NULL)
564 		{
565 			continue;
566 		}
567 
568 		cat_is_income = (category_type_get (bdg_category) == 1);
569 		cat_is_same_amount = (! (bdg_category->flags & GF_CUSTOM));
570 
571 		for (gint j=0; j<=11; ++j)
572 		{
573 			if (cat_is_income)
574 			{
575 				if (cat_is_same_amount)
576 				{
577 					total_income[j] += bdg_category->budget[0];
578 				}
579 				else
580 				{
581 					total_income[j] += bdg_category->budget[j+1];
582 				}
583 			}
584 			else
585 			{
586 				if (cat_is_same_amount)
587 				{
588 					total_expense[j] += bdg_category->budget[0];
589 				}
590 				else
591 				{
592 					total_expense[j] += bdg_category->budget[j+1];
593 				}
594 			}
595 		}
596 	}
597 
598 	// Retrieve total root and insert required total rows
599 	root_search.row_is_root = TRUE;
600 	root_search.row_is_total = FALSE;
601 	root_search.row_category_type = UI_BUD_TABVIEW_CAT_TYPE_NONE;
602 	gtk_tree_model_foreach(GTK_TREE_MODEL(budget),
603 		(GtkTreeModelForeachFunc) ui_bud_tabview_model_search_iterator,
604 		&root_search);
605 
606 	if (!root_search.iterator)
607 	{
608 		return;
609 	}
610 
611 	total_root = *root_search.iterator;
612 
613 	// Retrieve and set totals
614 	root_search.row_is_root = FALSE;
615 	root_search.row_is_total = TRUE;
616 
617 	// First, look for Incomes
618 	root_search.row_category_type = UI_BUD_TABVIEW_CAT_TYPE_INCOME;
619 
620 	gtk_tree_model_foreach(GTK_TREE_MODEL(budget),
621 		(GtkTreeModelForeachFunc) ui_bud_tabview_model_search_iterator,
622 		&root_search);
623 
624 	if (root_search.iterator)
625 	{
626 		child = *root_search.iterator;
627 	}
628 	else
629 	{
630 		gtk_tree_store_insert(budget, &child, &total_root, -1);
631 	}
632 
633 	gtk_tree_store_set (
634 		budget,
635 		&child,
636 		UI_BUD_TABVIEW_CATEGORY_NAME, _(UI_BUD_TABVIEW_VIEW_MODE[UI_BUD_TABVIEW_VIEW_INCOME]),
637 		UI_BUD_TABVIEW_CATEGORY_FULLNAME, _(UI_BUD_TABVIEW_VIEW_MODE[UI_BUD_TABVIEW_VIEW_INCOME]),
638 		UI_BUD_TABVIEW_CATEGORY_TYPE, UI_BUD_TABVIEW_CAT_TYPE_INCOME,
639 		UI_BUD_TABVIEW_IS_TOTAL, TRUE,
640 		UI_BUD_TABVIEW_JANUARY, total_income[0],
641 		UI_BUD_TABVIEW_FEBRUARY, total_income[1],
642 		UI_BUD_TABVIEW_MARCH, total_income[2],
643 		UI_BUD_TABVIEW_APRIL, total_income[3],
644 		UI_BUD_TABVIEW_MAY, total_income[4],
645 		UI_BUD_TABVIEW_JUNE, total_income[5],
646 		UI_BUD_TABVIEW_JULY, total_income[6],
647 		UI_BUD_TABVIEW_AUGUST, total_income[7],
648 		UI_BUD_TABVIEW_SEPTEMBER, total_income[8],
649 		UI_BUD_TABVIEW_OCTOBER, total_income[9],
650 		UI_BUD_TABVIEW_NOVEMBER, total_income[10],
651 		UI_BUD_TABVIEW_DECEMBER, total_income[11],
652 		-1);
653 
654 	// Then look for Expenses
655 	root_search.row_category_type = UI_BUD_TABVIEW_CAT_TYPE_EXPENSE;
656 
657 	gtk_tree_model_foreach(GTK_TREE_MODEL(budget),
658 		(GtkTreeModelForeachFunc) ui_bud_tabview_model_search_iterator,
659 		&root_search);
660 
661 	if (root_search.iterator)
662 	{
663 		child = *root_search.iterator;
664 	}
665 	else
666 	{
667 		gtk_tree_store_insert(budget, &child, &total_root, -1);
668 	}
669 
670 	gtk_tree_store_set (
671 		budget,
672 		&child,
673 		UI_BUD_TABVIEW_CATEGORY_NAME, _(UI_BUD_TABVIEW_VIEW_MODE[UI_BUD_TABVIEW_VIEW_EXPENSE]),
674 		UI_BUD_TABVIEW_CATEGORY_FULLNAME, _(UI_BUD_TABVIEW_VIEW_MODE[UI_BUD_TABVIEW_VIEW_EXPENSE]),
675 		UI_BUD_TABVIEW_CATEGORY_TYPE, UI_BUD_TABVIEW_CAT_TYPE_EXPENSE,
676 		UI_BUD_TABVIEW_IS_TOTAL, TRUE,
677 		UI_BUD_TABVIEW_JANUARY, total_expense[0],
678 		UI_BUD_TABVIEW_FEBRUARY, total_expense[1],
679 		UI_BUD_TABVIEW_MARCH, total_expense[2],
680 		UI_BUD_TABVIEW_APRIL, total_expense[3],
681 		UI_BUD_TABVIEW_MAY, total_expense[4],
682 		UI_BUD_TABVIEW_JUNE, total_expense[5],
683 		UI_BUD_TABVIEW_JULY, total_expense[6],
684 		UI_BUD_TABVIEW_AUGUST, total_expense[7],
685 		UI_BUD_TABVIEW_SEPTEMBER, total_expense[8],
686 		UI_BUD_TABVIEW_OCTOBER, total_expense[9],
687 		UI_BUD_TABVIEW_NOVEMBER, total_expense[10],
688 		UI_BUD_TABVIEW_DECEMBER, total_expense[11],
689 		-1);
690 
691 	// Finally, set Balance total row
692 	root_search.row_category_type = UI_BUD_TABVIEW_CAT_TYPE_NONE;
693 
694 	gtk_tree_model_foreach(GTK_TREE_MODEL(budget),
695 		(GtkTreeModelForeachFunc) ui_bud_tabview_model_search_iterator,
696 		&root_search);
697 
698 	if (root_search.iterator)
699 	{
700 		child = *root_search.iterator;
701 	}
702 	else
703 	{
704 		gtk_tree_store_insert(budget, &child, &total_root, -1);
705 	}
706 
707 	gtk_tree_store_set (
708 		budget,
709 		&child,
710 		UI_BUD_TABVIEW_CATEGORY_NAME, _(UI_BUD_TABVIEW_VIEW_MODE[UI_BUD_TABVIEW_VIEW_SUMMARY]),
711 		UI_BUD_TABVIEW_CATEGORY_FULLNAME, _(UI_BUD_TABVIEW_VIEW_MODE[UI_BUD_TABVIEW_VIEW_SUMMARY]),
712 		UI_BUD_TABVIEW_CATEGORY_TYPE, UI_BUD_TABVIEW_CAT_TYPE_NONE,
713 		UI_BUD_TABVIEW_IS_TOTAL, TRUE,
714 		UI_BUD_TABVIEW_JANUARY, total_income[0] + total_expense[0],
715 		UI_BUD_TABVIEW_FEBRUARY, total_income[1] + total_expense[1],
716 		UI_BUD_TABVIEW_MARCH, total_income[2] + total_expense[2],
717 		UI_BUD_TABVIEW_APRIL, total_income[3] + total_expense[3],
718 		UI_BUD_TABVIEW_MAY, total_income[4] + total_expense[4],
719 		UI_BUD_TABVIEW_JUNE, total_income[5] + total_expense[5],
720 		UI_BUD_TABVIEW_JULY, total_income[6] + total_expense[6],
721 		UI_BUD_TABVIEW_AUGUST, total_income[7] + total_expense[7],
722 		UI_BUD_TABVIEW_SEPTEMBER, total_income[8] + total_expense[8],
723 		UI_BUD_TABVIEW_OCTOBER, total_income[9] + total_expense[9],
724 		UI_BUD_TABVIEW_NOVEMBER, total_income[10] + total_expense[10],
725 		UI_BUD_TABVIEW_DECEMBER, total_income[11] + total_expense[11],
726 		-1);
727 
728 	g_free(root_search.iterator);
729 
730 	return;
731 }
732 
733 // Filter shown rows according to VIEW mode
ui_bud_tabview_model_row_filter(GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)734 static gboolean ui_bud_tabview_model_row_filter (GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
735 {
736 gboolean is_visible, is_root, is_total, is_separator, is_childheader;
737 ui_bud_tabview_data_t* data;
738 ui_bud_tabview_view_mode_t view_mode;
739 guint32 category_key;
740 ui_bud_tabview_cat_type_t category_type;
741 Category *bdg_category;
742 
743 	is_visible = TRUE;
744 	data = user_data;
745 	view_mode = hbtk_radio_button_get_active(GTK_CONTAINER(data->RA_mode));
746 
747 	gtk_tree_model_get(model, iter,
748 		UI_BUD_TABVIEW_IS_ROOT, &is_root,
749 		UI_BUD_TABVIEW_IS_TOTAL, &is_total,
750 		UI_BUD_TABVIEW_IS_SEPARATOR, &is_separator,
751 		UI_BUD_TABVIEW_IS_CHILD_HEADER, &is_childheader,
752 		UI_BUD_TABVIEW_CATEGORY_KEY, &category_key,
753 		UI_BUD_TABVIEW_CATEGORY_TYPE, &category_type,
754 		-1);
755 
756 	// On specific mode, hide categories of opposite type
757 	if (!is_total
758 		&& category_type == UI_BUD_TABVIEW_CAT_TYPE_INCOME
759 		&& view_mode == UI_BUD_TABVIEW_VIEW_EXPENSE)
760 	{
761 		is_visible = FALSE;
762 	}
763 
764 	if (!is_total
765 		&& category_type == UI_BUD_TABVIEW_CAT_TYPE_EXPENSE
766 		&& view_mode == UI_BUD_TABVIEW_VIEW_INCOME)
767 	{
768 		is_visible = FALSE;
769 	}
770 
771 	// Hide fake first child root used for add dialog
772 	if (is_childheader
773 		|| is_separator)
774 	{
775 		is_visible = FALSE;
776 	}
777 
778 	// On balance mode, hide not forced empty categories
779 	if (!is_total
780 		&& !is_root
781 		&& !is_childheader
782 		&& !is_separator
783 		&& view_mode == UI_BUD_TABVIEW_VIEW_SUMMARY)
784 	{
785 		bdg_category = da_cat_get(category_key);
786 
787 		if (bdg_category != NULL)
788 		{
789 			// Either the category has some budget, or its display is forced
790 			is_visible = (bdg_category->flags & (GF_BUDGET|GF_FORCED));
791 
792 			// Force display if one of its children should be displayed
793 			if (!is_visible)
794 			{
795 			GtkTreeIter child;
796 			Category *subcat;
797 			guint32 subcat_key;
798 			gint child_id=0;
799 
800 				while (gtk_tree_model_iter_nth_child(model,
801 					&child,
802 					iter,
803 					child_id))
804 				{
805 					gtk_tree_model_get(model, &child,
806 						UI_BUD_TABVIEW_CATEGORY_KEY, &subcat_key,
807 						-1);
808 
809 					if (subcat_key != 0)
810 					{
811 						subcat = da_cat_get (subcat_key);
812 
813 						if (subcat != NULL)
814 						{
815 							is_visible = (subcat->flags & (GF_BUDGET|GF_FORCED));
816 						}
817 					}
818 
819 					// Stop loop on first visible children
820 					if (is_visible)
821 					{
822 						break;
823 					}
824 
825 					++child_id;
826 				}
827 			}
828 		}
829 	}
830 
831 	return is_visible;
832 }
833 
834 #if HB_BUD_TABVIEW_EDIT_ENABLE
835 // Filter rows to show only parent categories
ui_bud_tabview_model_row_filter_parents(GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)836 static gboolean ui_bud_tabview_model_row_filter_parents (GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
837 {
838 ui_bud_tabview_data_t *data = user_data;
839 gboolean is_visible, is_root, is_total, is_separator, is_childheader;
840 Category *bdg_category;
841 guint32 category_key;
842 ui_bud_tabview_cat_type_t category_type;
843 ui_bud_tabview_view_mode_t view_mode = UI_BUD_TABVIEW_VIEW_SUMMARY;
844 
845 	view_mode = hbtk_radio_button_get_active(GTK_CONTAINER(data->RA_mode));
846 
847 	is_visible = TRUE;
848 
849 	gtk_tree_model_get(model, iter,
850 		UI_BUD_TABVIEW_CATEGORY_KEY, &category_key,
851 		UI_BUD_TABVIEW_CATEGORY_TYPE, &category_type,
852 		UI_BUD_TABVIEW_IS_ROOT, &is_root,
853 		UI_BUD_TABVIEW_IS_TOTAL, &is_total,
854 		UI_BUD_TABVIEW_IS_CHILD_HEADER, &is_childheader,
855 		UI_BUD_TABVIEW_IS_SEPARATOR, &is_separator,
856 		-1);
857 
858 	// Show root according to view_mode
859 	if (is_root)
860 	{
861 		// Always hide total root
862 		if(category_type == UI_BUD_TABVIEW_CAT_TYPE_NONE)
863 		{
864 			is_visible = FALSE;
865 		}
866 		else if(view_mode == UI_BUD_TABVIEW_VIEW_EXPENSE && category_type == UI_BUD_TABVIEW_CAT_TYPE_INCOME)
867 		{
868 			is_visible = FALSE;
869 		}
870 		else if(view_mode == UI_BUD_TABVIEW_VIEW_INCOME && category_type == UI_BUD_TABVIEW_CAT_TYPE_EXPENSE)
871 		{
872 			is_visible = FALSE;
873 		}
874 	}
875 
876 	// Hide Total rows
877 	if (is_total) {
878 		is_visible = FALSE;
879 	}
880 
881 	if (category_key > 0 && (is_separator || is_childheader))
882 	{
883 		is_visible = FALSE;
884 	}
885 	else if (category_key > 0)
886 	{
887 		// Hide rows according to currently view mode
888 		if(view_mode == UI_BUD_TABVIEW_VIEW_EXPENSE && category_type == UI_BUD_TABVIEW_CAT_TYPE_INCOME)
889 		{
890 			is_visible = FALSE;
891 		}
892 		else if(view_mode == UI_BUD_TABVIEW_VIEW_INCOME && category_type == UI_BUD_TABVIEW_CAT_TYPE_EXPENSE)
893 		{
894 			is_visible = FALSE;
895 		}
896 		else
897 		{
898 			// Show categories without parents
899 			bdg_category = da_cat_get(category_key);
900 
901 			if (bdg_category != NULL)
902 			{
903 				if (bdg_category->parent > 0)
904 				{
905 					is_visible = FALSE;
906 				}
907 			}
908 		}
909 	}
910 
911 	return is_visible;
912 }
913 
914 // Filter rows to show only mergeable categories
ui_bud_tabview_model_row_merge_filter_with_headers(GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)915 static gboolean ui_bud_tabview_model_row_merge_filter_with_headers (GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
916 {
917 ui_bud_tabview_data_t *data = user_data;
918 gboolean is_visible, is_root, is_total, is_separator, is_childheader;
919 guint32 category_key;
920 ui_bud_tabview_cat_type_t category_type;
921 
922 	is_visible = TRUE;
923 
924 	gtk_tree_model_get(model, iter,
925 		UI_BUD_TABVIEW_CATEGORY_KEY, &category_key,
926 		UI_BUD_TABVIEW_CATEGORY_TYPE, &category_type,
927 		UI_BUD_TABVIEW_IS_ROOT, &is_root,
928 		UI_BUD_TABVIEW_IS_TOTAL, &is_total,
929 		UI_BUD_TABVIEW_IS_CHILD_HEADER, &is_childheader,
930 		UI_BUD_TABVIEW_IS_SEPARATOR, &is_separator,
931 		-1);
932 
933 	// Hide source merge row
934 	if (data->MERGE_source_category_key == category_key)
935 	{
936 		is_visible = FALSE;
937 	}
938 
939 	// Hide Total root
940 	if (is_root
941 		&& category_type == UI_BUD_TABVIEW_CAT_TYPE_NONE )
942 	{
943 		is_visible = FALSE;
944 	}
945 
946 	// Hide Total rows
947 	if (is_total)
948 	{
949 		is_visible = FALSE;
950 	}
951 
952 	if ((is_separator || is_childheader))
953 	{
954 	GtkTreeIter parent;
955 		gtk_tree_model_iter_parent(model, &parent, iter);
956 
957 		// Show child header and separator if parent has more than 2 children
958 		is_visible = (gtk_tree_model_iter_n_children(model, &parent) > 2);
959 	}
960 
961 	return is_visible;
962 }
963 #endif
964 
ui_bud_tabview_model_row_sort(GtkTreeModel * model,GtkTreeIter * cat_a,GtkTreeIter * cat_b,gpointer user_data)965 static gint ui_bud_tabview_model_row_sort (GtkTreeModel *model, GtkTreeIter *cat_a, GtkTreeIter *cat_b, gpointer user_data)
966 {
967 const gchar* cat_a_name;
968 const gchar* cat_b_name;
969 ui_bud_tabview_cat_type_t cat_a_type, cat_b_type;
970 guint32 cat_a_key, cat_b_key;
971 gboolean cat_a_is_childheader, cat_a_is_separator, cat_b_is_childheader, cat_b_is_separator;
972 gint order = 0;
973 
974 	gtk_tree_model_get(model, cat_a,
975 		UI_BUD_TABVIEW_CATEGORY_NAME, &cat_a_name,
976 		UI_BUD_TABVIEW_CATEGORY_TYPE, &cat_a_type,
977 		UI_BUD_TABVIEW_CATEGORY_KEY, &cat_a_key,
978 		UI_BUD_TABVIEW_IS_CHILD_HEADER, &cat_a_is_childheader,
979 		UI_BUD_TABVIEW_IS_SEPARATOR, &cat_a_is_separator,
980 		-1);
981 
982 	gtk_tree_model_get(model, cat_b,
983 		UI_BUD_TABVIEW_CATEGORY_NAME, &cat_b_name,
984 		UI_BUD_TABVIEW_CATEGORY_TYPE, &cat_b_type,
985 		UI_BUD_TABVIEW_CATEGORY_KEY, &cat_b_key,
986 		UI_BUD_TABVIEW_IS_CHILD_HEADER, &cat_b_is_childheader,
987 		UI_BUD_TABVIEW_IS_SEPARATOR, &cat_b_is_separator,
988 		-1);
989 
990 	// Sort first by category type
991 	if (cat_a_type != cat_b_type)
992 	{
993 		switch (cat_a_type)
994 		{
995 			case UI_BUD_TABVIEW_CAT_TYPE_INCOME:
996 				order = -1;
997 				break;
998 			case UI_BUD_TABVIEW_CAT_TYPE_EXPENSE:
999 				order = 0;
1000 				break;
1001 			case UI_BUD_TABVIEW_CAT_TYPE_NONE:
1002 				order = 1;
1003 				break;
1004 		}
1005 	}
1006 	else
1007 	{
1008 		// On standard categories, just order by name
1009 		if (!cat_a_is_childheader && !cat_a_is_separator
1010 		    && !cat_b_is_childheader && !cat_b_is_separator)
1011 		{
1012 			order = g_utf8_collate(g_utf8_casefold(cat_a_name, -1),
1013 				g_utf8_casefold(cat_b_name, -1)
1014 				);
1015 		}
1016 		// Otherwise, fake categories have to be first (header and separator)
1017 		else if (cat_a_is_childheader || cat_a_is_separator)
1018 		{
1019 			if (!cat_b_is_separator && !cat_b_is_childheader)
1020 			{
1021 				order = -1;
1022 			}
1023 			// When both are fake, header has to be first
1024 			else
1025 			{
1026 				order = (cat_a_is_childheader ? -1 : 1);
1027 			}
1028 		}
1029 		else
1030 		{
1031 			// Same idea for fake categories when cat_b is fake, but
1032 			// with reversed result, because sort function return
1033 			// result according to cat_a
1034 
1035 			if (!cat_a_is_separator && !cat_a_is_childheader)
1036 			{
1037 				order = 1;
1038 			}
1039 			else
1040 			{
1041 				order = (cat_b_is_childheader ? 1 : -1);
1042 			}
1043 		}
1044 	}
1045 
1046 	return order;
1047 }
1048 
1049 // the budget model creation
ui_bud_tabview_model_new()1050 static GtkTreeModel * ui_bud_tabview_model_new ()
1051 {
1052 GtkTreeStore *budget;
1053 GtkTreeIter *iter_income, *iter_expense;
1054 guint32 n_category;
1055 ui_bud_tabview_search_criteria_t root_search = ui_bud_tabview_search_criteria_default;
1056 
1057 	// Create Tree Store
1058 	budget = gtk_tree_store_new ( UI_BUD_TABVIEW_NUMBER_COLOMNS,
1059 		G_TYPE_UINT, // UI_BUD_TABVIEW_CATEGORY_KEY
1060 		G_TYPE_STRING, // UI_BUD_TABVIEW_CATEGORY_NAME
1061 		G_TYPE_STRING, // UI_BUD_TABVIEW_CATEGORY_FULLNAME
1062 		G_TYPE_INT, // UI_BUD_TABVIEW_CATEGORY_TYPE
1063 		G_TYPE_BOOLEAN, // UI_BUD_TABVIEW_IS_ROOT
1064 		G_TYPE_BOOLEAN, // UI_BUD_TABVIEW_IS_TOTAL
1065 		G_TYPE_BOOLEAN, // UI_BUD_TABVIEW_IS_CHILD_HEADER
1066 		G_TYPE_BOOLEAN, // UI_BUD_TABVIEW_IS_SEPARATOR
1067 		G_TYPE_BOOLEAN, // UI_BUD_TABVIEW_IS_MONITORING_FORCED
1068 		G_TYPE_BOOLEAN, // UI_BUD_TABVIEW_IS_SAME_AMOUNT
1069 		G_TYPE_BOOLEAN, // UI_BUD_TABVIEW_IS_SUB_CATEGORY
1070 		G_TYPE_BOOLEAN, // UI_BUD_TABVIEW_HAS_BUDGET
1071 		G_TYPE_DOUBLE, // UI_BUD_TABVIEW_SAME_AMOUNT
1072 		G_TYPE_DOUBLE, // UI_BUD_TABVIEW_JANUARY
1073 		G_TYPE_DOUBLE, // UI_BUD_TABVIEW_FEBRUARY
1074 		G_TYPE_DOUBLE, // UI_BUD_TABVIEW_MARCH
1075 		G_TYPE_DOUBLE, // UI_BUD_TABVIEW_APRIL
1076 		G_TYPE_DOUBLE, // UI_BUD_TABVIEW_MAY
1077 		G_TYPE_DOUBLE, // UI_BUD_TABVIEW_JUNE
1078 		G_TYPE_DOUBLE, // UI_BUD_TABVIEW_JULY
1079 		G_TYPE_DOUBLE, // UI_BUD_TABVIEW_AUGUST
1080 		G_TYPE_DOUBLE, // UI_BUD_TABVIEW_SEPTEMBER
1081 		G_TYPE_DOUBLE, // UI_BUD_TABVIEW_OCTOBER
1082 		G_TYPE_DOUBLE, // UI_BUD_TABVIEW_NOVEMBER
1083 		G_TYPE_DOUBLE  // UI_BUD_TABVIEW_DECEMBER
1084 	);
1085 
1086 	// Populate the store
1087 
1088 	/* Create tree roots */
1089 	ui_bud_tabview_model_insert_roots (budget);
1090 
1091 	// Retrieve required root
1092 	root_search.row_is_root = TRUE;
1093 	root_search.row_is_total = FALSE;
1094 
1095 	root_search.row_category_type = UI_BUD_TABVIEW_CAT_TYPE_INCOME;
1096 	gtk_tree_model_foreach(GTK_TREE_MODEL(budget),
1097 		(GtkTreeModelForeachFunc) ui_bud_tabview_model_search_iterator,
1098 		&root_search);
1099 	iter_income = root_search.iterator;
1100 
1101 	root_search.row_category_type = UI_BUD_TABVIEW_CAT_TYPE_EXPENSE;
1102 	gtk_tree_model_foreach(GTK_TREE_MODEL(budget),
1103 		(GtkTreeModelForeachFunc) ui_bud_tabview_model_search_iterator,
1104 		&root_search);
1105 	iter_expense = root_search.iterator;
1106 
1107 	/* Create rows for real categories */
1108 	n_category = da_cat_get_max_key();
1109 
1110 	for(guint32 i=1; i<=n_category; ++i)
1111 	{
1112 	Category *bdg_category;
1113 	gboolean cat_is_income;
1114 
1115 		bdg_category = da_cat_get(i);
1116 
1117 		if (bdg_category == NULL)
1118 		{
1119 			continue;
1120 		}
1121 
1122 		cat_is_income = (category_type_get (bdg_category) == 1);
1123 
1124 		DB(g_print(" category %d:'%s' isincome=%d, issub=%d hasbudget=%d parent=%d\n",
1125 			bdg_category->key, bdg_category->name,
1126 			cat_is_income, (bdg_category->flags & GF_SUB),
1127 			(bdg_category->flags & GF_BUDGET), bdg_category->parent));
1128 
1129 		// Compute totals and initiate category in right tree root
1130 		if (cat_is_income)
1131 		{
1132 			ui_bud_tabview_model_add_category_with_lineage(budget, iter_income, &(bdg_category->key));
1133 		}
1134 		else if (!cat_is_income)
1135 		{
1136 			ui_bud_tabview_model_add_category_with_lineage(budget, iter_expense, &(bdg_category->key));
1137 		}
1138 	}
1139 
1140 	/* Create rows for total root */
1141 	ui_bud_tabview_model_update_monthly_total(GTK_TREE_STORE(budget));
1142 
1143 	/* Sort categories on same node level */
1144 	gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(budget),
1145 		UI_BUD_TABVIEW_CATEGORY_NAME, ui_bud_tabview_model_row_sort,
1146 		NULL, NULL);
1147 	gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (budget),
1148 		UI_BUD_TABVIEW_CATEGORY_NAME, GTK_SORT_ASCENDING);
1149 
1150 	g_free(root_search.iterator);
1151 
1152 	return GTK_TREE_MODEL(budget);
1153 }
1154 
1155 /**
1156  * GtkTreeView functions
1157  **/
1158 
ui_bud_tabview_icon_cell_data_function(GtkTreeViewColumn * col,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)1159 static void ui_bud_tabview_icon_cell_data_function (GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
1160 {
1161 ui_bud_tabview_data_t *data = user_data;
1162 gchar *iconname = NULL;
1163 gboolean has_budget, is_monitoring_forced;
1164 ui_bud_tabview_view_mode_t view_mode = UI_BUD_TABVIEW_VIEW_SUMMARY;
1165 
1166 	gtk_tree_model_get(model, iter,
1167 	UI_BUD_TABVIEW_IS_MONITORING_FORCED, &is_monitoring_forced,
1168 	UI_BUD_TABVIEW_HAS_BUDGET, &has_budget,
1169 	-1);
1170 
1171 	view_mode = hbtk_radio_button_get_active(GTK_CONTAINER(data->RA_mode));
1172 
1173 	//5.3 added
1174 	if( is_monitoring_forced )
1175 		iconname = ICONNAME_HB_OPE_FORCED;
1176 	else
1177 		if (view_mode != UI_BUD_TABVIEW_VIEW_SUMMARY )
1178 		{
1179 			if( has_budget )
1180 				iconname = ICONNAME_HB_OPE_BUDGET;
1181 		}
1182 
1183 	g_object_set(renderer, "icon-name", iconname, NULL);
1184 }
1185 
1186 
1187 
1188 // Display category name in bold if it has budget
ui_bud_tabview_view_display_category_name(GtkTreeViewColumn * col,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)1189 static void ui_bud_tabview_view_display_category_name (GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
1190 {
1191 ui_bud_tabview_data_t *data = user_data;
1192 gboolean has_budget, is_sub_category;
1193 PangoWeight weight = PANGO_WEIGHT_NORMAL;
1194 ui_bud_tabview_view_mode_t view_mode = UI_BUD_TABVIEW_VIEW_SUMMARY;
1195 
1196 	gtk_tree_model_get(model, iter,
1197 		UI_BUD_TABVIEW_IS_SUB_CATEGORY, &is_sub_category,
1198 		UI_BUD_TABVIEW_HAS_BUDGET, &has_budget,
1199 		-1);
1200 
1201 	view_mode = hbtk_radio_button_get_active(GTK_CONTAINER(data->RA_mode));
1202 
1203 	if (view_mode != UI_BUD_TABVIEW_VIEW_SUMMARY && has_budget)
1204 	{
1205 		weight = PANGO_WEIGHT_BOLD;
1206 	}
1207 
1208 	g_object_set(renderer,
1209 		"style", is_sub_category ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL,
1210 		"weight", weight,
1211 		NULL);
1212 }
1213 
1214 // to enable or not edition on month columns
ui_bud_tabview_view_display_amount(GtkTreeViewColumn * col,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)1215 static void ui_bud_tabview_view_display_amount (GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
1216 {
1217 GtkAdjustment *adjustment;
1218 gboolean is_same_amount, is_root, is_total, is_visible, is_editable;
1219 ui_bud_tabview_cat_type_t row_category_type;
1220 gdouble amount = 0.0;
1221 gchar *text;
1222 gchar *fgcolor;
1223 const ui_bud_tabview_store_t column_id = GPOINTER_TO_INT(user_data);
1224 
1225 	gtk_tree_model_get(model, iter,
1226 		UI_BUD_TABVIEW_CATEGORY_TYPE, &row_category_type,
1227 		UI_BUD_TABVIEW_IS_ROOT, &is_root,
1228 		UI_BUD_TABVIEW_IS_SAME_AMOUNT, &is_same_amount,
1229 		UI_BUD_TABVIEW_IS_TOTAL, &is_total,
1230 		-1);
1231 
1232 	// Text to display
1233 	if (is_same_amount)
1234 	{
1235 		gtk_tree_model_get(model, iter, UI_BUD_TABVIEW_SAME_AMOUNT, &amount, -1);
1236 	}
1237 	else if (column_id >= UI_BUD_TABVIEW_JANUARY && column_id <= UI_BUD_TABVIEW_DECEMBER)
1238 	{
1239 		gtk_tree_model_get(model, iter, column_id, &amount, -1);
1240 	}
1241 
1242 	text = g_strdup_printf("%.2f", amount);
1243 	fgcolor = get_normal_color_amount(amount);
1244 
1245 	// Default styling values
1246 	is_visible = TRUE;
1247 	is_editable = FALSE;
1248 
1249 	if (is_root)
1250 	{
1251 		is_visible = FALSE;
1252 		is_editable = FALSE;
1253 	}
1254 	else if (is_total)
1255 	{
1256 		is_visible = TRUE;
1257 		is_editable = FALSE;
1258 
1259 		if (column_id == UI_BUD_TABVIEW_SAME_AMOUNT)
1260 		{
1261 			is_visible = FALSE;
1262 		}
1263 	}
1264 	else if (is_same_amount)
1265 	{
1266 		is_visible = TRUE;
1267 		is_editable = FALSE;
1268 
1269 		if (column_id == UI_BUD_TABVIEW_SAME_AMOUNT)
1270 		{
1271 			is_editable = TRUE;
1272 		}
1273 	}
1274 	else if (! is_same_amount)
1275 	{
1276 		is_visible = TRUE;
1277 		is_editable = TRUE;
1278 
1279 		if (column_id == UI_BUD_TABVIEW_SAME_AMOUNT)
1280 		{
1281 			is_editable = FALSE;
1282 		}
1283 	}
1284 
1285 	// Finally, visibility depends on set amount
1286 	is_visible = (is_visible && (is_editable || amount != 0.0));
1287 
1288 	adjustment = gtk_adjustment_new(
1289 		0.0, // initial-value
1290 		-G_MAXDOUBLE, // minmal-value
1291 		G_MAXDOUBLE, // maximal-value
1292 		0.5, // step increment
1293 		10, // page increment
1294 		0); // page size (0 because irrelevant for GtkSpinButton)
1295 
1296 	g_object_set(renderer,
1297 		"text", text,
1298 		"visible", is_visible,
1299 		"editable", is_editable,
1300 		"foreground", fgcolor,
1301 		"xalign", 1.0,
1302 		"adjustment", adjustment,
1303 		"digits", 2,
1304 		NULL);
1305 
1306 	g_free(text);
1307 }
1308 
1309 // to enable or not edition on month columns
ui_bud_tabview_view_display_is_same_amount(GtkTreeViewColumn * col,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)1310 static void ui_bud_tabview_view_display_is_same_amount (GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
1311 {
1312 gboolean is_same_amount, is_root, is_total, is_visible, is_sensitive;
1313 
1314 	gtk_tree_model_get(model, iter,
1315 		UI_BUD_TABVIEW_IS_ROOT, &is_root,
1316 		UI_BUD_TABVIEW_IS_SAME_AMOUNT, &is_same_amount,
1317 		UI_BUD_TABVIEW_IS_TOTAL, &is_total,
1318 		-1);
1319 
1320 	// Default values
1321 	is_visible = TRUE;
1322 	is_sensitive = TRUE;
1323 
1324 	if (is_root || is_total)
1325 	{
1326 		is_visible = FALSE;
1327 		is_sensitive = FALSE;
1328 	}
1329 
1330 	g_object_set(renderer,
1331 		"activatable", TRUE,
1332 		"active", is_same_amount,
1333 		"visible", is_visible,
1334 		"sensitive", is_sensitive,
1335 		NULL);
1336 }
1337 
1338 // Compute dynamically the annual total
ui_bud_tabview_view_display_annual_total(GtkTreeViewColumn * col,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)1339 static void ui_bud_tabview_view_display_annual_total (GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
1340 {
1341 gboolean is_same_amount = FALSE, is_total = FALSE, is_root = FALSE;
1342 gdouble amount = 0.0;
1343 gdouble total = 0.0;
1344 gchar *text;
1345 gchar *fgcolor;
1346 gboolean is_visible = TRUE;
1347 
1348 	gtk_tree_model_get(model, iter,
1349 		UI_BUD_TABVIEW_IS_ROOT, &is_root,
1350 		UI_BUD_TABVIEW_IS_SAME_AMOUNT, &is_same_amount,
1351 		UI_BUD_TABVIEW_SAME_AMOUNT, &amount,
1352 		UI_BUD_TABVIEW_IS_TOTAL, &is_total,
1353 		-1);
1354 
1355 	if (is_same_amount)
1356 	{
1357 		total = 12.0 * amount;
1358 	}
1359 	else
1360 	{
1361 		for (int i = UI_BUD_TABVIEW_JANUARY ; i <= UI_BUD_TABVIEW_DECEMBER ; ++i)
1362 		{
1363 			gtk_tree_model_get(model, iter, i, &amount, -1);
1364 			total += amount;
1365 		}
1366 	}
1367 
1368 	text = g_strdup_printf("%.2f", total);
1369 	fgcolor = get_normal_color_amount(total);
1370 
1371 	if (is_root)
1372 	{
1373 		is_visible = FALSE;
1374 	}
1375 
1376 	// Finally, visibility depends on set amount
1377 	//is_visible = (is_visible && amount != 0.0);
1378 	//#1859275 visibility to be tested on total
1379 	is_visible = (is_visible && total != 0.0);
1380 
1381 	g_object_set(renderer,
1382 		"text", text,
1383 		"foreground", fgcolor,
1384 		"visible", is_visible,
1385 		"xalign", 1.0,
1386 		NULL);
1387 
1388 	g_free(text);
1389 }
1390 
1391 // Compute monthly average
ui_bud_tabview_view_display_monthly_average(GtkTreeViewColumn * col,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)1392 static void ui_bud_tabview_view_display_monthly_average(GtkTreeViewColumn *col, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
1393 {
1394 gboolean is_same_amount = FALSE, is_total = FALSE, is_root = FALSE;
1395 gdouble amount = 0.0;
1396 gdouble average = 0.0;
1397 gchar *text;
1398 gchar *fgcolor;
1399 gboolean is_visible = TRUE;
1400 
1401 	gtk_tree_model_get(model, iter,
1402 		UI_BUD_TABVIEW_IS_ROOT, &is_root,
1403 		UI_BUD_TABVIEW_IS_SAME_AMOUNT, &is_same_amount,
1404 		UI_BUD_TABVIEW_SAME_AMOUNT, &amount,
1405 		UI_BUD_TABVIEW_IS_TOTAL, &is_total,
1406 		-1);
1407 
1408 	if (is_same_amount)
1409 	{
1410 		average = amount;
1411 	}
1412 	else
1413 	{
1414 		for (int i = UI_BUD_TABVIEW_JANUARY ; i <= UI_BUD_TABVIEW_DECEMBER ; ++i)
1415 		{
1416 			gtk_tree_model_get(model, iter, i, &amount, -1);
1417 			average += amount;
1418 		}
1419 		average = hb_amount_round(average / 12.0, 2);
1420 	}
1421 
1422 	text = g_strdup_printf("%.2f", average);
1423 	fgcolor = get_normal_color_amount(average);
1424 
1425 	if (is_root)
1426 	{
1427 		is_visible = FALSE;
1428 	}
1429 
1430 	// Finally, visibility depends on set amount
1431 	is_visible = (is_visible && average != 0.0);
1432 
1433 	g_object_set(renderer,
1434 		"text", text,
1435 		"foreground", fgcolor,
1436 		"visible", is_visible,
1437 		"xalign", 1.0,
1438 		NULL);
1439 
1440 	g_free(text);
1441 }
1442 
1443 // When view mode is toggled:
1444 // - recreate the view to update columns rendering
ui_bud_tabview_view_toggle(gpointer user_data,ui_bud_tabview_view_mode_t view_mode)1445 static void ui_bud_tabview_view_toggle (gpointer user_data, ui_bud_tabview_view_mode_t view_mode)
1446 {
1447 ui_bud_tabview_data_t *data = user_data;
1448 GtkTreeModel *budget;
1449 GtkWidget *view;
1450 GtkTreePath* firstRow;
1451 
1452 	view = data->TV_budget;
1453 
1454 	budget = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
1455 
1456 	gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(budget));
1457 
1458 	if (data->TV_is_expanded)
1459 	{
1460 		gtk_tree_view_expand_all(GTK_TREE_VIEW(view));
1461 	}
1462 	else
1463 	{
1464 		ui_bud_tabview_model_collapse(GTK_TREE_VIEW(view));
1465 	}
1466 
1467 	gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(GTK_TREE_VIEW(view)));
1468 
1469 	firstRow = gtk_tree_path_new_from_string("0");
1470 	gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(view), firstRow, data->TVC_category, TRUE, 0, 0);
1471 
1472 	DB(g_print("[ui_bud_tabview] : button state changed to: %d\n", view_mode));
1473 
1474 	return;
1475 }
1476 
ui_bud_tabview_view_search(GtkTreeModel * filter,gint column,const gchar * key,GtkTreeIter * filter_iter,gpointer data)1477 static gboolean ui_bud_tabview_view_search (GtkTreeModel *filter, gint column, const gchar *key, GtkTreeIter *filter_iter, gpointer data)
1478 {
1479 gboolean is_matching = FALSE, is_root, is_total;
1480 GtkTreeModel *budget;
1481 GtkTreeIter iter;
1482 gchar *category_name;
1483 
1484 	budget = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(filter));
1485 	gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(filter),
1486 		&iter,
1487 		filter_iter);
1488 
1489 	gtk_tree_model_get(budget, &iter,
1490 		UI_BUD_TABVIEW_CATEGORY_NAME, &category_name,
1491 		UI_BUD_TABVIEW_IS_ROOT, &is_root,
1492 		UI_BUD_TABVIEW_IS_TOTAL, &is_total,
1493 		-1);
1494 
1495 	if (!is_root && !is_total
1496 		&& g_strstr_len(g_utf8_casefold(category_name, -1), -1,
1497 			g_utf8_casefold(key, -1)))
1498 	{
1499 		is_matching = TRUE;
1500 	}
1501 
1502 	// GtkTreeViewSearchEqualFunc has to return FALSE only if iter matches.
1503 	return !is_matching;
1504 }
1505 
1506 #if HB_BUD_TABVIEW_EDIT_ENABLE
ui_bud_tabview_view_separator(GtkTreeModel * model,GtkTreeIter * iter,gpointer data)1507 static gboolean ui_bud_tabview_view_separator (GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
1508 {
1509 gboolean is_separator;
1510 
1511 	gtk_tree_model_get(model, iter,
1512 		UI_BUD_TABVIEW_IS_SEPARATOR, &is_separator,
1513 		-1);
1514 
1515 	return is_separator;
1516 }
1517 #endif
1518 
1519 // the budget view creation which run the model creation tool
1520 
ui_bud_tabview_view_new(gpointer user_data)1521 static GtkWidget *ui_bud_tabview_view_new (gpointer user_data)
1522 {
1523 GtkTreeViewColumn *col;
1524 GtkCellRenderer *renderer, *cat_name_renderer;
1525 GtkWidget *view;
1526 ui_bud_tabview_data_t *data = user_data;
1527 
1528 	view = gtk_tree_view_new();
1529 
1530 	/* icon column */
1531 	col = gtk_tree_view_column_new();
1532 	renderer = gtk_cell_renderer_pixbuf_new ();
1533 	//gtk_cell_renderer_set_fixed_size(renderer, GLOBALS->lst_pixbuf_maxwidth, -1);
1534 	gtk_tree_view_column_pack_start(col, renderer, TRUE);
1535 	gtk_tree_view_column_set_cell_data_func(col, renderer, ui_bud_tabview_icon_cell_data_function, (gpointer) data, NULL);
1536 	gtk_tree_view_append_column (GTK_TREE_VIEW(view), col);
1537 
1538 
1539 	/* --- Category column --- */
1540 	col = gtk_tree_view_column_new();
1541 	data->TVC_category = col;
1542 
1543 	gtk_tree_view_column_set_title(col, _("Category"));
1544 	gtk_tree_view_column_set_alignment(col, 0.5);
1545 	gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
1546 
1547 	gtk_tree_view_set_expander_column(GTK_TREE_VIEW(view), col);
1548 
1549 	// Category Name
1550 	cat_name_renderer = gtk_cell_renderer_text_new();
1551 	gtk_tree_view_column_pack_start (col, cat_name_renderer, TRUE);
1552 	gtk_tree_view_column_add_attribute(col, cat_name_renderer, "text", UI_BUD_TABVIEW_CATEGORY_NAME);
1553 	gtk_tree_view_column_set_cell_data_func(col, cat_name_renderer, ui_bud_tabview_view_display_category_name, (gpointer) data, NULL);
1554 #if HB_BUD_TABVIEW_EDIT_ENABLE
1555 	g_object_set(cat_name_renderer, "editable", TRUE, NULL);
1556 	g_signal_connect(cat_name_renderer, "edited", G_CALLBACK(ui_bud_tabview_cell_update_category), (gpointer) data);
1557 #endif
1558 
1559 	/* --- Annual Total --- */
1560 	col = gtk_tree_view_column_new();
1561 	gtk_tree_view_column_set_title(col, _("Annual Total"));
1562 	gtk_tree_view_column_set_alignment(col, 0.5);
1563 
1564 	gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
1565 	renderer = gtk_cell_renderer_text_new();
1566 	gtk_tree_view_column_pack_start(col, renderer, TRUE);
1567 
1568 	gtk_tree_view_column_set_cell_data_func(col, renderer, ui_bud_tabview_view_display_annual_total, NULL, NULL);
1569 
1570 	/* --- Monthly average --- */
1571 	col = gtk_tree_view_column_new();
1572 	gtk_tree_view_column_set_title(col, _("Monthly Average"));
1573 	gtk_tree_view_column_set_alignment(col, 0.5);
1574 
1575 	gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
1576 	renderer = gtk_cell_renderer_text_new();
1577 	gtk_tree_view_column_pack_start(col, renderer, TRUE);
1578 
1579 	gtk_tree_view_column_set_cell_data_func(col, renderer, ui_bud_tabview_view_display_monthly_average, NULL, NULL);
1580 
1581 	/* --- Monthly column --- */
1582 	col = gtk_tree_view_column_new();
1583 	gtk_tree_view_column_set_title(col, _("Monthly"));
1584 	gtk_tree_view_column_set_alignment(col, 0.5);
1585 	gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
1586 
1587 	// Monthly toggler
1588 	renderer = gtk_cell_renderer_toggle_new();
1589 	g_object_set(renderer,
1590 		"xalign", 0,
1591 		NULL);
1592 	gtk_tree_view_column_pack_start(col, renderer, FALSE);
1593 	gtk_tree_view_column_set_cell_data_func(col, renderer, ui_bud_tabview_view_display_is_same_amount, NULL, NULL);
1594 
1595 	g_signal_connect (renderer, "toggled", G_CALLBACK(ui_bud_tabview_cell_update_is_same_amount), (gpointer) data);
1596 
1597 	// Monthly amount
1598 	renderer = gtk_cell_renderer_spin_new();
1599 	gtk_tree_view_column_pack_start(col, renderer, TRUE);
1600 	gtk_tree_view_column_set_cell_data_func(col, renderer, ui_bud_tabview_view_display_amount, GINT_TO_POINTER(UI_BUD_TABVIEW_SAME_AMOUNT), NULL);
1601 
1602 	g_object_set_data(G_OBJECT(renderer), "ui_bud_tabview_column_id", GINT_TO_POINTER(UI_BUD_TABVIEW_SAME_AMOUNT));
1603 
1604 	g_signal_connect(renderer, "edited", G_CALLBACK(ui_bud_tabview_cell_update_amount), (gpointer) data);
1605 
1606 	/* --- Each month amount --- */
1607 	for (int i = UI_BUD_TABVIEW_JANUARY ; i <= UI_BUD_TABVIEW_DECEMBER ; ++i)
1608 	{
1609 		int month = i - UI_BUD_TABVIEW_JANUARY ;
1610 		col = gtk_tree_view_column_new();
1611 
1612 		gtk_tree_view_column_set_title(col, _(UI_BUD_TABVIEW_MONTHS[month]));
1613 		gtk_tree_view_column_set_alignment(col, 0.5);
1614 		gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
1615 		renderer = gtk_cell_renderer_spin_new();
1616 
1617 		gtk_tree_view_column_pack_start(col, renderer, TRUE);
1618 		gtk_tree_view_column_set_cell_data_func(col, renderer, ui_bud_tabview_view_display_amount, GINT_TO_POINTER(i), NULL);
1619 
1620 		g_object_set_data(G_OBJECT(renderer), "ui_bud_tabview_column_id", GINT_TO_POINTER(i));
1621 
1622 		g_signal_connect(renderer, "edited", G_CALLBACK(ui_bud_tabview_cell_update_amount), (gpointer) data);
1623 
1624 	}
1625 
1626 	/* --- Empty column to expand according to the window width --- */
1627 	col = gtk_tree_view_column_new();
1628 	gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
1629 
1630 	gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(view)), GTK_SELECTION_SINGLE);
1631 
1632 	gtk_tree_view_set_enable_search(GTK_TREE_VIEW(view), TRUE);
1633 	gtk_tree_view_set_search_column(GTK_TREE_VIEW(view), UI_BUD_TABVIEW_CATEGORY_NAME);
1634 	gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(view), (GtkTreeViewSearchEqualFunc) ui_bud_tabview_view_search, NULL, NULL);
1635 
1636 	g_object_set(view,
1637 		"enable-grid-lines", PREFS->grid_lines,
1638 		"enable-tree-lines", FALSE,
1639 		NULL);
1640 
1641 	return view;
1642 }
1643 
1644 /*
1645  * UI actions
1646  **/
1647 #if HB_BUD_TABVIEW_EDIT_ENABLE
1648 // Update homebank category on user change
ui_bud_tabview_cell_update_category(GtkCellRendererText * renderer,gchar * filter_path,gchar * new_text,gpointer user_data)1649 static void ui_bud_tabview_cell_update_category(GtkCellRendererText *renderer, gchar *filter_path, gchar *new_text, gpointer user_data)
1650 {
1651 ui_bud_tabview_data_t *data = user_data;
1652 GtkWidget *view;
1653 GtkTreeIter filter_iter, iter;
1654 GtkTreeModel *filter, *budget;
1655 Category* category;
1656 guint32 category_key;
1657 gboolean is_root, is_total;
1658 
1659 	DB(g_print("\n[ui_bud_tabview] category name updated with new name '%s'\n", new_text));
1660 
1661 	view = data->TV_budget;
1662 
1663 	// Read filter data
1664 	filter = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
1665 	gtk_tree_model_get_iter_from_string (filter, &filter_iter, filter_path);
1666 
1667 	// Convert data to budget model
1668 	budget = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(filter));
1669 	gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(filter),
1670 		&iter,
1671 		&filter_iter);
1672 
1673 	gtk_tree_model_get (budget, &iter,
1674 		UI_BUD_TABVIEW_CATEGORY_KEY, &category_key,
1675 		UI_BUD_TABVIEW_IS_ROOT, &is_root,
1676 		UI_BUD_TABVIEW_IS_TOTAL, &is_total,
1677 		-1);
1678 
1679 	category = da_cat_get (category_key);
1680 
1681 	if (! category || is_root || is_total)
1682 	{
1683 		return;
1684 	}
1685 
1686 	// Update category name
1687 	category_rename(category, new_text);
1688 
1689 	// Notify of changes
1690 	data->change++;
1691 
1692 	// Update budget model
1693 
1694 	// Current row
1695 	gtk_tree_store_set(
1696 		GTK_TREE_STORE(budget),
1697 		&iter,
1698 		UI_BUD_TABVIEW_CATEGORY_NAME, category->name,
1699 		UI_BUD_TABVIEW_CATEGORY_FULLNAME, category->fullname,
1700 		-1);
1701 
1702 	return;
1703 }
1704 #endif
1705 
1706 
1707 // Update amount in budget model and homebank category on user change
ui_bud_tabview_cell_update_amount(GtkCellRendererText * renderer,gchar * filter_path,gchar * new_text,gpointer user_data)1708 static void ui_bud_tabview_cell_update_amount(GtkCellRendererText *renderer, gchar *filter_path, gchar *new_text, gpointer user_data)
1709 {
1710 const ui_bud_tabview_store_t column_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(renderer), "ui_bud_tabview_column_id"));
1711 ui_bud_tabview_data_t *data = user_data;
1712 GtkWidget *view;
1713 GtkTreeIter filter_iter, iter;
1714 GtkTreeModel *filter, *budget;
1715 Category* category;
1716 gdouble amount;
1717 guint32 category_key;
1718 
1719 	DB(g_print("\n[ui_bud_tabview] amount updated:\n"));
1720 
1721 	view = data->TV_budget;
1722 
1723 	// Read filter data
1724 	filter = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
1725 	gtk_tree_model_get_iter_from_string (filter, &filter_iter, filter_path);
1726 
1727 	// Convert data to budget model
1728 	budget = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(filter));
1729 	gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(filter),
1730 		&iter,
1731 		&filter_iter);
1732 
1733 	gtk_tree_model_get (budget, &iter,
1734 		UI_BUD_TABVIEW_CATEGORY_KEY, &category_key,
1735 		-1);
1736 
1737 	category = da_cat_get (category_key);
1738 
1739 	if (! category)
1740 	{
1741 		return;
1742 	}
1743 
1744 	amount = g_strtod(new_text, NULL);
1745 
1746 	DB(g_print("\tcolumn: %d (month: %d), category key: %d, amount %.2f\n", column_id, column_id - UI_BUD_TABVIEW_JANUARY + 1, category_key, amount));
1747 
1748 	// Update Category
1749 	category->budget[column_id - UI_BUD_TABVIEW_JANUARY + 1] = amount;
1750 
1751 	// Reset Budget Flag
1752 	category->flags &= ~(GF_BUDGET);
1753 	if (category->flags & GF_FORCED)
1754 	{
1755 		category->flags |= GF_BUDGET;
1756 	}
1757 	else
1758 	{
1759 		for(gint budget_id = 0; budget_id <=12; ++budget_id)
1760 		{
1761 			if( category->budget[budget_id] != 0.0)
1762 			{
1763 				category->flags |= GF_BUDGET;
1764 				break;
1765 			}
1766 		}
1767 	}
1768 
1769 	// Notify of changes
1770 	data->change++;
1771 
1772 	// Update budget model
1773 
1774 	// Current row
1775 	gtk_tree_store_set(
1776 		GTK_TREE_STORE(budget),
1777 		&iter,
1778 		UI_BUD_TABVIEW_HAS_BUDGET, (category->flags & GF_BUDGET),
1779 		column_id, amount,
1780 		-1);
1781 
1782 	// Refresh total rows
1783 	ui_bud_tabview_model_update_monthly_total (GTK_TREE_STORE(budget));
1784 
1785 	return;
1786 }
1787 
1788 // Update the row to (dis/enable) same amount for this category
ui_bud_tabview_cell_update_is_same_amount(GtkCellRendererText * renderer,gchar * filter_path,gpointer user_data)1789 static void ui_bud_tabview_cell_update_is_same_amount(GtkCellRendererText *renderer, gchar *filter_path, gpointer user_data)
1790 {
1791 ui_bud_tabview_data_t *data = user_data;
1792 GtkWidget *view;
1793 GtkTreeIter filter_iter, iter;
1794 GtkTreeModel *filter, *budget;
1795 Category* category;
1796 gboolean issame;
1797 guint32 category_key;
1798 
1799 	DB(g_print("\n[ui_bud_tabview] Is same amount updated:\n"));
1800 
1801 	view = data->TV_budget;
1802 
1803 	// Read filter data
1804 	filter = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
1805 	gtk_tree_model_get_iter_from_string (filter, &filter_iter, filter_path);
1806 
1807 	// Convert data to budget model
1808 	budget = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(filter));
1809 	gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(filter),
1810 		&iter,
1811 		&filter_iter);
1812 
1813 	gtk_tree_model_get (budget, &iter,
1814 		UI_BUD_TABVIEW_CATEGORY_KEY, &category_key,
1815 		UI_BUD_TABVIEW_IS_SAME_AMOUNT, &issame,
1816 		-1);
1817 
1818 	category = da_cat_get (category_key);
1819 
1820 	if (! category)
1821 	{
1822 		return;
1823 	}
1824 
1825 	// Value has been toggled !
1826 	issame = !(issame);
1827 
1828 	DB(g_print("\tcategory key: %d, issame: %d (before: %d)\n", category_key, issame, !(issame)));
1829 
1830 	// Update Category
1831 
1832 	// Reset Forced Flag
1833 	category->flags &= ~(GF_CUSTOM);
1834 
1835 	if (issame == FALSE)
1836 	{
1837 		category->flags |= (GF_CUSTOM);
1838 	}
1839 
1840 	// Notify of changes
1841 	data->change++;
1842 
1843 	// Update budget model
1844 
1845 	// Current row
1846 	gtk_tree_store_set(
1847 		GTK_TREE_STORE(budget),
1848 		&iter,
1849 		UI_BUD_TABVIEW_IS_SAME_AMOUNT, issame,
1850 		-1);
1851 
1852 	// Refresh total rows
1853 	ui_bud_tabview_model_update_monthly_total (GTK_TREE_STORE(budget));
1854 
1855 	return;
1856 }
1857 
1858 
1859 // Update budget view and model according to the new view mode selected
ui_bud_tabview_view_update_mode(GtkToggleButton * button,gpointer user_data)1860 static void ui_bud_tabview_view_update_mode (GtkToggleButton *button, gpointer user_data)
1861 {
1862 ui_bud_tabview_data_t *data = user_data;
1863 ui_bud_tabview_view_mode_t view_mode = UI_BUD_TABVIEW_VIEW_SUMMARY;
1864 
1865 	// Only run once the view update, so only run on the activated button signal
1866 	if(!gtk_toggle_button_get_active(button))
1867 	{
1868 		return;
1869 	}
1870 
1871 	// Mode is directly set by radio button, because the UI_BUD_TABVIEW_VIEW_MODE and enum
1872 	// for view mode are constructed to correspond (manually)
1873 	view_mode = hbtk_radio_button_get_active(GTK_CONTAINER(data->RA_mode));
1874 
1875 	DB(g_print("\n[ui_bud_tabview] view mode toggled to: %d\n", view_mode));
1876 
1877 	ui_bud_tabview_view_toggle((gpointer) data, view_mode);
1878 
1879 	return;
1880 }
1881 
1882 // Expand all categories inside the current view
ui_bud_tabview_view_expand(GtkButton * button,gpointer user_data)1883 static void ui_bud_tabview_view_expand (GtkButton *button, gpointer user_data)
1884 {
1885 ui_bud_tabview_data_t *data = user_data;
1886 GtkWidget *view;
1887 
1888 	view = data->TV_budget;
1889 
1890 	data->TV_is_expanded = TRUE;
1891 
1892 	gtk_tree_view_expand_all(GTK_TREE_VIEW(view));
1893 
1894 	return;
1895 }
1896 
1897 // Collapse all categories inside the current view
ui_bud_tabview_view_collapse(GtkButton * button,gpointer user_data)1898 static void ui_bud_tabview_view_collapse (GtkButton *button, gpointer user_data)
1899 {
1900 ui_bud_tabview_data_t *data = user_data;
1901 GtkWidget *view;
1902 
1903 	view = data->TV_budget;
1904 
1905 	data->TV_is_expanded = FALSE;
1906 
1907 	ui_bud_tabview_model_collapse (GTK_TREE_VIEW(view));
1908 
1909 	return;
1910 }
1911 
1912 
1913 // From TreeView, retrieve the category, the budget TreeStore and the iter inside that store.
1914 //   * budget: a GtkTreeModel
1915 //   * iter: an unintialized GtkTreeIter
1916 //   * category: a pointer of Category which will point to the found Category
1917 // If category is not retrieved, return FALSE and reset budget, iter and category.
1918 // That's especillaly useful for buttons outside the GtkTreeView.
1919 // Warning: iter has to be already allocated
ui_bud_tabview_get_selected_category(GtkTreeModel ** budget,GtkTreeIter * iter,Category ** category,ui_bud_tabview_data_t * data)1920 static gboolean ui_bud_tabview_get_selected_category (GtkTreeModel **budget, GtkTreeIter *iter, Category **category, ui_bud_tabview_data_t *data)
1921 {
1922 GtkWidget *view;
1923 GtkTreeModel *filter;
1924 GtkTreeSelection *selection;
1925 GtkTreeIter filter_iter;
1926 guint32 item_key;
1927 
1928 	DB( g_print("[ui_bud_tabview_get_selected_category] retrieve category from selected row\n") );
1929 
1930 	view = data->TV_budget;
1931 
1932 	// Read filter to retrieve the currently selected row
1933 	filter = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
1934 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1935 	*budget = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(filter));
1936 
1937 	// Retrieve selected row from filter if possible
1938 	if (gtk_tree_selection_get_selected(selection, &filter, &filter_iter))
1939 	{
1940 		// Convert data to budget model
1941 		gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(filter),
1942 			iter,
1943 			&filter_iter);
1944 
1945 		// Avoid to return fake categories
1946 		gtk_tree_model_get (*budget, iter,
1947 			UI_BUD_TABVIEW_CATEGORY_KEY, &item_key,
1948 			-1);
1949 
1950 		if (item_key != 0)
1951 		{
1952 			DB(g_print("look category with key: %d\n", item_key));
1953 
1954 			*category = da_cat_get(item_key);
1955 
1956 			return (*category != NULL);
1957 		}
1958 	}
1959 
1960 	return FALSE;
1961 }
1962 
1963 #if HB_BUD_TABVIEW_EDIT_ENABLE
1964 // From TreeView, retrieve the the budget TreeStore and the root iter inside that store.
1965 //   * budget: a GtkTreeModel
1966 //   * iter: an unintialized GtkTreeIter
1967 // Return true if selected row is a root row.
1968 // Warning: iter has to be already allocated
ui_bud_tabview_get_selected_root_iter(GtkTreeModel ** budget,GtkTreeIter * iter,ui_bud_tabview_data_t * data)1969 static gboolean ui_bud_tabview_get_selected_root_iter (GtkTreeModel **budget, GtkTreeIter *iter, ui_bud_tabview_data_t *data)
1970 {
1971 GtkWidget *view;
1972 GtkTreeModel *filter;
1973 GtkTreeSelection *selection;
1974 GtkTreeIter filter_iter;
1975 gboolean is_root;
1976 
1977 	DB( g_print("[ui_bud_tabview_get_selected_root_iter] retrieve root iter from selected row\n") );
1978 
1979 	view = data->TV_budget;
1980 
1981 	// Read filter to retrieve the currently selected row
1982 	filter = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
1983 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
1984 	*budget = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(filter));
1985 
1986 	// Retrieve selected row from filter if possible
1987 	if (gtk_tree_selection_get_selected(selection, &filter, &filter_iter))
1988 	{
1989 		// Convert data to budget model
1990 		gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(filter),
1991 			iter,
1992 			&filter_iter);
1993 
1994 		gtk_tree_model_get (*budget, iter,
1995 			UI_BUD_TABVIEW_IS_ROOT, &is_root,
1996 			-1);
1997 
1998 		return (is_root);
1999 	}
2000 
2001 	return FALSE;
2002 }
2003 
2004 
2005 // Check if add category dialog is full filled
ui_bud_tabview_category_add_full_filled(GtkWidget * source,gpointer user_data)2006 static void ui_bud_tabview_category_add_full_filled (GtkWidget *source, gpointer user_data)
2007 {
2008 ui_bud_tabview_data_t *data = user_data;
2009 const gchar* new_raw_name;
2010 gchar* new_name;
2011 gboolean is_name_filled = FALSE, is_parent_choosen = FALSE;
2012 
2013 	// Check a name for the new category is given:
2014 	new_raw_name = gtk_entry_get_text(GTK_ENTRY(data->EN_add_name));
2015 
2016 	if (new_raw_name && *new_raw_name)
2017 	{
2018 		new_name = g_strdup(new_raw_name);
2019 		g_strstrip(new_name);
2020 
2021 		if (strlen(new_name) > 0)
2022 		{
2023 			is_name_filled = TRUE;
2024 		}
2025 
2026 		g_free(new_name);
2027 	}
2028 
2029 	// Check an entry has been selected in parent combobox
2030 	is_parent_choosen = (gtk_combo_box_get_active(GTK_COMBO_BOX(data->COMBO_add_parent)) > -1);
2031 
2032 	// Dis/Enable apply dialog button
2033 	gtk_widget_set_sensitive(data->BT_apply, is_name_filled && is_parent_choosen);
2034 
2035 	return;
2036 }
2037 
2038 // Add a category according to the current selection
ui_bud_tabview_category_add(GtkButton * button,gpointer user_data)2039 static void ui_bud_tabview_category_add (GtkButton *button, gpointer user_data)
2040 {
2041 ui_bud_tabview_data_t *data = user_data;
2042 GtkWidget *apply;
2043 ui_bud_tabview_view_mode_t view_mode;
2044 GtkTreeModel *budget, *categories;
2045 GtkTreeIter default_parent_iter, categories_iter;
2046 GtkWidget *dialog, *content_area, *grid, *combobox, *textentry, *widget;
2047 GtkCellRenderer *renderer;
2048 Category *category;
2049 gint gridrow, response;
2050 gboolean exists_default_select = FALSE;
2051 
2052 	view_mode = hbtk_radio_button_get_active(GTK_CONTAINER(data->RA_mode));
2053 
2054 	// Setup budget and retrieve default selection from budget dialog
2055 	if (ui_bud_tabview_get_selected_category (&budget, &default_parent_iter, &category, data))
2056 	{
2057 		exists_default_select = TRUE;
2058 
2059 		if (category->parent != 0)
2060 		{
2061 		ui_bud_tabview_search_criteria_t parent_search = ui_bud_tabview_search_criteria_default;
2062 			parent_search.row_category_key = category->parent;
2063 
2064 			gtk_tree_model_foreach(GTK_TREE_MODEL(budget),
2065 				(GtkTreeModelForeachFunc) ui_bud_tabview_model_search_iterator,
2066 				&parent_search);
2067 
2068 			if (!parent_search.iterator) {
2069 				DB(g_print(" -> error: not found good parent iterator !\n"));
2070 				return;
2071 			}
2072 
2073 			default_parent_iter = *parent_search.iterator;
2074 
2075 			g_free(parent_search.iterator);
2076 		}
2077 	}
2078 	else if (ui_bud_tabview_get_selected_root_iter (&budget, &default_parent_iter, data))
2079 	{
2080 		// If currently selected row is a root row, use it as default value
2081 		exists_default_select = TRUE;
2082 	}
2083 
2084 	// Selectable categories from original model
2085 	categories = gtk_tree_model_filter_new(budget, NULL);
2086 	gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(categories),
2087 		ui_bud_tabview_model_row_filter_parents, data, NULL);
2088 
2089 	if (exists_default_select)
2090 	{
2091 		gtk_tree_model_filter_convert_child_iter_to_iter(GTK_TREE_MODEL_FILTER(categories),
2092 			&categories_iter,
2093 			&default_parent_iter);
2094 	}
2095 
2096 	DB( g_print("[ui_bud_tabview] open sub-dialog to add a category\n") );
2097 
2098 	dialog = gtk_dialog_new_with_buttons (_("Add a category"),
2099 		GTK_WINDOW(data->dialog),
2100 		GTK_DIALOG_MODAL,
2101 		_("_Cancel"),
2102 		GTK_RESPONSE_CANCEL,
2103 		NULL);
2104 
2105 	// Apply button will be enabled only when parent category and name are choosen
2106 	apply = gtk_dialog_add_button(GTK_DIALOG(dialog),
2107 		_("_Apply"),
2108 		GTK_RESPONSE_APPLY);
2109 	data->BT_apply = apply;
2110 	gtk_widget_set_sensitive(apply, FALSE);
2111 
2112 	//window contents
2113 	content_area = gtk_dialog_get_content_area(GTK_DIALOG (dialog));
2114 
2115 	// design content
2116 	grid = gtk_grid_new ();
2117 	gtk_grid_set_row_spacing (GTK_GRID (grid), SPACING_MEDIUM);
2118 	gtk_grid_set_column_spacing (GTK_GRID (grid), SPACING_MEDIUM);
2119 	g_object_set(grid, "margin", SPACING_MEDIUM, NULL);
2120 	gtk_container_add(GTK_CONTAINER(content_area), grid);
2121 
2122 	// First row display parent selector
2123 	gridrow = 0;
2124 
2125 	widget = gtk_label_new(_("Parent category"));
2126 	gtk_grid_attach (GTK_GRID (grid), widget, 0, gridrow, 1, 1);
2127 
2128 	combobox = gtk_combo_box_new_with_model(categories);
2129 	data->COMBO_add_parent = combobox;
2130 	gtk_grid_attach (GTK_GRID (grid), combobox, 1, gridrow, 1, 1);
2131 
2132 	gtk_combo_box_set_row_separator_func(
2133 		GTK_COMBO_BOX(combobox),
2134 		ui_bud_tabview_view_separator,
2135 		data,
2136 		NULL
2137 	);
2138 
2139 	gtk_combo_box_set_id_column(GTK_COMBO_BOX(combobox), UI_BUD_TABVIEW_CATEGORY_KEY);
2140 
2141 	renderer = gtk_cell_renderer_text_new();
2142 	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT(combobox), renderer, TRUE);
2143 	gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(combobox), renderer, "text", UI_BUD_TABVIEW_CATEGORY_FULLNAME);
2144 
2145 	// Next row displays the new category entry
2146 	gridrow++;
2147 
2148 	widget = gtk_label_new(_("Category name"));
2149 	gtk_grid_attach (GTK_GRID (grid), widget, 0, gridrow, 1, 1);
2150 
2151 	textentry = gtk_entry_new();
2152 	data->EN_add_name = textentry;
2153 	gtk_grid_attach (GTK_GRID (grid), textentry, 1, gridrow, 1, 1);
2154 
2155 	// Signals to enable Apply button
2156 	g_signal_connect (data->COMBO_add_parent, "changed", G_CALLBACK(ui_bud_tabview_category_add_full_filled), (gpointer)data);
2157 	g_signal_connect (data->EN_add_name, "changed", G_CALLBACK(ui_bud_tabview_category_add_full_filled), (gpointer)data);
2158 
2159 	if (exists_default_select)
2160 	{
2161 		gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combobox), &categories_iter);
2162 	}
2163 
2164 	gtk_widget_show_all (dialog);
2165 
2166 	response = gtk_dialog_run (GTK_DIALOG (dialog));
2167 
2168 	// When the response is APPLY, the form was full filled
2169 	if (response == GTK_RESPONSE_APPLY) {
2170 	Category *new_item;
2171 	const gchar *new_name;
2172 	gchar *parent_name;
2173 	guint32 parent_key;
2174 	ui_bud_tabview_cat_type_t parent_type;
2175 	ui_bud_tabview_search_criteria_t root_search = ui_bud_tabview_search_criteria_default;
2176 	GtkTreeIter parent_iter, *root_iter;
2177 
2178 		DB( g_print("[ui_bud_tabview] applying creation of a new category\n") );
2179 
2180 		// Retrieve info from dialog
2181 		gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combobox), &categories_iter);
2182 
2183 		gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(categories),
2184 			&parent_iter,
2185 			&categories_iter);
2186 
2187 		gtk_tree_model_get (budget, &parent_iter,
2188 			UI_BUD_TABVIEW_CATEGORY_NAME, &parent_name,
2189 			UI_BUD_TABVIEW_CATEGORY_KEY, &parent_key,
2190 			UI_BUD_TABVIEW_CATEGORY_TYPE, &parent_type,
2191 			-1);
2192 
2193 		DB( g_print(" -> from parent cat: %s (key: %d, type: %d)\n",
2194 			parent_name, parent_key ,parent_type) );
2195 
2196 		// Retrieve required root
2197 		root_search.row_is_root = TRUE;
2198 		root_search.row_is_total = FALSE;
2199 		root_search.row_category_type = parent_type;
2200 		gtk_tree_model_foreach(GTK_TREE_MODEL(budget),
2201 			(GtkTreeModelForeachFunc) ui_bud_tabview_model_search_iterator,
2202 			&root_search);
2203 
2204 		if (!root_search.iterator) {
2205 			DB(g_print(" -> error: not found good tree root !\n"));
2206 			return;
2207 		}
2208 
2209 		root_iter = root_search.iterator;
2210 
2211 		// Build new category from name and parent iterator
2212 		new_name = gtk_entry_get_text(GTK_ENTRY(textentry));
2213 
2214 		data->change++;
2215 		new_item = da_cat_malloc();
2216 		new_item->name = g_strdup(new_name);
2217 		g_strstrip(new_item->name);
2218 
2219 		new_item->parent = parent_key;
2220 
2221 		if (parent_key)
2222 		{
2223 			new_item->flags |= GF_SUB;
2224 		}
2225 
2226 		if (parent_type == UI_BUD_TABVIEW_CAT_TYPE_INCOME)
2227 		{
2228 			new_item->flags |= GF_INCOME;
2229 		}
2230 
2231 		// On balance mode, enable forced display too to render it to user
2232 		if (view_mode == UI_BUD_TABVIEW_VIEW_SUMMARY)
2233 		{
2234 			new_item->flags |= GF_FORCED;
2235 		}
2236 
2237 		if(da_cat_append(new_item))
2238 		{
2239 		GtkWidget *view;
2240 		GtkTreeModel *filter;
2241 		GtkTreeIter filter_iter;
2242 		GtkTreePath *path;
2243 
2244 			DB( g_print(" => add cat: %p (%d), type=%d\n", new_item->name, new_item->key, category_type_get(new_item)) );
2245 
2246 			// Finally add it to model
2247 			ui_bud_tabview_model_add_category_with_lineage (GTK_TREE_STORE(budget), root_iter, &(new_item->key));
2248 
2249 			// Expand view up to the newly added item, so expand its parent which is already known as iter
2250 
2251 			view = data->TV_budget;
2252 			filter = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
2253 
2254 			if(gtk_tree_model_filter_convert_child_iter_to_iter(GTK_TREE_MODEL_FILTER(filter),
2255 				&filter_iter,
2256 				&parent_iter)
2257 			)
2258 			{
2259 				path = gtk_tree_model_get_path(filter, &filter_iter);
2260 				gtk_tree_view_expand_row(GTK_TREE_VIEW(view), path, TRUE);
2261 			}
2262 		}
2263 	}
2264 
2265 	gtk_widget_destroy(dialog);
2266 
2267 	return;
2268 }
2269 
2270 // Delete a category according to the current selection
ui_bud_tabview_category_delete(GtkButton * button,gpointer user_data)2271 static void ui_bud_tabview_category_delete (GtkButton *button, gpointer user_data)
2272 {
2273 ui_bud_tabview_data_t *data = user_data;
2274 GtkTreeModel *budget;
2275 GtkTreeIter iter;
2276 Category* category;
2277 gint response;
2278 
2279 	DB( g_print("[ui_bud_tabview] open sub-dialog to delete a category\n") );
2280 
2281 	// Retrieve selected row from filter if possible
2282 	if (ui_bud_tabview_get_selected_category (&budget, &iter, &category, data))
2283 	{
2284 	gchar *title = NULL;
2285 	gchar *secondtext = NULL;
2286 
2287 		title = g_strdup_printf (
2288 			_("Are you sure you want to permanently delete '%s'?"), category->name);
2289 
2290 		if( category->usage_count > 0 )
2291 		{
2292 			secondtext = _("This category is used.\n"
2293 				"Any transaction using that category will be set to (no category)");
2294 		}
2295 
2296 		response = ui_dialog_msg_confirm_alert(
2297 				GTK_WINDOW(data->dialog),
2298 				title,
2299 				secondtext,
2300 				_("_Delete")
2301 			);
2302 
2303 		g_free(title);
2304 
2305 		if( response == GTK_RESPONSE_OK )
2306 		{
2307 			gtk_tree_store_remove(GTK_TREE_STORE(budget),
2308 				&iter);
2309 
2310 			category_move(category->key, 0);
2311 			da_cat_delete(category->key);
2312 			data->change++;
2313 		}
2314 	}
2315 
2316 	return;
2317 }
2318 
2319 // Check if add category dialog is full filled
ui_bud_tabview_category_merge_full_filled(GtkWidget * source,gpointer user_data)2320 static void ui_bud_tabview_category_merge_full_filled (GtkWidget *source, gpointer user_data)
2321 {
2322 ui_bud_tabview_data_t *data = user_data;
2323 gboolean is_target_choosen = FALSE;
2324 
2325 	is_target_choosen = (gtk_combo_box_get_active(GTK_COMBO_BOX(data->COMBO_merge_target)) > -1);
2326 
2327 	// Dis/Enable apply dialog button
2328 	gtk_widget_set_sensitive(data->BT_apply, is_target_choosen);
2329 
2330 	return;
2331 }
2332 
ui_bud_tabview_category_merge(GtkButton * button,gpointer user_data)2333 static void ui_bud_tabview_category_merge (GtkButton *button, gpointer user_data)
2334 {
2335 ui_bud_tabview_data_t *data = user_data;
2336 GtkWidget *apply;
2337 GtkTreeModel *budget, *categories;
2338 GtkTreeIter iter_source, iter, categories_iter;
2339 GtkWidget *dialog, *content_area, *grid, *combobox, *widget, *checkbutton;
2340 GtkCellRenderer *renderer;
2341 gint gridrow, response;
2342 Category *merge_source;
2343 gchar *label_source, *label_delete;
2344 
2345 	// Retrieve source of merge
2346 	if (ui_bud_tabview_get_selected_category(&budget, &iter_source, &merge_source, data))
2347 	{
2348 		DB( g_print("[ui_bud_tabview] open sub-dialog to merge category: %s\n", merge_source->name) );
2349 
2350 		dialog = gtk_dialog_new_with_buttons (_("Merge categories"),
2351 			GTK_WINDOW(data->dialog),
2352 			GTK_DIALOG_MODAL,
2353 			_("_Cancel"),
2354 			GTK_RESPONSE_CANCEL,
2355 			NULL);
2356 
2357 		// Apply button will be enabled only when a target merge category is choosen
2358 		apply = gtk_dialog_add_button(GTK_DIALOG(dialog),
2359 			_("_Apply"),
2360 			GTK_RESPONSE_APPLY);
2361 		data->BT_apply = apply;
2362 		gtk_widget_set_sensitive(apply, FALSE);
2363 
2364 		//window contents
2365 		content_area = gtk_dialog_get_content_area(GTK_DIALOG (dialog));
2366 
2367 		// design content
2368 		grid = gtk_grid_new ();
2369 		gtk_grid_set_row_spacing (GTK_GRID (grid), SPACING_MEDIUM);
2370 		gtk_grid_set_column_spacing (GTK_GRID (grid), SPACING_MEDIUM);
2371 		g_object_set(grid, "margin", SPACING_MEDIUM, NULL);
2372 		gtk_container_add(GTK_CONTAINER(content_area), grid);
2373 
2374 		// First row display parent selector
2375 		gridrow = 0;
2376 
2377 		label_source = g_strdup_printf(_("Transactions assigned to category '%s', will be moved to the category selected below."), merge_source->name);
2378 		widget = gtk_label_new (label_source);
2379 		gtk_grid_attach (GTK_GRID (grid), widget, 0, gridrow, 4, 1);
2380 
2381 		// Line to select merge target
2382 		gridrow++;
2383 
2384 		widget = gtk_label_new(_("Target category"));
2385 		gtk_grid_attach (GTK_GRID (grid), widget, 1, gridrow, 1, 1);
2386 
2387 		// Target category list is built from original model with a filter
2388 		categories = gtk_tree_model_filter_new(budget, NULL);
2389 
2390 		data->MERGE_source_category_key = merge_source->key;
2391 
2392 		gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(categories),
2393 			ui_bud_tabview_model_row_merge_filter_with_headers, data, NULL);
2394 
2395 		combobox = gtk_combo_box_new_with_model(categories);
2396 		data->COMBO_merge_target = combobox;
2397 		gtk_grid_attach (GTK_GRID (grid), combobox, 2, gridrow, 1, 1);
2398 
2399 		gtk_combo_box_set_row_separator_func(
2400 			GTK_COMBO_BOX(combobox),
2401 			ui_bud_tabview_view_separator,
2402 			data,
2403 			NULL
2404 		);
2405 
2406 		gtk_combo_box_set_id_column(GTK_COMBO_BOX(combobox), UI_BUD_TABVIEW_CATEGORY_KEY);
2407 
2408 		renderer = gtk_cell_renderer_text_new();
2409 		gtk_cell_layout_pack_start (GTK_CELL_LAYOUT(combobox), renderer, TRUE);
2410 		gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(combobox), renderer, "text", UI_BUD_TABVIEW_CATEGORY_FULLNAME);
2411 
2412 		// Next row displays the automatic delete option
2413 		gridrow++;
2414 
2415 		label_delete = g_strdup_printf (
2416 			_("_Delete the category '%s'"), merge_source->name);
2417 		checkbutton = gtk_check_button_new_with_mnemonic(label_delete);
2418 		gtk_grid_attach (GTK_GRID (grid), checkbutton, 0, gridrow, 4, 1);
2419 
2420 		// Signals to enable Apply button
2421 		g_signal_connect (data->COMBO_merge_target, "changed", G_CALLBACK(ui_bud_tabview_category_merge_full_filled), (gpointer)data);
2422 
2423 		gtk_widget_show_all (dialog);
2424 
2425 		response = gtk_dialog_run (GTK_DIALOG (dialog));
2426 
2427 		// When the response is APPLY, the form was full filled
2428 		if (response == GTK_RESPONSE_APPLY)
2429 		{
2430 		Category *merge_target, *parent_source;
2431 		gint target_key;
2432 
2433 			DB( g_print("[ui_bud_tabview] applying merge\n") );
2434 
2435 			// Retrieve info from dialog
2436 			gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combobox), &categories_iter);
2437 
2438 			gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(categories),
2439 				&iter,
2440 				&categories_iter);
2441 
2442 			gtk_tree_model_get (budget, &iter,
2443 				UI_BUD_TABVIEW_CATEGORY_KEY, &target_key,
2444 				-1);
2445 
2446 			merge_target = da_cat_get(target_key);
2447 
2448 			DB( g_print(" -> to target category: %s (key: %d)\n",
2449 				merge_target->name, target_key) );
2450 
2451 			// Merge categories (according to ui-category.c)
2452 			category_move(merge_source->key, merge_target->key);
2453 
2454 			merge_target->usage_count += merge_source->usage_count;
2455 			merge_source->usage_count = 0;
2456 
2457 			// Keep the income type with us
2458 			parent_source = da_cat_get(merge_source->parent);
2459 			if(parent_source != NULL && (parent_source->flags & GF_INCOME))
2460 			{
2461 				merge_target->flags |= GF_INCOME;
2462 			}
2463 
2464 			// Clean source merge
2465 			if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbutton)))
2466 			{
2467 				da_cat_delete(merge_source->key);
2468 
2469 				gtk_tree_store_remove(GTK_TREE_STORE(budget),
2470 					&iter_source);
2471 			}
2472 
2473 			data->change++;
2474 		}
2475 
2476 		data->MERGE_source_category_key = 0;
2477 
2478 		gtk_widget_destroy(dialog);
2479 
2480 		g_free(label_source);
2481 		g_free(label_delete);
2482 	}
2483 
2484 	return;
2485 }
2486 #endif
2487 
2488 
2489 // Reset inputs done for the currently selected category
ui_bud_tabview_category_reset(GtkButton * button,gpointer user_data)2490 static void ui_bud_tabview_category_reset (GtkButton *button, gpointer user_data)
2491 {
2492 ui_bud_tabview_data_t *data = user_data;
2493 GtkTreeModel *budget;
2494 GtkTreeIter iter;
2495 Category* category;
2496 gint response;
2497 
2498 	DB( g_print("[ui_bud_tabview] open sub-dialog to confirm category reset\n") );
2499 
2500 	// Retrieve selected row from filter if possible
2501 	if (ui_bud_tabview_get_selected_category (&budget, &iter, &category, data))
2502 	{
2503 	gchar *title = NULL;
2504 	gchar *secondtext = NULL;
2505 
2506 		title = g_strdup_printf (
2507 			_("Are you sure you want to clear inputs for '%s'?"), category->name);
2508 		secondtext = _("If you proceed, every amount will be set to 0.");
2509 
2510 		response = ui_dialog_msg_confirm_alert(
2511 				GTK_WINDOW(data->dialog),
2512 				title,
2513 				secondtext,
2514 				_("_Clear")
2515 			);
2516 
2517 		g_free(title);
2518 
2519 		if( response == GTK_RESPONSE_OK )
2520 		{
2521 			// Update data
2522 			for(int i=0;i<=12;i++)
2523 			{
2524 				category->budget[i] = 0;
2525 			}
2526 
2527 			// Reset budget flag according to GF_FORCED
2528 			category->flags &= ~(GF_BUDGET);
2529 
2530 			//mdo: no
2531 			/*if (category->flags & GF_FORCED)
2532 			{
2533 				category->flags |= GF_BUDGET;
2534 			}*/
2535 
2536 			data->change++;
2537 
2538 			// Update GtkTreeStore
2539 			gtk_tree_store_set(
2540 				GTK_TREE_STORE(budget),
2541 				&iter,
2542 				UI_BUD_TABVIEW_HAS_BUDGET, (category->flags & GF_BUDGET),
2543 				UI_BUD_TABVIEW_SAME_AMOUNT, category->budget[0],
2544 				UI_BUD_TABVIEW_JANUARY, category->budget[1],
2545 				UI_BUD_TABVIEW_FEBRUARY, category->budget[2],
2546 				UI_BUD_TABVIEW_MARCH, category->budget[3],
2547 				UI_BUD_TABVIEW_APRIL, category->budget[4],
2548 				UI_BUD_TABVIEW_MAY, category->budget[5],
2549 				UI_BUD_TABVIEW_JUNE, category->budget[6],
2550 				UI_BUD_TABVIEW_JULY, category->budget[7],
2551 				UI_BUD_TABVIEW_AUGUST, category->budget[8],
2552 				UI_BUD_TABVIEW_SEPTEMBER, category->budget[9],
2553 				UI_BUD_TABVIEW_OCTOBER, category->budget[10],
2554 				UI_BUD_TABVIEW_NOVEMBER, category->budget[11],
2555 				UI_BUD_TABVIEW_DECEMBER, category->budget[12],
2556 				-1);
2557 
2558 			// Refresh total rows
2559 			ui_bud_tabview_model_update_monthly_total (GTK_TREE_STORE(budget));
2560 		}
2561 	}
2562 
2563 	return;
2564 }
2565 
2566 
2567 
ui_bud_tabview_category_toggle_monitoring(GtkButton * button,gpointer user_data)2568 static void ui_bud_tabview_category_toggle_monitoring(GtkButton *button, gpointer user_data)
2569 {
2570 ui_bud_tabview_data_t *data = user_data;
2571 GtkTreeModel *budget;
2572 GtkTreeIter iter;
2573 Category* category;
2574 
2575 	DB( g_print("[ui_bud_tabview] open sub-dialog to confirm category reset\n") );
2576 
2577 	// Retrieve selected row from filter if possible
2578 	if (ui_bud_tabview_get_selected_category (&budget, &iter, &category, data))
2579 	{
2580 	gboolean is_monitoring_forced;
2581 
2582 		// Toggle monitoring
2583 		is_monitoring_forced = !(category->flags & GF_FORCED);
2584 
2585 		// Update Category
2586 
2587 		// Reset Forced and Budget Flags
2588 		category->flags &= ~(GF_FORCED);
2589 		category->flags &= ~(GF_BUDGET);
2590 
2591 		if (is_monitoring_forced == TRUE)
2592 		{
2593 			category->flags |= (GF_FORCED);
2594 			category->flags |= (GF_BUDGET);
2595 		}
2596 		else
2597 		{
2598 			for(gint budget_id = 0; budget_id <=12; ++budget_id)
2599 			{
2600 				if( category->budget[budget_id] != 0.0)
2601 				{
2602 					category->flags |= GF_BUDGET;
2603 					break;
2604 				}
2605 			}
2606 		}
2607 
2608 		// Notify of changes
2609 		data->change++;
2610 
2611 		// Update budget model
2612 
2613 		// Current row
2614 		gtk_tree_store_set(
2615 			GTK_TREE_STORE(budget),
2616 			&iter,
2617 			UI_BUD_TABVIEW_IS_MONITORING_FORCED, is_monitoring_forced,
2618 			UI_BUD_TABVIEW_HAS_BUDGET, (category->flags & GF_BUDGET),
2619 			-1);
2620 
2621 		// Refresh total rows
2622 		ui_bud_tabview_model_update_monthly_total (GTK_TREE_STORE(budget));
2623 	}
2624 
2625 	return;
2626 }
2627 
2628 
2629 
ui_bud_tabview_on_key_press(GtkWidget * source,GdkEventKey * event,gpointer user_data)2630 static gboolean ui_bud_tabview_on_key_press(GtkWidget *source, GdkEventKey *event, gpointer user_data)
2631 {
2632 ui_bud_tabview_data_t *data = user_data;
2633 
2634 	// On Control-f enable search entry
2635 	if (event->state & GDK_CONTROL_MASK
2636 		&& event->keyval == GDK_KEY_f)
2637 	{
2638 		gtk_widget_grab_focus(data->EN_search);
2639 	}
2640 
2641 	return GDK_EVENT_PROPAGATE;
2642 }
2643 
2644 
ui_bud_tabview_view_on_select(GtkTreeSelection * treeselection,gpointer user_data)2645 static void ui_bud_tabview_view_on_select(GtkTreeSelection *treeselection, gpointer user_data)
2646 {
2647 ui_bud_tabview_data_t *data = user_data;
2648 GtkWidget *view;
2649 GtkTreeModel *filter, *budget;
2650 GtkTreeIter filter_iter, iter;
2651 GtkTreeSelection *selection;
2652 gboolean is_root, is_total, is_monitoring_forced;
2653 ui_bud_tabview_cat_type_t category_type;
2654 
2655 	view = data->TV_budget;
2656 	selection = data->TV_selection;
2657 
2658 	// Block signals
2659 	g_signal_handler_block(data->BT_category_force_monitoring, data->HID_category_monitoring_toggle);
2660 
2661 	// Reset buttons
2662 	#if HB_BUD_TABVIEW_EDIT_ENABLE
2663 	gtk_widget_set_sensitive(data->BT_category_add, FALSE);
2664 	gtk_widget_set_sensitive(data->BT_category_delete, FALSE);
2665 	gtk_widget_set_sensitive(data->BT_category_merge, FALSE);
2666 	#endif
2667 	gtk_widget_set_sensitive(data->BT_category_reset, FALSE);
2668 	gtk_widget_set_sensitive(data->BT_category_force_monitoring, FALSE);
2669 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->BT_category_force_monitoring), FALSE);
2670 
2671 	// Read filter to retrieve the currently selected row in real model
2672 	filter = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
2673 	budget = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(filter));
2674 
2675 	// Activate buttons if selected row is editable
2676 	if (gtk_tree_selection_get_selected(selection, &filter, &filter_iter))
2677 	{
2678 		// Convert data to budget model
2679 		gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(filter),
2680 			&iter,
2681 			&filter_iter);
2682 
2683 		// Check the iter is an editable one
2684 		gtk_tree_model_get (budget, &iter,
2685 			UI_BUD_TABVIEW_CATEGORY_TYPE, &category_type,
2686 			UI_BUD_TABVIEW_IS_ROOT, &is_root,
2687 			UI_BUD_TABVIEW_IS_TOTAL, &is_total,
2688 			UI_BUD_TABVIEW_IS_MONITORING_FORCED, &is_monitoring_forced,
2689 			-1);
2690 
2691 		// If category is neither a root, neither a total row, every operations can be applied
2692 		if (!is_root && !is_total)
2693 		{
2694 			#if HB_BUD_TABVIEW_EDIT_ENABLE
2695 			gtk_widget_set_sensitive(data->BT_category_add, TRUE);
2696 			gtk_widget_set_sensitive(data->BT_category_delete, TRUE);
2697 			gtk_widget_set_sensitive(data->BT_category_merge, TRUE);
2698 			#endif
2699 			gtk_widget_set_sensitive(data->BT_category_reset, TRUE);
2700 			gtk_widget_set_sensitive(data->BT_category_force_monitoring, TRUE);
2701 			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->BT_category_force_monitoring), is_monitoring_forced);
2702 		}
2703 		// If category is a root (except the Total one), we can use it to add a child row
2704 		#if HB_BUD_TABVIEW_EDIT_ENABLE
2705 		else if (category_type != UI_BUD_TABVIEW_CAT_TYPE_NONE
2706 			&& is_root
2707 			&& !is_total)
2708 		{
2709 			gtk_widget_set_sensitive(data->BT_category_add, TRUE);
2710 		}
2711 		#endif
2712 	}
2713 
2714 	// Unblock signals
2715 	g_signal_handler_unblock(data->BT_category_force_monitoring, data->HID_category_monitoring_toggle);
2716 
2717 	return;
2718 }
2719 
2720 
ui_bud_tabview_dialog_close(ui_bud_tabview_data_t * data,gint response)2721 static void ui_bud_tabview_dialog_close(ui_bud_tabview_data_t *data, gint response)
2722 {
2723 	DB( g_print("[ui_bud_tabview] dialog close\n") );
2724 
2725 	GLOBALS->changes_count += data->change;
2726 
2727 	return;
2728 }
2729 
2730 // Open / create the main dialog, the budget view and the budget model
ui_bud_tabview_manage_dialog(void)2731 GtkWidget *ui_bud_tabview_manage_dialog(void)
2732 {
2733 ui_bud_tabview_data_t *data;
2734 GtkWidget *dialog, *content_area, *grid;
2735 GtkWidget *radiomode;
2736 GtkWidget *widget;
2737 GtkWidget *vbox, *hbox;
2738 GtkWidget *search_entry;
2739 GtkWidget *scrolledwindow, *treeview;
2740 GtkWidget *toolbar;
2741 GtkToolItem *toolitem;
2742 GtkTreeModel *model, *filter;
2743 gint response;
2744 gint w, h, dw, dh;
2745 gint gridrow;
2746 
2747 	data = g_malloc0(sizeof(ui_bud_tabview_data_t));
2748 	data->change = 0;
2749 	if(!data) return NULL;
2750 
2751 	DB( g_print("\n[ui_bud_tabview] open dialog\n") );
2752 
2753 	// create window
2754 	dialog = gtk_dialog_new_with_buttons (_("Budget (table view)"),
2755 		GTK_WINDOW(GLOBALS->mainwindow),
2756 		GTK_DIALOG_MODAL,
2757 		_("_Close"),
2758 		GTK_RESPONSE_ACCEPT,
2759 		NULL);
2760 
2761 	data->dialog = dialog;
2762 
2763 	gtk_window_set_icon_name(GTK_WINDOW (dialog), ICONNAME_HB_BUDGET);
2764 
2765 	//set a nice dialog size
2766 	gtk_window_get_size(GTK_WINDOW(GLOBALS->mainwindow), &w, &h);
2767 	dh = (h*1.33/PHI);
2768 	//ratio 3:2
2769 	dw = (dh * 3) / 2;
2770 	DB( g_print(" main w=%d h=%d => diag w=%d h=%d\n", w, h, dw, dh) );
2771 	gtk_window_set_default_size (GTK_WINDOW(dialog), dw, dh);
2772 
2773 	// store data inside dialog property to retrieve them easily in callbacks
2774 	g_object_set_data(G_OBJECT(dialog), "inst_data", (gpointer)&data);
2775 	DB( g_print(" - new dialog=%p, inst_data=%p\n", dialog, data) );
2776 
2777 	//window contents
2778 	content_area = gtk_dialog_get_content_area(GTK_DIALOG (dialog)); // return a vbox
2779 
2780 	// design content
2781 	grid = gtk_grid_new ();
2782 	gtk_grid_set_row_spacing (GTK_GRID (grid), SPACING_MEDIUM);
2783 	gtk_grid_set_column_spacing (GTK_GRID (grid), SPACING_MEDIUM);
2784 	g_object_set(grid, "margin", SPACING_MEDIUM, NULL);
2785 	gtk_container_add(GTK_CONTAINER(content_area), grid);
2786 
2787 	// First row displays radio button to change mode (edition / view) and search entry
2788 	gridrow = 0;
2789 
2790 	hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
2791 	gtk_grid_attach (GTK_GRID (grid), hbox, 0, gridrow, 1, 1);
2792 
2793 	// edition mode radio buttons
2794 	radiomode = hbtk_radio_button_new(GTK_ORIENTATION_HORIZONTAL, UI_BUD_TABVIEW_VIEW_MODE, TRUE);
2795 	data->RA_mode = radiomode;
2796 	gtk_box_set_center_widget(GTK_BOX (hbox), radiomode);
2797 
2798 	// Search
2799 	search_entry = gtk_search_entry_new();
2800 	data->EN_search = search_entry;
2801 	gtk_box_pack_end (GTK_BOX (hbox), search_entry, FALSE, FALSE, 0);
2802 
2803 	// Next row displays the budget tree with its toolbar
2804 	gridrow++;
2805 
2806 	// We use a Vertical Box to link tree with searchbar and toolbar
2807 	vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
2808 	gtk_grid_attach (GTK_GRID (grid), vbox, 0, gridrow, 1, 1);
2809 
2810 	// Scrolled Window will permit to display budgets with a lot of active categories
2811 	scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
2812 	gtk_widget_set_hexpand (scrolledwindow, TRUE);
2813 	gtk_widget_set_vexpand (scrolledwindow, TRUE);
2814 	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow), GTK_SHADOW_ETCHED_IN);
2815 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2816 	gtk_box_pack_start (GTK_BOX (vbox), scrolledwindow, TRUE, TRUE, 0);
2817 
2818 	treeview = ui_bud_tabview_view_new ((gpointer) data);
2819 	data->TV_budget = treeview;
2820 	gtk_container_add(GTK_CONTAINER(scrolledwindow), treeview);
2821 
2822 	data->TV_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
2823 
2824 	// Toolbar to add, remove categories, expand and collapse categorie
2825 	toolbar = gtk_toolbar_new();
2826 	gtk_toolbar_set_icon_size (GTK_TOOLBAR(toolbar), GTK_ICON_SIZE_MENU);
2827 	gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);
2828 	gtk_box_pack_start (GTK_BOX (vbox), toolbar, FALSE, FALSE, 0);
2829 
2830 	gtk_style_context_add_class (gtk_widget_get_style_context (toolbar), GTK_STYLE_CLASS_INLINE_TOOLBAR);
2831 
2832 	hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
2833 
2834 	toolitem = gtk_tool_item_new();
2835 	gtk_container_add (GTK_CONTAINER(toolitem), hbox);
2836 	gtk_toolbar_insert(GTK_TOOLBAR(toolbar), GTK_TOOL_ITEM(toolitem), -1);
2837 
2838 #if HB_BUD_TABVIEW_EDIT_ENABLE
2839 	// Add / Remove / Merge
2840 	widget = make_image_button(ICONNAME_LIST_ADD, _("Add category"));
2841 	data->BT_category_add = widget;
2842 	gtk_widget_set_sensitive(widget, FALSE);
2843 	gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
2844 
2845 	widget = make_image_button(ICONNAME_LIST_DELETE, _("Remove category"));
2846 	data->BT_category_delete = widget;
2847 	gtk_widget_set_sensitive(widget, FALSE);
2848 	gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
2849 
2850 	widget = gtk_button_new_with_label (_("Merge"));
2851 	data->BT_category_merge = widget;
2852 	gtk_widget_set_sensitive(widget, FALSE);
2853 	gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
2854 
2855 	// Separator
2856 	toolitem = gtk_separator_tool_item_new ();
2857 	gtk_tool_item_set_expand (toolitem, FALSE);
2858 	gtk_separator_tool_item_set_draw(GTK_SEPARATOR_TOOL_ITEM(toolitem), FALSE);
2859 	gtk_toolbar_insert(GTK_TOOLBAR(toolbar), GTK_TOOL_ITEM(toolitem), -1);
2860 #endif
2861 
2862 	// Clear Input
2863 	toolitem = gtk_tool_item_new();
2864 	gtk_tool_item_set_expand (toolitem, FALSE);
2865 	gtk_toolbar_insert(GTK_TOOLBAR(toolbar), GTK_TOOL_ITEM(toolitem), -1);
2866 
2867 	widget = gtk_button_new_with_label (_("Clear input"));
2868 	data->BT_category_reset = widget;
2869 	gtk_widget_set_sensitive(widget, FALSE);
2870 	gtk_container_add (GTK_CONTAINER(toolitem), widget);
2871 
2872 	// Separator
2873 	toolitem = gtk_separator_tool_item_new ();
2874 	gtk_tool_item_set_expand (toolitem, FALSE);
2875 	gtk_separator_tool_item_set_draw(GTK_SEPARATOR_TOOL_ITEM(toolitem), FALSE);
2876 	gtk_toolbar_insert(GTK_TOOLBAR(toolbar), GTK_TOOL_ITEM(toolitem), -1);
2877 
2878 	// Force monitoring
2879 	toolitem = gtk_tool_item_new();
2880 	gtk_tool_item_set_expand (toolitem, FALSE);
2881 	gtk_toolbar_insert(GTK_TOOLBAR(toolbar), GTK_TOOL_ITEM(toolitem), -1);
2882 
2883 	widget = gtk_check_button_new_with_mnemonic (_("_Force monitoring this category"));
2884 	data->BT_category_force_monitoring = widget;
2885 	gtk_widget_set_sensitive (widget, FALSE);
2886 	gtk_container_add (GTK_CONTAINER(toolitem), widget);
2887 	g_object_set(widget,
2888 		"draw-indicator", TRUE,
2889 		NULL);
2890 
2891 	// Separator
2892 	toolitem = gtk_separator_tool_item_new ();
2893 	gtk_tool_item_set_expand (toolitem, TRUE);
2894 	gtk_separator_tool_item_set_draw(GTK_SEPARATOR_TOOL_ITEM(toolitem), FALSE);
2895 	gtk_toolbar_insert(GTK_TOOLBAR(toolbar), GTK_TOOL_ITEM(toolitem), -1);
2896 
2897 	// Expand / Collapse
2898 	hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
2899 
2900 	toolitem = gtk_tool_item_new();
2901 	gtk_container_add (GTK_CONTAINER(toolitem), hbox);
2902 	gtk_toolbar_insert(GTK_TOOLBAR(toolbar), GTK_TOOL_ITEM(toolitem), -1);
2903 
2904 	widget = make_image_button(ICONNAME_HB_BUTTON_EXPAND, _("Expand all"));
2905 	data->BT_expand = widget;
2906 	gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
2907 
2908 	widget = make_image_button(ICONNAME_HB_BUTTON_COLLAPSE, _("Collapse all"));
2909 	data->BT_collapse = widget;
2910 	gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
2911 
2912 	/* signal connect */
2913 
2914 	// connect every radio button to the toggled signal to correctly update the view
2915 	for (int i=0; UI_BUD_TABVIEW_VIEW_MODE[i] != NULL; i++)
2916 	{
2917 		widget = hbtk_radio_button_get_nth (GTK_CONTAINER(radiomode), i);
2918 
2919 		if (widget)
2920 		{
2921 			g_signal_connect (widget, "toggled", G_CALLBACK (ui_bud_tabview_view_update_mode), (gpointer)data);
2922 		}
2923 	}
2924 
2925 	// Connect to key press to handle some events like Control-f
2926 	gtk_tree_view_set_search_entry(GTK_TREE_VIEW(treeview), GTK_ENTRY(search_entry));
2927 	g_signal_connect (dialog, "key-press-event", G_CALLBACK (ui_bud_tabview_on_key_press), (gpointer)data);
2928 
2929 
2930 	// Tree View
2931 	g_signal_connect (data->TV_selection, "changed", G_CALLBACK(ui_bud_tabview_view_on_select), (gpointer)data);
2932 
2933 
2934 	// toolbar buttons
2935 #if HB_BUD_TABVIEW_EDIT_ENABLE
2936 	g_signal_connect (data->BT_category_add, "clicked", G_CALLBACK(ui_bud_tabview_category_add), (gpointer)data);
2937 	g_signal_connect (data->BT_category_delete, "clicked", G_CALLBACK (ui_bud_tabview_category_delete), (gpointer)data);
2938 	g_signal_connect (data->BT_category_merge, "clicked", G_CALLBACK (ui_bud_tabview_category_merge), (gpointer)data);
2939 #endif
2940 	g_signal_connect (data->BT_category_reset, "clicked", G_CALLBACK (ui_bud_tabview_category_reset), (gpointer)data);
2941 	data->HID_category_monitoring_toggle = g_signal_connect (data->BT_category_force_monitoring, "toggled", G_CALLBACK (ui_bud_tabview_category_toggle_monitoring), (gpointer)data);
2942 	g_signal_connect (data->BT_expand, "clicked", G_CALLBACK (ui_bud_tabview_view_expand), (gpointer)data);
2943 	g_signal_connect (data->BT_collapse, "clicked", G_CALLBACK (ui_bud_tabview_view_collapse), (gpointer)data);
2944 
2945 	// dialog
2946 	g_signal_connect (dialog, "destroy", G_CALLBACK (gtk_widget_destroyed), &dialog);
2947 
2948 	// tree model to map HomeBank categories to the tree view
2949 	model = ui_bud_tabview_model_new();
2950 
2951 	filter = gtk_tree_model_filter_new(model, NULL);
2952 	gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(filter), ui_bud_tabview_model_row_filter, data, NULL);
2953 	gtk_tree_view_set_model(GTK_TREE_VIEW(treeview), filter);
2954 
2955 	g_object_unref(model); // Remove model with filter
2956 	g_object_unref(filter); // Remove filter with view
2957 
2958 	// By default, show the balance mode with all categories expanded
2959 	data->TV_is_expanded = TRUE;
2960 	ui_bud_tabview_view_toggle((gpointer) data, UI_BUD_TABVIEW_VIEW_SUMMARY);
2961 
2962 	gtk_widget_show_all (dialog);
2963 
2964 	response = gtk_dialog_run (GTK_DIALOG (dialog));
2965 
2966 	ui_bud_tabview_dialog_close(data, response);
2967 	gtk_widget_destroy (dialog);
2968 
2969 	g_free(data);
2970 
2971 	return NULL;
2972 }
2973 
2974