diff --git a/gnucash/gnome/gnc-budget-view.c b/gnucash/gnome/gnc-budget-view.c index 6fe4d9edad..3710196b35 100644 --- a/gnucash/gnome/gnc-budget-view.c +++ b/gnucash/gnome/gnc-budget-view.c @@ -118,10 +118,8 @@ static void gbv_create_widget(GncBudgetView *view); static gboolean gbv_button_press_cb( GtkWidget *widget, GdkEventButton *event, GncBudgetView *view); #endif -#if 0 -static gboolean gbv_key_press_cb( - GtkWidget *treeview, GdkEventKey *event, gpointer userdata); -#endif +static gboolean gbv_key_press_cb(GtkWidget *treeview, GdkEventKey *event, + gpointer userdata); static void gbv_row_activated_cb( GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *col, GncBudgetView *view); @@ -163,11 +161,10 @@ struct GncBudgetViewPrivate GtkTreeViewColumn* total_col; AccountFilterDialog *fd; - Account* income; - Account* expenses; - Account* assets; - Account* liabilities; Account* rootAcct; + + GtkCellRenderer *temp_cr; + GtkCellEditable *temp_ce; }; G_DEFINE_TYPE_WITH_PRIVATE(GncBudgetView, gnc_budget_view, GTK_TYPE_BOX) @@ -234,29 +231,6 @@ gnc_budget_view_init(GncBudgetView *budget_view) priv->rootAcct = root; - for (i = 0; i < num_top_accounts; ++i) - { - Account* acc = gnc_account_nth_child(root, i); - GNCAccountType type = xaccAccountGetType(acc); - - if (type == ACCT_TYPE_ASSET) - { - priv->assets = acc; - } - else if (type == ACCT_TYPE_LIABILITY) - { - priv->liabilities = acc; - } - else if (type == ACCT_TYPE_INCOME) - { - priv->income = acc; - } - else if (type == ACCT_TYPE_EXPENSE) - { - priv->expenses = acc; - } - } - LEAVE(""); } @@ -427,9 +401,6 @@ gbv_create_widget(GncBudgetView *view) G_CALLBACK(gbv_selection_changed_cb), view); g_signal_connect(G_OBJECT(tree_view), "button-press-event", G_CALLBACK(gbv_button_press_cb), view); - g_signal_connect_after(G_OBJECT(tree_view), "key-press-event", - G_CALLBACK(gbv_key_press_cb), NULL); - gbv_selection_changed_cb(NULL, view); #endif @@ -669,39 +640,103 @@ gbv_button_press_cb(GtkWidget *widget, GdkEventButton *event, } #endif -#if 0 -/** \brief Key press action for gnc budget view. +/** \brief Key press action for gnc budget view when in editing mode. + * Used for navigating with tab while editing. + * The handler is for the cell-editable, not for the treeview */ static gboolean -gbv_key_press_cb(GtkWidget *treeview, GdkEventKey *event, gpointer userdata) +gbv_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer userdata) { - GtkTreeView *tv = GTK_TREE_VIEW(treeview); GtkTreeViewColumn *col; - GtkTreePath *path = NULL; - - if (event->type != GDK_KEY_PRESS) return TRUE; + GtkTreePath *path = NULL; + GncBudgetViewPrivate *priv = GNC_BUDGET_VIEW_GET_PRIVATE(userdata); + GtkTreeView *tv = priv->tree_view; + gboolean shifted; + gint period_num, num_periods; + gpointer data; + + if (event->type != GDK_KEY_PRESS || !priv->temp_cr) + return FALSE; switch (event->keyval) { case GDK_KEY_Tab: case GDK_KEY_ISO_Left_Tab: case GDK_KEY_KP_Tab: - case GDK_KEY_Return: - case GDK_KEY_KP_Enter: + shifted = event->state & GDK_SHIFT_MASK; gtk_tree_view_get_cursor(tv, &path, &col); - if (!path) return TRUE; - //finish_edit(col); + if (!path) + return TRUE; + data = g_object_get_data(G_OBJECT(col), "period_num"); + period_num = GPOINTER_TO_UINT(data); + num_periods = gnc_budget_get_num_periods(priv->budget); + + if (period_num >= num_periods) + period_num = num_periods - 1; + + if (shifted) + period_num--; + else + period_num++; + + if (period_num >= num_periods) + { + period_num = 0; + if (gtk_tree_view_row_expanded(tv, path)) + { + gtk_tree_path_down(path); + } + else + { + gtk_tree_path_next(path); + while (!gnc_tree_view_path_is_valid(GNC_TREE_VIEW(tv), path) && + gtk_tree_path_get_depth(path) > 1) + { + gtk_tree_path_up(path); + gtk_tree_path_next(path); + } + } + } + else if (period_num < 0) + { + period_num = num_periods - 1; + if (!gtk_tree_path_prev(path)) + gtk_tree_path_up(path); + else + while (gtk_tree_view_row_expanded(tv, path)) + { + gtk_tree_path_down(path); + do + { + gtk_tree_path_next(path); + } while ( + gnc_tree_view_path_is_valid(GNC_TREE_VIEW(tv), path)); + gtk_tree_path_prev(path); + } + } + + col = g_list_nth_data(priv->period_col_list, period_num); + + // finish editing + if (priv->temp_ce) + { + gtk_cell_editable_editing_done(priv->temp_ce); + gtk_cell_editable_remove_widget(priv->temp_ce); + + while (gtk_events_pending()) + gtk_main_iteration(); + } + + if (gnc_tree_view_path_is_valid(GNC_TREE_VIEW(tv), path)) + gtk_tree_view_set_cursor(tv, path, col, TRUE); + gtk_tree_path_free(path); break; default: - return TRUE; + return FALSE; } - gnc_tree_view_keynav(GNC_TREE_VIEW(tv), &col, path, event); - if (path && gnc_tree_view_path_is_valid(GNC_TREE_VIEW(tv), path)) - gtk_tree_view_set_cursor(tv, path, col, TRUE); return TRUE; } -#endif /** \brief gnc budget view actions for resize of treeview. */ @@ -804,6 +839,8 @@ typedef struct gnc_numeric total; GncBudget* budget; guint period_num; + GNCPriceDB *pdb; + gnc_commodity *total_currency; } BudgetAccumulationInfo; /** \brief Function to assist in the calculation of sub-account totals. @@ -815,16 +852,29 @@ budget_accum_helper(Account* account, gpointer data) { BudgetAccumulationInfo* info = (BudgetAccumulationInfo*)data; gnc_numeric numeric; + gnc_commodity *currency; + + currency = gnc_account_get_currency_or_parent(account); if (gnc_budget_is_account_period_value_set(info->budget, account, info->period_num)) { - numeric = gnc_budget_get_account_period_value(info->budget, account, info->period_num); - info->total = gnc_numeric_add(info->total, numeric, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); + numeric = gnc_budget_get_account_period_value(info->budget, account, + info->period_num); + numeric = gnc_pricedb_convert_balance_nearest_price_t64( + info->pdb, numeric, currency, info->total_currency, + gnc_budget_get_period_start_date(info->budget, info->period_num)); + info->total = gnc_numeric_add(info->total, numeric, GNC_DENOM_AUTO, + GNC_HOW_DENOM_LCD); } else if (gnc_account_n_children(account) != 0) { - numeric = gbv_get_accumulated_budget_amount(info->budget, account, info->period_num); - info->total = gnc_numeric_add(info->total, numeric, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); + numeric = gbv_get_accumulated_budget_amount(info->budget, account, + info->period_num); + numeric = gnc_pricedb_convert_balance_nearest_price_t64( + info->pdb, numeric, currency, info->total_currency, + gnc_budget_get_period_start_date(info->budget, info->period_num)); + info->total = gnc_numeric_add(info->total, numeric, GNC_DENOM_AUTO, + GNC_HOW_DENOM_LCD); } } @@ -840,6 +890,8 @@ gbv_get_accumulated_budget_amount(GncBudget* budget, Account* account, guint per info.total = gnc_numeric_zero(); info.budget = budget; info.period_num = period_num; + info.pdb = gnc_pricedb_get_db (gnc_account_get_book (account)); + info.total_currency = gnc_account_get_currency_or_parent(account); if (!gnc_budget_is_account_period_value_set(budget, account, period_num)) { @@ -852,6 +904,19 @@ gbv_get_accumulated_budget_amount(GncBudget* budget, Account* account, guint per return info.total; } +static gchar* +get_negative_color (void) +{ + GdkRGBA color; + GtkWidget *label = gtk_label_new ("Color"); + GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET(label)); + gtk_style_context_add_class (context, "negative-numbers"); + gtk_style_context_get_color (context, GTK_STATE_FLAG_NORMAL, &color); + gtk_widget_destroy(label); + + return gdk_rgba_to_string(&color); +} + /** \brief Calculates and displays budget amount for a period in a defined account. Displays budget amount for a period for an account. If a budget @@ -868,6 +933,7 @@ budget_col_source(Account *account, GtkTreeViewColumn *col, guint period_num; gnc_numeric numeric; gchar amtbuff[100]; //FIXME: overkill, where's the #define? + gboolean red = gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_NEGATIVE_IN_RED); budget = GNC_BUDGET(g_object_get_data(G_OBJECT(col), "budget")); bview = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(col), "budget_view")); @@ -889,10 +955,18 @@ budget_col_source(Account *account, GtkTreeViewColumn *col, numeric = gbv_get_accumulated_budget_amount(budget, account, period_num); xaccSPrintAmount(amtbuff, numeric, gnc_account_print_info(account, FALSE)); - if (gnc_is_dark_theme (&color)) - g_object_set(cell, "foreground", "darkgray", NULL); + if (gnc_is_dark_theme(&color)) + g_object_set(cell, "foreground", + red && gnc_numeric_negative_p(numeric) + ? "darkred" + : "darkgray", + NULL); else - g_object_set(cell, "foreground", "dimgray", NULL); + g_object_set(cell, "foreground", + red && gnc_numeric_negative_p(numeric) + ? "PaleVioletRed" + : "dimgray", + NULL); } } else @@ -907,7 +981,11 @@ budget_col_source(Account *account, GtkTreeViewColumn *col, { xaccSPrintAmount(amtbuff, numeric, gnc_account_print_info(account, FALSE)); - g_object_set(cell, "foreground", NULL, NULL); + g_object_set(cell, "foreground", + red && gnc_numeric_negative_p(numeric) + ? get_negative_color() + : NULL, + NULL); } } return g_strdup(amtbuff); @@ -917,12 +995,20 @@ budget_col_source(Account *account, GtkTreeViewColumn *col, totals column to the right. */ static gnc_numeric -bgv_get_total_for_account(Account* account, GncBudget* budget) +bgv_get_total_for_account(Account* account, GncBudget* budget, gnc_commodity *new_currency) { guint num_periods; int period_num; gnc_numeric numeric; gnc_numeric total = gnc_numeric_zero(); + GNCPriceDB *pdb; + gnc_commodity *currency; + + if (new_currency) + { + pdb = gnc_pricedb_get_db(gnc_get_current_book()); + currency = gnc_account_get_currency_or_parent(account); + } num_periods = gnc_budget_get_num_periods(budget); for (period_num = 0; period_num < num_periods; ++period_num) @@ -932,6 +1018,13 @@ bgv_get_total_for_account(Account* account, GncBudget* budget) if (gnc_account_n_children(account) != 0) { numeric = gbv_get_accumulated_budget_amount(budget, account, period_num); + + if (new_currency) + { + numeric = gnc_pricedb_convert_balance_nearest_price_t64( + pdb, numeric, currency, new_currency, + gnc_budget_get_period_start_date(budget, period_num)); + } total = gnc_numeric_add(total, numeric, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); } } @@ -940,6 +1033,12 @@ bgv_get_total_for_account(Account* account, GncBudget* budget) numeric = gnc_budget_get_account_period_value(budget, account, period_num); if (!gnc_numeric_check(numeric)) { + if (new_currency) + { + numeric = gnc_pricedb_convert_balance_nearest_price_t64( + pdb, numeric, currency, new_currency, + gnc_budget_get_period_start_date(budget, period_num)); + } total = gnc_numeric_add(total, numeric, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); } } @@ -956,11 +1055,14 @@ budget_total_col_source(Account *account, GtkTreeViewColumn *col, GncBudget *budget; gnc_numeric total; gchar amtbuff[100]; //FIXME: overkill, where's the #define? + gboolean red = gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_NEGATIVE_IN_RED); budget = GNC_BUDGET(g_object_get_data(G_OBJECT(col), "budget")); - total = bgv_get_total_for_account(account, budget); + total = bgv_get_total_for_account(account, budget, NULL); xaccSPrintAmount(amtbuff, total, - gnc_account_print_info(account, FALSE)); + gnc_account_print_info(account, TRUE)); + g_object_set(cell, "foreground", + red && gnc_numeric_negative_p(total) ? get_negative_color () : NULL, NULL); return g_strdup(amtbuff); } @@ -1022,107 +1124,92 @@ totals_col_source(GtkTreeViewColumn *col, GtkCellRenderer *cell, gchar amtbuff[100]; //FIXME: overkill, where's the #define? gint i; gint num_top_accounts; + gboolean neg, red; + GNCPriceDB *pdb; + gnc_commodity *total_currency, *currency; + - gnc_numeric totalincome = gnc_numeric_zero(); - gnc_numeric totalexpenses = gnc_numeric_zero(); - gnc_numeric totalassets = gnc_numeric_zero(); - gnc_numeric totalliabilities = gnc_numeric_zero(); + gnc_numeric total = gnc_numeric_zero(); view = GNC_BUDGET_VIEW(user_data); priv = GNC_BUDGET_VIEW_GET_PRIVATE(view); + red = gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_NEGATIVE_IN_RED); gtk_tree_model_get(s_model, s_iter, 1, &row_type, -1); budget = GNC_BUDGET(g_object_get_data(G_OBJECT(col), "budget")); period_num = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(col), "period_num")); + pdb = gnc_pricedb_get_db (gnc_get_current_book()); + total_currency = gnc_default_currency(); num_top_accounts = gnc_account_n_children(priv->rootAcct); // step through each child account of the root, find the total income, expenses, liabilities, and assets. for (i = 0; i < num_top_accounts; ++i) { - account = gnc_account_nth_child(priv->rootAcct, i); + account = gnc_account_nth_child(priv->rootAcct, i); + currency = gnc_account_get_currency_or_parent(account); + neg = FALSE; + switch (xaccAccountGetType(account)) + { + case ACCT_TYPE_INCOME: + if (row_type != TOTALS_TYPE_INCOME && + row_type != TOTALS_TYPE_TOTAL) + continue; + break; + case ACCT_TYPE_LIABILITY: + case ACCT_TYPE_EQUITY: + if (row_type != TOTALS_TYPE_TRANSFERS && + row_type != TOTALS_TYPE_TOTAL) + continue; + break; + case ACCT_TYPE_EXPENSE: + if (row_type == TOTALS_TYPE_TOTAL) + neg = TRUE; + else if (row_type != TOTALS_TYPE_EXPENSES) + continue; + break; + case ACCT_TYPE_ASSET: + if (row_type != TOTALS_TYPE_TRANSFERS && + row_type != TOTALS_TYPE_TOTAL) + continue; + neg = TRUE; + break; + default: + continue; + } // find the total for this account if (period_num < 0) { - value = bgv_get_total_for_account(account, budget); + value = bgv_get_total_for_account(account, budget, total_currency); } else { - value = gbv_get_accumulated_budget_amount(budget, account, period_num); - } + value = + gbv_get_accumulated_budget_amount(budget, account, period_num); - // test for what account type, and add 'value' to the appopriate total - - if (xaccAccountGetType(account) == ACCT_TYPE_INCOME) - { - totalincome = gnc_numeric_add(totalincome, value, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); - } - else if (xaccAccountGetType(account) == ACCT_TYPE_EXPENSE) - { - totalexpenses = gnc_numeric_add(totalexpenses, value, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); - } - else if (xaccAccountGetType(account) == ACCT_TYPE_ASSET) - { - totalassets = gnc_numeric_add(totalassets, value, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); - } - else if (xaccAccountGetType(account) == ACCT_TYPE_LIABILITY) - { - totalliabilities = gnc_numeric_add(totalliabilities, value, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); + value = gnc_pricedb_convert_balance_nearest_price_t64( + pdb, value, currency, total_currency, + gnc_budget_get_period_start_date(budget, period_num)); } + + if (neg) + total = gnc_numeric_sub(total, value, GNC_DENOM_AUTO, + GNC_HOW_DENOM_LCD); else - { - // Do nothing because this account is not of interest - } + total = gnc_numeric_add(total, value, GNC_DENOM_AUTO, + GNC_HOW_DENOM_LCD); } - // at this point we should have variables holding the values for assets, liabilities, expenses and incomes. - - // Set the text to display, depending on which of the totals rows we are currently looking at + xaccSPrintAmount(amtbuff, total, + gnc_commodity_print_info(total_currency, + period_num < 0 ? TRUE : FALSE)); + g_object_set(cell, "foreground", + red && gnc_numeric_negative_p(total) ? get_negative_color () : NULL, NULL); - if (row_type == TOTALS_TYPE_INCOME) - { - // FIXME: There must be a better way to get the GncAccountPrintInfo object than this. Would prefer to depreciate the tracking of top level accounts. - xaccSPrintAmount(amtbuff, totalincome, - gnc_account_print_info(priv->income, FALSE)); - g_object_set(cell, "foreground", NULL, NULL); - } - else if (row_type == TOTALS_TYPE_EXPENSES) - { - xaccSPrintAmount(amtbuff, totalexpenses, - gnc_account_print_info(priv->expenses, FALSE)); - g_object_set(cell, "foreground", NULL, NULL); - } - else if (row_type == TOTALS_TYPE_TRANSFERS) - { - xaccSPrintAmount(amtbuff, gnc_numeric_sub(totalassets, totalliabilities, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD), - gnc_account_print_info(priv->assets, FALSE)); - g_object_set(cell, "foreground", NULL, NULL); - } - else if (row_type == TOTALS_TYPE_TOTAL) - { - value = gnc_numeric_sub(totalincome, totalexpenses, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); - value = gnc_numeric_sub(value, totalassets, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); - value = gnc_numeric_add(value, totalliabilities, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD); - xaccSPrintAmount(amtbuff, value, - gnc_account_print_info(priv->assets, FALSE)); - if (gnc_numeric_negative_p(value)) - { - g_object_set(cell, "foreground", "red", NULL); - } - else - { - g_object_set(cell, "foreground", NULL, NULL); - } - } - else - { - // if it reaches here then the row type was not set correctly - g_strlcpy(amtbuff, "error", sizeof(amtbuff)); - } g_object_set(G_OBJECT(cell), "text", amtbuff, "xalign", 1.0, NULL); } @@ -1218,6 +1305,30 @@ gbv_col_edited_cb(GtkCellRendererText* cell, gchar* path_string, gchar* new_text gtk_widget_queue_draw(GTK_WIDGET(priv->totals_tree_view)); } +/* The main Start Editing Call back for the budget columns, for key navigation + */ +static void +gdv_editing_started_cb(GtkCellRenderer *cr, GtkCellEditable *editable, + const gchar *path_string, gpointer user_data) +{ + GncBudgetViewPrivate *priv = GNC_BUDGET_VIEW_GET_PRIVATE(user_data); + + priv->temp_cr = cr; + priv->temp_ce = editable; + + g_signal_connect(G_OBJECT(editable), "key-press-event", + G_CALLBACK(gbv_key_press_cb), user_data); +} + +static void +gdv_editing_canceled_cb(GtkCellRenderer *cr, gpointer user_data) +{ + GncBudgetViewPrivate *priv = GNC_BUDGET_VIEW_GET_PRIVATE(user_data); + + priv->temp_cr = NULL; + priv->temp_ce = NULL; +} + /** \brief refreshes the current budget view The function will step through to only display the columns that are set @@ -1291,7 +1402,10 @@ gnc_budget_view_refresh(GncBudgetView *view) gbv_renderer_add_padding (renderer); g_signal_connect(G_OBJECT(renderer), "edited", (GCallback)gbv_col_edited_cb, view); - + g_signal_connect(G_OBJECT(renderer), "editing-started", + (GCallback)gdv_editing_started_cb, view); + g_signal_connect(G_OBJECT(renderer), "editing-canceled", + (GCallback)gdv_editing_canceled_cb, view); col = gbv_create_totals_column(view, num_periods_visible); if (col != NULL) { diff --git a/gnucash/gnome/gnc-plugin-budget.c b/gnucash/gnome/gnc-plugin-budget.c index c1afd95f79..3585673039 100644 --- a/gnucash/gnome/gnc-plugin-budget.c +++ b/gnucash/gnome/gnc-plugin-budget.c @@ -312,6 +312,13 @@ gnc_budget_gui_select_budget(GtkWindow *parent, QofBook *book) gtk_container_add(GTK_CONTAINER (gtk_dialog_get_content_area (dlg)), GTK_WIDGET(tv)); gtk_widget_show_all(GTK_WIDGET(dlg)); + // Preselect the default budget + bgt = gnc_budget_get_default(book); + if (bgt && gnc_tree_model_budget_get_iter_for_budget(tm, &iter, bgt)) + { + gtk_tree_view_set_cursor(tv, gtk_tree_model_get_path(tm, &iter), NULL, + FALSE); + } bgt = NULL; response = gtk_dialog_run(dlg); switch (response) diff --git a/gnucash/gnome/gnc-plugin-page-budget.c b/gnucash/gnome/gnc-plugin-page-budget.c index ccddfd306a..d47ae121ca 100644 --- a/gnucash/gnome/gnc-plugin-page-budget.c +++ b/gnucash/gnome/gnc-plugin-page-budget.c @@ -115,6 +115,8 @@ static void gnc_plugin_page_budget_cmd_view_options( GtkAction *action, GncPluginPageBudget *page); static void gnc_plugin_page_budget_cmd_estimate_budget( GtkAction *action, GncPluginPageBudget *page); +static void gnc_plugin_page_budget_cmd_allperiods_budget( + GtkAction *action, GncPluginPageBudget *page); static GtkActionEntry gnc_plugin_page_budget_actions [] = { @@ -151,6 +153,12 @@ static GtkActionEntry gnc_plugin_page_budget_actions [] = N_("Estimate a budget value for the selected accounts from past transactions"), G_CALLBACK (gnc_plugin_page_budget_cmd_estimate_budget) }, + { + "AllPeriodsBudgetAction", "system-run", N_("All Periods"), + NULL, + N_("Edit budget for all periods for the selected accounts"), + G_CALLBACK (gnc_plugin_page_budget_cmd_allperiods_budget) + }, /* View menu */ { @@ -179,9 +187,18 @@ static action_toolbar_labels toolbar_labels[] = { "DeleteBudgetAction", N_("Delete") }, { "OptionsBudgetAction", N_("Options") }, { "EstimateBudgetAction", N_("Estimate") }, + { "AllPeriodsBudgetAction", N_("All Periods") }, { NULL, NULL }, }; +typedef enum allperiods_action +{ + REPLACE, + ADD, + MULTIPLY, + UNSET +} allperiods_action; + typedef struct GncPluginPageBudgetPrivate { GtkActionGroup *action_group; @@ -204,6 +221,12 @@ typedef struct GncPluginPageBudgetPrivate /* For the estimation dialog */ Recurrence r; gint sigFigs; + gboolean useAvg; + + /* For the allPeriods value dialog */ + gnc_numeric allValue; + allperiods_action action; + } GncPluginPageBudgetPrivate; G_DEFINE_TYPE_WITH_PRIVATE(GncPluginPageBudget, gnc_plugin_page_budget, GNC_TYPE_PLUGIN_PAGE) @@ -309,6 +332,7 @@ gnc_plugin_page_budget_init (GncPluginPageBudget *plugin_page) priv->fd.filter_override = g_hash_table_new (g_direct_hash, g_direct_equal); priv->sigFigs = 1; + priv->useAvg = FALSE; recurrenceSet(&priv->r, 1, PERIOD_MONTH, NULL, WEEKEND_ADJ_NONE); LEAVE("page %p, priv %p, action group %p", @@ -859,19 +883,40 @@ estimate_budget_helper(GtkTreeModel *model, GtkTreePath *path, num_periods = gnc_budget_get_num_periods(priv->budget); - for (i = 0; i < num_periods; i++) + if (priv->useAvg && num_periods) { - num = recurrenceGetAccountPeriodValue(&priv->r, acct, i); - if (!gnc_numeric_check(num)) + num = xaccAccountGetBalanceChangeForPeriod(acct, + recurrenceGetPeriodTime(&priv->r, 0, FALSE), + recurrenceGetPeriodTime(&priv->r, num_periods - 1, TRUE), TRUE); + num = gnc_numeric_div(num, + gnc_numeric_create(num_periods, 1), + GNC_DENOM_AUTO, + GNC_HOW_DENOM_SIGFIGS(priv->sigFigs) | + GNC_HOW_RND_ROUND_HALF_UP); + + if (gnc_reverse_balance(acct)) + num = gnc_numeric_neg(num); + + for (i = 0; i < num_periods; i++) { - if (gnc_reverse_balance (acct)) - num = gnc_numeric_neg (num); - + gnc_budget_set_account_period_value(priv->budget, acct, i, num); + } + } + else + { + for (i = 0; i < num_periods; i++) + { + num = recurrenceGetAccountPeriodValue(&priv->r, acct, i); + if (!gnc_numeric_check(num)) + { + if (gnc_reverse_balance(acct)) + num = gnc_numeric_neg(num); - num = gnc_numeric_convert(num, GNC_DENOM_AUTO, - GNC_HOW_DENOM_SIGFIGS(priv->sigFigs) | GNC_HOW_RND_ROUND_HALF_UP); - gnc_budget_set_account_period_value( - priv->budget, acct, i, num); + num = gnc_numeric_convert(num, GNC_DENOM_AUTO, + GNC_HOW_DENOM_SIGFIGS(priv->sigFigs) | + GNC_HOW_RND_ROUND_HALF_UP); + gnc_budget_set_account_period_value(priv->budget, acct, i, num); + } } } } @@ -886,7 +931,7 @@ gnc_plugin_page_budget_cmd_estimate_budget(GtkAction *action, { GncPluginPageBudgetPrivate *priv; GtkTreeSelection *sel; - GtkWidget *dialog, *gde, *dtr, *hb; + GtkWidget *dialog, *gde, *dtr, *hb, *avg; gint result; GDate date; const Recurrence *r; @@ -930,6 +975,9 @@ gnc_plugin_page_budget_cmd_estimate_budget(GtkAction *action, gtk_spin_button_set_value(GTK_SPIN_BUTTON(dtr), (gdouble)priv->sigFigs); + avg = GTK_WIDGET(gtk_builder_get_object(builder, "UseAverage")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(avg), priv->useAvg); + gtk_widget_show_all (dialog); result = gtk_dialog_run(GTK_DIALOG(dialog)); switch (result) @@ -944,6 +992,8 @@ gnc_plugin_page_budget_cmd_estimate_budget(GtkAction *action, priv->sigFigs = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(dtr)); + priv->useAvg = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(avg)); + gtk_tree_selection_selected_foreach(sel, estimate_budget_helper, page); break; default: @@ -953,6 +1003,133 @@ gnc_plugin_page_budget_cmd_estimate_budget(GtkAction *action, g_object_unref(G_OBJECT(builder)); } +static void +allperiods_budget_helper(GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer data) +{ + Account *acct; + guint num_periods, i; + gnc_numeric num; + GncPluginPageBudgetPrivate *priv; + GncPluginPageBudget *page = data; + + g_return_if_fail(GNC_IS_PLUGIN_PAGE_BUDGET(page)); + priv = GNC_PLUGIN_PAGE_BUDGET_GET_PRIVATE(page); + acct = gnc_budget_view_get_account_from_path(priv->budget_view, path); + num_periods = gnc_budget_get_num_periods(priv->budget); + num = priv->allValue; + + for (i = 0; i < num_periods; i++) + { + switch (priv->action) + { + case ADD: + num = gnc_budget_get_account_period_value(priv->budget, acct, i); + num = gnc_numeric_add(num, priv->allValue, GNC_DENOM_AUTO, + GNC_HOW_DENOM_SIGFIGS(priv->sigFigs) | + GNC_HOW_RND_ROUND_HALF_UP); + gnc_budget_set_account_period_value(priv->budget, acct, i, num); + break; + case MULTIPLY: + num = gnc_budget_get_account_period_value(priv->budget, acct, i); + num = gnc_numeric_mul(num, priv->allValue, GNC_DENOM_AUTO, + GNC_HOW_DENOM_SIGFIGS(priv->sigFigs) | + GNC_HOW_RND_ROUND_HALF_UP); + gnc_budget_set_account_period_value(priv->budget, acct, i, num); + break; + case UNSET: + gnc_budget_unset_account_period_value(priv->budget, acct, i); + break; + default: + gnc_budget_set_account_period_value(priv->budget, acct, i, + priv->allValue); + break; + } + } +} + +/*******************************/ +/* All Periods Value Dialog */ +/*******************************/ +static void +gnc_plugin_page_budget_cmd_allperiods_budget(GtkAction *action, + GncPluginPageBudget *page) +{ + GncPluginPageBudgetPrivate *priv; + GtkTreeSelection *sel; + GtkWidget *dialog, *gde, *val, *dtr, *add, *mult; + gint result; + GtkBuilder *builder; + const gchar *txt; + + g_return_if_fail(GNC_IS_PLUGIN_PAGE_BUDGET(page)); + priv = GNC_PLUGIN_PAGE_BUDGET_GET_PRIVATE(page); + sel = gnc_budget_view_get_selection(priv->budget_view); + + if (gtk_tree_selection_count_selected_rows(sel) <= 0) + { + dialog = gtk_message_dialog_new( + GTK_WINDOW(gnc_plugin_page_get_window(GNC_PLUGIN_PAGE(page))), + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, + GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "%s", + _("You must select at least one account to edit.")); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return; + } + + builder = gtk_builder_new(); + gnc_builder_add_from_file(builder, "gnc-plugin-page-budget.glade", + "DigitsToRound_Adj"); + gnc_builder_add_from_file(builder, "gnc-plugin-page-budget.glade", + "budget_allperiods_dialog"); + + dialog = GTK_WIDGET( + gtk_builder_get_object(builder, "budget_allperiods_dialog")); + + gtk_window_set_transient_for( + GTK_WINDOW(dialog), + GTK_WINDOW(gnc_plugin_page_get_window(GNC_PLUGIN_PAGE(page)))); + + val = GTK_WIDGET(gtk_builder_get_object(builder, "Value")); + gtk_entry_set_text(GTK_ENTRY(val), ""); + + dtr = GTK_WIDGET(gtk_builder_get_object(builder, "DigitsToRound1")); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(dtr), (gdouble)priv->sigFigs); + + add = GTK_WIDGET(gtk_builder_get_object(builder, "RB_Add")); + mult = GTK_WIDGET(gtk_builder_get_object(builder, "RB_Multiply")); + + gtk_widget_show_all(dialog); + result = gtk_dialog_run(GTK_DIALOG(dialog)); + switch (result) + { + case GTK_RESPONSE_OK: + + priv->sigFigs = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(dtr)); + priv->action = REPLACE; + txt = gtk_entry_get_text(GTK_ENTRY(val)); + + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(add))) + priv->action = ADD; + else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(mult))) + priv->action = MULTIPLY; + + if (priv->action == REPLACE && + !gtk_entry_get_text_length(GTK_ENTRY(val))) + priv->action = UNSET; + + if (xaccParseAmount(txt, TRUE, &priv->allValue, NULL) || + priv->action == UNSET) + gtk_tree_selection_selected_foreach(sel, allperiods_budget_helper, + page); + break; + default: + break; + } + gtk_widget_destroy(dialog); + g_object_unref(G_OBJECT(builder)); +} static void gnc_plugin_page_budget_cmd_view_filter_by (GtkAction *action, diff --git a/gnucash/gtkbuilder/gnc-plugin-page-budget.glade b/gnucash/gtkbuilder/gnc-plugin-page-budget.glade index da3c11e75d..7f102b3dde 100644 --- a/gnucash/gtkbuilder/gnc-plugin-page-budget.glade +++ b/gnucash/gtkbuilder/gnc-plugin-page-budget.glade @@ -9,6 +9,244 @@ 1 1 + + False + 5 + Edit budget for all periods + True + dialog + + + + + + True + False + vertical + + + True + False + end + + + _Cancel + True + True + True + False + True + + + False + False + 0 + + + + + _OK + True + True + True + False + True + + + False + False + 1 + + + + + False + True + end + 0 + + + + + True + False + vertical + + + True + False + start + start + + Use a fixed value or apply transformation for all periods. + + True + 40 + 40 + + + False + False + 0 + + + + + False + False + 2 + + + + + True + False + + + True + False + start + Value: + + + 0 + 0 + + + + + True + True + number + + + 1 + 0 + + + + + True + False + Action + + + 0 + 2 + + + + + True + False + vertical + + + Replace + True + True + False + + Replace the budget for all periods with new 'value'. Use empty value to unset budget for the accounts. + + True + True + + + False + True + 0 + + + + + Add + True + True + False + + Add 'value' to current budget for each period + + True + True + RB_Replace + + + False + True + 1 + + + + + Multiply + True + True + False + + Multiply current budget for each period by 'value' + + True + True + RB_Replace + + + False + True + 2 + + + + + 1 + 2 + + + + + True + True + The number of leading digits to keep when rounding + True + 1 + False + False + DigitsToRound_Adj + 1 + True + 1 + + + 1 + 1 + + + + + True + False + start + Significant Digits: + + + 0 + 1 + + + + + False + False + 3 + + + + + + cancelbutton3 + okbutton3 + + False 5 @@ -151,6 +389,22 @@ 0 + + + Use Average + True + True + False + + Use the average value over all actual periods for all projected periods + + True + + + 1 + 2 + + False diff --git a/gnucash/ui/gnc-plugin-page-budget-ui.xml b/gnucash/ui/gnc-plugin-page-budget-ui.xml index 87fcceb11e..9f45fe45ab 100644 --- a/gnucash/ui/gnc-plugin-page-budget-ui.xml +++ b/gnucash/ui/gnc-plugin-page-budget-ui.xml @@ -3,6 +3,7 @@ + @@ -22,6 +23,7 @@ + diff --git a/libgnucash/engine/qofbook.cpp b/libgnucash/engine/qofbook.cpp index 3991e96d6c..8e7dc93cfe 100644 --- a/libgnucash/engine/qofbook.cpp +++ b/libgnucash/engine/qofbook.cpp @@ -163,6 +163,8 @@ qof_book_init (QofBook *book) static const std::string str_KVP_OPTION_PATH(KVP_OPTION_PATH); static const std::string str_OPTION_SECTION_ACCOUNTS(OPTION_SECTION_ACCOUNTS); +static const std::string str_OPTION_SECTION_BUDGETING(OPTION_SECTION_BUDGETING); +static const std::string str_OPTION_NAME_DEFAULT_BUDGET(OPTION_NAME_DEFAULT_BUDGET); static const std::string str_OPTION_NAME_TRADING_ACCOUNTS(OPTION_NAME_TRADING_ACCOUNTS); static const std::string str_OPTION_NAME_AUTO_READONLY_DAYS(OPTION_NAME_AUTO_READONLY_DAYS); static const std::string str_OPTION_NAME_NUM_FIELD_SOURCE(OPTION_NAME_NUM_FIELD_SOURCE); @@ -206,7 +208,7 @@ qof_book_get_property (GObject* object, break; case PROP_OPT_DEFAULT_BUDGET: qof_instance_get_path_kvp (QOF_INSTANCE (book), value, {str_KVP_OPTION_PATH, - str_OPTION_SECTION_ACCOUNTS, OPTION_NAME_DEFAULT_BUDGET}); + str_OPTION_SECTION_BUDGETING, str_OPTION_NAME_DEFAULT_BUDGET}); break; case PROP_OPT_FY_END: qof_instance_get_path_kvp (QOF_INSTANCE (book), value, {"fy_end"}); @@ -261,7 +263,7 @@ qof_book_set_property (GObject *object, break; case PROP_OPT_DEFAULT_BUDGET: qof_instance_set_path_kvp (QOF_INSTANCE (book), value, {str_KVP_OPTION_PATH, - str_OPTION_SECTION_ACCOUNTS, OPTION_NAME_DEFAULT_BUDGET}); + str_OPTION_SECTION_BUDGETING, OPTION_NAME_DEFAULT_BUDGET}); break; case PROP_OPT_FY_END: qof_instance_set_path_kvp (QOF_INSTANCE (book), value, {"fy_end"});