Files
gnucash/src/SplitLedger.c
Linas Vepstas 3cbe9d91de remove usage of depricated routines.
In fact, this patch should be a no-op, since the
'depricated' routines merely have a new name.
Everything works exactly as before.


git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@4355 57a11ea4-9604-0410-9ed3-97b8803252fd
2001-06-01 22:46:02 +00:00

5139 lines
134 KiB
C

/********************************************************************\
* 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 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
/*
* FILE:
* SplitLedger.c
*
* FUNCTION:
* Provide view for SplitRegister object.
*
*
* DESIGN NOTES:
* Some notes about the "blank split":
* Q: What is the "blank split"?
* A: A new, empty split appended to the bottom of the ledger
* window. The blank split provides an area where the user
* can type in new split/transaction info.
* The "blank split" is treated in a special way for a number
* of reasons:
* (1) it must always appear as the bottom-most split
* in the Ledger window,
* (2) it must be committed if the user edits it, and
* a new blank split must be created.
* (3) it must be deleted when the ledger window is closed.
* To implement the above, the register "user_data" is used
* to store an SRInfo structure containing the blank split.
*
* =====================================================================
* Some notes on Commit/Rollback:
*
* There's an engine component and a gui component to the commit/rollback
* scheme. On the engine side, one must always call BeginEdit()
* before starting to edit a transaction. When you think you're done,
* you can call CommitEdit() to commit the changes, or RollbackEdit() to
* go back to how things were before you started the edit. Think of it as
* a one-shot mega-undo for that transaction.
*
* Note that the query engine uses the original values, not the currently
* edited values, when performing a sort. This allows your to e.g. edit
* the date without having the transaction hop around in the gui while you
* do it.
*
* On the gui side, commits are now performed on a per-transaction basis,
* rather than a per-split (per-journal-entry) basis. This means that
* if you have a transaction with a lot of splits in it, you can edit them
* all you want without having to commit one before moving to the next.
*
* Similarly, the "cancel" button will now undo the changes to all of the
* lines in the transaction display, not just to one line (one split) at a
* time.
*
* =====================================================================
* Some notes on Reloads & Redraws:
*
* Reloads and redraws tend to be heavyweight. We try to use change flags
* as much as possible in this code, but imagine the following scenario:
*
* Create two bank accounts. Transfer money from one to the other.
* Open two registers, showing each account. Change the amount in one window.
* Note that the other window also redraws, to show the new correct amount.
*
* Since you changed an amount value, potentially *all* displayed
* balances change in *both* register windows (as well as the ledger
* balance in the main window). Three or more windows may be involved
* if you have e.g. windows open for bank, employer, taxes and your
* entering a paycheck (or correcting a typo in an old paycheck).
* Changing a date might even cause all entries in all three windows
* to be re-ordered.
*
* The only thing I can think of is a bit stored with every table
* entry, stating 'this entry has changed since lst time, redraw it'.
* But that still doesn't avoid the overhead of reloading the table
* from the engine.
*
*
* HISTORY:
* Copyright (c) 1998-2000 Linas Vepstas
* Copyright (c) 2000 Dave Peticolas
*/
#define _GNU_SOURCE
#include "config.h"
#include <glib.h>
#include <guile/gh.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "Account.h"
#include "AccWindow.h"
#include "FileDialog.h"
#include "MultiLedger.h"
#include "Scrub.h"
#include "SplitLedger.h"
#include "global-options.h"
#include "gnc-component-manager.h"
#include "gnc-engine-util.h"
#include "gnc-ui-util.h"
#include "gnc-ui.h"
#include "guile-util.h"
#include "messages.h"
#include "splitreg.h"
#include "table-allgui.h"
#define SPLIT_TRANS_STR _("-- Split Transaction --")
#define STOCK_SPLIT_STR _("-- Stock Split --")
typedef struct _SRInfo SRInfo;
struct _SRInfo
{
/* The blank split at the bottom of the register */
GUID blank_split_guid;
/* The currently open transaction, if any */
GUID pending_trans_guid;
/* A transaction used to remember where to put the cursor */
Transaction *cursor_hint_trans;
/* A split used to remember where to put the cursor */
Split *cursor_hint_split;
/* A split used to remember where to put the cursor */
Split *cursor_hint_trans_split;
/* Used to remember where to put the cursor */
CursorClass cursor_hint_cursor_class;
/* If the hints were set by the traverse callback */
gboolean hint_set_by_traverse;
/* If traverse is to the newly created split */
gboolean traverse_to_new;
/* A flag indicating if the last traversal was 'exact'.
* See table-allgui.[ch] for details. */
gboolean exact_traversal;
/* Indicates that the current transaction is expanded
* in ledger mode. Meaningless in other modes. */
gboolean trans_expanded;
/* set to TRUE after register is loaded */
gboolean reg_loaded;
/* flag indicating whether full refresh is ok */
gboolean full_refresh;
/* The default account where new splits are added */
GUID default_account;
/* The last date recorded in the blank split */
time_t last_date_entered;
/* true if the current blank split has been edited and commited */
gboolean blank_split_edited;
/* true if the demarcation between 'past' and 'future' transactions
* should be visible */
gboolean show_present_divider;
/* true if we are loading the register for the first time */
gboolean first_pass;
/* true if the user has already confirmed changes of a reconciled
* split */
gboolean change_confirmed;
/* User data for users of SplitRegisters */
gpointer user_data;
/* hook to get parent widget */
SRGetParentCallback get_parent;
/* hook to set help string */
SRSetHelpCallback set_help;
};
/** static variables ******************************************************/
/* This static indicates the debugging module that this .o belongs to. */
static short module = MOD_LEDGER;
/* The character used to separate accounts. */
static char account_separator = ':';
/* The reverse balance callback, if any. */
static SRReverseBalanceCallback reverse_balance = NULL;
/* The copied split or transaction, if any */
static CursorClass copied_class = CURSOR_CLASS_NONE;
static SCM copied_item = SCM_UNDEFINED;
static GUID copied_leader_guid;
/* Flag for determining colorization of negative amounts. */
static gboolean use_red_for_negative = TRUE;
/** static prototypes *****************************************************/
static Split * xaccSRGetTransSplit (SplitRegister *reg,
VirtualCellLocation vcell_loc,
VirtualCellLocation *trans_split_loc);
static gboolean xaccSRSaveRegEntryToSCM (SplitRegister *reg,
SCM trans_scm, SCM split_scm,
gboolean use_cut_semantics);
static Transaction * xaccSRGetTrans (SplitRegister *reg,
VirtualCellLocation vcell_loc);
static Split * xaccSRGetCurrentTransSplit (SplitRegister *reg,
VirtualCellLocation *vcell_loc);
static void xaccSRSaveChangedCells (SplitRegister *reg, Transaction *trans,
Split *split);
static gboolean xaccSRFindSplit (SplitRegister *reg,
Transaction *trans, Split *trans_split,
Split *split, CursorClass cursor_class,
VirtualCellLocation *vcell_loc);
static Split * sr_get_split (SplitRegister *reg,
VirtualCellLocation vcell_loc);
static void xaccSRSetTransVisible (SplitRegister *reg,
VirtualCellLocation vcell_loc,
gboolean visible,
gboolean only_blank_split);
static gboolean trans_has_reconciled_splits (Transaction *trans);
static void sr_set_last_num (SplitRegister *reg, const char *num);
static guint32 sr_split_auto_calc (SplitRegister *reg, Split *split,
guint32 changed);
/** implementations *******************************************************/
static time_t
get_today_midnight (void)
{
time_t present;
struct tm tm;
present = time (NULL);
tm = *localtime (&present);
tm.tm_sec = 0;
tm.tm_min = 0;
tm.tm_hour = 0;
tm.tm_isdst = -1;
return mktime (&tm);
}
/* The routines below create, access, and destroy the SRInfo structure
* used by SplitLedger routines to store data for a particular register.
* This is the only code that should access the user_data member of a
* SplitRegister directly. If additional user data is needed, just add
* it to the SRInfo structure above. */
static void
xaccSRInitRegisterData (SplitRegister *reg)
{
SRInfo *info;
if (reg == NULL)
return;
info = g_new0 (SRInfo, 1);
info->blank_split_guid = *xaccGUIDNULL ();
info->pending_trans_guid = *xaccGUIDNULL ();
info->default_account = *xaccGUIDNULL ();
info->last_date_entered = get_today_midnight ();
info->first_pass = TRUE;
info->full_refresh = TRUE;
reg->user_data = info;
}
static void
xaccSRDestroyRegisterData (SplitRegister *reg)
{
if (reg == NULL)
return;
g_free (reg->user_data);
reg->user_data = NULL;
}
static SRInfo *
xaccSRGetInfo (SplitRegister *reg)
{
if (!reg)
return NULL;
if (reg->user_data == NULL)
xaccSRInitRegisterData (reg);
return reg->user_data;
}
static gncUIWidget
xaccSRGetParent (SplitRegister *reg)
{
SRInfo *info = xaccSRGetInfo (reg);
if (reg == NULL)
return NULL;
if (info->get_parent == NULL)
return NULL;
return info->get_parent (info->user_data);
}
void
xaccSRSetData (SplitRegister *reg, void *user_data,
SRGetParentCallback get_parent,
SRSetHelpCallback set_help)
{
SRInfo *info = xaccSRGetInfo (reg);
g_return_if_fail (reg != NULL);
info->user_data = user_data;
info->get_parent = get_parent;
info->set_help = set_help;
}
void
xaccSRSetAccountSeparator (char separator)
{
account_separator = separator;
}
void
xaccSRSetReverseBalanceCallback (SRReverseBalanceCallback callback)
{
reverse_balance = callback;
}
static int
gnc_trans_split_index (Transaction *trans, Split *split)
{
GList *node;
int i;
for (i = 0, node = xaccTransGetSplitList (trans); node;
i++, node = node->next)
{
Split *s = node->data;
if (s == split)
return i;
}
return -1;
}
/* Uses the scheme split copying routines */
static void
gnc_copy_split_onto_split(Split *from, Split *to, gboolean use_cut_semantics)
{
SCM split_scm;
if ((from == NULL) || (to == NULL))
return;
split_scm = gnc_copy_split(from, use_cut_semantics);
if (split_scm == SCM_UNDEFINED)
return;
gnc_copy_split_scm_onto_split(split_scm, to);
}
/* Uses the scheme transaction copying routines */
static void
gnc_copy_trans_onto_trans(Transaction *from, Transaction *to,
gboolean use_cut_semantics,
gboolean do_commit)
{
SCM trans_scm;
if ((from == NULL) || (to == NULL))
return;
trans_scm = gnc_copy_trans(from, use_cut_semantics);
if (trans_scm == SCM_UNDEFINED)
return;
gnc_copy_trans_scm_onto_trans(trans_scm, to, do_commit);
}
static Split *
gnc_find_split_in_trans_by_memo (Transaction *trans, const char *memo,
Transaction *dest_trans, gboolean unit_price)
{
GList *node;
for (node = xaccTransGetSplitList (trans); node; node = node->next)
{
Split *split = node->data;
if (unit_price)
{
gnc_numeric price = xaccSplitGetSharePrice(split);
if (!gnc_numeric_equal (price, gnc_numeric_create (1, 1)))
continue;
}
if (safe_strcmp(memo, xaccSplitGetMemo(split)) == 0)
{
Account *account = xaccSplitGetAccount(split);
gnc_commodity *currency, *security;
if (account == NULL)
return split;
currency = xaccAccountGetCurrency(account);
if (xaccTransIsCommonCurrency(dest_trans, currency))
return split;
security = xaccAccountGetSecurity(account);
if (xaccTransIsCommonCurrency(dest_trans, security))
return split;
}
}
return NULL;
}
/* This routine is for finding a matching split in an account or related
* accounts by matching on the memo field. This routine is used for auto-
* filling in registers with a default leading account. The dest_trans
* is a transaction used for currency checking. */
static Split *
gnc_find_split_in_account_by_memo(Account *account, const char *memo,
Transaction *dest_trans, 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, dest_trans,
unit_price);
if (split != NULL) return split;
}
return NULL;
}
static Split *
gnc_find_split_in_reg_by_memo (SplitRegister *reg, const char *memo,
Transaction *dest_tran, gboolean unit_price)
{
int virt_row, virt_col;
int num_rows, num_cols;
Transaction *last_trans;
if (reg == NULL)
return NULL;
if (reg->table == NULL)
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 = sr_get_split (reg, vcell_loc);
trans = xaccSplitGetParent(split);
if (trans == last_trans)
continue;
split = gnc_find_split_in_trans_by_memo (trans, memo, dest_tran,
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 == NULL)
return NULL;
if (reg->table == NULL)
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 = sr_get_split (reg, vcell_loc);
trans = xaccSplitGetParent(split);
if (trans == last_trans)
continue;
if (safe_strcmp (description, xaccTransGetDescription (trans)) == 0)
return trans;
last_trans = trans;
}
return NULL;
}
static Account *
sr_get_default_account (SplitRegister *reg)
{
SRInfo *info = xaccSRGetInfo (reg);
return xaccAccountLookup (&info->default_account);
}
static Split *
sr_get_split (SplitRegister *reg, VirtualCellLocation vcell_loc)
{
GUID *guid;
if (reg == NULL)
return NULL;
guid = gnc_table_get_vcell_data (reg->table, vcell_loc);
if (guid == NULL)
return NULL;
return xaccSplitLookup (guid);
}
static int
gnc_split_get_value_denom (Split *split)
{
int denom;
denom = xaccAccountGetCurrencySCU (xaccSplitGetAccount (split));
if (denom == 0)
{
gnc_commodity *commodity = gnc_locale_default_currency ();
denom = gnc_commodity_get_fraction (commodity);
if (denom == 0)
denom = 100;
}
return denom;
}
static int
gnc_split_get_quantity_denom (Split *split)
{
int denom;
denom = xaccAccountGetCommoditySCU (xaccSplitGetAccount (split));
if (denom == 0)
{
gnc_commodity *commodity = gnc_locale_default_currency ();
denom = gnc_commodity_get_fraction (commodity);
if (denom == 0)
denom = 100;
}
return denom;
}
static void
sr_set_cell_fractions (SplitRegister *reg, Split *split)
{
Account *account;
account = xaccSplitGetAccount (split);
if (account == NULL)
account = sr_get_default_account (reg);
if (account)
{
xaccSetPriceCellFraction (reg->sharesCell,
xaccAccountGetCommoditySCU (account));
xaccSetPriceCellFraction (reg->debitCell,
xaccAccountGetCurrencySCU (account));
xaccSetPriceCellFraction (reg->creditCell,
xaccAccountGetCurrencySCU (account));
return;
}
{
gnc_commodity *commodity;
int fraction;
xaccSetPriceCellFraction (reg->sharesCell, 10000);
commodity = gnc_locale_default_currency ();
fraction = gnc_commodity_get_fraction (commodity);
xaccSetPriceCellFraction (reg->debitCell, fraction);
xaccSetPriceCellFraction (reg->creditCell, fraction);
}
}
static CellBlock *
sr_get_passive_cursor (SplitRegister *reg)
{
switch (reg->style)
{
case REG_STYLE_LEDGER:
case REG_STYLE_AUTO_LEDGER:
return reg->use_double_line ?
reg->cursor_ledger_double : reg->cursor_ledger_single;
case REG_STYLE_JOURNAL:
return reg->use_double_line ?
reg->cursor_journal_double : reg->cursor_journal_single;
}
PWARN ("bad register style");
return NULL;
}
static CellBlock *
sr_get_active_cursor (SplitRegister *reg)
{
SRInfo *info = xaccSRGetInfo (reg);
switch (reg->style)
{
case REG_STYLE_LEDGER:
if (!info->trans_expanded)
return reg->use_double_line ?
reg->cursor_ledger_double : reg->cursor_ledger_single;
/* fall through */
case REG_STYLE_AUTO_LEDGER:
case REG_STYLE_JOURNAL:
return reg->use_double_line ?
reg->cursor_journal_double : reg->cursor_journal_single;
}
PWARN ("bad register style");
return NULL;
}
static void
xaccSRShowTrans (SplitRegister *reg,
VirtualCellLocation start_loc)
{
VirtualCellLocation end_loc;
int v_row;
end_loc = start_loc;
for (v_row = end_loc.virt_row + 1;
v_row < reg->table->num_virt_rows; v_row++)
{
VirtualCellLocation vc_loc = { v_row, 0 };
CursorClass cursor_class;
cursor_class = xaccSplitRegisterGetCursorClass (reg, vc_loc);
if (cursor_class == CURSOR_CLASS_TRANS)
break;
if (cursor_class != CURSOR_CLASS_SPLIT)
{
v_row--;
break;
}
}
end_loc.virt_row = MIN (v_row, reg->table->num_virt_rows - 1);
gnc_table_show_range (reg->table, start_loc, end_loc);
}
void
xaccSRExpandCurrentTrans (SplitRegister *reg, gboolean expand)
{
SRInfo *info = xaccSRGetInfo (reg);
if (!reg)
return;
if (reg->style == REG_STYLE_AUTO_LEDGER ||
reg->style == REG_STYLE_JOURNAL)
return;
/* ok, so I just wanted an excuse to use exclusive-or */
if (!(expand ^ info->trans_expanded))
return;
if (!expand)
{
VirtualLocation virt_loc;
virt_loc = reg->table->current_cursor_loc;
xaccSRGetTransSplit (reg, virt_loc.vcell_loc, &virt_loc.vcell_loc);
if (gnc_table_find_close_valid_cell (reg->table, &virt_loc, FALSE))
gnc_table_move_cursor_gui (reg->table, virt_loc);
else
{
PERR ("Can't find place to go!");
return;
}
}
info->trans_expanded = expand;
gnc_table_set_virt_cell_cursor (reg->table,
reg->table->current_cursor_loc.vcell_loc,
sr_get_active_cursor (reg));
xaccSRSetTransVisible (reg, reg->table->current_cursor_loc.vcell_loc,
expand, FALSE);
{
VirtualLocation virt_loc;
virt_loc = reg->table->current_cursor_loc;
if (!expand || !gnc_table_virtual_loc_valid (reg->table, virt_loc, FALSE))
{
if (gnc_table_find_close_valid_cell (reg->table, &virt_loc, FALSE))
gnc_table_move_cursor_gui (reg->table, virt_loc);
else
{
PERR ("Can't find place to go!");
return;
}
}
}
gnc_table_refresh_gui (reg->table, TRUE);
if (expand)
xaccSRShowTrans (reg, reg->table->current_cursor_loc.vcell_loc);
}
gboolean
xaccSRCurrentTransExpanded (SplitRegister *reg)
{
SRInfo *info = xaccSRGetInfo (reg);
if (!reg)
return FALSE;
if (reg->style == REG_STYLE_AUTO_LEDGER ||
reg->style == REG_STYLE_JOURNAL)
return FALSE;
return info->trans_expanded;
}
static gboolean
sr_balance_trans (SplitRegister *reg, Transaction *trans)
{
gnc_numeric imbalance;
imbalance = xaccTransGetImbalance (trans);
if (!gnc_numeric_zero_p (imbalance))
{
int choice;
int default_value;
Account *default_account;
Account *other_account;
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;
split = xaccTransGetSplit (trans, 0);
other_split = xaccSplitGetOtherSplit (split);
if (other_split == NULL)
{
two_accounts = FALSE;
other_account = NULL;
}
else
{
two_accounts = TRUE;
other_account = xaccSplitGetAccount (other_split);
}
default_account = sr_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)
{
radio_list = g_list_append (radio_list,
_("Adjust current account split total"));
if (two_accounts)
radio_list = g_list_append (radio_list,
_("Adjust other account split total"));
default_value = 2;
}
else
default_value = 0;
choice = gnc_choose_radio_option_dialog_parented (xaccSRGetParent (reg),
title,
message,
default_value,
radio_list);
g_list_free (radio_list);
switch (choice)
{
default:
case 0:
break;
case 1:
xaccTransScrubImbalance (trans, gncGetCurrentGroup (), NULL);
break;
case 2:
xaccTransScrubImbalance (trans, gncGetCurrentGroup (),
default_account);
break;
case 3:
xaccTransScrubImbalance (trans, gncGetCurrentGroup (),
other_account);
break;
}
return TRUE;
}
return FALSE;
}
/* ======================================================== */
/* This callback gets called when the user clicks on the gui
* in such a way as to leave the current virtual cursor, and
* go to a new one. So, save the current transaction.
*
* This callback is centrally involved in the redraw sequence.
* When the user moves from one cell to another, causing a
* change in the current virtual cursor, the following
* sequence of events get triggered and cascade down:
* xaccVerifyCursorPosition()
* doMoveCursor()
* callback for move() (i.e., LedgerMoveCursor)
* xaccSRSaveRegEntry()
* RedrawRegEntry()
* SRLoadRegister()
* xaccMoveCursor()
*/
static void
LedgerMoveCursor (Table *table, VirtualLocation *p_new_virt_loc)
{
VirtualLocation new_virt_loc = *p_new_virt_loc;
VirtualCellLocation old_trans_split_loc;
SplitRegister *reg = table->user_data;
SRInfo *info = xaccSRGetInfo(reg);
Transaction *pending_trans = xaccTransLookup(&info->pending_trans_guid);
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;
PINFO ("start callback %d %d \n",
new_virt_loc.vcell_loc.virt_row,
new_virt_loc.vcell_loc.virt_col);
/* The transaction we are coming from */
old_split = xaccSRGetCurrentSplit (reg);
old_trans = xaccSRGetCurrentTrans (reg);
old_trans_split = xaccSRGetCurrentTransSplit (reg, &old_trans_split_loc);
old_class = xaccSplitRegisterGetCurrentCursorClass (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 = xaccSRGetTrans (reg, new_virt_loc.vcell_loc);
/* The split we are moving to */
new_split = sr_get_split (reg, new_virt_loc.vcell_loc);
/* The split at the transaction line we are moving to */
new_trans_split = xaccSRGetTransSplit (reg, new_virt_loc.vcell_loc, NULL);
new_class = xaccSplitRegisterGetCursorClass (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 = xaccSRSaveRegEntry (reg, old_trans != new_trans);
if ((pending_trans != NULL) &&
(pending_trans == old_trans) &&
(old_trans != new_trans))
{
if (sr_balance_trans (reg, old_trans))
{
new_trans = old_trans;
new_split = old_split;
new_trans_split = old_trans_split;
new_class = old_class;
new_virt_loc = table->current_cursor_loc;
}
if (xaccTransIsOpen (old_trans))
xaccTransCommitEdit (old_trans);
info->pending_trans_guid = *xaccGUIDNULL ();
pending_trans = NULL;
saved = TRUE;
}
else if (old_trans &&
(old_trans != new_trans) &&
!trans_has_reconciled_splits (old_trans) &&
!info->first_pass &&
sr_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 = 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;
gnc_resume_gui_refresh ();
/* redrawing the register can muck everything up */
if (saved)
{
VirtualCellLocation vcell_loc;
if (!info->reg_loaded)
xaccSRRedrawReg (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 (xaccSRFindSplit (reg, new_trans, new_trans_split,
new_split, new_class, &vcell_loc))
{
VirtualCell *vcell;
vcell = gnc_table_get_virtual_cell (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 = xaccSRGetTrans (reg, new_virt_loc.vcell_loc);
new_split = sr_get_split (reg, new_virt_loc.vcell_loc);
new_trans_split = xaccSRGetTransSplit (reg, new_virt_loc.vcell_loc, NULL);
new_class = xaccSplitRegisterGetCursorClass (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 (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)
{
sr_set_cell_fractions (reg, new_split);
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 (table, vc_loc,
sr_get_passive_cursor (reg));
xaccSRSetTransVisible (reg, vc_loc, FALSE,
reg->style == REG_STYLE_JOURNAL);
if ((REG_STYLE_AUTO_LEDGER == reg->style) ||
(REG_STYLE_JOURNAL == reg->style))
{
xaccSRGetTransSplit (reg, new_virt_loc.vcell_loc, &vc_loc);
gnc_table_set_virt_cell_cursor (table, vc_loc,
sr_get_active_cursor (reg));
xaccSRSetTransVisible (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;
sr_set_cell_fractions (reg, new_split);
gnc_table_find_close_valid_cell (table, p_new_virt_loc, exact_traversal);
if (do_refresh)
{
VirtualCellLocation vc_loc;
gnc_table_refresh_gui (table, FALSE);
gnc_table_leave_update (table, table->current_cursor_loc);
xaccSRGetTransSplit (reg, p_new_virt_loc->vcell_loc, &vc_loc);
xaccSRShowTrans (reg, vc_loc);
}
}
/* This function determines if auto-completion is appropriate and,
* if so, performs it. This should only be called by LedgerTraverse. */
static gboolean
LedgerAutoCompletion(SplitRegister *reg, gncTableTraversalDir dir,
VirtualLocation *p_new_virt_loc)
{
SRInfo *info = xaccSRGetInfo (reg);
Transaction *pending_trans = xaccTransLookup (&info->pending_trans_guid);
Split *blank_split = xaccSplitLookup (&info->blank_split_guid);
Transaction *blank_trans = xaccSplitGetParent (blank_split);
VirtualLocation new_virt_loc;
CursorClass cursor_class;
CellType cell_type;
Transaction *trans;
gnc_numeric amount;
guint32 changed;
Split *split;
/* auto-completion is only triggered by a tab out */
if (dir != GNC_TABLE_TRAVERSE_RIGHT)
return FALSE;
split = xaccSRGetCurrentSplit(reg);
trans = xaccSRGetCurrentTrans(reg);
if (trans == NULL)
return FALSE;
cursor_class = xaccSplitRegisterGetCurrentCursorClass (reg);
cell_type = xaccSplitRegisterGetCurrentCellType (reg);
changed = xaccSplitRegisterGetChangeFlag (reg);
changed |= xaccSplitRegisterGetConditionalChangeFlag (reg);
switch (cursor_class)
{
case CURSOR_CLASS_TRANS: {
Transaction *auto_trans;
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 (cell_type != DESC_CELL)
return FALSE;
/* nothing but the date, num, and description should be changed */
if ((changed & ~(MOD_DATE | MOD_NUM | MOD_DESC)) != 0)
return FALSE;
/* and the description should be changed */
if ((changed & MOD_DESC) == 0)
return FALSE;
/* to a non-empty value */
desc = reg->descCell->cell.value;
if ((desc == NULL) || (*desc == '\0'))
return FALSE;
/* find a transaction to auto-complete on */
if (sr_get_default_account (reg) != NULL)
{
Account *account = sr_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;
/* now perform the completion */
gnc_suspend_gui_refresh ();
xaccTransBeginEdit (trans);
gnc_copy_trans_onto_trans (auto_trans, trans, FALSE, FALSE);
if (sr_get_default_account (reg) != NULL)
{
Account *default_account = sr_get_default_account (reg);
int num_splits;
int i;
blank_split = NULL;
num_splits = xaccTransCountSplits(trans);
for (i = 0; i < num_splits; i++)
{
Split *s = xaccTransGetSplit(trans, i);
if (default_account == xaccSplitGetAccount(s))
{
blank_split = s;
info->blank_split_guid = *xaccSplitGetGUID(blank_split);
break;
}
}
if (blank_split == NULL)
{
blank_split = xaccTransGetSplit(trans, 0);
info->blank_split_guid = *xaccSplitGetGUID(blank_split);
}
}
else
{
blank_split = xaccTransGetSplit(trans, 0);
info->blank_split_guid = *xaccSplitGetGUID(blank_split);
}
if ((pending_trans != NULL) && (pending_trans != trans))
if (xaccTransIsOpen(pending_trans))
xaccTransCommitEdit(pending_trans);
pending_trans = trans;
info->pending_trans_guid = *xaccTransGetGUID(pending_trans);
info->blank_split_edited = TRUE;
xaccSRSaveChangedCells(reg, trans, blank_split);
gnc_resume_gui_refresh ();
/* now move to the non-empty amount column */
amount = xaccSplitGetShareAmount (blank_split);
cell_type = (gnc_numeric_negative_p (amount)) ? CRED_CELL : DEBT_CELL;
if (xaccSplitRegisterGetCurrentCellLoc (reg, cell_type, &new_virt_loc))
*p_new_virt_loc = new_virt_loc;
}
break;
case CURSOR_CLASS_SPLIT: {
char *memo, *fullname;
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 (cell_type != MEMO_CELL)
return FALSE;
/* nothing but the action memo, and amounts should be changed */
if ((changed & ~(MOD_ACTN | MOD_MEMO | MOD_AMNT)) != 0)
return FALSE;
/* and the memo should be changed */
if ((changed & MOD_MEMO) == 0)
return FALSE;
/* to a non-empty value */
memo = reg->memoCell->cell.value;
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 = !xaccSplitRegisterGetCurrentCellLoc(reg, PRIC_CELL, NULL);
/* find a split to auto-complete on */
if (sr_get_default_account (reg) != NULL)
{
Account *account = sr_get_default_account (reg);
auto_split = gnc_find_split_in_account_by_memo(account, memo, trans,
unit_price);
}
else
auto_split = gnc_find_split_in_reg_by_memo(reg, memo, trans,
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 (!(MOD_ACTN & changed))
xaccSetComboCellValue (reg->actionCell,
xaccSplitGetAction (auto_split));
/* auto-complete the account name */
fullname = xaccAccountGetFullName (xaccSplitGetAccount (auto_split),
account_separator);
xaccSetComboCellValue (reg->xfrmCell, fullname);
g_free(fullname);
xaccBasicCellSetChanged(&(reg->xfrmCell->cell), TRUE);
if (!(changed & MOD_AMNT))
{
amount = xaccSplitGetValue (auto_split);
xaccSetDebCredCellValue (reg->debitCell, reg->creditCell, amount);
xaccBasicCellSetChanged (&(reg->debitCell->cell), TRUE);
xaccBasicCellSetChanged (&(reg->creditCell->cell), TRUE);
}
/* and refresh the gui */
gnc_table_refresh_gui (reg->table, TRUE);
/* now move to the non-empty amount column */
amount = xaccSplitGetShareAmount (auto_split);
cell_type = (gnc_numeric_negative_p (amount)) ? CRED_CELL : DEBT_CELL;
if (xaccSplitRegisterGetCurrentCellLoc (reg, cell_type, &new_virt_loc))
*p_new_virt_loc = new_virt_loc;
}
break;
default:
break;
}
return TRUE;
}
/* ======================================================== */
/* This callback gets called when the user clicks on the gui
* in such a way as to leave the current transaction, and to
* go to a new one. It is called to verify what the coordinates
* of the new cell will be.
*/
static gboolean
LedgerTraverse (Table *table,
VirtualLocation *p_new_virt_loc,
gncTableTraversalDir dir)
{
SplitRegister *reg = table->user_data;
SRInfo *info = xaccSRGetInfo (reg);
Transaction *pending_trans = xaccTransLookup (&info->pending_trans_guid);
VirtualLocation virt_loc = *p_new_virt_loc;
Transaction *trans, *new_trans;
GNCVerifyResult result;
guint32 changed;
Split *split;
if (info->first_pass)
return FALSE;
info->exact_traversal = (dir == GNC_TABLE_TRAVERSE_POINTER);
split = xaccSRGetCurrentSplit (reg);
trans = xaccSRGetCurrentTrans (reg);
if (trans == NULL)
return FALSE;
/* no changes, make sure we aren't going off the end */
changed = xaccSplitRegisterGetChangeFlag (reg);
if (!changed && (pending_trans != trans))
{
gnc_table_find_close_valid_cell (table, &virt_loc, info->exact_traversal);
*p_new_virt_loc = virt_loc;
return FALSE;
}
/* See if we are leaving an account field */
do
{
CellType cell_type;
ComboCell *cell;
Account *account;
char *name;
cell_type = xaccSplitRegisterGetCurrentCellType (reg);
if (!(cell_type == XFRM_CELL ||
cell_type == MXFRM_CELL))
break;
cell = NULL;
switch (cell_type)
{
case XFRM_CELL:
if (changed & MOD_XFRM)
cell = reg->xfrmCell;
break;
case MXFRM_CELL:
if (changed & MOD_MXFRM)
cell = reg->mxfrmCell;
break;
default:
break;
}
if (!cell)
break;
name = cell->cell.value;
if (!name || *name == '\0' ||
safe_strcmp (name, SPLIT_TRANS_STR) == 0 ||
safe_strcmp (name, STOCK_SPLIT_STR) == 0)
break;
account = xaccGetAccountFromFullName (gncGetCurrentGroup (),
cell->cell.value,
account_separator);
if (account)
break;
{
const char *format = _("The account %s does not exist.\n"
"Would you like to create it?");
char *message;
gboolean result;
message = g_strdup_printf (format, name);
result = gnc_verify_dialog_parented (xaccSRGetParent (reg),
message, TRUE);
if (!result)
break;
}
info->full_refresh = FALSE;
account = gnc_ui_new_accounts_from_name_window (name);
if (!account)
break;
info->full_refresh = TRUE;
name = xaccAccountGetFullName (account, account_separator);
xaccSetComboCellValue (cell, name);
xaccBasicCellSetChanged (&cell->cell, TRUE);
g_free (name);
} while (FALSE);
/* 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 = table->current_cursor_loc;
if (gnc_table_move_vertical_position (table, &virt_loc, 1))
break;
virt_loc = table->current_cursor_loc;
if (gnc_table_move_tab (table, &virt_loc, TRUE))
break;
*p_new_virt_loc = 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;
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 (table, virt_loc.vcell_loc))
{
if (LedgerAutoCompletion(reg, dir, p_new_virt_loc))
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 = table->current_cursor_loc;
old_virt_row = virt_loc.vcell_loc.virt_row;
if (gnc_table_move_tab (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. */
info->cursor_hint_trans = trans;
info->cursor_hint_split = split;
info->cursor_hint_trans_split = xaccSRGetCurrentTransSplit (reg, NULL);
info->cursor_hint_cursor_class = CURSOR_CLASS_SPLIT;
info->hint_set_by_traverse = TRUE;
return FALSE;
} while (FALSE);
/* Check for going off the end */
gnc_table_find_close_valid_cell (table, &virt_loc, info->exact_traversal);
/* Same transaction, no problem */
new_trans = xaccSRGetTrans(reg, virt_loc.vcell_loc);
if (trans == new_trans)
{
*p_new_virt_loc = virt_loc;
return FALSE;
}
/* Ok, we are changing transactions and the current transaction has
* changed. See what the user wants to do. */
{
const char *message;
message = _("The current transaction has been changed.\n"
"Would you like to record it?");
result = gnc_verify_cancel_dialog_parented (xaccSRGetParent (reg),
message, GNC_VERIFY_YES);
}
switch (result)
{
case GNC_VERIFY_YES:
break;
case GNC_VERIFY_NO:
{
VirtualCellLocation vcell_loc;
Split *new_split;
Split *trans_split;
CursorClass new_class;
new_split = sr_get_split(reg, virt_loc.vcell_loc);
trans_split = xaccSRGetTransSplit(reg, virt_loc.vcell_loc, NULL);
new_class = xaccSplitRegisterGetCursorClass (reg, virt_loc.vcell_loc);
xaccSRCancelCursorTransChanges(reg);
if (xaccSRFindSplit (reg, new_trans, trans_split,
new_split, new_class, &vcell_loc))
virt_loc.vcell_loc = vcell_loc;
gnc_table_find_close_valid_cell (table, &virt_loc,
info->exact_traversal);
*p_new_virt_loc = virt_loc;
}
break;
case GNC_VERIFY_CANCEL:
return TRUE;
default:
break;
}
return FALSE;
}
/* ======================================================== */
static void
LedgerSetHelp (Table *table, const char *help_str)
{
SplitRegister *reg = table->user_data;
SRInfo *info = xaccSRGetInfo(reg);
if (info->set_help == NULL)
return;
info->set_help(info->user_data, help_str);
}
/* ======================================================== */
static void
LedgerDestroy (SplitRegister *reg)
{
SRInfo *info = xaccSRGetInfo(reg);
Split *blank_split = xaccSplitLookup(&info->blank_split_guid);
Transaction *pending_trans = xaccTransLookup(&info->pending_trans_guid);
Transaction *trans;
gnc_suspend_gui_refresh ();
/* be sure to destroy the "blank split" */
if (blank_split != NULL)
{
/* split destroy will automatically remove it
* from its parent account */
trans = xaccSplitGetParent (blank_split);
/* Make sure we don't commit this below */
if (trans == pending_trans)
{
info->pending_trans_guid = *xaccGUIDNULL ();
pending_trans = NULL;
}
xaccTransBeginEdit (trans);
xaccTransDestroy (trans);
xaccTransCommitEdit (trans);
info->blank_split_guid = *xaccGUIDNULL ();
blank_split = NULL;
}
/* be sure to take care of any open transactions */
if (pending_trans != NULL)
{
if (xaccTransIsOpen (pending_trans))
xaccTransCommitEdit (pending_trans);
info->pending_trans_guid = *xaccGUIDNULL ();
pending_trans = NULL;
}
xaccSRDestroyRegisterData (reg);
gnc_resume_gui_refresh ();
}
/* ======================================================== */
static Transaction *
xaccSRGetTrans (SplitRegister *reg, VirtualCellLocation vcell_loc)
{
Split *split;
if (!reg || !reg->table)
return NULL;
split = sr_get_split (reg, vcell_loc);
if (split != NULL)
return xaccSplitGetParent(split);
/* Split is blank. Assume it is the blank split of a multi-line
* transaction. Go back one row to find a split in the transaction. */
vcell_loc.virt_row--;
split = sr_get_split (reg, vcell_loc);
/* This split could be NULL during register initialization. */
if (split == NULL)
return NULL;
return xaccSplitGetParent(split);
}
/* ======================================================== */
static Split *
xaccSRGetTransSplit (SplitRegister *reg,
VirtualCellLocation vcell_loc,
VirtualCellLocation *trans_split_loc)
{
CursorClass cursor_class;
if (reg == NULL)
return NULL;
while (TRUE)
{
if ((0 > vcell_loc.virt_row) || (0 > vcell_loc.virt_col))
{
PERR ("bad row \n");
return NULL;
}
cursor_class = xaccSplitRegisterGetCursorClass (reg, vcell_loc);
if (cursor_class == CURSOR_CLASS_TRANS)
{
if (trans_split_loc)
*trans_split_loc = vcell_loc;
return sr_get_split (reg, vcell_loc);
}
vcell_loc.virt_row--;
}
}
/* ======================================================== */
static Split *
xaccSRGetCurrentTransSplit (SplitRegister *reg,
VirtualCellLocation *trans_split_loc)
{
VirtualCellLocation vcell_loc;
if (reg == NULL)
return NULL;
vcell_loc = reg->table->current_cursor_loc.vcell_loc;
return xaccSRGetTransSplit (reg, vcell_loc, trans_split_loc);
}
/* ======================================================== */
Transaction *
xaccSRGetCurrentTrans (SplitRegister *reg)
{
Split *split;
VirtualCellLocation vcell_loc;
if (reg == NULL)
return NULL;
split = xaccSRGetCurrentSplit (reg);
if (split != NULL)
return xaccSplitGetParent(split);
/* Split is blank. Assume it is the blank split of a multi-line
* transaction. Go back one row to find a split in the transaction. */
vcell_loc = reg->table->current_cursor_loc.vcell_loc;
vcell_loc.virt_row --;
split = sr_get_split (reg, vcell_loc);
return xaccSplitGetParent(split);
}
/* ======================================================== */
Split *
xaccSRGetCurrentSplit (SplitRegister *reg)
{
if (reg == NULL)
return NULL;
return sr_get_split (reg, reg->table->current_cursor_loc.vcell_loc);
}
/* ======================================================== */
Split *
xaccSRGetBlankSplit (SplitRegister *reg)
{
SRInfo *info = xaccSRGetInfo(reg);
Split *blank_split = xaccSplitLookup(&info->blank_split_guid);
return blank_split;
}
/* ======================================================== */
gboolean
xaccSRGetSplitVirtLoc (SplitRegister *reg, Split *split,
VirtualCellLocation *vcell_loc)
{
Table *table;
int v_row;
int v_col;
if ((reg == NULL) || (split == NULL))
return FALSE;
table = reg->table;
/* go backwards because typically you search for splits at the end
* and because we find split rows before transaction rows. */
for (v_row = table->num_virt_rows - 1; v_row > 0; v_row--)
for (v_col = 0; v_col < table->num_virt_cols; v_col++)
{
VirtualCellLocation vc_loc = { v_row, v_col };
VirtualCell *vcell;
Split *s;
vcell = gnc_table_get_virtual_cell (table, vc_loc);
if (vcell == NULL)
continue;
if (!vcell->visible)
continue;
s = xaccSplitLookup (vcell->vcell_data);
if (s == split)
{
if (vcell_loc)
*vcell_loc = vc_loc;
return TRUE;
}
}
return FALSE;
}
/* ======================================================== */
gboolean
xaccSRGetSplitAmountVirtLoc (SplitRegister *reg, Split *split,
VirtualLocation *virt_loc)
{
VirtualLocation v_loc;
CursorClass cursor_class;
CellType cell_type;
gnc_numeric value;
if (!xaccSRGetSplitVirtLoc (reg, split, &v_loc.vcell_loc))
return FALSE;
cursor_class = xaccSplitRegisterGetCursorClass (reg, v_loc.vcell_loc);
value = xaccSplitGetValue (split);
switch (cursor_class)
{
case CURSOR_CLASS_SPLIT:
case CURSOR_CLASS_TRANS:
cell_type = (gnc_numeric_negative_p (value)) ? CRED_CELL : DEBT_CELL;
break;
default:
return FALSE;
}
if (!xaccSplitRegisterGetCellLoc (reg, cell_type, v_loc.vcell_loc, &v_loc))
return FALSE;
if (virt_loc == NULL)
return TRUE;
*virt_loc = v_loc;
return TRUE;
}
/* ======================================================== */
static gboolean
xaccSRFindSplit (SplitRegister *reg,
Transaction *trans, Split *trans_split,
Split *split, CursorClass find_class,
VirtualCellLocation *vcell_loc)
{
Table *table = reg->table;
gboolean found_trans = FALSE;
gboolean found_trans_split = FALSE;
gboolean found_something = FALSE;
CursorClass cursor_class;
int v_row, v_col;
Transaction *t;
Split *s;
for (v_row = 1; v_row < table->num_virt_rows; v_row++)
for (v_col = 0; v_col < table->num_virt_cols; v_col++)
{
VirtualCellLocation vc_loc = { v_row, v_col };
s = sr_get_split (reg, vc_loc);
t = xaccSplitGetParent(s);
cursor_class = xaccSplitRegisterGetCursorClass(reg, vc_loc);
if (t == trans)
found_trans = TRUE;
if ((cursor_class == CURSOR_CLASS_TRANS) && (s == trans_split))
found_trans_split = TRUE;
if (found_trans && (s == split))
{
if (vcell_loc != NULL)
*vcell_loc = vc_loc;
found_something = TRUE;
}
if (found_trans_split && (s == split))
{
if (vcell_loc != NULL)
*vcell_loc = vc_loc;
if (cursor_class == find_class)
return TRUE;
}
}
return found_something;
}
/* ======================================================== */
static void
xaccSRSetTransVisible (SplitRegister *reg,
VirtualCellLocation vcell_loc,
gboolean visible,
gboolean only_blank_split)
{
CursorClass cursor_class;
while (TRUE)
{
vcell_loc.virt_row++;
cursor_class = xaccSplitRegisterGetCursorClass (reg, vcell_loc);
if (cursor_class != CURSOR_CLASS_SPLIT)
return;
if (only_blank_split && sr_get_split (reg, vcell_loc))
continue;
gnc_table_set_virt_cell_visible (reg->table, vcell_loc, visible);
}
}
/* ======================================================== */
Split *
xaccSRDuplicateCurrent (SplitRegister *reg)
{
SRInfo *info = xaccSRGetInfo(reg);
Split *blank_split = xaccSplitLookup(&info->blank_split_guid);
CursorClass cursor_class;
Transaction *trans;
Split *return_split;
Split *trans_split;
guint32 changed;
Split *split;
split = xaccSRGetCurrentSplit(reg);
trans = xaccSRGetCurrentTrans(reg);
trans_split = xaccSRGetCurrentTransSplit(reg, NULL);
/* This shouldn't happen, but be paranoid. */
if (trans == NULL)
return NULL;
cursor_class = xaccSplitRegisterGetCurrentCursorClass (reg);
/* Can't do anything with this. */
if (cursor_class == CURSOR_CLASS_NONE)
return NULL;
/* This shouldn't happen, but be paranoid. */
if ((split == NULL) && (cursor_class == CURSOR_CLASS_TRANS))
return NULL;
changed = xaccSplitRegisterGetChangeFlag (reg);
/* See if we were asked to duplicate an unchanged blank split.
* There's no point in doing that! */
if (!changed && ((split == NULL) || (split == blank_split)))
return NULL;
gnc_suspend_gui_refresh ();
/* If the cursor has been edited, we are going to have to commit
* it before we can duplicate. Make sure the user wants to do that. */
if (changed)
{
const char *message = _("The current transaction has been changed.\n"
"Would you like to record it?");
GNCVerifyResult result;
result = gnc_ok_cancel_dialog_parented (xaccSRGetParent(reg),
message, GNC_VERIFY_OK);
if (result == GNC_VERIFY_CANCEL)
{
gnc_resume_gui_refresh ();
return NULL;
}
xaccSRSaveRegEntry (reg, TRUE);
/* If the split is NULL, then we were on a blank split row
* in an expanded transaction. The new split (created by
* xaccSRSaveRegEntry above) will be the last split in the
* current transaction, as it was just added. */
if (split == NULL)
split = xaccTransGetSplit (trans, xaccTransCountSplits (trans) - 1);
}
/* Ok, we are now ready to make the copy. */
if (cursor_class == CURSOR_CLASS_SPLIT)
{
Split *new_split;
/* We are on a split in an expanded transaction.
* Just copy the split and add it to the transaction. */
new_split = xaccMallocSplit ();
xaccTransBeginEdit (trans);
xaccTransAppendSplit (trans, new_split);
xaccTransCommitEdit (trans);
gnc_copy_split_onto_split (split, new_split, FALSE);
return_split = new_split;
info->cursor_hint_split = new_split;
info->cursor_hint_cursor_class = CURSOR_CLASS_SPLIT;
}
else
{
Transaction *new_trans;
int trans_split_index;
int split_index;
const char *in_num = NULL;
char *out_num;
time_t date;
/* We are on a transaction row. Copy the whole transaction. */
date = info->last_date_entered;
if (gnc_strisnum (xaccTransGetNum (trans)))
{
Account *account = sr_get_default_account (reg);
if (account)
in_num = xaccAccountGetLastNum (account);
else
in_num = xaccTransGetNum (trans);
}
if (!gnc_dup_trans_dialog (xaccSRGetParent (reg),
&date, in_num, &out_num))
{
gnc_resume_gui_refresh ();
return NULL;
}
split_index = gnc_trans_split_index (trans, split);
trans_split_index = gnc_trans_split_index (trans, trans_split);
/* we should *always* find the split, but be paranoid */
if (split_index < 0)
{
gnc_resume_gui_refresh ();
return NULL;
}
new_trans = xaccMallocTransaction ();
gnc_copy_trans_onto_trans (trans, new_trans, FALSE, TRUE);
xaccTransBeginEdit (new_trans);
xaccTransSetDateSecs (new_trans, date);
xaccTransSetNum (new_trans, out_num);
xaccTransCommitEdit (new_trans);
if (xaccSetNumCellLastNum (reg->numCell, out_num))
sr_set_last_num (reg, out_num);
g_free (out_num);
/* This shouldn't happen, but be paranoid. */
if (split_index >= xaccTransCountSplits (new_trans))
split_index = 0;
return_split = xaccTransGetSplit (new_trans, split_index);
trans_split = xaccTransGetSplit (new_trans, trans_split_index);
info->cursor_hint_trans = new_trans;
info->cursor_hint_split = return_split;
info->cursor_hint_trans_split = trans_split;
info->cursor_hint_cursor_class = CURSOR_CLASS_TRANS;
info->trans_expanded = FALSE;
}
/* Refresh the GUI. */
gnc_resume_gui_refresh ();
return return_split;
}
/* ======================================================== */
static void
xaccSRCopyCurrentInternal (SplitRegister *reg, gboolean use_cut_semantics)
{
SRInfo *info = xaccSRGetInfo(reg);
Split *blank_split = xaccSplitLookup(&info->blank_split_guid);
CursorClass cursor_class;
Transaction *trans;
guint32 changed;
Split *split;
SCM new_item;
split = xaccSRGetCurrentSplit(reg);
trans = xaccSRGetCurrentTrans(reg);
/* This shouldn't happen, but be paranoid. */
if (trans == NULL)
return;
cursor_class = xaccSplitRegisterGetCurrentCursorClass (reg);
/* Can't do anything with this. */
if (cursor_class == CURSOR_CLASS_NONE)
return;
/* This shouldn't happen, but be paranoid. */
if ((split == NULL) && (cursor_class == CURSOR_CLASS_TRANS))
return;
changed = xaccSplitRegisterGetChangeFlag(reg);
/* See if we were asked to copy an unchanged blank split. Don't. */
if (!changed && ((split == NULL) || (split == blank_split)))
return;
/* Ok, we are now ready to make the copy. */
if (cursor_class == CURSOR_CLASS_SPLIT)
{
/* We are on a split in an expanded transaction. Just copy the split. */
new_item = gnc_copy_split(split, use_cut_semantics);
if (new_item != SCM_UNDEFINED)
{
if (changed)
xaccSRSaveRegEntryToSCM(reg, SCM_UNDEFINED, new_item,
use_cut_semantics);
copied_leader_guid = *xaccGUIDNULL();
}
}
else
{
/* We are on a transaction row. Copy the whole transaction. */
new_item = gnc_copy_trans(trans, use_cut_semantics);
if (new_item != SCM_UNDEFINED)
{
if (changed)
{
int split_index;
SCM split_scm;
split_index = gnc_trans_split_index(trans, split);
if (split_index >= 0)
split_scm = gnc_trans_scm_get_split_scm(new_item, split_index);
else
split_scm = SCM_UNDEFINED;
xaccSRSaveRegEntryToSCM(reg, new_item, split_scm, use_cut_semantics);
}
copied_leader_guid = info->default_account;
}
}
if (new_item == SCM_UNDEFINED)
return;
/* unprotect the old object, if any */
if (copied_item != SCM_UNDEFINED)
scm_unprotect_object(copied_item);
copied_item = new_item;
scm_protect_object(copied_item);
copied_class = cursor_class;
}
/* ======================================================== */
void
xaccSRCopyCurrent (SplitRegister *reg)
{
xaccSRCopyCurrentInternal (reg, FALSE);
}
/* ======================================================== */
void
xaccSRCutCurrent (SplitRegister *reg)
{
SRInfo *info = xaccSRGetInfo(reg);
Split *blank_split = xaccSplitLookup(&info->blank_split_guid);
CursorClass cursor_class;
Transaction *trans;
guint32 changed;
Split *split;
split = xaccSRGetCurrentSplit(reg);
trans = xaccSRGetCurrentTrans(reg);
/* This shouldn't happen, but be paranoid. */
if (trans == NULL)
return;
cursor_class = xaccSplitRegisterGetCurrentCursorClass (reg);
/* Can't do anything with this. */
if (cursor_class == CURSOR_CLASS_NONE)
return;
/* This shouldn't happen, but be paranoid. */
if ((split == NULL) && (cursor_class == CURSOR_CLASS_TRANS))
return;
changed = xaccSplitRegisterGetChangeFlag(reg);
/* See if we were asked to cut an unchanged blank split. Don't. */
if (!changed && ((split == NULL) || (split == blank_split)))
return;
xaccSRCopyCurrentInternal(reg, TRUE);
if (cursor_class == CURSOR_CLASS_SPLIT)
xaccSRDeleteCurrentSplit(reg);
else
xaccSRDeleteCurrentTrans(reg);
}
/* ======================================================== */
void
xaccSRPasteCurrent (SplitRegister *reg)
{
SRInfo *info = xaccSRGetInfo(reg);
Split *blank_split = xaccSplitLookup(&info->blank_split_guid);
CursorClass cursor_class;
Transaction *trans;
Split *trans_split;
Split *split;
if (copied_class == CURSOR_CLASS_NONE)
return;
split = xaccSRGetCurrentSplit(reg);
trans = xaccSRGetCurrentTrans(reg);
trans_split = xaccSRGetCurrentTransSplit(reg, NULL);
/* This shouldn't happen, but be paranoid. */
if (trans == NULL)
return;
cursor_class = xaccSplitRegisterGetCurrentCursorClass (reg);
/* Can't do anything with this. */
if (cursor_class == CURSOR_CLASS_NONE)
return;
/* This shouldn't happen, but be paranoid. */
if ((split == NULL) && (cursor_class == CURSOR_CLASS_TRANS))
return;
if (cursor_class == CURSOR_CLASS_SPLIT)
{
const char *message = _("You are about to overwrite an existing split.\n"
"Are you sure you want to do that?");
gboolean result;
if (copied_class == CURSOR_CLASS_TRANS)
return;
if (split != NULL)
result = gnc_verify_dialog_parented(xaccSRGetParent(reg),
message, FALSE);
else
result = TRUE;
if (!result)
return;
gnc_suspend_gui_refresh ();
if (split == NULL)
{ /* We are on a null split in an expanded transaction. */
split = xaccMallocSplit();
xaccTransBeginEdit(trans);
xaccTransAppendSplit(trans, split);
xaccTransCommitEdit(trans);
}
gnc_copy_split_scm_onto_split(copied_item, split);
}
else {
const char *message = _("You are about to overwrite an existing "
"transaction.\n"
"Are you sure you want to do that?");
gboolean result;
const GUID *new_guid;
int trans_split_index;
int split_index;
int num_splits;
if (copied_class == CURSOR_CLASS_SPLIT)
return;
if (split != blank_split)
result = gnc_verify_dialog_parented(xaccSRGetParent(reg),
message, FALSE);
else
result = TRUE;
if (!result)
return;
gnc_suspend_gui_refresh ();
/* in pasting, the old split is deleted. */
if (split == blank_split)
{
info->blank_split_guid = *xaccGUIDNULL();
blank_split = NULL;
}
split_index = gnc_trans_split_index(trans, split);
trans_split_index = gnc_trans_split_index(trans, trans_split);
if ((sr_get_default_account (reg) != NULL) &&
(xaccGUIDType(&copied_leader_guid) != GNC_ID_NULL))
{
new_guid = &info->default_account;
gnc_copy_trans_scm_onto_trans_swap_accounts(copied_item, trans,
&copied_leader_guid,
new_guid, TRUE);
}
else
gnc_copy_trans_scm_onto_trans(copied_item, trans, TRUE);
num_splits = xaccTransCountSplits(trans);
if (split_index >= num_splits)
split_index = 0;
info->cursor_hint_trans = trans;
info->cursor_hint_split = xaccTransGetSplit(trans, split_index);
info->cursor_hint_trans_split = xaccTransGetSplit(trans,
trans_split_index);
info->cursor_hint_cursor_class = CURSOR_CLASS_TRANS;
}
/* Refresh the GUI. */
gnc_resume_gui_refresh ();
}
/* ======================================================== */
void
xaccSRDeleteCurrentSplit (SplitRegister *reg)
{
SRInfo *info = xaccSRGetInfo(reg);
Split *blank_split = xaccSplitLookup(&info->blank_split_guid);
Transaction *pending_trans = xaccTransLookup(&info->pending_trans_guid);
Transaction *trans;
Account *account;
Split *split;
/* get the current split based on cursor position */
split = xaccSRGetCurrentSplit(reg);
if (split == NULL)
return;
/* If we are deleting the blank split, just cancel. The user is
* allowed to delete the blank split as a method for discarding
* any edits they may have made to it. */
if (split == blank_split)
{
xaccSRCancelCursorSplitChanges(reg);
return;
}
gnc_suspend_gui_refresh ();
/* make a copy of all of the accounts that will be
* affected by this deletion, so that we can update
* their register windows after the deletion. */
trans = xaccSplitGetParent(split);
account = xaccSplitGetAccount(split);
xaccTransBeginEdit(trans);
xaccAccountBeginEdit(account);
xaccSplitDestroy(split);
xaccAccountCommitEdit(account);
xaccTransCommitEdit(trans);
/* Check pending transaction */
if (trans == pending_trans)
{
info->pending_trans_guid = *xaccGUIDNULL();
pending_trans = NULL;
}
gnc_resume_gui_refresh ();
}
/* ======================================================== */
void
xaccSRDeleteCurrentTrans (SplitRegister *reg)
{
SRInfo *info = xaccSRGetInfo(reg);
Split *blank_split = xaccSplitLookup(&info->blank_split_guid);
Transaction *pending_trans = xaccTransLookup(&info->pending_trans_guid);
Transaction *trans;
Account *account;
Split *split;
/* get the current split based on cursor position */
split = xaccSRGetCurrentSplit(reg);
if (split == NULL)
return;
/* If we just deleted the blank split, clean up. The user is
* allowed to delete the blank split as a method for discarding
* any edits they may have made to it. */
if (split == blank_split)
{
trans = xaccSplitGetParent (blank_split);
account = xaccSplitGetAccount(split);
/* Make sure we don't commit this later on */
if (trans == pending_trans)
{
info->pending_trans_guid = *xaccGUIDNULL();
pending_trans = NULL;
}
gnc_suspend_gui_refresh ();
xaccTransBeginEdit (trans);
xaccTransDestroy (trans);
xaccTransCommitEdit (trans);
info->blank_split_guid = *xaccGUIDNULL();
blank_split = NULL;
gnc_resume_gui_refresh ();
return;
}
info->trans_expanded = FALSE;
gnc_suspend_gui_refresh ();
/* make a copy of all of the accounts that will be
* affected by this deletion, so that we can update
* their register windows after the deletion. */
trans = xaccSplitGetParent(split);
xaccTransBeginEdit(trans);
xaccTransDestroy(trans);
xaccTransCommitEdit(trans);
/* Check pending transaction */
if (trans == pending_trans)
{
info->pending_trans_guid = *xaccGUIDNULL();
pending_trans = NULL;
}
gnc_resume_gui_refresh ();
}
/* ======================================================== */
void
xaccSREmptyCurrentTrans (SplitRegister *reg)
{
SRInfo *info = xaccSRGetInfo (reg);
Split *blank_split = xaccSplitLookup (&info->blank_split_guid);
Transaction *pending_trans = xaccTransLookup (&info->pending_trans_guid);
Transaction *trans;
Account *account;
GList *splits;
GList *node;
Split *split;
/* get the current split based on cursor position */
split = xaccSRGetCurrentSplit (reg);
if (split == NULL)
return;
/* If we just deleted the blank split, clean up. The user is
* allowed to delete the blank split as a method for discarding
* any edits they may have made to it. */
if (split == blank_split)
{
trans = xaccSplitGetParent (blank_split);
account = xaccSplitGetAccount (split);
/* Make sure we don't commit this later on */
if (trans == pending_trans)
{
info->pending_trans_guid = *xaccGUIDNULL ();
pending_trans = NULL;
}
gnc_suspend_gui_refresh ();
xaccTransBeginEdit (trans);
xaccTransDestroy (trans);
xaccTransCommitEdit (trans);
info->blank_split_guid = *xaccGUIDNULL ();
blank_split = NULL;
gnc_resume_gui_refresh ();
return;
}
gnc_suspend_gui_refresh ();
trans = xaccSplitGetParent (split);
splits = g_list_copy (xaccTransGetSplitList (trans));
xaccTransBeginEdit (trans);
for (node = splits; node; node = node->next)
if (node->data != split)
xaccSplitDestroy (node->data);
xaccTransCommitEdit (trans);
/* Check pending transaction */
if (trans == pending_trans)
{
info->pending_trans_guid = *xaccGUIDNULL ();
pending_trans = NULL;
}
gnc_resume_gui_refresh ();
g_list_free (splits);
}
/* ======================================================== */
void
xaccSRCancelCursorSplitChanges (SplitRegister *reg)
{
guint32 changed;
VirtualLocation virt_loc;
if (reg == NULL)
return;
virt_loc = reg->table->current_cursor_loc;
changed = xaccSplitRegisterGetChangeFlag (reg);
if (!changed)
return;
/* We're just cancelling the current split here, not the transaction.
* When cancelling edits, reload the cursor from the transaction. */
xaccSplitRegisterClearChangeFlag (reg);
if (gnc_table_find_close_valid_cell (reg->table, &virt_loc, FALSE))
gnc_table_move_cursor_gui (reg->table, virt_loc);
gnc_table_refresh_gui (reg->table, TRUE);
}
/* ======================================================== */
void
xaccSRCancelCursorTransChanges (SplitRegister *reg)
{
SRInfo *info = xaccSRGetInfo(reg);
Transaction *pending_trans = xaccTransLookup(&info->pending_trans_guid);
/* Get the currently open transaction, rollback the edits on it, and
* then repaint everything. To repaint everything, make a note of
* all of the accounts that will be affected by this rollback. */
if (!xaccTransIsOpen(pending_trans))
{
xaccSRCancelCursorSplitChanges (reg);
return;
}
if (!pending_trans)
return;
gnc_suspend_gui_refresh ();
xaccTransRollbackEdit (pending_trans);
info->pending_trans_guid = *xaccGUIDNULL ();
gnc_resume_gui_refresh ();
}
/* ======================================================== */
void
xaccSRRedrawReg (SplitRegister *reg)
{
xaccLedgerDisplayRefreshByReg (reg);
}
/* ======================================================== */
/* Copy from the register object to scheme. This needs to be
* in sync with xaccSRSaveRegEntry and xaccSRSaveChangedCells. */
static gboolean
xaccSRSaveRegEntryToSCM (SplitRegister *reg, SCM trans_scm, SCM split_scm,
gboolean use_cut_semantics)
{
SCM other_split_scm = SCM_UNDEFINED;
Transaction *trans;
guint32 changed;
/* use the changed flag to avoid heavy-weight updates
* of the split & transaction fields. This will help
* cut down on uneccessary register redraws. */
changed = xaccSplitRegisterGetChangeFlag (reg);
if (!changed)
return FALSE;
changed |= xaccSplitRegisterGetConditionalChangeFlag (reg);
/* get the handle to the current split and transaction */
trans = xaccSRGetCurrentTrans (reg);
if (trans == NULL)
return FALSE;
/* copy the contents from the cursor to the split */
if (MOD_DATE & changed)
{
Timespec ts;
xaccDateCellGetDate(reg->dateCell, &ts);
gnc_trans_scm_set_date(trans_scm, &ts);
}
if (MOD_NUM & changed)
gnc_trans_scm_set_num(trans_scm, reg->numCell->cell.value);
if (MOD_DESC & changed)
gnc_trans_scm_set_description(trans_scm, reg->descCell->cell.value);
if (MOD_NOTES & changed)
gnc_trans_scm_set_notes(trans_scm, reg->notesCell->cell.value);
if (MOD_RECN & changed)
gnc_split_scm_set_reconcile_state(split_scm,
xaccRecnCellGetFlag(reg->recnCell));
if (MOD_ACTN & changed)
gnc_split_scm_set_action(split_scm, reg->actionCell->cell.value);
if (MOD_MEMO & changed)
gnc_split_scm_set_memo(split_scm, reg->memoCell->cell.value);
if (MOD_XFRM & changed)
{
Account *new_account;
char *new_name;
new_name = reg->xfrmCell->cell.value;
new_account = xaccGetAccountFromFullName (gncGetCurrentGroup (),
new_name, account_separator);
if (new_account != NULL)
gnc_split_scm_set_account(split_scm, new_account);
}
if (reg->style == REG_STYLE_LEDGER)
other_split_scm = gnc_trans_scm_get_other_split_scm(trans_scm, split_scm);
if (MOD_MXFRM & changed)
{
other_split_scm = gnc_trans_scm_get_other_split_scm(trans_scm, split_scm);
if (other_split_scm == SCM_UNDEFINED)
{
if (gnc_trans_scm_get_num_splits(trans_scm) == 1)
{
Split *temp_split;
temp_split = xaccMallocSplit ();
other_split_scm = gnc_copy_split(temp_split, use_cut_semantics);
xaccSplitDestroy(temp_split);
gnc_trans_scm_append_split_scm(trans_scm, other_split_scm);
}
}
if (other_split_scm != SCM_UNDEFINED)
{
Account *new_account;
new_account = xaccGetAccountFromFullName (gncGetCurrentGroup (),
reg->mxfrmCell->cell.value,
account_separator);
if (new_account != NULL)
gnc_split_scm_set_account(other_split_scm, new_account);
}
}
if (MOD_AMNT & changed)
{
gnc_numeric new_value;
gnc_numeric credit;
gnc_numeric debit;
credit = xaccGetPriceCellValue (reg->creditCell);
debit = xaccGetPriceCellValue (reg->debitCell);
new_value = gnc_numeric_sub_fixed (debit, credit);
gnc_split_scm_set_value (split_scm, new_value);
}
if (MOD_PRIC & changed)
{
/* do nothing for now */
}
if (MOD_SHRS & changed)
{
gnc_numeric shares = xaccGetPriceCellValue(reg->sharesCell);
gnc_split_scm_set_quantity (split_scm, shares);
}
if ((MOD_AMNT | MOD_PRIC | MOD_SHRS) & changed)
{
if (other_split_scm != SCM_UNDEFINED)
{
gnc_numeric num;
num = gnc_split_scm_get_quantity (split_scm);
gnc_split_scm_set_quantity (other_split_scm, gnc_numeric_neg (num));
num = gnc_split_scm_get_value (split_scm);
gnc_split_scm_set_value (other_split_scm, gnc_numeric_neg (num));
}
}
return TRUE;
}
/* ======================================================== */
/* Copy from the register object to the engine */
gboolean
xaccSRSaveRegEntry (SplitRegister *reg, gboolean do_commit)
{
SRInfo *info = xaccSRGetInfo(reg);
Split *blank_split = xaccSplitLookup(&info->blank_split_guid);
Transaction *pending_trans = xaccTransLookup(&info->pending_trans_guid);
Transaction *blank_trans = xaccSplitGetParent(blank_split);
Transaction *trans;
guint32 changed;
Split *split;
const char *memo;
const char *desc;
/* get the handle to the current split and transaction */
split = xaccSRGetCurrentSplit (reg);
trans = xaccSRGetCurrentTrans (reg);
if (trans == NULL)
return FALSE;
/* use the changed flag to avoid heavy-weight updates
* of the split & transaction fields. This will help
* cut down on uneccessary register redraws. */
changed = xaccSplitRegisterGetChangeFlag (reg);
if (!changed)
{
if (!do_commit)
return FALSE;
if (trans == blank_trans)
{
if (xaccTransIsOpen(trans) || (info->blank_split_edited))
{
info->last_date_entered = xaccTransGetDate(trans);
info->blank_split_guid = *xaccGUIDNULL();
info->blank_split_edited = FALSE;
blank_split = NULL;
}
else
return FALSE;
}
else if (!xaccTransIsOpen(trans))
return FALSE;
if (xaccTransIsOpen(trans))
xaccTransCommitEdit(trans);
if (pending_trans == trans)
{
pending_trans = NULL;
info->pending_trans_guid = *xaccGUIDNULL();
}
return TRUE;
}
ENTER ("xaccSRSaveRegEntry(): save split is %p \n", split);
gnc_suspend_gui_refresh ();
/* determine whether we should commit the pending transaction */
if (pending_trans != trans)
{
if (xaccTransIsOpen (pending_trans))
xaccTransCommitEdit (pending_trans);
xaccTransBeginEdit (trans);
pending_trans = trans;
info->pending_trans_guid = *xaccTransGetGUID(trans);
}
/* If we are committing the blank split, add it to the account now */
if (trans == blank_trans)
{
xaccAccountInsertSplit (sr_get_default_account (reg), blank_split);
xaccTransSetDateEnteredSecs(trans, time(NULL));
}
if (split == NULL)
{
/* If we were asked to save data for a row for which there is no
* associated split, then assume that this was a row that was
* set aside for adding splits to an existing transaction.
* xaccSRGetCurrent will handle this case, too. We will create
* a new split, copy the row contents to that split, and append
* the split to the pre-existing transaction. */
Split *trans_split;
split = xaccMallocSplit ();
xaccTransAppendSplit (trans, split);
gnc_table_set_virt_cell_data (reg->table,
reg->table->current_cursor_loc.vcell_loc,
xaccSplitGetGUID (split));
trans_split = xaccSRGetCurrentTransSplit (reg, NULL);
if ((info->cursor_hint_trans == trans) &&
(info->cursor_hint_trans_split == trans_split) &&
(info->cursor_hint_split == NULL))
{
info->cursor_hint_split = split;
info->cursor_hint_cursor_class = CURSOR_CLASS_SPLIT;
}
}
DEBUG ("updating trans addr=%p\n", trans);
xaccSRSaveChangedCells (reg, trans, split);
memo = xaccSplitGetMemo (split);
memo = memo ? memo : "(null)";
desc = xaccTransGetDescription (trans);
desc = desc ? desc : "(null)";
PINFO ("finished saving split %s of trans %s \n", memo, desc);
/* If the modified split is the "blank split", then it is now an
* official part of the account. Set the blank split to NULL, so
* we can be sure of getting a new split. Also, save the date for
* the new blank split. */
if (trans == blank_trans)
{
if (do_commit)
{
info->blank_split_guid = *xaccGUIDNULL ();
blank_split = NULL;
info->last_date_entered = xaccTransGetDate (trans);
}
else
info->blank_split_edited = TRUE;
}
/* If requested, commit the current transaction and set the pending
* transaction to NULL. */
if (do_commit)
{
xaccTransCommitEdit (trans);
if (pending_trans == trans)
{
pending_trans = NULL;
info->pending_trans_guid = *xaccGUIDNULL ();
}
}
xaccSplitRegisterClearChangeFlag (reg);
gnc_resume_gui_refresh ();
return TRUE;
}
/* ======================================================== */
static void
sr_set_last_num (SplitRegister *reg, const char *num)
{
Account *account;
account = sr_get_default_account (reg);
if (!account)
return;
xaccAccountSetLastNum (account, num);
}
/* ======================================================== */
static guint32
sr_split_auto_calc (SplitRegister *reg, Split *split, guint32 changed)
{
gboolean recalc_shares = FALSE;
gboolean recalc_price = FALSE;
gboolean recalc_value = FALSE;
GNCAccountType account_type;
gnc_numeric calc_value;
gnc_numeric value;
gnc_numeric price;
gnc_numeric amount;
Account *account;
int denom;
/* First, check if this is an account other than STOCK or
* MUTUAL type. If it is, this is a bank balancing split, so
* don't recalc anything.*/
account = xaccSplitGetAccount (split);
account_type = xaccAccountGetType (account);
if (account_type != STOCK &&
account_type != MUTUAL &&
account_type != CURRENCY)
return changed;
if (MOD_SHRS & changed)
amount = xaccGetPriceCellValue (reg->sharesCell);
else
amount = xaccSplitGetShareAmount (split);
if (MOD_PRIC & changed)
price = xaccGetPriceCellValue (reg->priceCell);
else
price = xaccSplitGetSharePrice (split);
if (MOD_AMNT & changed)
{
gnc_numeric credit = xaccGetPriceCellValue (reg->creditCell);
gnc_numeric debit = xaccGetPriceCellValue (reg->debitCell);
value = gnc_numeric_sub_fixed (debit, credit);
}
else
value = xaccSplitGetValue (split);
/* Check if precisely one value is zero. If so, we can assume that the
* zero value needs to be recalculated. */
if (!gnc_numeric_zero_p (amount))
{
if (gnc_numeric_zero_p (price))
{
if (!gnc_numeric_zero_p (value))
recalc_price = TRUE;
}
else if (gnc_numeric_zero_p (value))
recalc_value = TRUE;
}
else if (!gnc_numeric_zero_p (price))
if (!gnc_numeric_zero_p (value))
recalc_shares = TRUE;
/* If we have not already flaged a recalc, check if this is a split
* which has 2 of the 3 values changed. */
if((!recalc_shares) &&
(!recalc_price) &&
(!recalc_value))
{
if (((MOD_PRIC | MOD_AMNT) & changed) == (MOD_PRIC | MOD_AMNT))
{
if (!(MOD_SHRS & changed))
recalc_shares = TRUE;
}
else
if (((MOD_SHRS | MOD_AMNT) & changed) == (MOD_SHRS | MOD_AMNT))
recalc_price = TRUE;
else
if (((MOD_SHRS | MOD_PRIC) & changed) == (MOD_SHRS | MOD_PRIC))
recalc_value = TRUE;
}
calc_value = gnc_numeric_mul (price, amount,
GNC_DENOM_AUTO, GNC_DENOM_LCD);
denom = gnc_split_get_value_denom (split);
/* Now, if we have not flagged one of the recalcs, and value and
* calc_value are not the same number, then we need to ask for
* help from the user. */
if (!recalc_shares &&
!recalc_price &&
!recalc_value &&
!gnc_numeric_same (value, calc_value, denom, GNC_RND_ROUND))
{
int choice;
int default_value;
GList *node;
GList *radio_list = NULL;
const char *title = _("Recalculate Transaction");
const char *message = _("The values entered for this transaction "
"are inconsistent.\nWhich value would you "
"like to have recalculated?");
if (MOD_SHRS & changed)
radio_list = g_list_append (radio_list,
g_strdup_printf ("%s (%s)",
_("Shares"), _("Changed")));
else
radio_list = g_list_append (radio_list, g_strdup (_("Shares")));
if (MOD_PRIC & changed)
radio_list = g_list_append (radio_list,
g_strdup_printf ("%s (%s)",
_("Price"), _("Changed")));
else
radio_list = g_list_append (radio_list, g_strdup (_("Price")));
if (MOD_AMNT & changed)
radio_list = g_list_append (radio_list,
g_strdup_printf ("%s (%s)",
_("Value"), _("Changed")));
else
radio_list = g_list_append (radio_list, g_strdup (_("Value")));
if (!(MOD_PRIC & changed))
default_value = 1;
if (!(MOD_SHRS & changed))
default_value = 0;
else if (!(MOD_AMNT & changed))
default_value = 2;
else
default_value = 1;
choice = gnc_choose_radio_option_dialog_parented (xaccSRGetParent(reg),
title,
message,
default_value,
radio_list);
for (node = radio_list; node; node = node->next)
g_free (node->data);
g_list_free (radio_list);
switch(choice)
{
case 0: /* Modify number of shares */
recalc_shares = TRUE;
break;
case 1: /* Modify the share price */
recalc_price = TRUE;
break;
case 2: /* Modify total value */
recalc_value = TRUE;
break;
default:
break;
}
}
if (recalc_shares)
if (!gnc_numeric_zero_p (price))
{
denom = gnc_split_get_quantity_denom (split);
amount = gnc_numeric_div (value, price, denom, GNC_RND_ROUND);
xaccSetPriceCellValue (reg->sharesCell, amount);
changed |= MOD_SHRS;
}
if (recalc_price)
if (!gnc_numeric_zero_p (amount))
{
price = gnc_numeric_div (value, amount,
GNC_DENOM_AUTO,
GNC_DENOM_EXACT);
if (gnc_numeric_negative_p (price))
{
price = gnc_numeric_neg (price);
xaccSetDebCredCellValue (reg->debitCell, reg->creditCell,
gnc_numeric_neg (value));
changed |= MOD_AMNT;
}
xaccSetPriceCellValue (reg->priceCell, price);
changed |= MOD_PRIC;
}
if (recalc_value)
{
denom = gnc_split_get_value_denom (split);
value = gnc_numeric_mul (price, amount, denom, GNC_RND_ROUND);
xaccSetDebCredCellValue (reg->debitCell, reg->creditCell, value);
changed |= MOD_AMNT;
}
return changed;
}
/* ======================================================== */
static void
xaccSRSaveChangedCells (SplitRegister *reg, Transaction *trans, Split *split)
{
SRInfo *info = xaccSRGetInfo (reg);
Split *other_split = NULL;
guint32 changed;
changed = xaccSplitRegisterGetChangeFlag (reg);
changed |= xaccSplitRegisterGetConditionalChangeFlag (reg);
/* copy the contents from the cursor to the split */
if (MOD_DATE & changed)
{
Timespec ts;
/* commit any pending changes */
xaccCommitDateCell (reg->dateCell);
DEBUG ("MOD_DATE: %s",
reg->dateCell->cell.value ? reg->dateCell->cell.value : "(null)");
xaccDateCellGetDate (reg->dateCell, &ts);
xaccTransSetDateTS (trans, &ts);
}
if (MOD_NUM & changed)
{
DEBUG ("MOD_NUM: %s\n",
reg->numCell->cell.value ? reg->numCell->cell.value : "(null)");
xaccTransSetNum (trans, reg->numCell->cell.value);
if (xaccSetNumCellLastNum (reg->numCell, reg->numCell->cell.value))
{
SRInfo *info = xaccSRGetInfo (reg);
Split *blank_split = xaccSplitLookup(&info->blank_split_guid);
Transaction *blank_trans = xaccSplitGetParent (blank_split);
if (trans != blank_trans)
sr_set_last_num (reg, reg->numCell->cell.value);
}
}
if (MOD_DESC & changed)
{
DEBUG ("MOD_DESC: %s",
reg->descCell->cell.value ? reg->descCell->cell.value : "(null)");
xaccTransSetDescription (trans, reg->descCell->cell.value);
}
if (MOD_NOTES & changed)
{
DEBUG ("MOD_NOTES: %s",
reg->notesCell->cell.value ? reg->notesCell->cell.value : "(null)");
xaccTransSetNotes (trans, reg->notesCell->cell.value);
}
if (MOD_RECN & changed)
{
DEBUG ("MOD_RECN: %c", xaccRecnCellGetFlag(reg->recnCell));
xaccSplitSetReconcile (split, xaccRecnCellGetFlag(reg->recnCell));
}
if (MOD_ACTN & changed)
{
DEBUG ("MOD_ACTN: %s",
reg->actionCell->cell.value ?
reg->actionCell->cell.value : "(null)");
xaccSplitSetAction (split, reg->actionCell->cell.value);
}
if (MOD_MEMO & changed)
{
DEBUG ("MOD_MEMO: %s",
reg->memoCell->cell.value ? reg->memoCell->cell.value : "(null)");
xaccSplitSetMemo (split, reg->memoCell->cell.value);
}
/* -------------------------------------------------------------- */
/* OK, the handling of transfers gets complicated because it depends
* on what was displayed to the user. For a multi-line display, we
* just reparent the indicated split, its it, and that's that. For
* a two-line display, we want to reparent the "other" split, but
* only if there is one. XFRM is the straight split, MXFRM is the
* mirrored split. */
if (MOD_XFRM & changed)
{
Account *old_acc;
Account *new_acc;
char *new_name;
DEBUG ("MOD_XFRM: %s",
reg->xfrmCell->cell.value ? reg->xfrmCell->cell.value : "(null)");
/* do some reparenting. Insertion into new account will automatically
* delete this split from the old account */
old_acc = xaccSplitGetAccount (split);
new_name = reg->xfrmCell->cell.value;
new_acc = xaccGetAccountFromFullName (gncGetCurrentGroup (),
new_name, account_separator);
if ((new_acc != NULL) && (old_acc != new_acc))
{
gnc_commodity * currency = NULL;
gnc_commodity * security = NULL;
currency = xaccAccountGetCurrency(new_acc);
currency = xaccTransIsCommonExclSCurrency(trans, currency, split);
if (currency == NULL)
{
security = xaccAccountGetSecurity(new_acc);
security = xaccTransIsCommonExclSCurrency(trans, security, split);
}
if ((currency != NULL) || (security != NULL))
xaccAccountInsertSplit (new_acc, split);
else
{
const char *format = _("You cannot transfer funds from the %s "
"account.\nIt does not have a matching "
"currency.\nTo transfer funds between "
"accounts with different currencies\n"
"you need an intermediate currency account.\n"
"Please see the GnuCash online manual.");
const char *name;
char *message;
name = xaccAccountGetName(new_acc);
name = name ? name : _("(no name)");
message = g_strdup_printf (format, name);
gnc_warning_dialog_parented (xaccSRGetParent(reg), message);
g_free(message);
}
}
}
if (reg->style == REG_STYLE_LEDGER && !info->trans_expanded)
other_split = xaccSplitGetOtherSplit (split);
if (MOD_MXFRM & changed)
{
DEBUG ("MOD_MXFRM: %s",
reg->mxfrmCell->cell.value ? reg->mxfrmCell->cell.value : "(null)");
other_split = xaccSplitGetOtherSplit (split);
/* other_split may be null for two very different reasons:
* (1) the parent transaction has three or more splits in it,
* and so the "other" split is ambiguous, and thus null.
* (2) the parent transaction has only this one split as a child.
* and "other" is null because there is no other.
*
* In the case (2), we want to create the other split, so that
* the user's request to transfer actually works out.
*/
if (!other_split)
{
other_split = xaccTransGetSplit (trans, 1);
if (!other_split)
{
other_split = xaccMallocSplit ();
xaccTransAppendSplit (trans, other_split);
}
}
if (other_split)
{
Account *old_acc=NULL, *new_acc=NULL;
/* do some reparenting. Insertion into new account will automatically
* delete from the old account */
old_acc = xaccSplitGetAccount (other_split);
new_acc = xaccGetAccountFromFullName (gncGetCurrentGroup (),
reg->mxfrmCell->cell.value,
account_separator);
if ((new_acc != NULL) && (old_acc != new_acc))
{
gnc_commodity * currency = NULL;
gnc_commodity * security = NULL;
currency = xaccAccountGetCurrency(new_acc);
currency = xaccTransIsCommonExclSCurrency(trans,
currency, other_split);
if (currency == NULL)
{
security = xaccAccountGetSecurity(new_acc);
security = xaccTransIsCommonExclSCurrency(trans,
security, other_split);
}
if ((currency != NULL) || (security != NULL))
xaccAccountInsertSplit (new_acc, other_split);
else
{
const char *format = _("You cannot transfer funds from the %s "
"account.\nIt does not have a matching "
"currency.\nTo transfer funds between "
"accounts with different currencies\n"
"you need an intermediate currency account.\n"
"Please see the GnuCash online manual.");
const char *name;
char *message;
name = xaccAccountGetName (new_acc);
name = name ? name : _("(no name)");
message = g_strdup_printf (format, name);
gnc_warning_dialog_parented (xaccSRGetParent(reg), message);
g_free (message);
}
}
}
}
/* If we have a stock, currency or portfolio register type, and
* changes have been made to the number of shares, the price, or the
* value, then we need to do some calculations to make sure it all
* balances properly.*/
if (((MOD_AMNT | MOD_PRIC | MOD_SHRS) & changed) &&
((STOCK_REGISTER == (reg->type)) ||
(CURRENCY_REGISTER == (reg->type)) ||
(PORTFOLIO_LEDGER == (reg->type))))
changed = sr_split_auto_calc (reg, split, changed);
if (MOD_SHRS & changed)
{
gnc_numeric amount = xaccGetPriceCellValue (reg->sharesCell);
gnc_numeric price = xaccGetPriceCellValue (reg->priceCell);
DEBUG ("MOD_SHRS");
xaccSplitSetShareAmount (split, amount);
xaccSplitSetSharePrice (split, price);
}
if (MOD_PRIC & changed)
{
gnc_numeric price;
price = xaccGetPriceCellValue (reg->priceCell);
DEBUG ("MOD_PRIC");
xaccSplitSetSharePrice (split, price);
}
if (MOD_AMNT & changed)
{
gnc_numeric new_amount;
gnc_numeric credit;
gnc_numeric debit;
credit = xaccGetPriceCellValue(reg->creditCell);
debit = xaccGetPriceCellValue(reg->debitCell);
new_amount = gnc_numeric_sub_fixed (debit, credit);
DEBUG ("MOD_AMNT");
xaccSplitSetValue (split, new_amount);
}
if ((MOD_AMNT | MOD_PRIC | MOD_SHRS) & changed)
{
xaccSplitScrub (split);
if (other_split)
{
gnc_numeric amount = xaccSplitGetShareAmount (split);
gnc_numeric price = xaccSplitGetSharePrice (split);
amount = gnc_numeric_neg (amount);
xaccSplitSetSharePriceAndAmount (other_split, price, amount);
xaccSplitScrub (other_split);
}
}
}
/* ======================================================== */
static gnc_numeric
get_trans_total_value (SplitRegister *reg, Transaction *trans)
{
GList *node;
Account *account;
gnc_numeric total = gnc_numeric_zero ();
account = sr_get_default_account (reg);
if (!account)
return total;
total = gnc_numeric_convert (total, xaccAccountGetCurrencySCU (account),
GNC_RND_ROUND);
for (node = xaccTransGetSplitList (trans); node; node = node->next)
{
Split *split = node->data;
if (xaccSplitGetAccount (split) != account)
continue;
total = gnc_numeric_add_fixed (total, xaccSplitGetValue (split));
}
return total;
}
static gnc_numeric
get_trans_total_shares (SplitRegister *reg, Transaction *trans)
{
GList *node;
Account *account;
gnc_numeric total = gnc_numeric_zero ();
account = sr_get_default_account (reg);
if (!account)
return total;
total = gnc_numeric_convert (total, xaccAccountGetCommoditySCU (account),
GNC_RND_ROUND);
for (node = xaccTransGetSplitList (trans); node; node = node->next)
{
Split *split = node->data;
if (xaccSplitGetAccount (split) != account)
continue;
total = gnc_numeric_add_fixed (total, xaccSplitGetShareAmount (split));
}
return total;
}
static Split *
get_trans_last_split (SplitRegister *reg, Transaction *trans)
{
GList *node;
Account *account;
Split *last_split = NULL;
account = sr_get_default_account (reg);
if (!account)
return last_split;
for (node = xaccTransGetSplitList (trans); node; node = node->next)
{
Split *split = node->data;
if (xaccSplitGetAccount (split) != account)
continue;
if (!last_split)
{
last_split = split;
continue;
}
if (xaccSplitDateOrder (last_split, split) < 0)
last_split = split;
}
return last_split;
}
static gnc_numeric
get_trans_total_share_balance (SplitRegister *reg, Transaction *trans)
{
Split *last_split;
last_split = get_trans_last_split (reg, trans);
return xaccSplitGetShareBalance (last_split);
}
static gnc_numeric
get_trans_total_balance (SplitRegister *reg, Transaction *trans)
{
Split *last_split;
last_split = get_trans_last_split (reg, trans);
return xaccSplitGetBalance (last_split);
}
/* ======================================================== */
static gboolean
use_security_cells (SplitRegister *reg, VirtualLocation virt_loc)
{
GNCAccountType account_type;
CursorClass cursor_class;
Account *account;
Split *split;
split = sr_get_split (reg, virt_loc.vcell_loc);
if (!split)
return TRUE;
cursor_class = xaccSplitRegisterGetCursorClass (reg,
virt_loc.vcell_loc);
if (cursor_class != CURSOR_CLASS_SPLIT)
return TRUE;
account = NULL;
if (virt_cell_loc_equal (virt_loc.vcell_loc,
reg->table->current_cursor_loc.vcell_loc))
{
guint32 changed = xaccSplitRegisterGetChangeFlag (reg);
if (MOD_XFRM & changed)
account = xaccGetAccountFromFullName (gncGetCurrentGroup (),
reg->xfrmCell->cell.value,
account_separator);
}
if (!account)
account = xaccSplitGetAccount (split);
if (!account)
return TRUE;
account_type = xaccAccountGetType (account);
if (account_type == STOCK ||
account_type == MUTUAL ||
account_type == CURRENCY)
return TRUE;
return FALSE;
}
const char *
xaccSRGetEntryHandler (VirtualLocation virt_loc,
gboolean *conditionally_changed, gpointer user_data)
{
SplitRegister *reg = user_data;
const char *value = "";
CellType cell_type;
Transaction *trans;
Split *split;
if (conditionally_changed)
*conditionally_changed = FALSE;
cell_type = xaccSplitRegisterGetCellType (reg, virt_loc);
split = sr_get_split (reg, virt_loc.vcell_loc);
if (split == NULL)
{
gnc_numeric imbalance;
gnc_commodity *currency;
trans = xaccSRGetTrans (reg, virt_loc.vcell_loc);
imbalance = xaccTransGetImbalance (trans);
if (gnc_numeric_zero_p (imbalance))
return value;
switch (cell_type)
{
case CRED_CELL:
case DEBT_CELL:
imbalance = gnc_numeric_neg (imbalance);
if (gnc_numeric_negative_p (imbalance) && (cell_type == DEBT_CELL))
return "";
if (gnc_numeric_positive_p (imbalance) && (cell_type == CRED_CELL))
return "";
if (conditionally_changed)
*conditionally_changed = TRUE;
imbalance = gnc_numeric_abs (imbalance);
currency = xaccTransGetCurrency (trans);
if (!currency)
currency = gnc_locale_default_currency ();
imbalance = gnc_numeric_convert (imbalance,
gnc_commodity_get_fraction (currency),
GNC_RND_ROUND);
return xaccPrintAmount (imbalance,
gnc_split_value_print_info (split, FALSE));
break;
default:
return value;
break;
}
}
trans = xaccSplitGetParent (split);
switch (cell_type)
{
case DATE_CELL:
{
Timespec ts;
xaccTransGetDateTS (trans, &ts);
return gnc_print_date (ts);
}
break;
case NUM_CELL:
return xaccTransGetNum (trans);
break;
case DESC_CELL:
return xaccTransGetDescription (trans);
break;
case NOTES_CELL:
return xaccTransGetNotes (trans);
break;
case RECN_CELL:
{
static char s[2];
s[0] = xaccSplitGetReconcile (split);
s[1] = '\0';
return s;
}
break;
case SHRBALN_CELL:
case TSHRBALN_CELL:
{
SRInfo *info = xaccSRGetInfo(reg);
Split *blank_split = xaccSplitLookup(&info->blank_split_guid);
gnc_numeric balance;
if (split == blank_split)
return "";
if (cell_type == SHRBALN_CELL)
balance = xaccSplitGetShareBalance (split);
else
balance = get_trans_total_share_balance (reg, trans);
return xaccPrintAmount (balance,
gnc_split_quantity_print_info (split, FALSE));
}
break;
case BALN_CELL:
case TBALN_CELL:
{
SRInfo *info = xaccSRGetInfo(reg);
Split *blank_split = xaccSplitLookup(&info->blank_split_guid);
gnc_numeric balance;
if (split == blank_split)
return "";
/* If the reverse_balance callback is present use that.
* Otherwise, reverse income and expense by default. */
if (cell_type == BALN_CELL)
balance = xaccSplitGetBalance (split);
else
balance = get_trans_total_balance (reg, trans);
if (reverse_balance != NULL)
{
Account *account;
account = xaccSplitGetAccount(split);
if (account == NULL)
account = sr_get_default_account (reg);
if (reverse_balance(account))
balance = gnc_numeric_neg (balance);
}
return xaccPrintAmount (balance,
gnc_split_value_print_info (split, FALSE));
}
break;
case ACTN_CELL:
return xaccSplitGetAction (split);
break;
case XFRM_CELL:
{
static char *name = NULL;
g_free (name);
name = xaccAccountGetFullName (xaccSplitGetAccount (split),
account_separator);
return name;
}
break;
case MEMO_CELL:
return xaccSplitGetMemo (split);
break;
case CRED_CELL:
case DEBT_CELL:
{
gnc_numeric amount;
amount = xaccSplitGetValue (split);
if (gnc_numeric_zero_p (amount))
return "";
if (gnc_numeric_negative_p (amount) && (cell_type == DEBT_CELL))
return "";
if (gnc_numeric_positive_p (amount) && (cell_type == CRED_CELL))
return "";
amount = gnc_numeric_abs (amount);
return xaccPrintAmount (amount,
gnc_split_value_print_info (split, FALSE));
}
break;
case PRIC_CELL:
{
gnc_numeric price;
if (!use_security_cells (reg, virt_loc))
return "";
price = xaccSplitGetSharePrice (split);
if (gnc_numeric_zero_p (price))
return "";
return xaccPrintAmount (price, gnc_default_price_print_info ());
}
break;
case SHRS_CELL:
{
gnc_numeric shares;
if (!use_security_cells (reg, virt_loc))
return "";
shares = xaccSplitGetShareAmount (split);
if (gnc_numeric_zero_p (shares))
return "";
return xaccPrintAmount (shares,
gnc_split_quantity_print_info (split, FALSE));
}
break;
case MXFRM_CELL:
{
Split *s = xaccSplitGetOtherSplit (split);
static char *name = NULL;
g_free (name);
if (s)
name = xaccAccountGetFullName (xaccSplitGetAccount (s),
account_separator);
else
{
/* for multi-split transactions and stock splits,
* use a special value. */
s = xaccTransGetSplit (xaccSplitGetParent(split), 1);
if (s)
name = g_strdup (SPLIT_TRANS_STR);
else if (safe_strcmp ("stock-split", xaccSplitGetType (split)) == 0)
name = g_strdup (STOCK_SPLIT_STR);
else
name = g_strdup ("");
}
return name;
}
break;
case TCRED_CELL:
case TDEBT_CELL:
{
gnc_numeric total;
total = get_trans_total_value (reg, trans);
if (gnc_numeric_zero_p (total))
return "";
if (gnc_numeric_negative_p (total) && (cell_type == TDEBT_CELL))
return "";
if (gnc_numeric_positive_p (total) && (cell_type == TCRED_CELL))
return "";
total = gnc_numeric_abs (total);
return xaccPrintAmount (total,
gnc_split_value_print_info (split, FALSE));
}
break;
case TSHRS_CELL:
{
gnc_numeric total;
total = get_trans_total_shares (reg, trans);
return xaccPrintAmount (total,
gnc_split_quantity_print_info (split, FALSE));
}
break;
default:
return "";
break;
}
}
static GNCAccountType
sr_type_to_account_type(SplitRegisterType sr_type)
{
switch (sr_type)
{
case BANK_REGISTER:
return BANK;
case CASH_REGISTER:
return CASH;
case ASSET_REGISTER:
return ASSET;
case CREDIT_REGISTER:
return CREDIT;
case LIABILITY_REGISTER:
return LIABILITY;
case INCOME_LEDGER:
case INCOME_REGISTER:
return INCOME;
case EXPENSE_REGISTER:
return EXPENSE;
case STOCK_REGISTER:
case PORTFOLIO_LEDGER:
return STOCK;
case CURRENCY_REGISTER:
return CURRENCY;
case GENERAL_LEDGER:
return NO_TYPE;
case EQUITY_REGISTER:
return EQUITY;
case SEARCH_LEDGER:
return NO_TYPE;
default:
return NO_TYPE;
}
}
const char *
xaccSRGetDebitString (SplitRegister *reg)
{
if (!reg)
return NULL;
reg->debit_str = gnc_get_debit_string (sr_type_to_account_type (reg->type));
if (reg->debit_str)
return reg->debit_str;
reg->debit_str = g_strdup (_("Debit"));
return reg->debit_str;
}
const char *
xaccSRGetCreditString (SplitRegister *reg)
{
if (reg->credit_str)
return reg->credit_str;
reg->credit_str =
gnc_get_credit_string (sr_type_to_account_type (reg->type));
if (reg->credit_str)
return reg->credit_str;
reg->credit_str = g_strdup (_("Credit"));
return reg->credit_str;
}
const char *
xaccSRGetLabelHandler (VirtualLocation virt_loc, gpointer user_data)
{
SplitRegister *reg = user_data;
CellType cell_type;
cell_type = xaccSplitRegisterGetCellType (reg, virt_loc);
switch (cell_type)
{
case DATE_CELL:
return _("Date");
case NUM_CELL:
return _("Num");
case DESC_CELL:
return _("Description");
case RECN_CELL:
return _("Reconciled:R") + 11;
case SHRBALN_CELL:
return _("Share Balance");
case BALN_CELL:
return _("Balance");
case ACTN_CELL:
return _("Action");
case XFRM_CELL:
return _("Account");
case MEMO_CELL:
return _("Memo");
case CRED_CELL:
return xaccSRGetCreditString (reg);
case DEBT_CELL:
return xaccSRGetDebitString (reg);
case PRIC_CELL:
if (!use_security_cells (reg, virt_loc))
return "";
return _("Price");
case SHRS_CELL:
if (!use_security_cells (reg, virt_loc))
return "";
return _("Shares");
case MXFRM_CELL:
return _("Transfer");
case TCRED_CELL:
if (reg->tcredit_str)
return reg->tcredit_str;
{
const char *string = xaccSRGetCreditString (reg);
if (string)
reg->tcredit_str = g_strdup_printf (_("Tot %s"), string);
}
if (reg->tcredit_str)
return reg->tcredit_str;
reg->tcredit_str = g_strdup (_("Tot Credit"));
return reg->tcredit_str;
case TDEBT_CELL:
if (reg->tdebit_str)
return reg->tdebit_str;
{
const char *string = xaccSRGetDebitString (reg);
if (string)
reg->tdebit_str = g_strdup_printf (_("Tot %s"), string);
}
if (reg->tdebit_str)
return reg->tdebit_str;
reg->tdebit_str = g_strdup (_("Tot Debit"));
return reg->tdebit_str;
case TSHRS_CELL:
return _("Tot Shares");
case TSHRBALN_CELL:
return _("Share Balance");
case TBALN_CELL:
return _("Balance");
case NOTES_CELL:
return _("Notes");
case NO_CELL:
return "";
default:
break;
}
PERR ("bad cell type: %d", cell_type);
return "";
}
CellIOFlags
xaccSRGetIOFlagsHandler (VirtualLocation virt_loc, gpointer user_data)
{
SplitRegister *reg = user_data;
CellType cell_type;
Split *split;
cell_type = xaccSplitRegisterGetCellType (reg, virt_loc);
switch (cell_type)
{
case DATE_CELL:
case NUM_CELL:
case DESC_CELL:
case ACTN_CELL:
case XFRM_CELL:
case MEMO_CELL:
case MXFRM_CELL:
case NOTES_CELL:
return XACC_CELL_ALLOW_ALL;
case CRED_CELL:
case DEBT_CELL:
split = sr_get_split (reg, virt_loc.vcell_loc);
if (safe_strcmp ("stock-split", xaccSplitGetType (split)) == 0)
return XACC_CELL_ALLOW_NONE;
return XACC_CELL_ALLOW_ALL;
case RECN_CELL:
return XACC_CELL_ALLOW_ALL | XACC_CELL_ALLOW_EXACT_ONLY;
case PRIC_CELL:
case SHRS_CELL:
if (use_security_cells (reg, virt_loc))
return XACC_CELL_ALLOW_ALL;
return XACC_CELL_ALLOW_SHADOW;
default:
return XACC_CELL_ALLOW_NONE;
}
}
guint32
xaccSRGetFGColorHandler (VirtualLocation virt_loc, gpointer user_data)
{
SplitRegister *reg = user_data;
const guint32 black = 0x000000;
const guint32 red = 0xff0000;
Transaction *trans;
VirtualCell *vcell;
gboolean is_current;
CellType cell_type;
Split *split;
if (!use_red_for_negative)
return black;
vcell = gnc_table_get_virtual_cell (reg->table, virt_loc.vcell_loc);
if (!vcell || !vcell->cellblock)
return black;
split = xaccSplitLookup (vcell->vcell_data);
if (split == NULL)
return black;
trans = xaccSplitGetParent (split);
cell_type = xaccSplitRegisterGetCellType (reg, virt_loc);
is_current = virt_cell_loc_equal (reg->table->current_cursor_loc.vcell_loc,
virt_loc.vcell_loc);
switch (cell_type)
{
case SHRS_CELL:
case TSHRS_CELL:
{
gnc_numeric shares;
if (cell_type == TSHRS_CELL)
shares = get_trans_total_shares (reg, trans);
else if (is_current)
shares = xaccGetPriceCellValue (reg->sharesCell);
else
shares = xaccSplitGetShareAmount (split);
if (gnc_numeric_negative_p (shares))
return red;
return black;
}
break;
case SHRBALN_CELL:
case TSHRBALN_CELL:
{
gnc_numeric balance;
if (cell_type == SHRBALN_CELL)
balance = xaccSplitGetShareBalance (split);
else
balance = get_trans_total_share_balance (reg, trans);
if (gnc_numeric_negative_p (balance))
return red;
return black;
}
break;
case BALN_CELL:
case TBALN_CELL:
{
gnc_numeric balance;
/* If the reverse_balance callback is present use that.
* Otherwise, reverse income and expense by default. */
if (cell_type == BALN_CELL)
balance = xaccSplitGetBalance (split);
else
balance = get_trans_total_balance (reg, trans);
if (reverse_balance != NULL)
{
Account *account;
account = xaccSplitGetAccount (split);
if (reverse_balance (account))
balance = gnc_numeric_neg (balance);
}
if (gnc_numeric_negative_p (balance))
return red;
return black;
}
break;
default:
break;
}
return black;
}
void
xaccSRGetCellBorderHandler (VirtualLocation virt_loc,
PhysicalCellBorders *borders,
gpointer user_data)
{
SplitRegister *reg = user_data;
CursorClass cursor_class;
VirtualCell *vcell;
vcell = gnc_table_get_virtual_cell (reg->table, virt_loc.vcell_loc);
if (!vcell || !vcell->cellblock)
return;
if (virt_loc.phys_col_offset < vcell->cellblock->start_col ||
virt_loc.phys_col_offset > vcell->cellblock->stop_col)
{
borders->top = CELL_BORDER_LINE_NONE;
borders->bottom = CELL_BORDER_LINE_NONE;
borders->left = CELL_BORDER_LINE_NONE;
borders->right = CELL_BORDER_LINE_NONE;
return;
}
cursor_class = xaccCursorTypeToClass (vcell->cellblock->cursor_type);
if (cursor_class == CURSOR_CLASS_TRANS &&
virt_loc.phys_col_offset == vcell->cellblock->start_col)
borders->left = CELL_BORDER_LINE_NONE;
if (cursor_class == CURSOR_CLASS_TRANS &&
virt_loc.phys_col_offset == vcell->cellblock->stop_col)
borders->right = CELL_BORDER_LINE_NONE;
if (cursor_class == CURSOR_CLASS_SPLIT)
{
borders->top = CELL_BORDER_LINE_LIGHT;
borders->bottom = CELL_BORDER_LINE_LIGHT;
borders->left = MIN (borders->left, CELL_BORDER_LINE_LIGHT);
borders->right = MIN (borders->right, CELL_BORDER_LINE_LIGHT);
if (virt_loc.phys_col_offset == vcell->cellblock->start_col)
borders->left = CELL_BORDER_LINE_LIGHT;
if (virt_loc.phys_col_offset == vcell->cellblock->stop_col)
borders->right = CELL_BORDER_LINE_LIGHT;
}
}
/* ======================================================== */
static SplitRegisterColors reg_colors =
{
0xffffff, /* white */
0xffffff,
0xffffff,
0xffffff,
0xffffff,
0xffffff,
0xffffff,
FALSE /* double mode alternate by physical row */
};
void
xaccSetSplitRegisterColors (SplitRegisterColors reg_colors_new)
{
reg_colors = reg_colors_new;
}
guint32
xaccSRGetBGColorHandler (VirtualLocation virt_loc,
gboolean *hatching,
gpointer user_data)
{
SplitRegister *reg = user_data;
VirtualCell *vcell;
guint32 bg_color;
gboolean is_current;
if (hatching)
{
CellType cell_type;
cell_type = xaccSplitRegisterGetCellType (reg, virt_loc);
if ((cell_type != DEBT_CELL) &&
(cell_type != CRED_CELL) &&
(cell_type != TDEBT_CELL) &&
(cell_type != TCRED_CELL))
*hatching = FALSE;
else
{
Transaction *trans;
trans = xaccSRGetTrans (reg, virt_loc.vcell_loc);
if (trans)
*hatching = !gnc_numeric_zero_p (xaccTransGetImbalance (trans));
else
*hatching = FALSE;
}
}
bg_color = 0xffffff; /* white */
if (!reg)
return bg_color;
vcell = gnc_table_get_virtual_cell (reg->table, virt_loc.vcell_loc);
if (!vcell || !vcell->cellblock)
return bg_color;
if ((virt_loc.phys_col_offset < vcell->cellblock->start_col) ||
(virt_loc.phys_col_offset > vcell->cellblock->stop_col))
return bg_color;
is_current = virt_cell_loc_equal (reg->table->current_cursor_loc.vcell_loc,
virt_loc.vcell_loc);
switch ((CursorType) vcell->cellblock->cursor_type)
{
case CURSOR_TYPE_HEADER:
return reg_colors.header_bg_color;
case CURSOR_TYPE_SINGLE_JOURNAL:
case CURSOR_TYPE_SINGLE_LEDGER:
if (is_current)
return vcell->start_primary_color ?
reg_colors.primary_active_bg_color :
reg_colors.secondary_active_bg_color;
return vcell->start_primary_color ?
reg_colors.primary_bg_color : reg_colors.secondary_bg_color;
case CURSOR_TYPE_DOUBLE_LEDGER:
case CURSOR_TYPE_DOUBLE_JOURNAL:
if (is_current)
{
if (reg_colors.double_alternate_virt)
return vcell->start_primary_color ?
reg_colors.primary_active_bg_color :
reg_colors.secondary_active_bg_color;
return (virt_loc.phys_row_offset % 2 == 0) ?
reg_colors.primary_active_bg_color :
reg_colors.secondary_active_bg_color;
}
if (reg_colors.double_alternate_virt)
return vcell->start_primary_color ?
reg_colors.primary_bg_color :
reg_colors.secondary_bg_color;
return (virt_loc.phys_row_offset % 2 == 0) ?
reg_colors.primary_bg_color :
reg_colors.secondary_bg_color;
case CURSOR_TYPE_SPLIT:
{
if (is_current)
return reg_colors.split_active_bg_color;
return reg_colors.split_bg_color;
}
default:
PWARN("Unexpected cursor type: %d\n", vcell->cellblock->cursor_type);
return bg_color;
}
}
gboolean
xaccSRConfirmHandler (VirtualLocation virt_loc,
gpointer user_data)
{
SplitRegister *reg = user_data;
SRInfo *info = xaccSRGetInfo (reg);
guint32 changed;
Split *split;
char recn;
/* This assumes we reset the flag whenver we change splits.
* This happens in LedgerMoveCursor. */
if (info->change_confirmed)
return TRUE;
split = sr_get_split (reg, virt_loc.vcell_loc);
if (!split)
return TRUE;
changed = xaccSplitRegisterGetChangeFlag (reg);
if (MOD_RECN & changed)
recn = xaccRecnCellGetFlag (reg->recnCell);
else
recn = xaccSplitGetReconcile (split);
if (recn == YREC)
{
gboolean confirm;
char *message = _("You are about to change a reconciled split.\n"
"Are you sure you want to do that?");
confirm = gnc_lookup_boolean_option ("Register",
"Confirm before changing reconciled",
TRUE);
if (!confirm)
return TRUE;
confirm = gnc_verify_dialog_parented (xaccSRGetParent (reg),
message, FALSE);
info->change_confirmed = confirm;
return confirm;
}
return TRUE;
}
static gboolean
recn_cell_confirm (char old_flag, gpointer data)
{
SplitRegister *reg = data;
if (old_flag == YREC)
{
const char *message = _("Do you really want to mark this transaction "
"not reconciled?\nDoing so might make future "
"reconciliation difficult!");
gboolean confirm;
confirm = gnc_lookup_boolean_option ("Register",
"Confirm before changing reconciled",
TRUE);
if (!confirm)
return TRUE;
return gnc_verify_dialog_parented (xaccSRGetParent (reg), message, TRUE);
}
return TRUE;
}
/* ======================================================== */
G_INLINE_FUNC void
sr_add_transaction (SplitRegister *reg,
Transaction *trans,
Split *split,
CellBlock *lead_cursor,
gboolean visible_splits,
gboolean start_primary_color,
gboolean sort_splits,
gboolean add_blank,
Transaction *find_trans,
Split *find_split,
CursorClass find_class,
int *new_split_row,
VirtualCellLocation *vcell_loc);
G_INLINE_FUNC void
sr_add_transaction (SplitRegister *reg,
Transaction *trans,
Split *split,
CellBlock *lead_cursor,
gboolean visible_splits,
gboolean start_primary_color,
gboolean sort_splits,
gboolean add_blank,
Transaction *find_trans,
Split *find_split,
CursorClass find_class,
int *new_split_row,
VirtualCellLocation *vcell_loc)
{
GList *node;
if (split == find_split)
*new_split_row = vcell_loc->virt_row;
gnc_table_set_vcell (reg->table, lead_cursor, xaccSplitGetGUID (split),
TRUE, start_primary_color, *vcell_loc);
vcell_loc->virt_row++;
if (sort_splits)
{
/* first debits */
for (node = xaccTransGetSplitList (trans); node; node = node->next)
{
Split *secondary = node->data;
if (gnc_numeric_negative_p (xaccSplitGetValue (secondary)))
continue;
if (secondary == find_split && find_class == CURSOR_CLASS_SPLIT)
*new_split_row = vcell_loc->virt_row;
gnc_table_set_vcell (reg->table, reg->cursor_split,
xaccSplitGetGUID (secondary),
visible_splits, TRUE, *vcell_loc);
vcell_loc->virt_row++;
}
/* then credits */
for (node = xaccTransGetSplitList (trans); node; node = node->next)
{
Split *secondary = node->data;
if (!gnc_numeric_negative_p (xaccSplitGetValue (secondary)))
continue;
if (secondary == find_split && find_class == CURSOR_CLASS_SPLIT)
*new_split_row = vcell_loc->virt_row;
gnc_table_set_vcell (reg->table, reg->cursor_split,
xaccSplitGetGUID (secondary),
visible_splits, TRUE, *vcell_loc);
vcell_loc->virt_row++;
}
}
else
{
for (node = xaccTransGetSplitList (trans); node; node = node->next)
{
Split *secondary = node->data;
if (secondary == find_split && find_class == CURSOR_CLASS_SPLIT)
*new_split_row = vcell_loc->virt_row;
gnc_table_set_vcell (reg->table, reg->cursor_split,
xaccSplitGetGUID (secondary),
visible_splits, TRUE, *vcell_loc);
vcell_loc->virt_row++;
}
}
if (!add_blank)
return;
if (find_trans == trans && find_split == NULL &&
find_class == CURSOR_CLASS_SPLIT)
*new_split_row = vcell_loc->virt_row;
/* Add blank transaction split */
gnc_table_set_vcell (reg->table, reg->cursor_split,
xaccSplitGetGUID (NULL), FALSE, TRUE, *vcell_loc);
vcell_loc->virt_row++;
}
/* ======================================================== */
void
xaccSRLoadRegister (SplitRegister *reg, GList * slist,
Account *default_account)
{
SRInfo *info = xaccSRGetInfo (reg);
Split *blank_split = xaccSplitLookup (&info->blank_split_guid);
Transaction *pending_trans = xaccTransLookup (&info->pending_trans_guid);
SplitRegisterBuffer *reg_buffer;
GHashTable *trans_table = NULL;
CellBlock *lead_cursor;
Transaction *blank_trans;
Transaction *find_trans;
Transaction *trans;
CursorClass find_class;
Split *find_trans_split;
Split *find_split;
Split *split;
Table *table;
GList *node;
gboolean start_primary_color = TRUE;
gboolean found_pending = FALSE;
gboolean found_divider = FALSE;
gboolean has_last_num = FALSE;
gboolean multi_line;
gboolean dynamic;
VirtualCellLocation vcell_loc;
VirtualLocation save_loc;
guint32 changed;
int new_trans_split_row = -1;
int new_trans_row = -1;
int new_split_row = -1;
time_t present;
/* make sure we have a blank split */
if (blank_split == NULL)
{
Transaction *trans;
gnc_suspend_gui_refresh ();
trans = xaccMallocTransaction ();
xaccTransBeginEdit (trans);
xaccTransSetDateSecs (trans, info->last_date_entered);
blank_split = xaccMallocSplit ();
xaccTransAppendSplit (trans, blank_split);
xaccTransCommitEdit (trans);
info->blank_split_guid = *xaccSplitGetGUID (blank_split);
info->blank_split_edited = FALSE;
gnc_resume_gui_refresh ();
}
blank_trans = xaccSplitGetParent (blank_split);
info->default_account = *xaccAccountGetGUID (default_account);
table = reg->table;
gnc_table_leave_update (table, table->current_cursor_loc);
multi_line = (reg->style == REG_STYLE_JOURNAL);
dynamic = (reg->style == REG_STYLE_AUTO_LEDGER);
lead_cursor = sr_get_passive_cursor (reg);
/* figure out where we are going to. */
if (info->traverse_to_new)
{
find_trans = xaccSplitGetParent (blank_split);
find_split = NULL;
find_trans_split = blank_split;
find_class = info->cursor_hint_cursor_class;
}
else
{
find_trans = info->cursor_hint_trans;
find_split = info->cursor_hint_split;
find_trans_split = info->cursor_hint_trans_split;
find_class = info->cursor_hint_cursor_class;
}
save_loc = table->current_cursor_loc;
/* If the current cursor has changed we save the values for later
* possible restoration. */
changed = xaccSplitRegisterGetChangeFlag (reg);
changed |= xaccSplitRegisterGetConditionalChangeFlag (reg);
if (changed && (find_split == xaccSRGetCurrentSplit (reg)))
{
reg_buffer = xaccMallocSplitRegisterBuffer ();
xaccSplitRegisterSaveCursor (reg, reg_buffer);
}
else
reg_buffer = NULL;
/* disable move callback -- we don't want the cascade of
* callbacks while we are fiddling with loading the register */
table->move_cursor = NULL;
/* invalidate the cursor */
{
VirtualLocation virt_loc;
virt_loc.vcell_loc.virt_row = -1;
virt_loc.vcell_loc.virt_col = -1;
virt_loc.phys_row_offset = -1;
virt_loc.phys_col_offset = -1;
gnc_table_move_cursor_gui (table, virt_loc);
}
/* make sure that the header is loaded */
vcell_loc.virt_row = 0;
vcell_loc.virt_col = 0;
gnc_table_set_vcell (table, reg->cursor_header, NULL, TRUE, TRUE, vcell_loc);
vcell_loc.virt_row++;
/* get the current time and reset the dividing row */
{
struct tm *tm;
present = time (NULL);
tm = localtime (&present);
tm->tm_sec = 59;
tm->tm_min = 59;
tm->tm_hour = 23;
tm->tm_isdst = -1;
present = mktime (tm);
}
if (info->first_pass)
{
if (default_account)
{
const char *last_num = xaccAccountGetLastNum (default_account);
if (last_num)
{
xaccSetNumCellLastNum (reg->numCell, last_num);
has_last_num = TRUE;
}
}
}
table->dividing_row = -1;
if (multi_line)
trans_table = g_hash_table_new (g_direct_hash, g_direct_equal);
/* populate the table */
for (node = slist; node; node = node->next)
{
split = node->data;
trans = xaccSplitGetParent (split);
if (pending_trans == trans)
found_pending = TRUE;
/* do not load the blank split */
if (split == blank_split)
continue;
if (multi_line)
{
if (trans == blank_trans)
continue;
if (g_hash_table_lookup (trans_table, trans))
continue;
g_hash_table_insert (trans_table, trans, trans);
}
if (info->show_present_divider &&
!found_divider &&
(present < xaccTransGetDate (trans)))
{
table->dividing_row = vcell_loc.virt_row;
found_divider = TRUE;
}
/* If this is the first load of the register,
* fill up the quickfill cells. */
if (info->first_pass)
{
GList *node;
xaccQuickFillAddCompletion (reg->descCell,
xaccTransGetDescription (trans));
xaccQuickFillAddCompletion (reg->notesCell,
xaccTransGetNotes (trans));
if (!has_last_num)
xaccSetNumCellLastNum (reg->numCell, xaccTransGetNum (trans));
for (node = xaccTransGetSplitList (trans); node; node = node->next)
{
Split *s = node->data;
xaccQuickFillAddCompletion (reg->memoCell, xaccSplitGetMemo (s));
}
}
if (trans == find_trans)
new_trans_row = vcell_loc.virt_row;
if (split == find_trans_split)
new_trans_split_row = vcell_loc.virt_row;
sr_add_transaction (reg, trans, split, lead_cursor, multi_line,
start_primary_color, TRUE, TRUE, find_trans,
find_split, find_class, &new_split_row, &vcell_loc);
if (!multi_line)
start_primary_color = !start_primary_color;
}
if (multi_line)
g_hash_table_destroy (trans_table);
/* add the blank split at the end. */
split = blank_split;
trans = xaccSplitGetParent (split);
if (pending_trans == trans)
found_pending = TRUE;
if (trans == find_trans)
new_trans_row = vcell_loc.virt_row;
if (split == find_trans_split)
new_trans_split_row = vcell_loc.virt_row;
/* go to blank on first pass */
if (info->first_pass)
{
new_split_row = -1;
new_trans_split_row = -1;
new_trans_row = -1;
save_loc.vcell_loc = vcell_loc;
save_loc.phys_row_offset = 0;
save_loc.phys_col_offset = 0;
}
sr_add_transaction (reg, trans, split, lead_cursor, multi_line,
start_primary_color, FALSE, info->blank_split_edited,
find_trans, find_split, find_class, &new_split_row,
&vcell_loc);
/* resize the table to the sizes we just counted above */
/* num_virt_cols is always one. */
gnc_table_set_size (table, vcell_loc.virt_row, 1);
/* restore the cursor to its rightful position */
{
VirtualLocation trans_split_loc;
Split *trans_split;
if (new_split_row > 0)
save_loc.vcell_loc.virt_row = new_split_row;
else if (new_trans_split_row > 0)
save_loc.vcell_loc.virt_row = new_trans_split_row;
else if (new_trans_row > 0)
save_loc.vcell_loc.virt_row = new_trans_row;
trans_split_loc = save_loc;
trans_split = xaccSRGetTransSplit (reg, save_loc.vcell_loc,
&trans_split_loc.vcell_loc);
if (dynamic || multi_line || info->trans_expanded)
{
gnc_table_set_virt_cell_cursor (table, trans_split_loc.vcell_loc,
sr_get_active_cursor (reg));
xaccSRSetTransVisible (reg, trans_split_loc.vcell_loc, TRUE, multi_line);
info->trans_expanded = (reg->style == REG_STYLE_LEDGER);
}
else
{
save_loc = trans_split_loc;
info->trans_expanded = FALSE;
}
if (gnc_table_find_close_valid_cell (table, &save_loc, FALSE))
{
gnc_table_move_cursor_gui (table, save_loc);
new_split_row = save_loc.vcell_loc.virt_row;
if (changed && (find_split == xaccSRGetCurrentSplit (reg)))
xaccSplitRegisterRestoreCursorChanged (reg, reg_buffer);
}
if (reg_buffer != NULL)
xaccDestroySplitRegisterBuffer (reg_buffer);
reg_buffer = NULL;
}
/* If we didn't find the pending transaction, it was removed
* from the account. */
if (!found_pending)
{
if (xaccTransIsOpen (pending_trans))
xaccTransCommitEdit (pending_trans);
info->pending_trans_guid = *xaccGUIDNULL ();
pending_trans = NULL;
}
/* Set up the hint transaction, split, transaction split, and column. */
info->cursor_hint_trans = xaccSRGetCurrentTrans (reg);
info->cursor_hint_split = xaccSRGetCurrentSplit (reg);
info->cursor_hint_trans_split = xaccSRGetCurrentTransSplit (reg, NULL);
info->cursor_hint_cursor_class = xaccSplitRegisterGetCurrentCursorClass(reg);
info->hint_set_by_traverse = FALSE;
info->traverse_to_new = FALSE;
info->exact_traversal = FALSE;
info->first_pass = FALSE;
info->reg_loaded = TRUE;
sr_set_cell_fractions (reg, xaccSRGetCurrentSplit (reg));
gnc_table_refresh_gui (table, TRUE);
xaccSRShowTrans (reg, table->current_cursor_loc.vcell_loc);
/* set the completion character for the xfer cells */
xaccComboCellSetCompleteChar (reg->mxfrmCell, account_separator);
xaccComboCellSetCompleteChar (reg->xfrmCell, account_separator);
/* set the confirmation callback for the reconcile cell */
xaccRecnCellSetConfirmCB (reg->recnCell, recn_cell_confirm, reg);
/* enable callback for cursor user-driven moves */
table->move_cursor = LedgerMoveCursor;
table->traverse = LedgerTraverse;
table->set_help = LedgerSetHelp;
table->user_data = reg;
reg->destroy = LedgerDestroy;
xaccSRLoadXferCells (reg, default_account);
}
/* ======================================================== */
/* walk account tree recursively, pulling out all the names */
static void
LoadXferCell (ComboCell * cell,
AccountGroup * grp,
gnc_commodity * base_currency,
gnc_commodity * base_security)
{
gboolean load_everything;
GList *list;
GList *node;
ENTER ("\n");
if (!grp) return;
load_everything = ((base_currency == NULL) && (base_security == NULL));
/* Build the xfer menu out of account names. Traverse sub-accounts
* recursively. Valid transfers can occur only between accounts
* with the same base currency. */
list = xaccGroupGetAccountList (grp);
for (node = list; node; node = node->next)
{
Account *account = node->data;
gnc_commodity * curr;
gnc_commodity * secu;
char *name;
curr = xaccAccountGetCurrency (account);
secu = xaccAccountGetSecurity (account);
if (load_everything ||
(gnc_commodity_equiv (curr,base_currency)) ||
(gnc_commodity_equiv (curr,base_security)) ||
(secu && (gnc_commodity_equiv (secu,base_currency))) ||
(secu && (gnc_commodity_equiv (secu,base_security))))
{
name = xaccAccountGetFullName (account, account_separator);
if (name != NULL)
{
xaccAddComboCellMenuItem (cell, name);
g_free(name);
}
}
LoadXferCell (cell, xaccAccountGetChildren (account),
base_currency, base_security);
}
LEAVE ("\n");
}
/* ======================================================== */
static void
xaccLoadXferCell (ComboCell *cell,
AccountGroup *grp,
Account *base_account)
{
gnc_commodity * curr, * secu;
curr = xaccAccountGetCurrency (base_account);
secu = xaccAccountGetSecurity (base_account);
xaccClearComboCellMenu (cell);
LoadXferCell (cell, grp, curr, secu);
}
/* ======================================================== */
void
xaccSRLoadXferCells (SplitRegister *reg, Account *base_account)
{
AccountGroup *group;
group = xaccGetAccountRoot(base_account);
if (group == NULL)
group = gncGetCurrentGroup();
if (group == NULL)
return;
xaccLoadXferCell (reg->xfrmCell, group, base_account);
xaccLoadXferCell (reg->mxfrmCell, group, base_account);
}
/* ======================================================== */
gboolean
xaccSRHasPendingChanges (SplitRegister *reg)
{
SRInfo *info = xaccSRGetInfo(reg);
Transaction *pending_trans = xaccTransLookup(&info->pending_trans_guid);
guint32 changed;
if (reg == NULL)
return FALSE;
changed = xaccSplitRegisterGetChangeFlag (reg);
if (changed)
return TRUE;
return xaccTransIsOpen(pending_trans);
}
/* ======================================================== */
static gboolean
trans_has_reconciled_splits (Transaction *trans)
{
GList *node;
for (node = xaccTransGetSplitList (trans); node; node = node->next)
{
Split *split = node->data;
switch (xaccSplitGetReconcile (split))
{
case YREC:
case FREC:
return TRUE;
default:
break;
}
}
return FALSE;
}
/* ======================================================== */
void
xaccSRShowPresentDivider (SplitRegister *reg, gboolean show_present)
{
SRInfo *info = xaccSRGetInfo (reg);
if (reg == NULL)
return;
info->show_present_divider = show_present;
}
/* ======================================================== */
void
xaccSetSplitRegisterColorizeNegative (gboolean use_red)
{
use_red_for_negative = use_red;
}
/* ======================================================== */
gboolean
xaccSRFullRefreshOK (SplitRegister *reg)
{
SRInfo *info = xaccSRGetInfo (reg);
if (!info)
return FALSE;
return info->full_refresh;
}
/* ======================= end of file =================== */