mirror of
https://github.com/Gnucash/gnucash.git
synced 2026-09-03 20:53:02 -05:00
git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@22627 57a11ea4-9604-0410-9ed3-97b8803252fd
1811 lines
58 KiB
C
1811 lines
58 KiB
C
/********************************************************************\
|
|
* split-register-control.c -- split register control object *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU General Public License as *
|
|
* published by the Free Software Foundation; either version 2 of *
|
|
* the License, or (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License*
|
|
* along with this program; if not, contact: *
|
|
* *
|
|
* Free Software Foundation Voice: +1-617-542-5942 *
|
|
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
|
|
* Boston, MA 02110-1301, USA gnu@gnu.org *
|
|
* *
|
|
\********************************************************************/
|
|
|
|
#include "config.h"
|
|
|
|
#include <glib.h>
|
|
#include <glib/gi18n.h>
|
|
|
|
#include "Scrub.h"
|
|
#include "combocell.h"
|
|
#include "gnc-component-manager.h"
|
|
#include "gnc-gconf-utils.h"
|
|
#include "gnc-ui.h"
|
|
#include "pricecell.h"
|
|
#include "datecell.h"
|
|
#include "dialog-transfer.h"
|
|
#include "dialog-utils.h"
|
|
#include "split-register-control.h"
|
|
#include "split-register-model-save.h"
|
|
#include "split-register-p.h"
|
|
#include "table-allgui.h"
|
|
|
|
|
|
/* This static indicates the debugging module that this .o belongs to. */
|
|
static QofLogModule log_module = GNC_MOD_LEDGER;
|
|
|
|
|
|
static gboolean
|
|
gnc_split_register_balance_trans (SplitRegister *reg, Transaction *trans)
|
|
{
|
|
int choice;
|
|
int default_value;
|
|
Account *default_account;
|
|
Account *other_account;
|
|
Account *root;
|
|
GList *radio_list = NULL;
|
|
const char *title = _("Rebalance Transaction");
|
|
const char *message = _("The current transaction is not balanced.");
|
|
Split *split;
|
|
Split *other_split;
|
|
gboolean two_accounts;
|
|
gboolean multi_currency;
|
|
|
|
|
|
if (xaccTransIsBalanced (trans))
|
|
return FALSE;
|
|
|
|
if (xaccTransUseTradingAccounts (trans))
|
|
{
|
|
MonetaryList *imbal_list;
|
|
gnc_monetary *imbal_mon;
|
|
imbal_list = xaccTransGetImbalance (trans);
|
|
|
|
/* See if the imbalance is only in the transaction's currency */
|
|
if (!imbal_list)
|
|
/* Value imbalance, but not commodity imbalance. This shouldn't
|
|
be something that scrubbing can cause to happen. Perhaps someone
|
|
entered invalid splits. */
|
|
multi_currency = TRUE;
|
|
else
|
|
{
|
|
imbal_mon = imbal_list->data;
|
|
if (!imbal_list->next &&
|
|
gnc_commodity_equiv(gnc_monetary_commodity(*imbal_mon),
|
|
xaccTransGetCurrency(trans)))
|
|
multi_currency = FALSE;
|
|
else
|
|
multi_currency = TRUE;
|
|
}
|
|
|
|
/* We're done with the imbalance list, the real work will be done
|
|
by xaccTransScrubImbalance which will get it again. */
|
|
gnc_monetary_list_free(imbal_list);
|
|
}
|
|
else
|
|
multi_currency = FALSE;
|
|
|
|
split = xaccTransGetSplit (trans, 0);
|
|
other_split = xaccSplitGetOtherSplit (split);
|
|
|
|
if (other_split == NULL)
|
|
{
|
|
/* Attempt to handle the inverted many-to-one mapping */
|
|
split = xaccTransGetSplit (trans, 1);
|
|
if (split) other_split = xaccSplitGetOtherSplit (split);
|
|
else split = xaccTransGetSplit (trans, 0);
|
|
}
|
|
if (other_split == NULL || multi_currency)
|
|
{
|
|
two_accounts = FALSE;
|
|
other_account = NULL;
|
|
}
|
|
else
|
|
{
|
|
two_accounts = TRUE;
|
|
other_account = xaccSplitGetAccount (other_split);
|
|
}
|
|
|
|
default_account = gnc_split_register_get_default_account (reg);
|
|
|
|
/* If the two pointers are the same, the account from other_split
|
|
* is actually the default account. We must make other_account
|
|
* the account from split instead. */
|
|
|
|
if (default_account == other_account)
|
|
other_account = xaccSplitGetAccount (split);
|
|
|
|
/* If the two pointers are still the same, we have two splits, but
|
|
* they both refer to the same account. While non-sensical, we don't
|
|
* object. */
|
|
|
|
if (default_account == other_account)
|
|
two_accounts = FALSE;
|
|
|
|
radio_list = g_list_append (radio_list,
|
|
_("Balance it _manually"));
|
|
radio_list = g_list_append (radio_list,
|
|
_("Let GnuCash _add an adjusting split"));
|
|
|
|
if (reg->type < NUM_SINGLE_REGISTER_TYPES && !multi_currency)
|
|
{
|
|
radio_list = g_list_append (radio_list,
|
|
_("Adjust current account _split total"));
|
|
|
|
default_value = 2;
|
|
if (two_accounts)
|
|
{
|
|
radio_list = g_list_append (radio_list,
|
|
_("Adjust _other account split total"));
|
|
default_value = 3;
|
|
}
|
|
}
|
|
else
|
|
default_value = 0;
|
|
|
|
choice = gnc_choose_radio_option_dialog
|
|
(gnc_split_register_get_parent (reg),
|
|
title,
|
|
message,
|
|
_("_Rebalance"),
|
|
default_value,
|
|
radio_list);
|
|
|
|
g_list_free (radio_list);
|
|
|
|
root = gnc_account_get_root(default_account);
|
|
switch (choice)
|
|
{
|
|
default:
|
|
case 0:
|
|
break;
|
|
|
|
case 1:
|
|
xaccTransScrubImbalance (trans, root, NULL);
|
|
break;
|
|
|
|
case 2:
|
|
xaccTransScrubImbalance (trans, root, default_account);
|
|
break;
|
|
|
|
case 3:
|
|
xaccTransScrubImbalance (trans, root, other_account);
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gnc_split_register_old_split_empty_p (SplitRegister *reg, Split *split)
|
|
{
|
|
BasicCell *cell;
|
|
gnc_numeric amount;
|
|
const char *string;
|
|
|
|
string = gnc_table_layout_get_cell_value (reg->table->layout, MEMO_CELL);
|
|
if ((string != NULL) && (*string != '\0'))
|
|
return FALSE;
|
|
|
|
string = gnc_table_layout_get_cell_value (reg->table->layout, XFRM_CELL);
|
|
if ((string != NULL) && (*string != '\0'))
|
|
return FALSE;
|
|
|
|
cell = gnc_table_layout_get_cell (reg->table->layout, CRED_CELL);
|
|
if (cell)
|
|
{
|
|
amount = gnc_price_cell_get_value ((PriceCell *) cell);
|
|
if (!gnc_numeric_zero_p (amount))
|
|
return FALSE;
|
|
}
|
|
|
|
cell = gnc_table_layout_get_cell (reg->table->layout, DEBT_CELL);
|
|
if (cell)
|
|
{
|
|
amount = gnc_price_cell_get_value ((PriceCell *) cell);
|
|
if (!gnc_numeric_zero_p (amount))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Checks a cell for an account change and takes any necessary action if
|
|
* one has occurred. Returns TRUE if the check passes, FALSE if it fails. */
|
|
static gboolean
|
|
gnc_split_register_check_account (SplitRegister *reg,
|
|
const char *cell_name)
|
|
{
|
|
SRInfo *info;
|
|
ComboCell *cell = NULL;
|
|
PriceCell *rate_cell;
|
|
Account* new_acct;
|
|
char *name;
|
|
|
|
g_return_val_if_fail(reg, TRUE);
|
|
|
|
/* See if we are leaving an account field */
|
|
if (gnc_cell_name_equal (cell_name, XFRM_CELL))
|
|
{
|
|
if (gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
XFRM_CELL, FALSE))
|
|
cell = (ComboCell *) gnc_table_layout_get_cell (reg->table->layout,
|
|
XFRM_CELL);
|
|
}
|
|
else if (gnc_cell_name_equal (cell_name, MXFRM_CELL))
|
|
{
|
|
if (gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
MXFRM_CELL, FALSE))
|
|
cell = (ComboCell *) gnc_table_layout_get_cell (reg->table->layout,
|
|
MXFRM_CELL);
|
|
}
|
|
|
|
if (!cell)
|
|
return TRUE;
|
|
|
|
/* The account has been changed. */
|
|
name = cell->cell.value;
|
|
DEBUG("Changed to %s", name ? name : "NULL");
|
|
if (!name || *name == '\0' ||
|
|
g_strcmp0 (name, SPLIT_TRANS_STR) == 0 ||
|
|
g_strcmp0 (name, STOCK_SPLIT_STR) == 0)
|
|
return TRUE;
|
|
|
|
/* Create the account if necessary. Also checks for a placeholder. */
|
|
info = gnc_split_register_get_info (reg);
|
|
new_acct = gnc_split_register_get_account_by_name (reg,
|
|
(BasicCell *) cell,
|
|
cell->cell.value);
|
|
if (!new_acct)
|
|
return FALSE;
|
|
|
|
/* See if we need to reset the exchange rate. */
|
|
rate_cell = (PriceCell *) gnc_table_layout_get_cell (reg->table->layout,
|
|
RATE_CELL);
|
|
if (rate_cell)
|
|
{
|
|
Split *split = gnc_split_register_get_current_split(reg);
|
|
Account *orig_acct = xaccSplitGetAccount(split);
|
|
gnc_commodity *orig_com = xaccAccountGetCommodity(orig_acct);
|
|
gnc_commodity *last_com = xaccAccountGetCommodity(info->rate_account);
|
|
gnc_commodity *new_com = xaccAccountGetCommodity(new_acct);
|
|
|
|
if (gnc_commodity_equal(last_com ? last_com : orig_com, new_com))
|
|
{
|
|
DEBUG("Commodity is still %s. Leaving rate unchanged.",
|
|
new_com ? gnc_commodity_get_mnemonic(new_com) : "NULL");
|
|
}
|
|
else if (!gnc_commodity_equal(orig_com, new_com))
|
|
{
|
|
/* The commodity has changed but is not the original. Reset the rate. */
|
|
DEBUG("Commodity now %s (originally %s). Clearing rate.",
|
|
new_com ? gnc_commodity_get_mnemonic(new_com) : "NULL",
|
|
orig_com ? gnc_commodity_get_mnemonic(orig_com) : "NULL");
|
|
|
|
gnc_price_cell_set_value (rate_cell, gnc_numeric_zero());
|
|
info->rate_account = new_acct;
|
|
info->rate_reset = TRUE;
|
|
}
|
|
else
|
|
{
|
|
/* Get the original rate from the split. */
|
|
gnc_numeric amt = xaccSplitGetAmount(split);
|
|
gnc_numeric val = xaccSplitGetValue(split);
|
|
gnc_numeric orig_rate = gnc_numeric_div(amt, val, GNC_DENOM_AUTO,
|
|
GNC_HOW_DENOM_REDUCE);
|
|
|
|
if (!gnc_numeric_check(orig_rate))
|
|
{
|
|
DEBUG("Using original rate of %s.",
|
|
gnc_num_dbg_to_string(orig_rate));
|
|
gnc_price_cell_set_value (rate_cell, orig_rate);
|
|
info->rate_account = new_acct;
|
|
info->rate_reset = FALSE;
|
|
}
|
|
else
|
|
{
|
|
DEBUG("Can't get rate. Using zero.");
|
|
gnc_price_cell_set_value (rate_cell, gnc_numeric_zero());
|
|
info->rate_account = new_acct;
|
|
info->rate_reset = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gnc_split_register_move_cursor (VirtualLocation *p_new_virt_loc,
|
|
gpointer user_data)
|
|
{
|
|
VirtualLocation new_virt_loc = *p_new_virt_loc;
|
|
VirtualCellLocation old_trans_split_loc;
|
|
SplitRegister *reg = user_data;
|
|
Transaction *pending_trans;
|
|
Transaction *new_trans;
|
|
Transaction *old_trans;
|
|
Split *old_trans_split;
|
|
Split *new_trans_split;
|
|
Split *new_split;
|
|
Split *old_split;
|
|
CursorClass new_class;
|
|
CursorClass old_class;
|
|
gboolean exact_traversal;
|
|
gboolean do_refresh;
|
|
gboolean saved;
|
|
SRInfo *info;
|
|
|
|
ENTER("reg=%p, p_new_virt_loc=%p (%d, %d)",
|
|
reg, p_new_virt_loc,
|
|
new_virt_loc.vcell_loc.virt_row,
|
|
new_virt_loc.vcell_loc.virt_col);
|
|
|
|
if (!reg)
|
|
{
|
|
LEAVE("no register");
|
|
return;
|
|
}
|
|
|
|
info = gnc_split_register_get_info (reg);
|
|
|
|
/* The transaction we are coming from */
|
|
old_split = gnc_split_register_get_current_split (reg);
|
|
old_trans = gnc_split_register_get_current_trans (reg);
|
|
old_trans_split =
|
|
gnc_split_register_get_current_trans_split (reg, &old_trans_split_loc);
|
|
old_class = gnc_split_register_get_current_cursor_class (reg);
|
|
|
|
exact_traversal = info->exact_traversal;
|
|
|
|
if (info->traverse_to_new)
|
|
{
|
|
if (old_class == CURSOR_CLASS_SPLIT)
|
|
new_trans = old_trans;
|
|
else
|
|
new_trans = NULL;
|
|
|
|
new_split = NULL;
|
|
new_trans_split = NULL;
|
|
new_class = CURSOR_CLASS_NONE;
|
|
}
|
|
else if (!info->hint_set_by_traverse)
|
|
{
|
|
/* The transaction where we are moving to */
|
|
new_trans = gnc_split_register_get_trans (reg, new_virt_loc.vcell_loc);
|
|
|
|
/* The split we are moving to */
|
|
new_split = gnc_split_register_get_split (reg, new_virt_loc.vcell_loc);
|
|
|
|
/* The split at the transaction line we are moving to */
|
|
new_trans_split = gnc_split_register_get_trans_split
|
|
(reg, new_virt_loc.vcell_loc, NULL);
|
|
|
|
new_class = gnc_split_register_get_cursor_class (reg,
|
|
new_virt_loc.vcell_loc);
|
|
}
|
|
else
|
|
{
|
|
new_trans = info->cursor_hint_trans;
|
|
new_split = info->cursor_hint_split;
|
|
new_trans_split = info->cursor_hint_trans_split;
|
|
new_class = info->cursor_hint_cursor_class;
|
|
}
|
|
|
|
info->hint_set_by_traverse = FALSE;
|
|
info->reg_loaded = FALSE;
|
|
|
|
gnc_suspend_gui_refresh ();
|
|
|
|
/* commit the contents of the cursor into the database */
|
|
saved = gnc_split_register_save (reg, old_trans != new_trans);
|
|
pending_trans = xaccTransLookup (&info->pending_trans_guid,
|
|
gnc_get_current_book ());
|
|
if ((old_class == CURSOR_CLASS_SPLIT) &&
|
|
old_split &&
|
|
(old_split != new_split) &&
|
|
gnc_split_register_old_split_empty_p(reg, old_split))
|
|
{
|
|
int current_row;
|
|
|
|
xaccSplitDestroy(old_split);
|
|
old_split = NULL;
|
|
|
|
/*
|
|
* If the user is moving down a row, we've just thrown off the
|
|
* numbers by deleting a split. Correct for that.
|
|
*/
|
|
current_row = reg->table->current_cursor_loc.vcell_loc.virt_row;
|
|
if (new_virt_loc.vcell_loc.virt_row > current_row)
|
|
new_virt_loc.vcell_loc.virt_row--;
|
|
}
|
|
else if ((pending_trans != NULL) &&
|
|
(pending_trans == old_trans) &&
|
|
(old_trans != new_trans))
|
|
{
|
|
if (gnc_split_register_balance_trans (reg, pending_trans))
|
|
{
|
|
/* Trans was unbalanced. */
|
|
new_trans = old_trans;
|
|
new_split = old_split;
|
|
new_trans_split = old_trans_split;
|
|
new_class = old_class;
|
|
new_virt_loc = reg->table->current_cursor_loc;
|
|
}
|
|
else
|
|
{
|
|
/* Trans was balanced. Let it go. */
|
|
info->pending_trans_guid = *guid_null ();
|
|
if (xaccTransIsOpen (pending_trans))
|
|
xaccTransCommitEdit (pending_trans);
|
|
else g_assert_not_reached();
|
|
|
|
pending_trans = NULL;
|
|
saved = TRUE;
|
|
}
|
|
}
|
|
else if (old_trans &&
|
|
(old_trans != new_trans) &&
|
|
!xaccTransHasReconciledSplits(old_trans) &&
|
|
!info->first_pass &&
|
|
gnc_split_register_balance_trans (reg, old_trans))
|
|
{
|
|
/* no matter what, stay there so the user can see what happened */
|
|
new_trans = old_trans;
|
|
new_split = old_split;
|
|
new_trans_split = old_trans_split;
|
|
new_class = old_class;
|
|
new_virt_loc = reg->table->current_cursor_loc;
|
|
}
|
|
|
|
if (saved)
|
|
{
|
|
info->cursor_hint_trans = new_trans;
|
|
info->cursor_hint_split = new_split;
|
|
info->cursor_hint_trans_split = new_trans_split;
|
|
info->cursor_hint_cursor_class = new_class;
|
|
}
|
|
|
|
if (old_split != new_split)
|
|
{
|
|
info->change_confirmed = FALSE;
|
|
info->rate_account = NULL;
|
|
info->rate_reset = FALSE;
|
|
}
|
|
|
|
gnc_resume_gui_refresh ();
|
|
|
|
/* redrawing the register can muck everything up */
|
|
if (saved)
|
|
{
|
|
VirtualCellLocation vcell_loc;
|
|
|
|
if (!info->reg_loaded)
|
|
gnc_split_register_redraw (reg);
|
|
|
|
/* if the split we were going to is still in the register,
|
|
* then it may have moved. Find out where it is now. */
|
|
if (gnc_split_register_find_split (reg, new_trans, new_trans_split,
|
|
new_split, new_class, &vcell_loc))
|
|
{
|
|
|
|
gnc_table_get_virtual_cell (reg->table, vcell_loc);
|
|
new_virt_loc.vcell_loc = vcell_loc;
|
|
}
|
|
else
|
|
new_virt_loc.vcell_loc = reg->table->current_cursor_loc.vcell_loc;
|
|
|
|
new_trans = gnc_split_register_get_trans (reg, new_virt_loc.vcell_loc);
|
|
new_split = gnc_split_register_get_split (reg, new_virt_loc.vcell_loc);
|
|
new_trans_split = gnc_split_register_get_trans_split(
|
|
reg, new_virt_loc.vcell_loc, NULL);
|
|
new_class = gnc_split_register_get_cursor_class (reg,
|
|
new_virt_loc.vcell_loc);
|
|
}
|
|
else if (info->traverse_to_new)
|
|
{
|
|
new_trans = info->cursor_hint_trans;
|
|
new_split = info->cursor_hint_split;
|
|
new_trans_split = info->cursor_hint_trans_split;
|
|
new_class = info->cursor_hint_cursor_class;
|
|
}
|
|
|
|
gnc_table_find_close_valid_cell (reg->table, &new_virt_loc, exact_traversal);
|
|
|
|
*p_new_virt_loc = new_virt_loc;
|
|
|
|
PINFO ("after move %d %d \n",
|
|
new_virt_loc.vcell_loc.virt_row,
|
|
new_virt_loc.vcell_loc.virt_col);
|
|
|
|
/* if the register was reloaded, then everything should be fine :)
|
|
* otherwise, we may need to change some visibility settings. */
|
|
if (saved)
|
|
{
|
|
gnc_split_register_set_cell_fractions (reg, new_split);
|
|
|
|
LEAVE("saved");
|
|
return;
|
|
}
|
|
|
|
/* in the mult-line and dynamic modes, we need to hide the old
|
|
* and show the new. */
|
|
if (((REG_STYLE_AUTO_LEDGER == reg->style) ||
|
|
(REG_STYLE_JOURNAL == reg->style) ||
|
|
info->trans_expanded) &&
|
|
(old_trans_split != new_trans_split))
|
|
{
|
|
VirtualCellLocation vc_loc;
|
|
|
|
vc_loc = old_trans_split_loc;
|
|
gnc_table_set_virt_cell_cursor(
|
|
reg->table, vc_loc, gnc_split_register_get_passive_cursor (reg));
|
|
gnc_split_register_set_trans_visible (reg, vc_loc, FALSE,
|
|
reg->style == REG_STYLE_JOURNAL);
|
|
|
|
if ((REG_STYLE_AUTO_LEDGER == reg->style) ||
|
|
(REG_STYLE_JOURNAL == reg->style))
|
|
{
|
|
gnc_split_register_get_trans_split (reg, new_virt_loc.vcell_loc,
|
|
&vc_loc);
|
|
gnc_table_set_virt_cell_cursor
|
|
(reg->table, vc_loc, gnc_split_register_get_active_cursor (reg));
|
|
gnc_split_register_set_trans_visible (reg, vc_loc, TRUE,
|
|
reg->style == REG_STYLE_JOURNAL);
|
|
}
|
|
|
|
info->trans_expanded = FALSE;
|
|
|
|
do_refresh = TRUE;
|
|
}
|
|
else
|
|
do_refresh = FALSE;
|
|
|
|
info->cursor_hint_trans = new_trans;
|
|
info->cursor_hint_split = new_split;
|
|
info->cursor_hint_trans_split = new_trans_split;
|
|
info->cursor_hint_cursor_class = new_class;
|
|
|
|
gnc_split_register_set_cell_fractions (reg, new_split);
|
|
|
|
gnc_table_find_close_valid_cell (reg->table, p_new_virt_loc,
|
|
exact_traversal);
|
|
|
|
if (do_refresh)
|
|
{
|
|
VirtualCellLocation vc_loc;
|
|
|
|
gnc_table_refresh_gui (reg->table, FALSE);
|
|
gnc_table_leave_update (reg->table, reg->table->current_cursor_loc);
|
|
|
|
gnc_split_register_get_trans_split (reg, p_new_virt_loc->vcell_loc,
|
|
&vc_loc);
|
|
gnc_split_register_show_trans (reg, vc_loc);
|
|
}
|
|
|
|
LEAVE(" ");
|
|
}
|
|
|
|
static Split *
|
|
gnc_find_split_in_trans_by_memo (Transaction *trans, const char *memo,
|
|
gboolean unit_price)
|
|
{
|
|
int i = 0;
|
|
Split *split;
|
|
|
|
while ((split = xaccTransGetSplit(trans, i)) != NULL)
|
|
{
|
|
i++;
|
|
if (unit_price)
|
|
{
|
|
gnc_numeric price = xaccSplitGetSharePrice (split);
|
|
if (!gnc_numeric_equal (price, gnc_numeric_create (1, 1)))
|
|
continue;
|
|
}
|
|
|
|
if (g_strcmp0 (memo, xaccSplitGetMemo (split)) == 0)
|
|
return split;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static Split *
|
|
gnc_find_split_in_account_by_memo (Account *account, const char *memo,
|
|
gboolean unit_price)
|
|
{
|
|
GList *slp;
|
|
|
|
if (account == NULL) return NULL;
|
|
|
|
for (slp = g_list_last (xaccAccountGetSplitList (account));
|
|
slp;
|
|
slp = slp->prev)
|
|
{
|
|
Split *split = slp->data;
|
|
Transaction *trans = xaccSplitGetParent (split);
|
|
|
|
split = gnc_find_split_in_trans_by_memo (trans, memo, unit_price);
|
|
|
|
if (split) return split;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static Split *
|
|
gnc_find_split_in_reg_by_memo (SplitRegister *reg, const char *memo,
|
|
gboolean unit_price)
|
|
{
|
|
int virt_row, virt_col;
|
|
int num_rows, num_cols;
|
|
Transaction *last_trans;
|
|
|
|
if (!reg || !reg->table)
|
|
return NULL;
|
|
|
|
num_rows = reg->table->num_virt_rows;
|
|
num_cols = reg->table->num_virt_cols;
|
|
|
|
last_trans = NULL;
|
|
|
|
for (virt_row = num_rows - 1; virt_row >= 0; virt_row--)
|
|
for (virt_col = num_cols - 1; virt_col >= 0; virt_col--)
|
|
{
|
|
Split *split;
|
|
Transaction *trans;
|
|
VirtualCellLocation vcell_loc = { virt_row, virt_col };
|
|
|
|
split = gnc_split_register_get_split (reg, vcell_loc);
|
|
trans = xaccSplitGetParent (split);
|
|
|
|
if (trans == last_trans)
|
|
continue;
|
|
|
|
split = gnc_find_split_in_trans_by_memo (trans, memo, unit_price);
|
|
if (split != NULL)
|
|
return split;
|
|
|
|
last_trans = trans;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static Transaction *
|
|
gnc_find_trans_in_reg_by_desc (SplitRegister *reg, const char *description)
|
|
{
|
|
int virt_row, virt_col;
|
|
int num_rows, num_cols;
|
|
Transaction *last_trans;
|
|
|
|
if (!reg || !reg->table)
|
|
return NULL;
|
|
|
|
num_rows = reg->table->num_virt_rows;
|
|
num_cols = reg->table->num_virt_cols;
|
|
|
|
last_trans = NULL;
|
|
|
|
for (virt_row = num_rows - 1; virt_row >= 0; virt_row--)
|
|
for (virt_col = num_cols - 1; virt_col >= 0; virt_col--)
|
|
{
|
|
Split *split;
|
|
Transaction *trans;
|
|
VirtualCellLocation vcell_loc = { virt_row, virt_col };
|
|
|
|
split = gnc_split_register_get_split (reg, vcell_loc);
|
|
trans = xaccSplitGetParent(split);
|
|
|
|
if (trans == last_trans)
|
|
continue;
|
|
|
|
if (g_strcmp0 (description, xaccTransGetDescription (trans)) == 0)
|
|
return trans;
|
|
|
|
last_trans = trans;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* This function determines if auto-completion is appropriate and,
|
|
* if so, performs it. This should only be called by LedgerTraverse. */
|
|
static gboolean
|
|
gnc_split_register_auto_completion (SplitRegister *reg,
|
|
gncTableTraversalDir dir,
|
|
VirtualLocation *p_new_virt_loc)
|
|
{
|
|
SRInfo *info = gnc_split_register_get_info (reg);
|
|
VirtualLocation new_virt_loc;
|
|
CursorClass cursor_class;
|
|
Transaction *pending_trans;
|
|
Transaction *blank_trans;
|
|
const char *cell_name;
|
|
Transaction *trans;
|
|
Split *blank_split;
|
|
gnc_numeric amount;
|
|
BasicCell *cell;
|
|
Split *split;
|
|
|
|
if (!reg->do_auto_complete)
|
|
return FALSE;
|
|
|
|
blank_split = xaccSplitLookup (&info->blank_split_guid,
|
|
gnc_get_current_book ());
|
|
blank_trans = xaccSplitGetParent (blank_split);
|
|
|
|
pending_trans = xaccTransLookup (&info->pending_trans_guid,
|
|
gnc_get_current_book ());
|
|
|
|
/* auto-completion is only triggered by a tab out */
|
|
if (dir != GNC_TABLE_TRAVERSE_RIGHT)
|
|
return FALSE;
|
|
|
|
split = gnc_split_register_get_current_split (reg);
|
|
trans = gnc_split_register_get_current_trans (reg);
|
|
if (trans == NULL)
|
|
return FALSE;
|
|
|
|
cursor_class = gnc_split_register_get_current_cursor_class (reg);
|
|
cell_name = gnc_table_get_current_cell_name (reg->table);
|
|
|
|
switch (cursor_class)
|
|
{
|
|
case CURSOR_CLASS_TRANS:
|
|
{
|
|
Transaction *auto_trans;
|
|
const char *desc;
|
|
|
|
/* there must be a blank transaction * */
|
|
if (blank_trans == NULL)
|
|
return FALSE;
|
|
|
|
/* we must be on the blank split */
|
|
if (trans != blank_trans)
|
|
return FALSE;
|
|
|
|
/* and leaving the description cell */
|
|
if (!gnc_cell_name_equal (cell_name, DESC_CELL))
|
|
return FALSE;
|
|
|
|
/* nothing but the date, num, and description should be changed */
|
|
/* FIXME, this should be refactored. */
|
|
if (gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
XFRM_CELL, TRUE) ||
|
|
gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
MXFRM_CELL, TRUE) ||
|
|
gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
PRIC_CELL, TRUE) ||
|
|
gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
SHRS_CELL, TRUE) ||
|
|
gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
DEBT_CELL, TRUE) ||
|
|
gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
CRED_CELL, TRUE) ||
|
|
gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
NOTES_CELL, TRUE) ||
|
|
gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
RECN_CELL, TRUE))
|
|
return FALSE;
|
|
|
|
/* and the description should be changed */
|
|
if (!gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
DESC_CELL, TRUE))
|
|
return FALSE;
|
|
|
|
/* to a non-empty value */
|
|
desc = gnc_table_layout_get_cell_value (reg->table->layout, DESC_CELL);
|
|
if ((desc == NULL) || (*desc == '\0'))
|
|
return FALSE;
|
|
|
|
/* find a transaction to auto-complete on */
|
|
if (gnc_split_register_get_default_account (reg) != NULL)
|
|
{
|
|
Account *account = gnc_split_register_get_default_account (reg);
|
|
|
|
auto_trans = xaccAccountFindTransByDesc(account, desc);
|
|
}
|
|
else
|
|
auto_trans = gnc_find_trans_in_reg_by_desc(reg, desc);
|
|
|
|
if (auto_trans == NULL)
|
|
return FALSE;
|
|
|
|
gnc_suspend_gui_refresh ();
|
|
|
|
/* We are guaranteed to be on the blank trans, so we can
|
|
discount the possibility that the current transaction is
|
|
being edited in another register. */
|
|
/* now perform the completion */
|
|
if (pending_trans != trans)
|
|
{
|
|
if (!xaccTransIsOpen(trans))
|
|
xaccTransBeginEdit(trans);
|
|
/* This is now the pending transaction */
|
|
info->pending_trans_guid = *xaccTransGetGUID(trans);
|
|
if (pending_trans != NULL)
|
|
{
|
|
if (xaccTransIsOpen (pending_trans))
|
|
xaccTransCommitEdit (pending_trans);
|
|
else g_assert_not_reached();
|
|
}
|
|
}
|
|
g_assert(xaccTransIsOpen(trans));
|
|
pending_trans = xaccTransLookup(&info->pending_trans_guid,
|
|
gnc_get_current_book ());
|
|
g_assert(pending_trans == trans);
|
|
|
|
gnc_copy_trans_onto_trans (auto_trans, trans, FALSE, FALSE);
|
|
blank_split = NULL;
|
|
|
|
if (gnc_split_register_get_default_account (reg) != NULL)
|
|
{
|
|
Account *default_account;
|
|
Split *s;
|
|
int i = 0;
|
|
|
|
default_account = gnc_split_register_get_default_account (reg);
|
|
|
|
while ((s = xaccTransGetSplit(trans, i)) != NULL)
|
|
{
|
|
if (default_account == xaccSplitGetAccount(s))
|
|
{
|
|
blank_split = s;
|
|
info->blank_split_guid = *xaccSplitGetGUID(blank_split);
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
if (blank_split == NULL)
|
|
{
|
|
blank_split = xaccTransGetSplit(trans, 0);
|
|
info->blank_split_guid = *xaccSplitGetGUID(blank_split);
|
|
}
|
|
DEBUG("blank_split=%p", blank_split);
|
|
|
|
info->blank_split_edited = TRUE;
|
|
|
|
{
|
|
SRSaveData *sd;
|
|
|
|
sd = gnc_split_register_save_data_new(
|
|
trans, blank_split, (info->trans_expanded ||
|
|
reg->style == REG_STYLE_AUTO_LEDGER ||
|
|
reg->style == REG_STYLE_JOURNAL));
|
|
gnc_table_save_cells (reg->table, sd);
|
|
gnc_split_register_save_data_destroy (sd);
|
|
}
|
|
|
|
gnc_resume_gui_refresh ();
|
|
|
|
/* now move to the non-empty amount column unless config setting says not */
|
|
if ( !gnc_gconf_get_bool(GCONF_GENERAL_REGISTER,
|
|
"tab_includes_transfer_on_memorised", NULL) )
|
|
{
|
|
amount = xaccSplitGetAmount (blank_split);
|
|
cell_name = (gnc_numeric_negative_p (amount)) ? CRED_CELL : DEBT_CELL;
|
|
|
|
if (gnc_table_get_current_cell_location (reg->table, cell_name,
|
|
&new_virt_loc))
|
|
*p_new_virt_loc = new_virt_loc;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case CURSOR_CLASS_SPLIT:
|
|
{
|
|
char *account_name;
|
|
const char *memo;
|
|
gboolean unit_price;
|
|
Split *auto_split;
|
|
|
|
/* we must be on a blank split of a transaction */
|
|
if (split != NULL)
|
|
return FALSE;
|
|
|
|
/* and leaving the memo cell */
|
|
if (!gnc_cell_name_equal (cell_name, MEMO_CELL))
|
|
return FALSE;
|
|
|
|
/* nothing but the action, memo, and amounts should be changed */
|
|
/* FIXME. This should be refactored. */
|
|
if (gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
XFRM_CELL, TRUE) ||
|
|
gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
MXFRM_CELL, TRUE) ||
|
|
gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
PRIC_CELL, TRUE) ||
|
|
gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
SHRS_CELL, TRUE) ||
|
|
gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
RECN_CELL, TRUE))
|
|
return FALSE;
|
|
|
|
/* and the memo should be changed */
|
|
if (!gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
MEMO_CELL, TRUE))
|
|
return FALSE;
|
|
|
|
/* to a non-empty value */
|
|
memo = gnc_table_layout_get_cell_value (reg->table->layout, MEMO_CELL);
|
|
if ((memo == NULL) || (*memo == '\0'))
|
|
return FALSE;
|
|
|
|
/* if there is no price field, only auto-complete from splits with
|
|
* a unit share price. */
|
|
unit_price = !gnc_table_get_current_cell_location (reg->table,
|
|
PRIC_CELL, NULL);
|
|
|
|
/* find a split to auto-complete on */
|
|
if (gnc_split_register_get_default_account (reg) != NULL)
|
|
{
|
|
Account *account = gnc_split_register_get_default_account (reg);
|
|
|
|
auto_split = gnc_find_split_in_account_by_memo (account, memo,
|
|
unit_price);
|
|
}
|
|
else
|
|
auto_split = gnc_find_split_in_reg_by_memo (reg, memo, unit_price);
|
|
|
|
if (auto_split == NULL)
|
|
return FALSE;
|
|
|
|
/* the auto-complete code below is taken from xaccSRGetEntryHandler */
|
|
|
|
/* auto-complete the action field if it wasn't changed */
|
|
if (!gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
ACTN_CELL, TRUE))
|
|
{
|
|
cell = gnc_table_layout_get_cell (reg->table->layout, ACTN_CELL);
|
|
gnc_combo_cell_set_value ((ComboCell *) cell,
|
|
xaccSplitGetAction (auto_split));
|
|
}
|
|
|
|
/* auto-complete the account name */
|
|
cell = gnc_table_layout_get_cell (reg->table->layout, XFRM_CELL);
|
|
|
|
account_name = gnc_get_account_name_for_register (xaccSplitGetAccount (auto_split));
|
|
gnc_combo_cell_set_value ((ComboCell *) cell, account_name);
|
|
g_free(account_name);
|
|
|
|
gnc_basic_cell_set_changed (cell, TRUE);
|
|
|
|
if (!gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
DEBT_CELL, TRUE) &&
|
|
!gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
CRED_CELL, TRUE))
|
|
{
|
|
BasicCell *debit_cell;
|
|
BasicCell *credit_cell;
|
|
|
|
amount = xaccSplitGetValue (auto_split);
|
|
|
|
debit_cell = gnc_table_layout_get_cell (reg->table->layout,
|
|
DEBT_CELL);
|
|
credit_cell = gnc_table_layout_get_cell (reg->table->layout,
|
|
CRED_CELL);
|
|
|
|
gnc_price_cell_set_debt_credit_value ((PriceCell *) debit_cell,
|
|
(PriceCell *) credit_cell,
|
|
amount);
|
|
|
|
gnc_basic_cell_set_changed (debit_cell, TRUE);
|
|
gnc_basic_cell_set_changed (credit_cell, TRUE);
|
|
}
|
|
|
|
/* and refresh the gui */
|
|
gnc_table_refresh_gui (reg->table, TRUE);
|
|
|
|
/* now move to the non-empty amount column */
|
|
amount = xaccSplitGetAmount (auto_split);
|
|
cell_name = (gnc_numeric_negative_p (amount)) ? CRED_CELL : DEBT_CELL;
|
|
|
|
if (gnc_table_get_current_cell_location (reg->table, cell_name,
|
|
&new_virt_loc))
|
|
*p_new_virt_loc = new_virt_loc;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gnc_split_register_check_stock_action (SplitRegister *reg,
|
|
const char *cell_name)
|
|
{
|
|
BasicCell *cell;
|
|
gnc_numeric shares;
|
|
gboolean buy, sell;
|
|
const char *name;
|
|
|
|
if (!gnc_cell_name_equal (cell_name, ACTN_CELL) ||
|
|
!gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
ACTN_CELL, FALSE))
|
|
return;
|
|
|
|
cell = gnc_table_layout_get_cell (reg->table->layout, ACTN_CELL);
|
|
if (!cell)
|
|
return;
|
|
name = ((ComboCell *)cell)->cell.value;
|
|
if ((name == NULL) || (*name == '\0'))
|
|
return;
|
|
|
|
buy = g_strcmp0 (name, ACTION_BUY_STR) == 0;
|
|
sell = g_strcmp0 (name, ACTION_SELL_STR) == 0;
|
|
if (!buy && !sell)
|
|
return;
|
|
|
|
cell = gnc_table_layout_get_cell (reg->table->layout, SHRS_CELL);
|
|
if (!cell)
|
|
return;
|
|
shares = gnc_price_cell_get_value ((PriceCell *) cell);
|
|
|
|
if ((buy && !gnc_numeric_positive_p (shares)) ||
|
|
(sell && gnc_numeric_positive_p (shares)))
|
|
{
|
|
gnc_price_cell_set_value ((PriceCell *)cell, gnc_numeric_neg (shares));
|
|
gnc_basic_cell_set_changed (cell, TRUE);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gnc_split_register_check_stock_shares (SplitRegister *reg,
|
|
const char *cell_name)
|
|
{
|
|
BasicCell *cell;
|
|
gnc_numeric shares;
|
|
gboolean buy;
|
|
const char *name;
|
|
|
|
if (!gnc_cell_name_equal (cell_name, SHRS_CELL) ||
|
|
!gnc_table_layout_get_cell_changed (reg->table->layout,
|
|
SHRS_CELL, FALSE))
|
|
return;
|
|
|
|
cell = gnc_table_layout_get_cell (reg->table->layout, SHRS_CELL);
|
|
if (!cell)
|
|
return;
|
|
shares = gnc_price_cell_get_value ((PriceCell *) cell);
|
|
if (gnc_numeric_zero_p (shares))
|
|
return;
|
|
buy = gnc_numeric_positive_p (shares);
|
|
|
|
cell = gnc_table_layout_get_cell (reg->table->layout, ACTN_CELL);
|
|
if (!cell)
|
|
return;
|
|
name = ((ComboCell *)cell)->cell.value;
|
|
|
|
if (!g_strcmp0(name, "") ||
|
|
!g_strcmp0(name, buy ? ACTION_SELL_STR : ACTION_BUY_STR))
|
|
{
|
|
gnc_combo_cell_set_value((ComboCell *)cell,
|
|
buy ? ACTION_BUY_STR : ACTION_SELL_STR);
|
|
gnc_basic_cell_set_changed (cell, TRUE);
|
|
}
|
|
}
|
|
|
|
/* This function checks a cell for changes and takes appropriate action if a
|
|
* change has occurred. It is recommended to call this function just before
|
|
* leaving a cell. Returns FALSE if control should remain in this cell. For
|
|
* example, the user may have made a mistake and needs another chance to
|
|
* edit the information before moving on. */
|
|
gboolean
|
|
gnc_split_register_check_cell (SplitRegister *reg, const char *cell_name)
|
|
{
|
|
ENTER("reg=%p, cell_name=%s", reg, cell_name ? cell_name : "NULL");
|
|
|
|
/* See if we are leaving an account field. */
|
|
if (!gnc_split_register_check_account (reg, cell_name))
|
|
{
|
|
LEAVE("account check failed");
|
|
return FALSE;
|
|
}
|
|
|
|
/* See if we are leaving an action field */
|
|
if ((reg->type == STOCK_REGISTER) ||
|
|
(reg->type == PORTFOLIO_LEDGER) ||
|
|
(reg->type == CURRENCY_REGISTER))
|
|
{
|
|
gnc_split_register_check_stock_action (reg, cell_name);
|
|
gnc_split_register_check_stock_shares (reg, cell_name);
|
|
}
|
|
|
|
LEAVE(" ");
|
|
return TRUE;
|
|
}
|
|
|
|
static Account *
|
|
gnc_split_register_get_account_always (SplitRegister *reg,
|
|
const char * cell_name)
|
|
{
|
|
BasicCell *cell;
|
|
const char *name;
|
|
|
|
cell = gnc_table_layout_get_cell (reg->table->layout, cell_name);
|
|
if (!cell)
|
|
return NULL;
|
|
name = gnc_basic_cell_get_value (cell);
|
|
|
|
/* If 'name' is "-- Split Transaction --" then return NULL or the
|
|
register acct */
|
|
if (!g_strcmp0 (name, SPLIT_TRANS_STR))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return gnc_split_register_get_account_by_name (reg, cell, name);
|
|
}
|
|
|
|
#if 0 /* Not Used */
|
|
static const char *
|
|
gnc_split_register_get_cell_string (SplitRegister *reg, const char *cell_name)
|
|
{
|
|
BasicCell *cell;
|
|
|
|
cell = gnc_table_layout_get_cell (reg->table->layout, cell_name);
|
|
if (!cell)
|
|
return "";
|
|
|
|
return gnc_basic_cell_get_value (cell);
|
|
}
|
|
|
|
static Timespec
|
|
gnc_split_register_get_cell_date (SplitRegister *reg, const char *cell_name)
|
|
{
|
|
DateCell *cell;
|
|
Timespec ts;
|
|
|
|
cell = (DateCell*) gnc_table_layout_get_cell (reg->table->layout, cell_name);
|
|
|
|
if (cell)
|
|
gnc_date_cell_get_date (cell, &ts);
|
|
else
|
|
timespecFromTime64 (&ts, gnc_time (NULL));
|
|
|
|
return ts;
|
|
}
|
|
#endif /* Not Used */
|
|
|
|
/* Creates a transfer dialog and fills its values from register cells (if
|
|
* available) or from the provided transaction and split.
|
|
*/
|
|
static XferDialog *
|
|
gnc_split_register_xfer_dialog(SplitRegister *reg, Transaction *txn,
|
|
Split *split)
|
|
{
|
|
XferDialog *xfer;
|
|
CellBlock *cur;
|
|
BasicCell *cell;
|
|
|
|
g_return_val_if_fail(reg, NULL);
|
|
g_return_val_if_fail(reg->table, NULL);
|
|
cur = reg->table->current_cursor;
|
|
|
|
/* Create the exchange rate dialog. */
|
|
xfer = gnc_xfer_dialog(NULL, NULL);
|
|
g_return_val_if_fail(xfer, NULL);
|
|
|
|
/* Set the description. */
|
|
cell = gnc_cellblock_get_cell_by_name(cur, DESC_CELL, NULL, NULL);
|
|
if (cell)
|
|
gnc_xfer_dialog_set_description(xfer, gnc_basic_cell_get_value(cell));
|
|
else
|
|
{
|
|
const char *str = xaccTransGetDescription(txn);
|
|
gnc_xfer_dialog_set_description(xfer, str ? str : "");
|
|
}
|
|
|
|
/* Set the memo. */
|
|
cell = gnc_cellblock_get_cell_by_name(cur, MEMO_CELL, NULL, NULL);
|
|
if (cell)
|
|
gnc_xfer_dialog_set_memo(xfer, gnc_basic_cell_get_value(cell));
|
|
else
|
|
{
|
|
const char *str = xaccSplitGetMemo(split);
|
|
gnc_xfer_dialog_set_memo(xfer, str ? str : "");
|
|
}
|
|
|
|
/* Set the num. */
|
|
cell = gnc_cellblock_get_cell_by_name(cur, NUM_CELL, NULL, NULL);
|
|
if (cell)
|
|
gnc_xfer_dialog_set_num(xfer, gnc_basic_cell_get_value(cell));
|
|
else
|
|
{
|
|
const char *str = xaccTransGetNum(txn);
|
|
gnc_xfer_dialog_set_num(xfer, str ? str : "");
|
|
}
|
|
|
|
/* Set the date. */
|
|
cell = gnc_cellblock_get_cell_by_name(cur, DATE_CELL, NULL, NULL);
|
|
if (cell)
|
|
{
|
|
Timespec ts;
|
|
gnc_date_cell_get_date((DateCell*) cell, &ts);
|
|
gnc_xfer_dialog_set_date(xfer, timespecToTime64(ts));
|
|
}
|
|
else
|
|
gnc_xfer_dialog_set_date(xfer, xaccTransGetDate(txn));
|
|
|
|
return xfer;
|
|
}
|
|
|
|
/* This function checks to see if we need to determine an exchange rate.
|
|
* If we need to determine an exchange rate, then pop up the dialog.
|
|
* If the dialog does not complete successfully, then return TRUE.
|
|
* Return FALSE in all other cases (meaning "move on")
|
|
*/
|
|
gboolean
|
|
gnc_split_register_handle_exchange (SplitRegister *reg, gboolean force_dialog)
|
|
{
|
|
SRInfo *info;
|
|
Transaction *txn;
|
|
Split *split, *osplit;
|
|
Account *xfer_acc, *reg_acc;
|
|
gnc_commodity *txn_cur, *xfer_com, *reg_com;
|
|
gnc_numeric amount, exch_rate;
|
|
XferDialog *xfer;
|
|
gboolean expanded = FALSE;
|
|
PriceCell *rate_cell;
|
|
const char *message;
|
|
CursorClass cursor_class;
|
|
|
|
ENTER("reg=%p, force_dialog=%s", reg, force_dialog ? "TRUE" : "FALSE" );
|
|
|
|
/* Make sure we NEED this for this type of register */
|
|
if (!gnc_split_reg_has_rate_cell (reg->type))
|
|
{
|
|
if (force_dialog)
|
|
{
|
|
message = _("This register does not support editing exchange rates.");
|
|
gnc_error_dialog(gnc_split_register_get_parent(reg), "%s", message);
|
|
}
|
|
LEAVE("no rate cell");
|
|
return FALSE;
|
|
}
|
|
|
|
rate_cell = (PriceCell*) gnc_table_layout_get_cell(
|
|
reg->table->layout, RATE_CELL);
|
|
if (!rate_cell)
|
|
{
|
|
if (force_dialog)
|
|
{
|
|
message = _("This register does not support editing exchange rates.");
|
|
gnc_error_dialog(gnc_split_register_get_parent(reg), "%s", message);
|
|
}
|
|
LEAVE("null rate cell");
|
|
return FALSE;
|
|
}
|
|
|
|
/* See if we already have an exchange rate... */
|
|
exch_rate = gnc_price_cell_get_value (rate_cell);
|
|
if (!gnc_numeric_zero_p(exch_rate) && !force_dialog)
|
|
{
|
|
LEAVE("rate already non-zero");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Are we expanded? */
|
|
expanded = gnc_split_register_current_trans_expanded (reg);
|
|
cursor_class = gnc_split_register_get_current_cursor_class (reg);
|
|
|
|
/* If we're expanded AND a transaction cursor, there is nothing to do */
|
|
if (expanded && cursor_class == CURSOR_CLASS_TRANS)
|
|
{
|
|
if (force_dialog)
|
|
{
|
|
message = _("You need to select a split in order to modify its exchange "
|
|
"rate.");
|
|
gnc_error_dialog(gnc_split_register_get_parent(reg), "%s", message);
|
|
}
|
|
LEAVE("expanded with transaction cursor; nothing to do");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Grab the xfer account */
|
|
xfer_acc = gnc_split_register_get_account_always(
|
|
reg, expanded ? XFRM_CELL : MXFRM_CELL);
|
|
|
|
/* If this is an un-expanded, multi-split transaction, then warn the user */
|
|
if (force_dialog && !expanded && !xfer_acc)
|
|
{
|
|
message = _("You need to expand the transaction in order to modify its "
|
|
"exchange rates.");
|
|
gnc_error_dialog (gnc_split_register_get_parent (reg), "%s", message);
|
|
LEAVE("%s", message);
|
|
return TRUE;
|
|
}
|
|
|
|
/* No account -- don't run the dialog */
|
|
if (!xfer_acc)
|
|
{
|
|
if (force_dialog)
|
|
{
|
|
message = _("The entered account could not be found.");
|
|
gnc_error_dialog(gnc_split_register_get_parent(reg), "%s", message);
|
|
}
|
|
LEAVE("no xfer account");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Grab the txn currency and xfer commodity */
|
|
txn = gnc_split_register_get_current_trans (reg);
|
|
txn_cur = xaccTransGetCurrency (txn);
|
|
xfer_com = xaccAccountGetCommodity (xfer_acc);
|
|
|
|
/* Grab the register account and commodity (may be used later) */
|
|
reg_acc = gnc_split_register_get_default_account (reg);
|
|
reg_com = xaccAccountGetCommodity (reg_acc);
|
|
|
|
/* Grab the split and perhaps the "other" split (if it is a two-split txn) */
|
|
split = gnc_split_register_get_current_split (reg);
|
|
osplit = xaccSplitGetOtherSplit (split);
|
|
|
|
/* Check if the txn- and xfer- commodities are the same */
|
|
if (gnc_commodity_equal (txn_cur, xfer_com))
|
|
{
|
|
/* If we're not forcing the dialog, then there is no reason to
|
|
* go on. We're using the correct accounts.
|
|
*/
|
|
if (!force_dialog)
|
|
{
|
|
LEAVE("txn and account currencies match, and not forcing");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Only proceed with two-split, basic, non-expanded registers */
|
|
if (expanded || osplit == NULL)
|
|
{
|
|
message = _("The two currencies involved equal each other.");
|
|
gnc_error_dialog(gnc_split_register_get_parent(reg), "%s", message);
|
|
LEAVE("register is expanded or osplit == NULL; not forcing dialog");
|
|
return FALSE;
|
|
}
|
|
|
|
/* If we're forcing, then compare the current account
|
|
* commodity to the transaction currency.
|
|
*/
|
|
xfer_acc = reg_acc;
|
|
xfer_com = reg_com;
|
|
if (gnc_commodity_equal (txn_cur, xfer_com))
|
|
{
|
|
message = _("The two currencies involved equal each other.");
|
|
gnc_error_dialog(gnc_split_register_get_parent(reg), "%s", message);
|
|
LEAVE("reg commodity == txn commodity; not forcing");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* If this is a non-expanded, two-split txn where BOTH splits need
|
|
* conversion rates, then require the user to actually expand the
|
|
* transaction in order to edit it.
|
|
*/
|
|
if (!expanded && osplit &&
|
|
gnc_split_register_split_needs_amount (reg, split) &&
|
|
gnc_split_register_split_needs_amount (reg, osplit))
|
|
{
|
|
message = _("You need to expand the transaction in order to modify its "
|
|
"exchange rates.");
|
|
if (force_dialog)
|
|
{
|
|
gnc_error_dialog (gnc_split_register_get_parent (reg), "%s", message);
|
|
}
|
|
LEAVE("%s", message);
|
|
return TRUE;
|
|
}
|
|
|
|
/* Strangely, if we're in a two-split, non-expanded txn, we need
|
|
* to do something really special with the exchange rate! In
|
|
* particular, we have to pick it up from the _other_ split --
|
|
* right?
|
|
* XXX: perhaps I should pop up an error here? Or maybe require the
|
|
* user to go into expanded-mode?
|
|
*/
|
|
if (!expanded && osplit && !gnc_commodity_equal(reg_com, txn_cur) &&
|
|
!gnc_commodity_equal(reg_com, xfer_com))
|
|
{
|
|
gnc_numeric amt = xaccSplitGetAmount (osplit);
|
|
gnc_numeric val = xaccSplitGetValue (osplit);
|
|
exch_rate = gnc_numeric_div (amt, val, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE);
|
|
}
|
|
|
|
/* Ok, we need to grab the exchange rate */
|
|
amount = gnc_split_register_debcred_cell_value (reg);
|
|
|
|
/*
|
|
* If "amount" is zero then we don't need an exchange-rate.. Return
|
|
* FALSE to let the user continue on.
|
|
*/
|
|
if (gnc_numeric_zero_p (amount))
|
|
{
|
|
if (force_dialog)
|
|
{
|
|
message = _("The split's amount is zero, so no exchange rate is needed.");
|
|
gnc_error_dialog(gnc_split_register_get_parent(reg), "%s", message);
|
|
}
|
|
LEAVE("amount is zero; no exchange rate needed");
|
|
return FALSE;
|
|
}
|
|
|
|
/* If the exch_rate is zero, we're not forcing the dialog, and this is
|
|
* _not_ the blank split, then return FALSE -- this is a "special"
|
|
* gain/loss stock transaction.
|
|
*/
|
|
info = gnc_split_register_get_info (reg);
|
|
if (gnc_numeric_zero_p(exch_rate) && !force_dialog && split &&
|
|
!info->rate_reset &&
|
|
split != gnc_split_register_get_blank_split (reg))
|
|
{
|
|
LEAVE("gain/loss split; no exchange rate needed");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Show the exchange-rate dialog */
|
|
xfer = gnc_split_register_xfer_dialog(reg, txn, split);
|
|
gnc_xfer_dialog_is_exchange_dialog(xfer, &exch_rate);
|
|
if (gnc_xfer_dialog_run_exchange_dialog(
|
|
xfer, &exch_rate, amount, reg_acc, txn, xfer_com, expanded))
|
|
{
|
|
/* FIXME: How should the dialog be destroyed? */
|
|
LEAVE("leaving rate unchanged");
|
|
return TRUE;
|
|
}
|
|
/* FIXME: How should the dialog be destroyed? */
|
|
|
|
/* Set the RATE_CELL on this cursor and mark it changed */
|
|
gnc_price_cell_set_value (rate_cell, exch_rate);
|
|
gnc_basic_cell_set_changed (&rate_cell->cell, TRUE);
|
|
info->rate_account = xfer_acc;
|
|
info->rate_reset = FALSE;
|
|
LEAVE("set rate=%s", gnc_num_dbg_to_string(exch_rate));
|
|
return FALSE;
|
|
}
|
|
|
|
/* Returns FALSE if dialog was canceled. */
|
|
static gboolean
|
|
transaction_changed_confirm(VirtualLocation *p_new_virt_loc,
|
|
VirtualLocation *virt_loc,
|
|
SplitRegister *reg, Transaction *new_trans,
|
|
gboolean exact_traversal)
|
|
{
|
|
GtkWidget *dialog, *window;
|
|
gint response;
|
|
const char *title = _("Save the changed transaction?");
|
|
const char *message =
|
|
_("The current transaction has been changed. Would you like to "
|
|
"record the changes before moving to a new transaction, discard the "
|
|
"changes, or return to the changed transaction?");
|
|
|
|
window = gnc_split_register_get_parent(reg);
|
|
dialog = gtk_message_dialog_new(GTK_WINDOW(window),
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_QUESTION,
|
|
GTK_BUTTONS_NONE,
|
|
"%s", title);
|
|
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
|
|
"%s", message);
|
|
gtk_dialog_add_buttons(GTK_DIALOG(dialog),
|
|
_("_Discard Changes"), GTK_RESPONSE_REJECT,
|
|
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
|
_("_Record Changes"), GTK_RESPONSE_ACCEPT,
|
|
NULL);
|
|
response = gnc_dialog_run(GTK_DIALOG(dialog), "transaction_changed");
|
|
gtk_widget_destroy(dialog);
|
|
|
|
switch (response)
|
|
{
|
|
case GTK_RESPONSE_ACCEPT:
|
|
break;
|
|
|
|
case GTK_RESPONSE_REJECT:
|
|
{
|
|
VirtualCellLocation vcell_loc;
|
|
Split *new_split;
|
|
Split *trans_split;
|
|
CursorClass new_class;
|
|
|
|
new_split = gnc_split_register_get_split (reg, virt_loc->vcell_loc);
|
|
trans_split = gnc_split_register_get_trans_split (reg,
|
|
virt_loc->vcell_loc,
|
|
NULL);
|
|
new_class = gnc_split_register_get_cursor_class (reg,
|
|
virt_loc->vcell_loc);
|
|
|
|
gnc_split_register_cancel_cursor_trans_changes (reg);
|
|
|
|
if (gnc_split_register_find_split (reg, new_trans, trans_split,
|
|
new_split, new_class, &vcell_loc))
|
|
virt_loc->vcell_loc = vcell_loc;
|
|
|
|
gnc_table_find_close_valid_cell (reg->table, virt_loc,
|
|
exact_traversal);
|
|
|
|
*p_new_virt_loc = *virt_loc;
|
|
}
|
|
break;
|
|
|
|
case GTK_RESPONSE_CANCEL:
|
|
default:
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/** Examine a request to traverse to a new location in the register and
|
|
* decide whether it should be allowed to proceed, and where the new
|
|
* location will be.
|
|
*
|
|
* @param p_new_virt_loc a pointer to storage for the new location
|
|
*
|
|
* @param dir the direction of the traversal
|
|
*
|
|
* @param user_data pointer to a ::SplitRegister to traverse
|
|
*
|
|
* @return @c TRUE if the traversal cannot be completed. Otherwise,
|
|
* @c FALSE is returned and the new location is stored at @a p_new_virt_loc. */
|
|
static gboolean
|
|
gnc_split_register_traverse (VirtualLocation *p_new_virt_loc,
|
|
gncTableTraversalDir dir,
|
|
gpointer user_data)
|
|
{
|
|
SplitRegister *reg = user_data;
|
|
Transaction *pending_trans;
|
|
VirtualLocation virt_loc;
|
|
Transaction *trans, *new_trans;
|
|
gboolean changed;
|
|
SRInfo *info;
|
|
Split *split;
|
|
const char *cell_name;
|
|
|
|
g_return_val_if_fail(p_new_virt_loc, TRUE);
|
|
|
|
ENTER("reg=%p, p_new_virt_loc=%p (%d,%d), dir=%d",
|
|
reg, p_new_virt_loc, (*p_new_virt_loc).vcell_loc.virt_row,
|
|
(*p_new_virt_loc).vcell_loc.virt_col, dir);
|
|
|
|
if (!reg)
|
|
{
|
|
LEAVE("no register");
|
|
return FALSE;
|
|
}
|
|
|
|
info = gnc_split_register_get_info (reg);
|
|
|
|
if (info->first_pass)
|
|
{
|
|
LEAVE("first pass");
|
|
return FALSE;
|
|
}
|
|
|
|
pending_trans = xaccTransLookup (&info->pending_trans_guid,
|
|
gnc_get_current_book ());
|
|
virt_loc = *p_new_virt_loc;
|
|
|
|
info->exact_traversal = (dir == GNC_TABLE_TRAVERSE_POINTER);
|
|
|
|
split = gnc_split_register_get_current_split (reg);
|
|
trans = gnc_split_register_get_current_trans (reg);
|
|
if (trans == NULL)
|
|
{
|
|
LEAVE("no transaction");
|
|
return FALSE;
|
|
}
|
|
|
|
/* no changes, make sure we aren't going off the end */
|
|
changed = gnc_table_current_cursor_changed (reg->table, FALSE);
|
|
if (!changed && (pending_trans != trans))
|
|
{
|
|
gnc_table_find_close_valid_cell (reg->table, &virt_loc,
|
|
info->exact_traversal);
|
|
|
|
*p_new_virt_loc = virt_loc;
|
|
|
|
LEAVE("no changes");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Get the current cell-name and check it for changes. */
|
|
cell_name = gnc_table_get_current_cell_name (reg->table);
|
|
if (!gnc_split_register_check_cell (reg, cell_name))
|
|
{
|
|
LEAVE("check cell");
|
|
return TRUE;
|
|
}
|
|
|
|
/* See if we are tabbing off the end of the very last line */
|
|
do
|
|
{
|
|
VirtualLocation virt_loc;
|
|
|
|
if (!changed && !info->blank_split_edited)
|
|
break;
|
|
|
|
if (dir != GNC_TABLE_TRAVERSE_RIGHT)
|
|
break;
|
|
|
|
virt_loc = reg->table->current_cursor_loc;
|
|
if (gnc_table_move_vertical_position (reg->table, &virt_loc, 1))
|
|
break;
|
|
|
|
virt_loc = reg->table->current_cursor_loc;
|
|
if (gnc_table_move_tab (reg->table, &virt_loc, TRUE))
|
|
break;
|
|
|
|
/* Deal with the exchange-rate */
|
|
if (gnc_split_register_handle_exchange (reg, FALSE))
|
|
{
|
|
LEAVE("no exchange rate");
|
|
return TRUE;
|
|
}
|
|
|
|
*p_new_virt_loc = reg->table->current_cursor_loc;
|
|
(p_new_virt_loc->vcell_loc.virt_row)++;
|
|
p_new_virt_loc->phys_row_offset = 0;
|
|
p_new_virt_loc->phys_col_offset = 0;
|
|
|
|
info->traverse_to_new = TRUE;
|
|
|
|
LEAVE("off end of last line");
|
|
return FALSE;
|
|
|
|
}
|
|
while (FALSE);
|
|
|
|
/* Now see if we are changing cursors. If not, we may be able to
|
|
* auto-complete. */
|
|
if (!gnc_table_virtual_cell_out_of_bounds (reg->table, virt_loc.vcell_loc))
|
|
{
|
|
if (gnc_split_register_auto_completion (reg, dir, p_new_virt_loc))
|
|
{
|
|
LEAVE("auto-complete");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* See if we are tabbing off the end of a blank split */
|
|
do
|
|
{
|
|
VirtualLocation virt_loc;
|
|
int old_virt_row;
|
|
|
|
if (!changed)
|
|
break;
|
|
|
|
if (split)
|
|
break;
|
|
|
|
if (dir != GNC_TABLE_TRAVERSE_RIGHT)
|
|
break;
|
|
|
|
virt_loc = reg->table->current_cursor_loc;
|
|
old_virt_row = virt_loc.vcell_loc.virt_row;
|
|
|
|
if (gnc_table_move_tab (reg->table, &virt_loc, TRUE) &&
|
|
old_virt_row == virt_loc.vcell_loc.virt_row)
|
|
break;
|
|
|
|
/* If we are here, then: (a) the current cursor has been
|
|
* edited, and (b) we are on the blank split of a multi-line
|
|
* transaction, and (c) we are tabbing out of the last cell
|
|
* on the line. Thus, we want to go ahead and add the new
|
|
* split and end up on the new blank split of the current
|
|
* transaction. */
|
|
|
|
/* Deal with the exchange-rate */
|
|
if (gnc_split_register_handle_exchange (reg, FALSE))
|
|
{
|
|
LEAVE("no exchange rate");
|
|
return TRUE;
|
|
}
|
|
|
|
info->cursor_hint_trans = trans;
|
|
info->cursor_hint_split = split;
|
|
info->cursor_hint_trans_split =
|
|
gnc_split_register_get_current_trans_split (reg, NULL);
|
|
info->cursor_hint_cursor_class = CURSOR_CLASS_SPLIT;
|
|
info->hint_set_by_traverse = TRUE;
|
|
|
|
LEAVE("off end of blank split");
|
|
return FALSE;
|
|
|
|
}
|
|
while (FALSE);
|
|
|
|
{
|
|
int old_virt_row = reg->table->current_cursor_loc.vcell_loc.virt_row;
|
|
|
|
/* Check for going off the end */
|
|
gnc_table_find_close_valid_cell (reg->table, &virt_loc,
|
|
info->exact_traversal);
|
|
|
|
|
|
/* Did we change vertical position? */
|
|
if (virt_loc.vcell_loc.virt_row != old_virt_row)
|
|
/* Deal with the exchange-rate */
|
|
if (gnc_split_register_handle_exchange (reg, FALSE))
|
|
{
|
|
LEAVE("no exchange rate");
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
/* Same transaction, no problem */
|
|
new_trans = gnc_split_register_get_trans (reg, virt_loc.vcell_loc);
|
|
if (trans == new_trans)
|
|
{
|
|
*p_new_virt_loc = virt_loc;
|
|
{
|
|
LEAVE("staying within txn");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* Ok, we are changing transactions and the current transaction has
|
|
* changed. See what the user wants to do. */
|
|
LEAVE("txn change");
|
|
return transaction_changed_confirm(p_new_virt_loc, &virt_loc, reg,
|
|
new_trans, info->exact_traversal);
|
|
}
|
|
|
|
TableControl *
|
|
gnc_split_register_control_new (void)
|
|
{
|
|
TableControl *control = gnc_table_control_new();
|
|
|
|
control->move_cursor = gnc_split_register_move_cursor;
|
|
control->traverse = gnc_split_register_traverse;
|
|
|
|
return control;
|
|
}
|
|
|
|
gboolean
|
|
gnc_split_register_recn_cell_confirm (char old_flag, gpointer data)
|
|
{
|
|
SplitRegister *reg = data;
|
|
GtkWidget *dialog, *window;
|
|
gint response;
|
|
const gchar *title = _("Mark split as unreconciled?");
|
|
const gchar *message =
|
|
_("You are about to mark a reconciled split as unreconciled. Doing "
|
|
"so might make future reconciliation difficult! Continue "
|
|
"with this change?");
|
|
|
|
if (old_flag != YREC)
|
|
return TRUE;
|
|
|
|
/* Does the user want to be warned? */
|
|
window = gnc_split_register_get_parent(reg);
|
|
dialog =
|
|
gtk_message_dialog_new(GTK_WINDOW(window),
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_WARNING,
|
|
GTK_BUTTONS_CANCEL,
|
|
"%s", title);
|
|
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
|
|
"%s", message);
|
|
gtk_dialog_add_button(GTK_DIALOG(dialog), _("_Unreconcile"),
|
|
GTK_RESPONSE_YES);
|
|
response = gnc_dialog_run(GTK_DIALOG(dialog), "mark_split_unreconciled");
|
|
gtk_widget_destroy(dialog);
|
|
return (response == GTK_RESPONSE_YES);
|
|
}
|