GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
gnc-budget-view.c
1 /********************************************************************
2  * gnc-budget_view.c -- Budget display widget *
3  * *
4  * Copyright (C) 2013, Phil Longstaff <[email protected]> *
5  * *
6  * This program is free software; you can redistribute it and/or *
7  * modify it under the terms of the GNU General Public License as *
8  * published by the Free Software Foundation; either version 2 of *
9  * the License, or (at your option) any later version. *
10  * *
11  * This program 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, contact: *
18  * *
19  * Free Software Foundation Voice: +1-617-542-5942 *
20  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
21  * Boston, MA 02110-1301, USA [email protected] *
22  *******************************************************************/
23 
24 /*
25  * TODO:
26  *
27  * *) I'd like to be able to update the budget estimates on a per cell
28  * basis, instead of a whole row (account) at one time. But, that
29  * would require some major coding.
30  *
31  */
32 
33 #include "config.h"
34 
35 #include <gtk/gtk.h>
36 #ifdef __G_IR_SCANNER__
37 #undef __G_IR_SCANNER__
38 #endif
39 #include <gdk/gdkkeysyms.h>
40 #include <glib/gi18n.h>
41 #include "gnc-date-edit.h"
42 
43 #include "gnc-budget-view.h"
44 #include "gnc-budget.h"
45 
46 #include "dialog-options.h"
47 #include "dialog-utils.h"
48 #include "gnc-gnome-utils.h"
49 #include "gnc-gobject-utils.h"
50 #include "gnc-icons.h"
51 
52 #include "gnc-session.h"
53 #include "gnc-tree-view-account.h"
54 #include "gnc-ui.h"
55 #include "gnc-ui-util.h"
56 #include "option-util.h"
57 #include "gnc-main-window.h"
58 #include "gnc-component-manager.h"
59 #include "gnc-state.h"
60 
61 #include "qof.h"
62 
63 #include "gnc-recurrence.h"
64 #include "Recurrence.h"
66 
67 
68 /* This static indicates the debugging module that this .o belongs to. */
69 static QofLogModule log_module = GNC_MOD_BUDGET;
70 
71 #define PLUGIN_PAGE_BUDGET_CM_CLASS "budget-view"
72 #define STATE_SECTION_PREFIX "Budget"
73 
75 
77 {
78  GtkVBox w;
79 };
80 
82 {
83  GtkVBoxClass w;
84 };
85 
86 enum
87 {
88  TOTALS_TYPE_INCOME,
89  TOTALS_TYPE_EXPENSES,
90  TOTALS_TYPE_TRANSFERS,
91  TOTALS_TYPE_TOTAL
92 };
93 
94 /************************************************************
95  * Prototypes *
96  ************************************************************/
97 /* Plugin Actions */
98 static void gnc_budget_view_class_init(GncBudgetViewClass *klass);
99 static void gnc_budget_view_init(GncBudgetView *budget_view);
100 static void gnc_budget_view_finalize(GObject *object);
101 
102 static void gbv_create_widget(GncBudgetView *view);
103 
104 static gboolean gbv_button_press_cb(
105  GtkWidget *widget, GdkEventButton *event, GncBudgetView *view);
106 static gboolean gbv_key_press_cb(
107  GtkWidget *treeview, GdkEventKey *event, gpointer userdata);
108 static void gbv_row_activated_cb(
109  GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *col,
110  GncBudgetView *view);
111 #if 0
112 static void gbv_selection_changed_cb(
113  GtkTreeSelection *selection, GncBudgetView *view);
114 #endif
115 static void gbv_treeview_resized_cb(GtkWidget* widget, GtkAllocation* allocation, GncBudgetView* view);
116 static gnc_numeric gbv_get_accumulated_budget_amount(GncBudget* budget,
117  Account* account, guint period_num);
118 
120 {
121  GtkTreeView *tree_view;
122  GtkTreeView *totals_tree_view;
123 
124  GncBudget* budget;
125  GncGUID key;
126 
127  GList *period_col_list;
128  GList *totals_col_list;
129  GtkTreeViewColumn* total_col;
131 
132  Account* income;
133  Account* expenses;
134  Account* assets;
135  Account* liabilities;
136 };
137 
138 #define GNC_BUDGET_VIEW_GET_PRIVATE(o) \
139  (G_TYPE_INSTANCE_GET_PRIVATE((o), GNC_TYPE_BUDGET_VIEW, GncBudgetViewPrivate))
140 
141 G_DEFINE_TYPE(GncBudgetView, gnc_budget_view, GTK_TYPE_VBOX)
142 
145 {
146  GncBudgetView *budget_view;
147  GncBudgetViewPrivate *priv;
148  gchar* label;
149  const GList *item;
150 
151  g_return_val_if_fail(GNC_IS_BUDGET(budget), NULL);
152  ENTER(" ");
153 
154  budget_view = g_object_new(GNC_TYPE_BUDGET_VIEW, NULL);
155 
156  priv = GNC_BUDGET_VIEW_GET_PRIVATE(budget_view);
157  priv->budget = budget;
158  priv->key = *gnc_budget_get_guid(budget);
159  priv->fd = fd;
160  priv->total_col = NULL;
161  gbv_create_widget(budget_view);
162 
163  LEAVE("new budget view %p", budget_view);
164  return budget_view;
165 }
166 
167 static void
168 gnc_budget_view_class_init(GncBudgetViewClass *klass)
169 {
170  GObjectClass *object_class = G_OBJECT_CLASS(klass);
171 
172  gnc_budget_view_parent_class = g_type_class_peek_parent(klass);
173 
174  object_class->finalize = gnc_budget_view_finalize;
175 
176  g_signal_new("account-activated", GNC_TYPE_BUDGET_VIEW, G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
177  G_TYPE_NONE, 1, GNC_TYPE_ACCOUNT);
178 
179  g_type_class_add_private(klass, sizeof(GncBudgetViewPrivate));
180 }
181 
182 
183 static void
184 gnc_budget_view_init(GncBudgetView *budget_view)
185 {
186  GncBudgetViewPrivate *priv;
187  Account* root;
188  gint num_top_accounts;
189  gint i;
190 
191  ENTER("view %p", budget_view);
192  priv = GNC_BUDGET_VIEW_GET_PRIVATE(budget_view);
193 
194  /* Keep track of the top level asset, liability, income and expense accounts */
195  root = gnc_book_get_root_account(gnc_get_current_book());
196  num_top_accounts = gnc_account_n_children(root);
197 
198  for (i = 0; i < num_top_accounts; ++i)
199  {
200  Account* acc = gnc_account_nth_child(root, i);
202 
203  if (type == ACCT_TYPE_ASSET)
204  {
205  priv->assets = acc;
206  }
207  else if (type == ACCT_TYPE_LIABILITY)
208  {
209  priv->liabilities = acc;
210  }
211  else if (type == ACCT_TYPE_INCOME)
212  {
213  priv->income = acc;
214  }
215  else if (type == ACCT_TYPE_EXPENSE)
216  {
217  priv->expenses = acc;
218  }
219  }
220 
221  LEAVE("");
222 }
223 
224 
225 static void
226 gnc_budget_view_finalize(GObject *object)
227 {
228  GncBudgetView *view;
229  GncBudgetViewPrivate *priv;
230 
231  ENTER("object %p", object);
232  view = GNC_BUDGET_VIEW(object);
233  g_return_if_fail(GNC_IS_BUDGET_VIEW(view));
234 
235  G_OBJECT_CLASS(gnc_budget_view_parent_class)->finalize(object);
236  LEAVE(" ");
237 }
238 
239 
240 GtkTreeSelection*
241 gnc_budget_view_get_selection(GncBudgetView* view)
242 {
243  GncBudgetViewPrivate *priv;
244 
245  g_return_val_if_fail(GNC_IS_BUDGET_VIEW(view), NULL);
246 
247  priv = GNC_BUDGET_VIEW_GET_PRIVATE(view);
248  return gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->tree_view));
249 }
250 
251 Account*
252 gnc_budget_view_get_account_from_path(GncBudgetView* view, GtkTreePath* path)
253 {
254  GncBudgetViewPrivate *priv;
255 
256  g_return_val_if_fail(GNC_IS_BUDGET_VIEW(view), NULL);
257 
258  priv = GNC_BUDGET_VIEW_GET_PRIVATE(view);
259  return gnc_tree_view_account_get_account_from_path(GNC_TREE_VIEW_ACCOUNT(priv->tree_view), path);
260 }
261 
262 GList*
263 gnc_budget_view_get_selected_accounts(GncBudgetView* view)
264 {
265  GncBudgetViewPrivate *priv;
266 
267  g_return_val_if_fail(GNC_IS_BUDGET_VIEW(view), NULL);
268 
269  priv = GNC_BUDGET_VIEW_GET_PRIVATE(view);
270  return gnc_tree_view_account_get_selected_accounts(GNC_TREE_VIEW_ACCOUNT(priv->tree_view));
271 }
272 
273 /****************************
274  * GncPluginPage Functions *
275  ***************************/
276 static void
277 gbv_create_widget(GncBudgetView *view)
278 {
279  GncBudgetViewPrivate* priv;
280  GtkTreeSelection *selection;
281  GtkTreeView *tree_view;
282  GtkWidget *scrolled_window;
283  GtkWidget *inner_scrolled_window;
284  GtkVBox* vbox;
285  GtkWidget* inner_vbox;
286  GtkListStore* totals_tree_model;
287  GtkTreeView* totals_tree_view;
288  GtkTreeViewColumn* totals_title_col;
289  GtkTreeIter iter;
290  GtkWidget* h_separator;
291  gchar *state_section;
292  gchar guidstr[GUID_ENCODING_LENGTH+1];
293 
294  priv = GNC_BUDGET_VIEW_GET_PRIVATE(view);
295  vbox = GTK_VBOX(view);
296 
297  gtk_widget_show(GTK_WIDGET(vbox));
298  gtk_box_set_homogeneous(GTK_BOX(vbox), FALSE);
299 
300  scrolled_window = gtk_scrolled_window_new(NULL, NULL);
301  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
302  GTK_POLICY_AUTOMATIC,
303  GTK_POLICY_NEVER);
304  gtk_widget_show(scrolled_window);
305  gtk_box_pack_start(GTK_BOX(vbox), scrolled_window, /*expand*/TRUE, /*fill*/TRUE, 0);
306 
307  inner_vbox = gtk_vbox_new(FALSE, 0);
308  gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window), GTK_WIDGET(inner_vbox));
309  gtk_widget_show(GTK_WIDGET(inner_vbox));
310 
311  inner_scrolled_window = gtk_scrolled_window_new(NULL, NULL);
312  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(inner_scrolled_window),
313  GTK_POLICY_NEVER,
314  GTK_POLICY_AUTOMATIC);
315  gtk_widget_show(inner_scrolled_window);
316  tree_view = gnc_tree_view_account_new(FALSE);
317  gtk_container_add(GTK_CONTAINER(inner_scrolled_window), GTK_WIDGET(tree_view));
318 
319  guid_to_string_buff(&priv->key, guidstr);
320  state_section = g_strjoin(" ", STATE_SECTION_PREFIX, guidstr, NULL);
321  g_object_set(G_OBJECT(tree_view), "state-section", state_section, NULL);
322  g_free (state_section);
323 
324  gnc_tree_view_configure_columns(GNC_TREE_VIEW(tree_view));
325  priv->tree_view = tree_view;
326  selection = gtk_tree_view_get_selection(tree_view);
327  gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
328 
329  g_signal_connect(G_OBJECT(tree_view), "row-activated",
330  G_CALLBACK(gbv_row_activated_cb), view);
331  g_signal_connect(G_OBJECT(tree_view), "size-allocate",
332  G_CALLBACK(gbv_treeview_resized_cb), view);
333 
334 #if 0
335  g_signal_connect(G_OBJECT(selection), "changed",
336  G_CALLBACK(gbv_selection_changed_cb), view);
337  g_signal_connect(G_OBJECT(tree_view), "button-press-event",
338  G_CALLBACK(gbv_button_press_cb), view);
339  g_signal_connect_after(G_OBJECT(tree_view), "key-press-event",
340  G_CALLBACK(gbv_key_press_cb), NULL);
341 
342  gbv_selection_changed_cb(NULL, view);
343 #endif
344  gtk_tree_view_set_headers_visible(tree_view, TRUE);
345  gtk_widget_show(GTK_WIDGET(tree_view));
346  gtk_box_pack_start(GTK_BOX(inner_vbox), GTK_WIDGET(inner_scrolled_window), /*expand*/TRUE, /*fill*/TRUE, 0);
347  priv->fd->tree_view = GNC_TREE_VIEW_ACCOUNT(priv->tree_view);
349  GNC_TREE_VIEW_ACCOUNT(tree_view),
351  priv->fd, NULL);
352 
353  totals_tree_model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
354  gtk_list_store_append(totals_tree_model, &iter);
355  gtk_list_store_set(totals_tree_model, &iter, 0, _("Income"), 1, TOTALS_TYPE_INCOME, -1);
356  gtk_list_store_append(totals_tree_model, &iter);
357  gtk_list_store_set(totals_tree_model, &iter, 0, _("Expenses"), 1, TOTALS_TYPE_EXPENSES, -1);
358  gtk_list_store_append(totals_tree_model, &iter);
359  gtk_list_store_set(totals_tree_model, &iter, 0, _("Transfers"), 1, TOTALS_TYPE_TRANSFERS, -1);
360  gtk_list_store_append(totals_tree_model, &iter);
361  gtk_list_store_set(totals_tree_model, &iter, 0, _("Total"), 1, TOTALS_TYPE_TOTAL, -1);
362 
363  totals_tree_view = GTK_TREE_VIEW(gtk_tree_view_new());
364  priv->totals_tree_view = totals_tree_view;
365 
366  gtk_widget_show(GTK_WIDGET(totals_tree_view));
367  gtk_tree_selection_set_mode(gtk_tree_view_get_selection(totals_tree_view),
368  GTK_SELECTION_NONE);
369  gtk_tree_view_set_headers_visible(totals_tree_view, FALSE);
370  gtk_tree_view_set_model(totals_tree_view, GTK_TREE_MODEL(totals_tree_model));
371 
372  totals_title_col = gtk_tree_view_column_new_with_attributes("", gtk_cell_renderer_text_new(), "text", 0, NULL);
373  gtk_tree_view_column_set_expand(totals_title_col, TRUE);
374  gtk_tree_view_column_set_sizing(totals_title_col, GTK_TREE_VIEW_COLUMN_FIXED);
375  gtk_tree_view_append_column(totals_tree_view, totals_title_col);
376 
377  gtk_box_pack_end(GTK_BOX(inner_vbox), GTK_WIDGET(totals_tree_view), /*expand*/FALSE, /*fill*/TRUE, 0);
378 
379  h_separator = gtk_hseparator_new();
380  gtk_widget_show(h_separator);
381  gtk_box_pack_end(GTK_BOX(inner_vbox), h_separator, /*expand*/FALSE, /*fill*/TRUE, 0);
382 
383  gnc_budget_view_refresh(view);
384 }
385 
386 
387 #define BUDGET_GUID "Budget GncGUID"
388 
389 /***********************************************************************
390  * Save enough information about this view that it can *
391  * be recreated next time the user starts gnucash. *
392  * *
393  * @param view The view to save. *
394  * *
395  * @param key_file A pointer to the GKeyFile data structure where the *
396  * page information should be written. *
397  * *
398  * @param group_name The group name to use when saving data. *
399  **********************************************************************/
400 void
401 gnc_budget_view_save(GncBudgetView *view, GKeyFile *key_file, const gchar *group_name)
402 {
403  GncBudgetViewPrivate *priv;
404  char guid_str[GUID_ENCODING_LENGTH+1];
405 
406  g_return_if_fail(view != NULL);
407  g_return_if_fail(key_file != NULL);
408  g_return_if_fail(group_name != NULL);
409 
410  ENTER("view %p, key_file %p, group_name %s", view, key_file, group_name);
411 
412  priv = GNC_BUDGET_VIEW_GET_PRIVATE(view);
413 
414  //FIXME
415  gnc_tree_view_account_save(GNC_TREE_VIEW_ACCOUNT(priv->tree_view),
416  priv->fd, key_file, group_name);
417  LEAVE(" ");
418 }
419 
420 
421 /***********************************************************************
422  * Create a new plugin page based on the information saved
423  * during a previous instantiation of gnucash.
424  *
425  * @param view The budget view to be restored
426  *
427  * @param key_file A pointer to the GKeyFile data structure where the
428  * page information should be read.
429  *
430  * @param group_name The group name to use when restoring data.
431  *
432  * @return TRUE if successful, FALSE if unsuccessful
433  **********************************************************************/
434 gboolean
435 gnc_budget_view_restore(GncBudgetView* view, GKeyFile *key_file, const gchar *group_name)
436 {
437  GncBudgetViewPrivate *priv;
438  GError *error = NULL;
439  char *guid_str;
440  GncGUID guid;
441  GncBudget *bgt;
442  QofBook *book;
443 
444  g_return_val_if_fail(key_file, FALSE);
445  g_return_val_if_fail(group_name, FALSE);
446 
447  ENTER("key_file %p, group_name %s", key_file, group_name);
448 
449  guid_str = g_key_file_get_string(key_file, group_name, BUDGET_GUID,
450  &error);
451  if (error)
452  {
453  g_warning("error reading group %s key %s: %s",
454  group_name, BUDGET_GUID, error->message);
455  g_error_free(error);
456  error = NULL;
457  return FALSE;
458  }
459  if (!string_to_guid(guid_str, &guid))
460  {
461  return FALSE;
462  }
463 
464  book = qof_session_get_book(gnc_get_current_session());
465  bgt = gnc_budget_lookup(&guid, book);
466  if (!bgt)
467  {
468  return FALSE;
469  }
470 
471  /* Create the new view */
472  priv = GNC_BUDGET_VIEW_GET_PRIVATE(view);
473 
474  //FIXME
475  gnc_tree_view_account_restore(GNC_TREE_VIEW_ACCOUNT(priv->tree_view),
476  priv->fd, key_file, group_name);
477  LEAVE(" ");
478 
479  return TRUE;
480 }
481 
482 /***********************************************************************
483  * The budget associated with this view is about to be removed from *
484  * the book. So drop any saved state we still have. *
485  * *
486  * @param view The view to which the budget is associated. *
487  **********************************************************************/
488 void
489 gnc_budget_view_delete_budget(GncBudgetView *view)
490 {
491  GncBudgetViewPrivate *priv;
492  gchar guidstr[GUID_ENCODING_LENGTH+1];
493 
494  g_return_if_fail(view != NULL);
495 
496  ENTER("view %p", view);
497 
498  priv = GNC_BUDGET_VIEW_GET_PRIVATE (view);
499 
500  guid_to_string_buff(&priv->key, guidstr);
501  gnc_state_drop_sections_for (guidstr);
502  g_object_set (G_OBJECT (priv->tree_view), "state-section", NULL, NULL);
503 
504  LEAVE(" ");
505 }
506 
507 
508 
509 #if 0
510 /***********************************************************************
511  * This button press handler calls the common button press handler
512  * for all pages. The GtkTreeView eats all button presses and
513  * doesn't pass them up the widget tree, even when it doesn't do
514  * anything with them. The only way to get access to the button
515  * presses in an account tree page is here on the tree view widget.
516  * Button presses on all other pages are caught by the signal
517  * registered in gnc-main-window.c.
518  **********************************************************************/
519 static gboolean
520 gbv_button_press_cb(GtkWidget *widget, GdkEventButton *event,
521  GncBudgetView *view)
522 {
523  gboolean result;
524 
525  g_return_val_if_fail(view != NULL, FALSE);
526 
527  ENTER("widget %p, event %p, page %p", widget, event, page);
528  result = gnc_main_window_button_press_cb(widget, event, page);
529  LEAVE(" ");
530  return result;
531 }
532 #endif
533 
534 static gboolean
535 gbv_key_press_cb(GtkWidget *treeview, GdkEventKey *event, gpointer userdata)
536 {
537  GtkTreeView *tv = GTK_TREE_VIEW(treeview);
538  GtkTreeViewColumn *col;
539  GtkTreePath *path = NULL;
540 
541  if (event->type != GDK_KEY_PRESS) return TRUE;
542 
543  switch (event->keyval)
544  {
545  case GDK_Tab:
546  case GDK_ISO_Left_Tab:
547  case GDK_KP_Tab:
548  case GDK_Return:
549  case GDK_KP_Enter:
550  gtk_tree_view_get_cursor(tv, &path, &col);
551  if (!path) return TRUE;
552  //finish_edit(col);
553  break;
554  default:
555  return TRUE;
556  }
557  gnc_tree_view_keynav(GNC_TREE_VIEW(tv), &col, path, event);
558 
559  if (path && gnc_tree_view_path_is_valid(GNC_TREE_VIEW(tv), path))
560  gtk_tree_view_set_cursor(tv, path, col, TRUE);
561  return TRUE;
562 }
563 
564 static void
565 gbv_treeview_resized_cb(GtkWidget* widget, GtkAllocation* allocation, GncBudgetView* view)
566 {
567  gint ncols;
568  GncBudgetViewPrivate* priv;
569  gint i;
570  gint j;
571  GList *columns;
572 
573  ENTER("");
574  priv = GNC_BUDGET_VIEW_GET_PRIVATE(view);
575 
576  /* There's no easy way to get this number. */
577  columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(priv->tree_view));
578  ncols = g_list_length(columns);
579  g_list_free(columns);
580  /* i is the column we are examining
581  * j is the corresponding column in totals_tree_view */
582  for (i = 0, j = 0; i < ncols; ++i)
583  {
584  gint col_width;
585  GtkTreeViewColumn* tree_view_col;
586  GtkTreeViewColumn* totals_view_col;
587 
588  tree_view_col = gtk_tree_view_get_column(priv->tree_view, i);
589 
590  if (gtk_tree_view_column_get_visible(tree_view_col))
591  {
592  col_width = gtk_tree_view_column_get_width(tree_view_col);
593  totals_view_col = gtk_tree_view_get_column(priv->totals_tree_view, j);
594  gtk_tree_view_column_set_fixed_width(totals_view_col, col_width);
595  j++;
596  }
597  }
598  LEAVE("");
599 }
600 
601 static void
602 gbv_row_activated_cb(GtkTreeView *treeview, GtkTreePath *path,
603  GtkTreeViewColumn *col, GncBudgetView *view)
604 {
605  GtkWidget *window;
606  GncPluginPage *new_page;
607  Account *account;
608 
609  g_return_if_fail(GNC_IS_BUDGET_VIEW(view));
611  GNC_TREE_VIEW_ACCOUNT(treeview), path);
612  if (account == NULL)
613  {
614  return;
615  }
616 
617  g_signal_emit_by_name(view, "account-activated", account);
618 }
619 
620 
621 #if 0
622 static void
623 gbv_selection_changed_cb(GtkTreeSelection *selection, GncBudgetView *view)
624 {
625  GtkTreeView *tree_view;
626  GList *acct_list;
627  gboolean sensitive;
628 
629  if (!selection)
630  {
631  sensitive = FALSE;
632  }
633  else
634  {
635  g_return_if_fail(GTK_IS_TREE_SELECTION(selection));
636  tree_view = gtk_tree_selection_get_tree_view (selection);
638  GNC_TREE_VIEW_ACCOUNT(tree_view));
639 
640  /* Check here for placeholder accounts, etc. */
641  sensitive = (g_list_length(acct_list) > 0);
642  g_list_free(acct_list);
643  }
644 }
645 #endif
646 
647 typedef struct
648 {
649  gnc_numeric total;
650  GncBudget* budget;
651  guint period_num;
653 
654 static void
655 budget_accum_helper(Account* account, gpointer data)
656 {
659 
660  if (gnc_budget_is_account_period_value_set(info->budget, account, info->period_num))
661  {
662  numeric = gnc_budget_get_account_period_value(info->budget, account, info->period_num);
663  info->total = gnc_numeric_add(info->total, numeric, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
664  }
665  else if (gnc_account_n_children(account) != 0)
666  {
667  numeric = gbv_get_accumulated_budget_amount(info->budget, account, info->period_num);
668  info->total = gnc_numeric_add(info->total, numeric, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
669  }
670 }
671 
672 static gnc_numeric
673 gbv_get_accumulated_budget_amount(GncBudget* budget, Account* account, guint period_num)
674 {
676 
677  info.total = gnc_numeric_zero();
678  info.budget = budget;
679  info.period_num = period_num;
680  gnc_account_foreach_child(account, budget_accum_helper, &info);
681 
682  return info.total;
683 }
684 
685 /* Displays budget amount for a period for an account. If a budget
686  amount is set, it is displayed in black. If no budget amount is
687  set and the account has children, the total of the children's
688  budget amounts (if any) is displayed in dark grey.
689 */
690 static gchar *
691 budget_col_source(Account *account, GtkTreeViewColumn *col,
692  GtkCellRenderer *cell)
693 {
694  GncBudget *budget;
695  guint period_num;
696  gnc_numeric numeric;
697  gchar amtbuff[100]; //FIXME: overkill, where's the #define?
698 
699  budget = GNC_BUDGET(g_object_get_data(G_OBJECT(col), "budget"));
700  period_num = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(col),
701  "period_num"));
702 
703  if (!gnc_budget_is_account_period_value_set(budget, account, period_num))
704  {
705  if (gnc_account_n_children(account) == 0)
706  {
707  amtbuff[0] = '\0';
708  }
709  else
710  {
711  numeric = gbv_get_accumulated_budget_amount(budget, account, period_num);
712  xaccSPrintAmount(amtbuff, numeric,
713  gnc_account_print_info(account, FALSE));
714  g_object_set(cell, "foreground", "dark gray", NULL);
715  }
716  }
717  else
718  {
719  numeric = gnc_budget_get_account_period_value(budget, account,
720  period_num);
721  if (gnc_numeric_check(numeric))
722  {
723  strcpy(amtbuff, "error");
724  }
725  else
726  {
727  xaccSPrintAmount(amtbuff, numeric,
728  gnc_account_print_info(account, FALSE));
729  g_object_set(cell, "foreground", "black", NULL);
730  }
731  }
732  return g_strdup(amtbuff);
733 }
734 
735 static gnc_numeric
736 bgv_get_total_for_account(Account* account, GncBudget* budget)
737 {
738  guint num_periods;
739  int period_num;
740  gnc_numeric numeric;
741  gnc_numeric total = gnc_numeric_zero();
742 
743  num_periods = gnc_budget_get_num_periods(budget);
744  for (period_num = 0; period_num < num_periods; ++period_num)
745  {
746  if (!gnc_budget_is_account_period_value_set(budget, account, period_num))
747  {
748  if (gnc_account_n_children(account) != 0)
749  {
750  numeric = gbv_get_accumulated_budget_amount(budget, account, period_num);
751  total = gnc_numeric_add(total, numeric, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
752  }
753  }
754  else
755  {
756  numeric = gnc_budget_get_account_period_value(budget, account, period_num);
757  if (!gnc_numeric_check(numeric))
758  {
759  total = gnc_numeric_add(total, numeric, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
760  }
761  }
762  }
763 
764  return total;
765 }
766 
767 static gchar *
768 budget_total_col_source(Account *account, GtkTreeViewColumn *col,
769  GtkCellRenderer *cell)
770 {
771  GncBudget *budget;
772  gnc_numeric total = gnc_numeric_zero();
773  gchar amtbuff[100]; //FIXME: overkill, where's the #define?
774 
775  budget = GNC_BUDGET(g_object_get_data(G_OBJECT(col), "budget"));
776  total = bgv_get_total_for_account(account, budget);
777  xaccSPrintAmount(amtbuff, total,
778  gnc_account_print_info(account, FALSE));
779  return g_strdup(amtbuff);
780 }
781 
782 static void
783 budget_col_edited(Account *account, GtkTreeViewColumn *col,
784  const gchar *new_text)
785 {
786  GncBudget *budget;
787  guint period_num;
789 
790  if (!xaccParseAmount(new_text, TRUE, &numeric, NULL) &&
791  !(new_text && *new_text == '\0'))
792  return;
793 
794  period_num = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(col),
795  "period_num"));
796 
797  budget = GNC_BUDGET(g_object_get_data(G_OBJECT(col), "budget"));
798 
799  if (new_text && *new_text == '\0')
800  gnc_budget_unset_account_period_value(budget, account, period_num);
801  else
802  gnc_budget_set_account_period_value(budget, account, period_num,
803  numeric);
804 }
805 
806 
807 static void
808 totals_col_source(GtkTreeViewColumn *col, GtkCellRenderer *cell,
809  GtkTreeModel *s_model, GtkTreeIter *s_iter,
810  gpointer user_data)
811 {
812  GncBudgetView* view;
813  GncBudgetViewPrivate* priv;
814  gint row_type;
815  GncBudget *budget;
816  gint period_num;
817  gnc_numeric value;
818  gchar amtbuff[100]; //FIXME: overkill, where's the #define?
819  gint width;
820 
821  view = GNC_BUDGET_VIEW(user_data);
822  priv = GNC_BUDGET_VIEW_GET_PRIVATE(view);
823 
824  gtk_tree_model_get(s_model, s_iter, 1, &row_type, -1);
825  budget = GNC_BUDGET(g_object_get_data(G_OBJECT(col), "budget"));
826  period_num = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(col),
827  "period_num"));
828 
829  if (row_type == TOTALS_TYPE_INCOME)
830  {
831  if (period_num >= 0)
832  {
833  value = gbv_get_accumulated_budget_amount(budget, priv->income, period_num);
834  }
835  else
836  {
837  value = bgv_get_total_for_account(priv->income, budget);
838  }
839  xaccSPrintAmount(amtbuff, value,
840  gnc_account_print_info(priv->income, FALSE));
841  g_object_set(cell, "foreground", "black", NULL);
842  }
843  else if (row_type == TOTALS_TYPE_EXPENSES)
844  {
845  if (period_num >= 0)
846  {
847  value = gbv_get_accumulated_budget_amount(budget, priv->expenses, period_num);
848  }
849  else
850  {
851  value = bgv_get_total_for_account(priv->expenses, budget);
852  }
853  xaccSPrintAmount(amtbuff, value,
854  gnc_account_print_info(priv->expenses, FALSE));
855  g_object_set(cell, "foreground", "black", NULL);
856  }
857  else if (row_type == TOTALS_TYPE_TRANSFERS)
858  {
859  gnc_numeric assets;
860  gnc_numeric liabilities;
861 
862  if (period_num >= 0)
863  {
864  assets = gbv_get_accumulated_budget_amount(budget, priv->assets, period_num);
865  liabilities = gbv_get_accumulated_budget_amount(budget, priv->liabilities, period_num);
866  }
867  else
868  {
869  assets = bgv_get_total_for_account(priv->assets, budget);
870  liabilities = bgv_get_total_for_account(priv->liabilities, budget);
871  }
872  value = gnc_numeric_sub(assets, liabilities, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
873  xaccSPrintAmount(amtbuff, value,
874  gnc_account_print_info(priv->assets, FALSE));
875  g_object_set(cell, "foreground", "black", NULL);
876  }
877  else if (row_type == TOTALS_TYPE_TOTAL)
878  {
879  gnc_numeric income;
880  gnc_numeric expenses;
881  gnc_numeric assets;
882  gnc_numeric liabilities;
883 
884  if (period_num >= 0)
885  {
886  income = gbv_get_accumulated_budget_amount(budget, priv->income, period_num);
887  expenses = gbv_get_accumulated_budget_amount(budget, priv->expenses, period_num);
888  assets = gbv_get_accumulated_budget_amount(budget, priv->assets, period_num);
889  liabilities = gbv_get_accumulated_budget_amount(budget, priv->liabilities, period_num);
890  }
891  else
892  {
893  income = bgv_get_total_for_account(priv->income, budget);
894  expenses = bgv_get_total_for_account(priv->expenses, budget);
895  assets = bgv_get_total_for_account(priv->assets, budget);
896  liabilities = bgv_get_total_for_account(priv->liabilities, budget);
897  }
898  value = gnc_numeric_sub(income, expenses, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
899  value = gnc_numeric_sub(value, assets, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
900  value = gnc_numeric_add(value, liabilities, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
901  xaccSPrintAmount(amtbuff, value,
902  gnc_account_print_info(priv->assets, FALSE));
903  if (gnc_numeric_negative_p(value))
904  {
905  g_object_set(cell, "foreground", "red", NULL);
906  }
907  else
908  {
909  g_object_set(cell, "foreground", "black", NULL);
910  }
911  }
912  else
913  {
914  g_strlcpy(amtbuff, "error", sizeof(amtbuff));
915  }
916 
917  g_object_set(G_OBJECT(cell), "text", amtbuff, "xalign", 1.0, NULL);
918 }
919 
920 static void
921 gbv_refresh_col_titles(GncBudgetView *view)
922 {
923  GncBudgetViewPrivate *priv;
924  const Recurrence *r;
925  GDate date, nextdate;
926  GtkTreeViewColumn *col;
927  guint titlelen;
928  gint num_periods_visible;
929  gchar title[MAX_DATE_LENGTH];
930  GList *col_list;
931  gint i;
932 
933  g_return_if_fail(view != NULL);
934  priv = GNC_BUDGET_VIEW_GET_PRIVATE(view);
935 
936  col_list = priv->period_col_list;
937  num_periods_visible = g_list_length(col_list);
938 
939  /* Show the dates in column titles */
940  r = gnc_budget_get_recurrence(priv->budget);
941  date = r->start;
942  for (i = 0; i < num_periods_visible; i++)
943  {
944  col = GTK_TREE_VIEW_COLUMN(g_list_nth_data(col_list, i));
945  titlelen = qof_print_gdate(title, MAX_DATE_LENGTH, &date);
946  if (titlelen > 0)
947  {
948  gtk_tree_view_column_set_title(col, title);
949  }
950  recurrenceNextInstance(r, &date, &nextdate);
951  date = nextdate;
952  }
953 }
954 
955 
956 static GtkTreeViewColumn*
957 gbv_create_totals_column(GncBudgetView* view, gint period_num)
958 {
959  GncBudgetViewPrivate *priv;
960  GtkTreeViewColumn *col;
961  GtkCellRenderer* renderer;
962 
963  g_return_val_if_fail(view != NULL, NULL);
964  priv = GNC_BUDGET_VIEW_GET_PRIVATE(view);
965 
966  renderer = gtk_cell_renderer_text_new();
967  col = gtk_tree_view_column_new_with_attributes("", renderer, NULL);
968 
969  gtk_tree_view_column_set_cell_data_func(col, renderer, totals_col_source, view, NULL);
970  g_object_set_data(G_OBJECT(col), "budget", priv->budget);
971  g_object_set_data(G_OBJECT(col), "period_num", GUINT_TO_POINTER(period_num));
972  gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
973 
974  return col;
975 }
976 
977 
978 static void
979 gbv_col_edited_cb(GtkCellRendererText* cell, gchar* path_string, gchar* new_text, gpointer user_data)
980 {
981  GncBudgetView *view;
982  GncBudgetViewPrivate *priv;
983  const EventInfo* ei;
984 
985  view = GNC_BUDGET_VIEW(user_data);
986  priv = GNC_BUDGET_VIEW_GET_PRIVATE(view);
987 
988  gtk_widget_queue_draw(GTK_WIDGET(priv->totals_tree_view));
989 }
990 
991 void
992 gnc_budget_view_refresh(GncBudgetView *view)
993 {
994  GncBudgetViewPrivate *priv;
995  gint num_periods;
996  gint num_periods_visible;
997  GtkTreeViewColumn *col;
998  GList *col_list;
999  GList *totals_col_list;
1000  ENTER("view %p", view);
1001 
1002  g_return_if_fail(view != NULL);
1003  priv = GNC_BUDGET_VIEW_GET_PRIVATE(view);
1004 
1005  num_periods = gnc_budget_get_num_periods(priv->budget);
1006  col_list = priv->period_col_list;
1007  totals_col_list = priv->totals_col_list;
1008  num_periods_visible = g_list_length(col_list);
1009 
1010  /* Hide any unneeded extra columns */
1011  while (num_periods_visible > num_periods)
1012  {
1013  col = GTK_TREE_VIEW_COLUMN((g_list_last(col_list))->data);
1014  gtk_tree_view_remove_column(GTK_TREE_VIEW(priv->tree_view), col);
1015  col_list = g_list_delete_link(col_list, g_list_last(col_list));
1016  num_periods_visible = g_list_length(col_list);
1017 
1018  col = GTK_TREE_VIEW_COLUMN((g_list_last(totals_col_list))->data);
1019  gtk_tree_view_remove_column(GTK_TREE_VIEW(priv->totals_tree_view), col);
1020  totals_col_list = g_list_delete_link(totals_col_list, g_list_last(totals_col_list));
1021  }
1022 
1023  gnc_tree_view_configure_columns(GNC_TREE_VIEW(priv->tree_view));
1024 
1025  /* If we're creating new columns to be appended to already existing
1026  * columns, first delete the total column. (Then regenerate after
1027  * new columns have been appended */
1028  if (num_periods_visible != 0 && num_periods > num_periods_visible)
1029  {
1030  /* Delete the totals column */
1031  col = priv->total_col;
1032  gtk_tree_view_remove_column(GTK_TREE_VIEW(priv->tree_view), col);
1033  priv->total_col = NULL;
1034  col = gtk_tree_view_get_column(GTK_TREE_VIEW(priv->totals_tree_view), num_periods_visible+1);
1035  gtk_tree_view_remove_column(GTK_TREE_VIEW(priv->totals_tree_view), col);
1036  }
1037 
1038  /* Create any needed columns */
1039  while (num_periods_visible < num_periods)
1040  {
1041  GList* renderer_list;
1042  GList* renderer_node;
1043 
1045  GNC_TREE_VIEW_ACCOUNT(priv->tree_view), "",
1046  budget_col_source, budget_col_edited);
1047  g_object_set_data(G_OBJECT(col), "budget", priv->budget);
1048  g_object_set_data(G_OBJECT(col), "period_num",
1049  GUINT_TO_POINTER(num_periods_visible));
1050  col_list = g_list_append(col_list, col);
1051 
1052  renderer_list = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(col));
1053  for (renderer_node = renderer_list; renderer_node != NULL; renderer_node = g_list_next(renderer_node))
1054  {
1055  GtkCellRenderer* renderer = GTK_CELL_RENDERER(renderer_node->data);
1056  g_signal_connect(G_OBJECT(renderer), "edited", (GCallback)gbv_col_edited_cb, view);
1057  }
1058  g_list_free(renderer_list);
1059 
1060  col = gbv_create_totals_column(view, num_periods_visible);
1061  if (col != NULL)
1062  {
1063  gtk_tree_view_append_column(priv->totals_tree_view, col);
1064  totals_col_list = g_list_append(totals_col_list, col);
1065  }
1066 
1067  num_periods_visible = g_list_length(col_list);
1068  }
1069  priv->period_col_list = col_list;
1070  priv->totals_col_list = totals_col_list;
1071 
1072  if (priv->total_col == NULL)
1073  {
1074  priv->total_col = gnc_tree_view_account_add_custom_column(
1075  GNC_TREE_VIEW_ACCOUNT(priv->tree_view), _("Total"),
1076  budget_total_col_source, NULL);
1077  g_object_set_data(G_OBJECT(priv->total_col), "budget", priv->budget);
1078 
1079  col = gbv_create_totals_column(view, -1);
1080  if (col != NULL)
1081  {
1082  gtk_tree_view_append_column(priv->totals_tree_view, col);
1083  }
1084  }
1085 
1086  gbv_refresh_col_titles(view);
1087  LEAVE(" ");
1088 }
1089 
GncBudgetView * gnc_budget_view_new(GncBudget *budget, AccountFilterDialog *fd)
Functions to load, save and get gui state.
gboolean gnc_main_window_button_press_cb(GtkWidget *whatever, GdkEventButton *event, GncPluginPage *page)
GList * gnc_tree_view_account_get_selected_accounts(GncTreeViewAccount *view)
utility functions for the GnuCash UI
GNCAccountType xaccAccountGetType(const Account *acc)
Definition: Account.c:3009
GnuCash Budgets.
gint gnc_state_drop_sections_for(const gchar *partial_name)
Definition: gnc-state.c:264
gboolean string_to_guid(const gchar *string, GncGUID *guid)
gnc_numeric gnc_numeric_add(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
gchar * guid_to_string_buff(const GncGUID *guid, gchar *buff)
#define ENTER(format, args...)
Definition: qoflog.h:261
Functions for adding content to a window.
Definition: guid.h:65
gboolean gnc_numeric_negative_p(gnc_numeric a)
Definition: finvar.h:98
void gnc_tree_view_account_set_filter(GncTreeViewAccount *view, gnc_tree_view_account_filter_func func, gpointer data, GSourceFunc destroy)
QofBook * qof_session_get_book(const QofSession *session)
Gobject helper routines.
GtkTreeView implementation for gnucash account tree.
void gnc_account_foreach_child(const Account *acc, AccountCb thunk, gpointer user_data)
Definition: Account.c:2940
GtkTreeViewColumn * gnc_tree_view_account_add_custom_column(GncTreeViewAccount *account_view, const gchar *column_title, GncTreeViewAccountColumnSource col_source_cb, GncTreeViewAccountColumnTextEdited col_edited_cb)
#define GUID_ENCODING_LENGTH
Definition: guid.h:74
gnc_numeric gnc_numeric_error(GNCNumericErrorCode error_code)
GtkTreeView * gnc_tree_view_account_new(gboolean show_root)
void gnc_tree_view_configure_columns(GncTreeView *view)
gboolean gnc_plugin_page_account_tree_filter_accounts(Account *account, gpointer user_data)
Account * gnc_tree_view_account_get_account_from_path(GncTreeViewAccount *view, GtkTreePath *s_path)
Gnome specific utility functions.
#define MAX_DATE_LENGTH
Definition: gnc-date.h:106
gint gnc_account_n_children(const Account *account)
Definition: Account.c:2676
int xaccSPrintAmount(char *bufp, gnc_numeric val, GNCPrintAmountInfo info)
Definition: gnc-ui-util.c:1437
GNCAccountType
Definition: Account.h:96
GtkTreeModel implementation to display account types in a GtkTreeView.
gnc_numeric gnc_numeric_sub(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
#define LEAVE(format, args...)
Definition: qoflog.h:271
Account * gnc_account_nth_child(const Account *parent, gint num)
Definition: Account.c:2691
GNCNumericErrorCode gnc_numeric_check(gnc_numeric a)
#define GNC_DENOM_AUTO
Definition: gnc-numeric.h:246
size_t qof_print_gdate(char *buf, size_t bufflen, const GDate *gd)
const gchar * QofLogModule
Definition: qofid.h:89