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