mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@2199 57a11ea4-9604-0410-9ed3-97b8803252fd
2441 lines
74 KiB
C
2441 lines
74 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:
|
|
* copy transaction data from engine into split-register 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 */
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
|
|
#include "top-level.h"
|
|
|
|
#include "ui-callbacks.h"
|
|
#include "messages.h"
|
|
#include "SplitLedger.h"
|
|
#include "MultiLedger.h"
|
|
#include "FileDialog.h"
|
|
#include "Refresh.h"
|
|
#include "splitreg.h"
|
|
#include "table-allgui.h"
|
|
#include "util.h"
|
|
|
|
#define BUFSIZE 1024
|
|
|
|
typedef struct _SRInfo SRInfo;
|
|
struct _SRInfo
|
|
{
|
|
/* The blank split at the bottom of the register */
|
|
Split * blank_split;
|
|
|
|
/* The currently open transaction, if any */
|
|
Transaction *pending_trans;
|
|
|
|
/* 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 column used to remember which column to put the cursor */
|
|
int cursor_hint_phys_col;
|
|
|
|
/* The default account where new splits are added */
|
|
Account *default_source_account;
|
|
|
|
/* The last date recorded in the blank split */
|
|
time_t last_date_entered;
|
|
|
|
/* User data for users of SplitRegisters */
|
|
void *user_data;
|
|
|
|
/* hook to get parent widget */
|
|
SRGetParentCallback get_parent;
|
|
|
|
/* hook to set help string */
|
|
SRSetHelpCallback set_help;
|
|
};
|
|
|
|
|
|
/* ======================================================== */
|
|
/* The force_double_entry_awareness flag controls how the
|
|
* register behaves if the user failed to specify a transfer-to
|
|
* account when creating a new split. What it does is simple,
|
|
* although it can lead to some confusion to the user.
|
|
* If this flag is set, then any new split will be put into
|
|
* the leader account. What happens visually is that it appears
|
|
* as if there are two transactions, one debiting and one crediting
|
|
* this account by exactly the same amount. Thus, the user is forced
|
|
* to deal with this somewhat nutty situation.
|
|
*
|
|
* If this flag is *not* set, then the split just sort of
|
|
* hangs out, without belonging to any account. This will
|
|
* of course lead to a ledger that fails to balance.
|
|
* Bummer, duude !
|
|
*
|
|
* hack alert -- this flag should really be made a configurable
|
|
* item in some config script.
|
|
*/
|
|
|
|
static int force_double_entry_awareness = 0;
|
|
|
|
/* 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;
|
|
|
|
/* static prototypes */
|
|
static void xaccSRLoadRegEntry (SplitRegister *reg, Split *split);
|
|
static Transaction * xaccSRGetTrans (SplitRegister *reg,
|
|
int phys_row, int phys_col);
|
|
|
|
|
|
/* 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;
|
|
|
|
/* calloc initializes to 0 */
|
|
info = calloc(1, sizeof(SRInfo));
|
|
assert(info != NULL);
|
|
|
|
info->last_date_entered = time(NULL);
|
|
|
|
reg->user_data = info;
|
|
}
|
|
|
|
static void
|
|
xaccSRDestroyRegisterData(SplitRegister *reg)
|
|
{
|
|
if (reg == NULL)
|
|
return;
|
|
|
|
if (reg->user_data != NULL)
|
|
free(reg->user_data);
|
|
|
|
reg->user_data = NULL;
|
|
}
|
|
|
|
static SRInfo *
|
|
xaccSRGetInfo(SplitRegister *reg)
|
|
{
|
|
assert(reg != NULL);
|
|
|
|
if (reg->user_data == NULL)
|
|
xaccSRInitRegisterData(reg);
|
|
|
|
return (SRInfo *) 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);
|
|
|
|
assert(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;
|
|
}
|
|
|
|
|
|
/* copies basic split values from 'from' split to 'to' split.
|
|
* doesn't copy reconciled flag, or open the parent transactions
|
|
* for editing. Does *not* insert the 'to' split into an account!! */
|
|
static void
|
|
gnc_copy_split(Split *from, Split *to)
|
|
{
|
|
if ((from == NULL) || (to == NULL))
|
|
return;
|
|
|
|
xaccSplitSetMemo(to, xaccSplitGetMemo(from));
|
|
xaccSplitSetAction(to, xaccSplitGetAction(from));
|
|
xaccSplitSetDocref(to, xaccSplitGetDocref(from));
|
|
xaccSplitSetSharePriceAndAmount(to,
|
|
xaccSplitGetSharePrice(from),
|
|
xaccSplitGetShareAmount(from));
|
|
}
|
|
|
|
/* copies the basic transaction values and the splits from the
|
|
* 'from' trans to the 'to' trans. Any existing splits in the
|
|
* 'to' trans are deleted. Does *not* open the 'to' trans for
|
|
* editing!!! Splits are copied using gnc_copy_split above.
|
|
* The new splits will be in exactly the same order as in
|
|
* the 'from' transaction. */
|
|
static void
|
|
gnc_copy_trans(Transaction *from, Transaction *to)
|
|
{
|
|
Split *from_split, *to_split;
|
|
Timespec timespec;
|
|
int num_splits;
|
|
int i;
|
|
|
|
if ((from == NULL) || (to == NULL))
|
|
return;
|
|
|
|
/* remove the old splits */
|
|
to_split = xaccTransGetSplit(to, 0);
|
|
while (to_split != NULL)
|
|
{
|
|
xaccSplitDestroy(to_split);
|
|
to_split = xaccTransGetSplit(to, 0);
|
|
}
|
|
|
|
/* add in the new splits */
|
|
num_splits = xaccTransCountSplits(from);
|
|
for (i = 0; i < num_splits; i++)
|
|
{
|
|
from_split = xaccTransGetSplit(from, i);
|
|
|
|
to_split = xaccMallocSplit();
|
|
gnc_copy_split(from_split, to_split);
|
|
|
|
xaccTransAppendSplit(to, to_split);
|
|
}
|
|
|
|
/* now do the transaction-specific values */
|
|
xaccTransGetDateTS(from, ×pec);
|
|
xaccTransSetDateTS(to, ×pec);
|
|
xaccTransSetDescription(to, xaccTransGetDescription(from));
|
|
xaccTransSetDocref(to, xaccTransGetDocref(from));
|
|
}
|
|
|
|
/* ======================================================== */
|
|
/* 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, the following
|
|
* sequence of events get triggered and cascade down:
|
|
* VerifyCursorPosition() {
|
|
* MoveCursor() {
|
|
* callback for move() which is this function (LedgerMoveCursor) {
|
|
* SaveRegEntry() {...}
|
|
* RedrawRegEntry() {
|
|
* SRLoadRegister() {
|
|
* SRLoadRegEntry() {
|
|
* MoveCursor () { }
|
|
* }
|
|
* }
|
|
* }}}}
|
|
*/
|
|
|
|
static void
|
|
LedgerMoveCursor (Table *table,
|
|
int *p_new_phys_row,
|
|
int *p_new_phys_col,
|
|
void * client_data)
|
|
{
|
|
int new_phys_row = *p_new_phys_row;
|
|
int new_phys_col = *p_new_phys_col;
|
|
SplitRegister *reg = (SplitRegister *) client_data;
|
|
SRInfo *info = xaccSRGetInfo(reg);
|
|
Transaction *pending_trans;
|
|
Transaction *new_trans;
|
|
Transaction *trans;
|
|
Split *new_split;
|
|
gncBoolean saved;
|
|
Locator *locator;
|
|
int new_cell_row;
|
|
int new_cell_col;
|
|
int phys_row_offset;
|
|
int phys_col_offset;
|
|
int style;
|
|
|
|
PINFO ("LedgerMoveCursor(): start callback %d %d \n",
|
|
new_phys_row, new_phys_col);
|
|
|
|
/* The transaction we are coming from */
|
|
trans = xaccSRGetCurrentTrans(reg);
|
|
|
|
/* The change in physical location */
|
|
phys_row_offset = new_phys_row - table->current_cursor_phys_row;
|
|
phys_col_offset = new_phys_col - table->current_cursor_phys_col;
|
|
|
|
/* The transaction where we are moving to */
|
|
new_trans = xaccSRGetTrans(reg, new_phys_row, new_phys_col);
|
|
|
|
/* The split we are moving to */
|
|
new_split = xaccGetUserData(reg->table, new_phys_row, new_phys_col);
|
|
|
|
/* The cell offset we are moving to */
|
|
locator = table->locators[new_phys_row][new_phys_col];
|
|
new_cell_row = locator->phys_row_offset;
|
|
new_cell_col = locator->phys_col_offset;
|
|
|
|
/* commit the contents of the cursor into the database */
|
|
saved = xaccSRSaveRegEntry (reg, new_trans);
|
|
if ((info->pending_trans != NULL) &&
|
|
(info->pending_trans == trans) &&
|
|
(trans != new_trans))
|
|
{
|
|
if (xaccTransIsOpen(trans))
|
|
xaccTransCommitEdit (trans);
|
|
info->pending_trans = NULL;
|
|
pending_trans = NULL;
|
|
saved = GNC_T;
|
|
}
|
|
else
|
|
pending_trans = info->pending_trans;
|
|
|
|
/* redrawing the register can muck everything up */
|
|
if (saved) {
|
|
int virt_row, virt_col;
|
|
|
|
xaccSRRedrawRegEntry (reg);
|
|
|
|
/* if the transaction is no longer in the register,
|
|
* we commit it now. This is a hack that can go away
|
|
* once we have transaction ids. */
|
|
if ((pending_trans != info->pending_trans) &&
|
|
xaccTransIsOpen(pending_trans))
|
|
xaccTransCommitEdit (pending_trans);
|
|
|
|
/* if the split we were going to is still in the register,
|
|
* then it may have moved. Find out where it is now. */
|
|
if (xaccSRGetTransSplitRowCol (reg, new_trans, new_split,
|
|
&virt_row, &virt_col)) {
|
|
RevLocator *rev_locator;
|
|
|
|
rev_locator = table->rev_locators[virt_row][virt_col];
|
|
|
|
new_phys_row = rev_locator->phys_row;
|
|
new_phys_col = rev_locator->phys_col;
|
|
|
|
new_phys_row += new_cell_row;
|
|
new_phys_col += new_cell_col;
|
|
}
|
|
/* otherwise, the split is not in the register and we
|
|
* have to figure out where to go. We make a guess based
|
|
* on the change in physical location that was going to
|
|
* happen before the refresh. */
|
|
else {
|
|
new_phys_row = table->current_cursor_phys_row + phys_row_offset;
|
|
new_phys_col = table->current_cursor_phys_col + phys_col_offset;
|
|
}
|
|
|
|
/* just because I'm paranoid doesn't
|
|
* mean they're not out to get me! */
|
|
if (new_phys_row < reg->num_header_rows)
|
|
new_phys_row = reg->num_header_rows;
|
|
else if (new_phys_row >= table->num_phys_rows)
|
|
new_phys_row = table->num_phys_rows - 1;
|
|
|
|
if (new_phys_col < 0)
|
|
new_phys_col = 0;
|
|
else if (new_phys_col >= table->num_phys_cols)
|
|
new_phys_col = table->num_phys_cols - 1;
|
|
|
|
gnc_table_find_valid_cell_horiz(table, &new_phys_row, &new_phys_col, GNC_F);
|
|
|
|
*p_new_phys_row = new_phys_row;
|
|
*p_new_phys_col = new_phys_col;
|
|
}
|
|
|
|
PINFO ("LedgerMoveCursor(): after redraw %d %d \n",
|
|
new_phys_row, new_phys_col);
|
|
|
|
reg->cursor_phys_row = new_phys_row;
|
|
|
|
locator = table->locators[new_phys_row][new_phys_col];
|
|
reg->cursor_virt_row = locator->virt_row;
|
|
|
|
/* if auto-expansion is enabled, we need to redraw the register
|
|
* to expand out the splits at the new location. We use the
|
|
* cursor_hint data members to tell the refresh routine where
|
|
* to go. */
|
|
style = ((reg->type) & REG_STYLE_MASK);
|
|
if ((REG_SINGLE_DYNAMIC == style) ||
|
|
(REG_DOUBLE_DYNAMIC == style))
|
|
{
|
|
new_trans = xaccSRGetTrans(reg, new_phys_row, new_phys_col);
|
|
info->cursor_hint_trans = new_trans;
|
|
|
|
new_split = xaccGetUserData (reg->table, new_phys_row, new_phys_col);
|
|
info->cursor_hint_split = new_split;
|
|
|
|
info->cursor_hint_phys_col = new_phys_col;
|
|
|
|
xaccRegisterRefresh (reg);
|
|
|
|
/* indicate what row we should go to */
|
|
*p_new_phys_row = table->current_cursor_phys_row;
|
|
*p_new_phys_col = table->current_cursor_phys_col;
|
|
|
|
PINFO ("LedgerMoveCursor(): after dynamic %d %d stored val %d\n",
|
|
*p_new_phys_row, *p_new_phys_col, reg->cursor_phys_row);
|
|
}
|
|
}
|
|
|
|
/* ======================================================== */
|
|
/* 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. It currently does nothing.
|
|
*/
|
|
|
|
static void
|
|
LedgerTraverse (Table *table,
|
|
int *p_new_phys_row,
|
|
int *p_new_phys_col,
|
|
void * client_data)
|
|
{
|
|
SplitRegister *reg = client_data;
|
|
SRInfo *info = xaccSRGetInfo(reg);
|
|
Transaction *trans, *new_trans;
|
|
int phys_row = *p_new_phys_row;
|
|
int phys_col = *p_new_phys_col;
|
|
int virt_row, virt_col;
|
|
unsigned int changed;
|
|
GNCVerifyResult result;
|
|
|
|
trans = xaccSRGetCurrentTrans(reg);
|
|
if (trans == NULL)
|
|
return;
|
|
|
|
/* no changes, no worries */
|
|
changed = xaccSplitRegisterGetChangeFlag(reg);
|
|
if (!changed && (info->pending_trans != trans))
|
|
return;
|
|
|
|
/* Now see if we are changing cursors. If not, no problems */
|
|
if ((phys_row >= 0) && (phys_col >= 0) &&
|
|
(phys_row < table->num_phys_rows) && (phys_col < table->num_phys_cols))
|
|
{
|
|
virt_row = table->locators[phys_row][phys_col]->virt_row;
|
|
virt_col = table->locators[phys_row][phys_col]->virt_col;
|
|
|
|
if ((virt_row == table->current_cursor_virt_row) &&
|
|
(virt_col == table->current_cursor_virt_col))
|
|
return;
|
|
}
|
|
|
|
/* Same transaction, no problem */
|
|
new_trans = xaccSRGetTrans(reg, phys_row, phys_col);
|
|
if (trans == new_trans)
|
|
return;
|
|
|
|
/* Ok, we are changing transactions and the current transaction has
|
|
* changed. See what the user wants to do. */
|
|
|
|
result = gnc_verify_cancel_dialog_parented(xaccSRGetParent(reg),
|
|
TRANS_CHANGED_MSG,
|
|
GNC_VERIFY_YES);
|
|
|
|
switch (result)
|
|
{
|
|
case GNC_VERIFY_NO:
|
|
xaccSRCancelCursorTransChanges(reg);
|
|
break;
|
|
case GNC_VERIFY_CANCEL:
|
|
*p_new_phys_row = table->current_cursor_phys_row;
|
|
*p_new_phys_col = table->current_cursor_phys_col;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
static void
|
|
LedgerSetHelp (Table *table, const char *help_str, void *client_data)
|
|
{
|
|
SplitRegister *reg = client_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);
|
|
Transaction *trans;
|
|
|
|
/* be sure to destroy the "blank split" */
|
|
if (info->blank_split) {
|
|
/* split destroy will automatically remove it
|
|
* from its parent account */
|
|
trans = xaccSplitGetParent (info->blank_split);
|
|
|
|
/* Make sure we don't commit this below */
|
|
if (trans == info->pending_trans)
|
|
info->pending_trans = NULL;
|
|
|
|
xaccTransBeginEdit (trans, 1);
|
|
xaccTransDestroy (trans);
|
|
xaccTransCommitEdit (trans);
|
|
|
|
info->blank_split = NULL;
|
|
}
|
|
|
|
/* be sure to take care of any open transactions */
|
|
if (info->pending_trans) {
|
|
/* Committing this should have been taken care of by
|
|
* xaccLedgerDisplayClose. But, we'll check again. */
|
|
if (xaccTransIsOpen(info->pending_trans))
|
|
xaccTransCommitEdit (info->pending_trans);
|
|
|
|
info->pending_trans = NULL;
|
|
}
|
|
|
|
xaccSRDestroyRegisterData(reg);
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
static Transaction *
|
|
xaccSRGetTrans (SplitRegister *reg, int phys_row, int phys_col)
|
|
{
|
|
Split *split;
|
|
int virt_row, virt_col;
|
|
|
|
if (reg == NULL)
|
|
return NULL;
|
|
|
|
if ((phys_row < 0) || (phys_col < 0) ||
|
|
(phys_row >= reg->table->num_phys_rows) ||
|
|
(phys_col >= reg->table->num_phys_cols))
|
|
return NULL;
|
|
|
|
split = xaccGetUserData(reg->table, phys_row, phys_col);
|
|
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. */
|
|
virt_row = reg->table->locators[phys_row][phys_col]->virt_row;
|
|
virt_col = reg->table->locators[phys_row][phys_col]->virt_col;
|
|
|
|
virt_row --;
|
|
if ((0 > virt_row) || (0 > virt_col)) {
|
|
PERR ("Internal Error: xaccSRGetTrans(): bad row \n");
|
|
return NULL;
|
|
}
|
|
|
|
split = (Split *) reg->table->user_data[virt_row][virt_col];
|
|
if (split == NULL) {
|
|
PERR ("Internal Error: xaccSRGetTrans(): no parent \n");
|
|
return NULL;
|
|
}
|
|
|
|
return xaccSplitGetParent(split);
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
Transaction *
|
|
xaccSRGetCurrentTrans (SplitRegister *reg)
|
|
{
|
|
Split *split;
|
|
int phys_row, phys_col;
|
|
int virt_row, virt_col;
|
|
|
|
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. */
|
|
phys_row = reg->table->current_cursor_phys_row;
|
|
phys_col = reg->table->current_cursor_phys_col;
|
|
|
|
if ((phys_row < 0) || (phys_col < 0) ||
|
|
(phys_row >= reg->table->num_phys_rows) ||
|
|
(phys_col >= reg->table->num_phys_cols))
|
|
return NULL;
|
|
|
|
virt_row = reg->table->locators[phys_row][phys_col]->virt_row;
|
|
virt_col = reg->table->locators[phys_row][phys_col]->virt_col;
|
|
|
|
virt_row --;
|
|
if ((0 > virt_row) || (0 > virt_col)) {
|
|
PERR ("Internal Error: xaccSRGetCurrentTrans(): bad row \n");
|
|
return NULL;
|
|
}
|
|
|
|
split = (Split *) reg->table->user_data[virt_row][virt_col];
|
|
|
|
return xaccSplitGetParent(split);
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
Split *
|
|
xaccSRGetCurrentSplit (SplitRegister *reg)
|
|
{
|
|
CellBlock *cursor;
|
|
Split *split;
|
|
|
|
/* get the handle to the current split and transaction */
|
|
cursor = reg->table->current_cursor;
|
|
if (!cursor) return NULL;
|
|
split = (Split *) (cursor->user_data);
|
|
|
|
return split;
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
Split *
|
|
xaccSRGetBlankSplit (SplitRegister *reg)
|
|
{
|
|
SRInfo *info = xaccSRGetInfo(reg);
|
|
|
|
return info->blank_split;
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
gncBoolean
|
|
xaccSRGetSplitRowCol (SplitRegister *reg, Split *split,
|
|
int *virt_row, int *virt_col)
|
|
{
|
|
Table *table = reg->table;
|
|
int v_row, v_col;
|
|
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++)
|
|
{
|
|
s = (Split *) table->user_data[v_row][v_col];
|
|
|
|
if (s == split)
|
|
{
|
|
if (virt_row != NULL)
|
|
*virt_row = v_row;
|
|
if (virt_col != NULL)
|
|
*virt_col = v_col;
|
|
|
|
return GNC_T;
|
|
}
|
|
}
|
|
|
|
return GNC_F;
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
gncBoolean
|
|
xaccSRGetTransSplitRowCol (SplitRegister *reg,
|
|
Transaction *trans, Split *split,
|
|
int *virt_row, int *virt_col)
|
|
{
|
|
Table *table = reg->table;
|
|
gncBoolean found = GNC_F;
|
|
int v_row, v_col;
|
|
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++)
|
|
{
|
|
s = (Split *) table->user_data[v_row][v_col];
|
|
|
|
if (xaccSplitGetParent(s) == trans)
|
|
found = GNC_T;
|
|
|
|
if (found && (s == split))
|
|
{
|
|
if (virt_row != NULL)
|
|
*virt_row = v_row;
|
|
if (virt_col != NULL)
|
|
*virt_col = v_col;
|
|
|
|
return GNC_T;
|
|
}
|
|
}
|
|
|
|
return GNC_F;
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
Split *
|
|
xaccSRDuplicateCurrent (SplitRegister *reg)
|
|
{
|
|
SRInfo *info = xaccSRGetInfo(reg);
|
|
CursorType cursor_type;
|
|
unsigned int changed;
|
|
Transaction *trans;
|
|
Split *return_split;
|
|
Split *split;
|
|
|
|
split = xaccSRGetCurrentSplit(reg);
|
|
trans = xaccSRGetCurrentTrans(reg);
|
|
|
|
/* This shouldn't happen, but be paranoid. */
|
|
if (trans == NULL)
|
|
return NULL;
|
|
|
|
cursor_type = xaccSplitRegisterGetCursorType(reg);
|
|
|
|
/* Can't do anything with this. */
|
|
if (cursor_type == CURSOR_NONE)
|
|
return NULL;
|
|
|
|
/* This shouldn't happen, but be paranoid. */
|
|
if ((split == NULL) && (cursor_type == CURSOR_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 == info->blank_split)))
|
|
return NULL;
|
|
|
|
/* 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)
|
|
{
|
|
GNCVerifyResult result;
|
|
|
|
result = gnc_ok_cancel_dialog_parented(xaccSRGetParent(reg),
|
|
TRANS_CHANGED_MSG,
|
|
GNC_VERIFY_OK);
|
|
|
|
if (result == GNC_VERIFY_CANCEL)
|
|
return NULL;
|
|
|
|
xaccSRSaveRegEntry(reg, NULL);
|
|
|
|
/* 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_type == CURSOR_SPLIT)
|
|
{
|
|
Split *new_split;
|
|
Account *account;
|
|
|
|
/* We are on a split in an expanded transaction.
|
|
* Just copy the split and add it to the transaction. */
|
|
|
|
new_split = xaccMallocSplit();
|
|
|
|
gnc_copy_split(split, new_split);
|
|
|
|
account = xaccSplitGetAccount(split);
|
|
|
|
xaccTransBeginEdit(trans, GNC_T);
|
|
xaccAccountBeginEdit(account, GNC_T);
|
|
|
|
xaccTransAppendSplit(trans, new_split);
|
|
xaccAccountInsertSplit(account, new_split);
|
|
|
|
xaccAccountCommitEdit(account);
|
|
xaccTransCommitEdit(trans);
|
|
|
|
return_split = new_split;
|
|
}
|
|
else
|
|
{
|
|
Transaction *new_trans;
|
|
int num_splits;
|
|
int i;
|
|
|
|
/* We are on a transaction row. Copy the whole transaction. */
|
|
|
|
new_trans = xaccMallocTransaction();
|
|
|
|
xaccTransBeginEdit(new_trans, GNC_T);
|
|
|
|
gnc_copy_trans(trans, new_trans);
|
|
|
|
num_splits = xaccTransCountSplits(trans);
|
|
return_split = NULL;
|
|
|
|
/* Link the new splits into the accounts. */
|
|
for (i = 0; i < num_splits; i++)
|
|
{
|
|
Account *account;
|
|
Split *old_split;
|
|
Split *new_split;
|
|
|
|
old_split = xaccTransGetSplit(trans, i);
|
|
account = xaccSplitGetAccount(old_split);
|
|
|
|
new_split = xaccTransGetSplit(new_trans, i);
|
|
|
|
xaccAccountBeginEdit(account, GNC_T);
|
|
xaccAccountInsertSplit(account, new_split);
|
|
xaccAccountCommitEdit(account);
|
|
|
|
/* Returned split is the transaction split */
|
|
if (old_split == split)
|
|
return_split = new_split;
|
|
}
|
|
|
|
xaccTransCommitEdit(new_trans);
|
|
|
|
/* This shouldn't happen, but be paranoid. */
|
|
if (return_split == NULL)
|
|
return_split = xaccTransGetSplit(new_trans, 0);
|
|
}
|
|
|
|
/* Refresh the GUI. */
|
|
gnc_transaction_ui_refresh(trans);
|
|
gnc_refresh_main_window();
|
|
|
|
return return_split;
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
void
|
|
xaccSRDeleteCurrentSplit (SplitRegister *reg)
|
|
{
|
|
SRInfo *info = xaccSRGetInfo(reg);
|
|
Split *split, *s;
|
|
Transaction *trans;
|
|
int i, num_splits;
|
|
Account *account, **affected_accounts;
|
|
|
|
/* 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 == info->blank_split)
|
|
{
|
|
xaccSRCancelCursorSplitChanges(reg);
|
|
return;
|
|
}
|
|
|
|
/* 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);
|
|
num_splits = xaccTransCountSplits(trans);
|
|
affected_accounts = (Account **) malloc((num_splits + 1) *
|
|
sizeof(Account *));
|
|
assert(affected_accounts != NULL);
|
|
|
|
for (i = 0; i < num_splits; i++)
|
|
{
|
|
s = xaccTransGetSplit(trans, i);
|
|
affected_accounts[i] = xaccSplitGetAccount(s);
|
|
}
|
|
affected_accounts[num_splits] = NULL;
|
|
|
|
account = xaccSplitGetAccount(split);
|
|
|
|
xaccTransBeginEdit(trans, 1);
|
|
xaccAccountBeginEdit(account, 1);
|
|
xaccSplitDestroy(split);
|
|
xaccAccountCommitEdit(account);
|
|
xaccTransCommitEdit(trans);
|
|
|
|
/* Check pending transaction */
|
|
if (trans == info->pending_trans)
|
|
info->pending_trans = NULL;
|
|
|
|
gnc_account_list_ui_refresh(affected_accounts);
|
|
|
|
free(affected_accounts);
|
|
|
|
gnc_refresh_main_window ();
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
void
|
|
xaccSRDeleteCurrentTrans (SplitRegister *reg)
|
|
{
|
|
SRInfo *info = xaccSRGetInfo(reg);
|
|
Split *split, *s;
|
|
Transaction *trans;
|
|
int i, num_splits;
|
|
Account *account, **affected_accounts;
|
|
|
|
/* 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 == info->blank_split)
|
|
{
|
|
trans = xaccSplitGetParent (info->blank_split);
|
|
account = xaccSplitGetAccount(split);
|
|
|
|
/* Make sure we don't commit this later on */
|
|
if (trans == info->pending_trans)
|
|
info->pending_trans = NULL;
|
|
|
|
xaccTransBeginEdit (trans, 1);
|
|
xaccTransDestroy (trans);
|
|
xaccTransCommitEdit (trans);
|
|
|
|
info->blank_split = NULL;
|
|
|
|
xaccAccountDisplayRefresh(account);
|
|
return;
|
|
}
|
|
|
|
/* 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);
|
|
num_splits = xaccTransCountSplits(trans);
|
|
affected_accounts = (Account **) malloc((num_splits + 1) *
|
|
sizeof(Account *));
|
|
assert(affected_accounts != NULL);
|
|
|
|
for (i = 0; i < num_splits; i++)
|
|
{
|
|
s = xaccTransGetSplit(trans, i);
|
|
affected_accounts[i] = xaccSplitGetAccount(s);
|
|
}
|
|
affected_accounts[num_splits] = NULL;
|
|
|
|
xaccTransBeginEdit(trans, 1);
|
|
xaccTransDestroy(trans);
|
|
xaccTransCommitEdit(trans);
|
|
|
|
/* Check pending transaction */
|
|
if (trans == info->pending_trans)
|
|
info->pending_trans = NULL;
|
|
|
|
gnc_account_list_ui_refresh(affected_accounts);
|
|
|
|
free(affected_accounts);
|
|
|
|
gnc_refresh_main_window ();
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
void
|
|
xaccSREmptyCurrentTrans (SplitRegister *reg)
|
|
{
|
|
SRInfo *info = xaccSRGetInfo(reg);
|
|
Split *split, *s;
|
|
Split **splits;
|
|
Transaction *trans;
|
|
int i, num_splits;
|
|
Account *account, **affected_accounts;
|
|
|
|
/* 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 == info->blank_split)
|
|
{
|
|
trans = xaccSplitGetParent (info->blank_split);
|
|
account = xaccSplitGetAccount(split);
|
|
|
|
/* Make sure we don't commit this later on */
|
|
if (trans == info->pending_trans)
|
|
info->pending_trans = NULL;
|
|
|
|
xaccTransBeginEdit (trans, 1);
|
|
xaccTransDestroy (trans);
|
|
xaccTransCommitEdit (trans);
|
|
|
|
info->blank_split = NULL;
|
|
|
|
xaccAccountDisplayRefresh(account);
|
|
return;
|
|
}
|
|
|
|
/* 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);
|
|
num_splits = xaccTransCountSplits(trans);
|
|
affected_accounts = (Account **) malloc((num_splits + 1) *
|
|
sizeof(Account *));
|
|
splits = calloc(num_splits, sizeof(Split *));
|
|
|
|
assert(affected_accounts != NULL);
|
|
assert(splits != NULL);
|
|
|
|
for (i = 0; i < num_splits; i++)
|
|
{
|
|
s = xaccTransGetSplit(trans, i);
|
|
splits[i] = s;
|
|
affected_accounts[i] = xaccSplitGetAccount(s);
|
|
}
|
|
affected_accounts[num_splits] = NULL;
|
|
|
|
xaccTransBeginEdit(trans, 1);
|
|
for (i = 0; i < num_splits; i++)
|
|
if (splits[i] != split)
|
|
xaccSplitDestroy(splits[i]);
|
|
xaccTransCommitEdit(trans);
|
|
|
|
/* Check pending transaction */
|
|
if (trans == info->pending_trans)
|
|
info->pending_trans = NULL;
|
|
|
|
gnc_account_list_ui_refresh(affected_accounts);
|
|
|
|
free(affected_accounts);
|
|
free(splits);
|
|
|
|
gnc_refresh_main_window ();
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
void
|
|
xaccSRCancelCursorSplitChanges (SplitRegister *reg)
|
|
{
|
|
Split * split;
|
|
unsigned int changed;
|
|
int row = reg->table->current_cursor_phys_row;
|
|
int col = reg->table->current_cursor_phys_col;
|
|
|
|
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 */
|
|
split = xaccSRGetCurrentSplit(reg);
|
|
xaccSRLoadRegEntry(reg, split);
|
|
xaccSplitRegisterClearChangeFlag(reg);
|
|
|
|
if (gnc_table_find_valid_cell_horiz(reg->table, &row, &col, GNC_F))
|
|
xaccMoveCursorGUI(reg->table, row, col);
|
|
|
|
xaccRefreshTableGUI(reg->table);
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
void
|
|
xaccSRCancelCursorTransChanges (SplitRegister *reg)
|
|
{
|
|
SRInfo *info = xaccSRGetInfo(reg);
|
|
Transaction *trans;
|
|
Split *split;
|
|
int i, num_splits = 0, more_splits = 0;
|
|
Account **affected_accounts = NULL;
|
|
|
|
/* 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. Geez,
|
|
* there must be some easier way of doing redraw notification. */
|
|
trans = info->pending_trans;
|
|
|
|
if (!xaccTransIsOpen(trans))
|
|
{
|
|
xaccSRCancelCursorSplitChanges(reg);
|
|
return;
|
|
}
|
|
|
|
num_splits = xaccTransCountSplits (trans);
|
|
affected_accounts = (Account **) malloc ((num_splits+1) *
|
|
sizeof (Account *));
|
|
|
|
for (i = 0; i < num_splits; i++)
|
|
{
|
|
split = xaccTransGetSplit (trans, i);
|
|
affected_accounts[i] = xaccSplitGetAccount (split);
|
|
}
|
|
affected_accounts[num_splits] = NULL;
|
|
|
|
xaccTransRollbackEdit (trans);
|
|
|
|
/* and do some more redraw, for the new set of accounts .. */
|
|
more_splits = xaccTransCountSplits (trans);
|
|
affected_accounts = (Account **) realloc (affected_accounts,
|
|
(more_splits+num_splits+1) *
|
|
sizeof (Account *));
|
|
|
|
for (i = 0; i < more_splits; i++)
|
|
{
|
|
split = xaccTransGetSplit (trans, i);
|
|
affected_accounts[i+num_splits] = xaccSplitGetAccount (split);
|
|
}
|
|
affected_accounts[num_splits+more_splits] = NULL;
|
|
|
|
xaccAccListDisplayRefresh (affected_accounts);
|
|
free (affected_accounts);
|
|
|
|
info->pending_trans = NULL;
|
|
|
|
gnc_refresh_main_window ();
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
void
|
|
xaccSRRedrawRegEntry (SplitRegister *reg)
|
|
{
|
|
Transaction *trans;
|
|
|
|
trans = xaccSRGetCurrentTrans (reg);
|
|
|
|
/* refresh the register windows */
|
|
/* This split belongs to a transaction that might be displayed
|
|
* in any number of windows. Changing any one split is likely
|
|
* to affect any account windows associated with the other splits
|
|
* in this transaction. So basically, send redraw events to all
|
|
* of the splits. */
|
|
gnc_transaction_ui_refresh(trans);
|
|
gnc_refresh_main_window();
|
|
}
|
|
|
|
/* ======================================================== */
|
|
/* Copy from the register object to the engine */
|
|
|
|
gncBoolean
|
|
xaccSRSaveRegEntry (SplitRegister *reg, Transaction *new_trans)
|
|
{
|
|
Account *account_refresh[5];
|
|
SRInfo *info = xaccSRGetInfo(reg);
|
|
Split *split;
|
|
Transaction *trans;
|
|
unsigned int changed;
|
|
int style;
|
|
int i;
|
|
|
|
/* 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 GNC_F;
|
|
|
|
/* HACK. This list will be used to refresh changed accounts.
|
|
* The list is 5 long for: 2 accounts for xfrm, 2 accounts
|
|
* for mxfrm, 1 account for NULL. This can go away once we
|
|
* have engine change callbacks. */
|
|
for (i = 0; i < 5; i++)
|
|
account_refresh[i] = NULL;
|
|
|
|
style = (reg->type) & REG_STYLE_MASK;
|
|
|
|
/* get the handle to the current split and transaction */
|
|
split = xaccSRGetCurrentSplit (reg);
|
|
trans = xaccSRGetCurrentTrans (reg);
|
|
if (trans == NULL)
|
|
return GNC_F;
|
|
|
|
ENTER ("xaccSRSaveRegEntry(): save split is %p \n", split);
|
|
|
|
/* determine whether we should commit the pending transaction */
|
|
if (info->pending_trans != trans) {
|
|
if (xaccTransIsOpen (info->pending_trans))
|
|
xaccTransCommitEdit (info->pending_trans);
|
|
xaccTransBeginEdit (trans, 0);
|
|
info->pending_trans = trans;
|
|
}
|
|
|
|
/* If we are committing the blank split, add it to the account now */
|
|
if (xaccTransGetSplit(trans, 0) == info->blank_split)
|
|
xaccAccountInsertSplit (info->default_source_account, info->blank_split);
|
|
|
|
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 = xaccMallocSplit ();
|
|
xaccTransAppendSplit (trans, split);
|
|
|
|
if (force_double_entry_awareness)
|
|
xaccAccountInsertSplit (info->default_source_account, split);
|
|
|
|
assert (reg->table->current_cursor);
|
|
reg->table->current_cursor->user_data = (void *) split;
|
|
}
|
|
|
|
DEBUG ("xaccSRSaveRegEntry(): updating trans addr=%p\n", trans);
|
|
|
|
/* copy the contents from the cursor to the split */
|
|
if (MOD_DATE & changed) {
|
|
/* commit any pending changes */
|
|
xaccCommitDateCell (reg->dateCell);
|
|
DEBUG ("xaccSRSaveRegEntry(): MOD_DATE DMY= %2d/%2d/%4d \n",
|
|
reg->dateCell->date.tm_mday,
|
|
reg->dateCell->date.tm_mon+1,
|
|
reg->dateCell->date.tm_year+1900);
|
|
|
|
xaccTransSetDate (trans, reg->dateCell->date.tm_mday,
|
|
reg->dateCell->date.tm_mon+1,
|
|
reg->dateCell->date.tm_year+1900);
|
|
}
|
|
|
|
if (MOD_NUM & changed) {
|
|
DEBUG ("xaccSRSaveRegEntry(): MOD_NUM: %s\n", reg->numCell->cell.value);
|
|
xaccTransSetNum (trans, reg->numCell->cell.value);
|
|
xaccSetNumCellLastNum(reg->numCell, reg->numCell->cell.value);
|
|
}
|
|
|
|
if (MOD_DESC & changed) {
|
|
DEBUG ("xaccSRSaveRegEntry(): MOD_DESC: %s\n",
|
|
reg->descCell->cell.value);
|
|
xaccTransSetDescription (trans, reg->descCell->cell.value);
|
|
}
|
|
|
|
if (MOD_RECN & changed) {
|
|
DEBUG ("xaccSRSaveRegEntry(): MOD_RECN: %c\n", reg->recnCell->value[0]);
|
|
xaccSplitSetReconcile (split, reg->recnCell->value[0]);
|
|
}
|
|
|
|
if (MOD_ACTN & changed) {
|
|
DEBUG ("xaccSRSaveRegEntry(): MOD_ACTN: %s\n",
|
|
reg->actionCell->cell.value);
|
|
xaccSplitSetAction (split, reg->actionCell->cell.value);
|
|
}
|
|
|
|
if (MOD_MEMO & changed) {
|
|
DEBUG ("xaccSRSaveRegEntry(): MOD_MEMO: %s\n",
|
|
reg->memoCell->cell.value);
|
|
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.
|
|
* XTO is the straight split, too :) Only one of XFRM or XTO
|
|
* should be in a given cursor.
|
|
*/
|
|
if ((MOD_XFRM | MOD_XTO) & changed) {
|
|
Account *old_acc=NULL, *new_acc=NULL;
|
|
char *new_name;
|
|
|
|
if (MOD_XFRM & changed) {
|
|
DEBUG ("xaccSRSaveRegEntry(): MOD_XFRM: %s\n",
|
|
reg->xfrmCell->cell.value);
|
|
}
|
|
else {
|
|
DEBUG ("xaccSRSaveRegEntry(): MOD_XTO: %s\n",
|
|
reg->xtoCell->cell.value);
|
|
}
|
|
|
|
/* do some reparenting. Insertion into new account will automatically
|
|
* delete this split from the old account */
|
|
old_acc = xaccSplitGetAccount (split);
|
|
|
|
if (MOD_XFRM & changed)
|
|
new_name = reg->xfrmCell->cell.value;
|
|
else
|
|
new_name = reg->xtoCell->cell.value;
|
|
|
|
new_acc = xaccGetAccountByFullName (trans, new_name, account_separator);
|
|
|
|
if ((new_acc != NULL) && (old_acc != new_acc))
|
|
{
|
|
const char *currency = NULL;
|
|
const char *security = NULL;
|
|
|
|
currency = xaccAccountGetCurrency(new_acc);
|
|
currency = xaccTransIsCommonCurrency(trans, currency);
|
|
|
|
if (currency == NULL) {
|
|
security = xaccAccountGetSecurity(new_acc);
|
|
security = xaccTransIsCommonCurrency(trans, security);
|
|
}
|
|
|
|
if ((currency != NULL) || (security != NULL)) {
|
|
xaccAccountInsertSplit (new_acc, split);
|
|
|
|
/* HACK. */
|
|
account_refresh[0] = old_acc;
|
|
account_refresh[1] = new_acc;
|
|
}
|
|
else {
|
|
char *message = NULL;
|
|
|
|
asprintf(&message, REG_CURR_MSG, xaccAccountGetName(new_acc));
|
|
assert(message != NULL);
|
|
|
|
gnc_warning_dialog_parented(xaccSRGetParent(reg), message);
|
|
free(message);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (MOD_MXFRM & changed) {
|
|
Split *other_split = NULL;
|
|
|
|
DEBUG ("xaccSRSaveRegEntry(): MOD_MXFRM: %s\n",
|
|
reg->mxfrmCell->cell.value);
|
|
|
|
other_split = xaccGetOtherSplit(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) {
|
|
double amt = xaccSplitGetShareAmount (split);
|
|
double prc = xaccSplitGetSharePrice (split);
|
|
|
|
other_split = xaccMallocSplit ();
|
|
|
|
xaccSplitSetMemo (other_split, xaccSplitGetMemo (split));
|
|
xaccSplitSetAction (other_split, xaccSplitGetAction (split));
|
|
xaccSplitSetSharePriceAndAmount (other_split, prc, -amt);
|
|
|
|
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 = xaccGetAccountByFullName (trans, reg->mxfrmCell->cell.value,
|
|
account_separator);
|
|
|
|
if ((new_acc != NULL) && (old_acc != new_acc))
|
|
{
|
|
const char *currency = NULL;
|
|
const char *security = NULL;
|
|
|
|
currency = xaccAccountGetCurrency(new_acc);
|
|
currency = xaccTransIsCommonCurrency(trans, currency);
|
|
|
|
if (currency == NULL) {
|
|
security = xaccAccountGetSecurity(new_acc);
|
|
security = xaccTransIsCommonCurrency(trans, security);
|
|
}
|
|
|
|
if ((currency != NULL) || (security != NULL)) {
|
|
xaccAccountInsertSplit (new_acc, other_split);
|
|
|
|
/* HACK. */
|
|
if (account_refresh[0] == NULL) {
|
|
account_refresh[0] = old_acc;
|
|
account_refresh[1] = new_acc;
|
|
}
|
|
else {
|
|
account_refresh[2] = old_acc;
|
|
account_refresh[3] = new_acc;
|
|
}
|
|
}
|
|
else {
|
|
char *message = NULL;
|
|
|
|
asprintf(&message, REG_CURR_MSG, xaccAccountGetName(new_acc));
|
|
assert(message != NULL);
|
|
|
|
gnc_warning_dialog_parented(xaccSRGetParent(reg), message);
|
|
free(message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (((MOD_AMNT | MOD_PRIC | MOD_VALU) & changed) &&
|
|
((STOCK_REGISTER == (reg->type & REG_TYPE_MASK)) ||
|
|
(CURRENCY_REGISTER == (reg->type & REG_TYPE_MASK)) ||
|
|
(PORTFOLIO_LEDGER == (reg->type & REG_TYPE_MASK)))) {
|
|
|
|
double value;
|
|
double price;
|
|
double new_amount;
|
|
|
|
if (MOD_VALU & changed)
|
|
value = xaccGetPriceCellValue(reg->valueCell);
|
|
else
|
|
value = xaccSplitGetValue(split);
|
|
|
|
if (MOD_PRIC & changed)
|
|
price = xaccGetPriceCellValue(reg->priceCell);
|
|
else
|
|
price = xaccSplitGetSharePrice(split);
|
|
|
|
if (MOD_AMNT & changed) {
|
|
double credit = xaccGetPriceCellValue(reg->creditCell);
|
|
double debit = xaccGetPriceCellValue(reg->debitCell);
|
|
new_amount = credit - debit;
|
|
}
|
|
else
|
|
new_amount = xaccSplitGetShareAmount(split);
|
|
|
|
if (value != price*new_amount) {
|
|
int i;
|
|
int choice;
|
|
int default_value;
|
|
char *radio_list[4] = { NULL, NULL, NULL, NULL };
|
|
|
|
if (MOD_AMNT & changed)
|
|
asprintf(&radio_list[0], "%s (%s)", AMT_STR, CHANGED_STR);
|
|
else
|
|
radio_list[0] = strdup(AMT_STR);
|
|
|
|
if (MOD_PRIC & changed)
|
|
asprintf(&radio_list[1], "%s (%s)", PRICE_STR, CHANGED_STR);
|
|
else
|
|
radio_list[1] = strdup(PRICE_STR);
|
|
|
|
if (MOD_VALU & changed)
|
|
asprintf(&radio_list[2], "%s (%s)", VALUE_STR, CHANGED_STR);
|
|
else
|
|
radio_list[2] = strdup(VALUE_STR);
|
|
|
|
for (i = 0; i < 3; i++)
|
|
assert(radio_list[i] != NULL);
|
|
|
|
if (!(MOD_AMNT & changed))
|
|
default_value = 0;
|
|
else if (!(MOD_PRIC & changed))
|
|
default_value = 1;
|
|
else if (!(MOD_VALU & changed))
|
|
default_value = 2;
|
|
else
|
|
default_value = 0;
|
|
|
|
choice = gnc_choose_radio_option_dialog_parented(xaccSRGetParent(reg),
|
|
TRANS_RECALC_TITLE,
|
|
TRANS_RECALC_MSG,
|
|
default_value,
|
|
radio_list);
|
|
|
|
for (i = 0; i < 3; i++)
|
|
free(radio_list[i]);
|
|
|
|
switch(choice)
|
|
{
|
|
case 0: /* Modify number of shares */
|
|
if (price == 0)
|
|
break;
|
|
|
|
new_amount = value/price;
|
|
|
|
xaccSetDebCredCellValue (reg->debitCell,
|
|
reg->creditCell, new_amount);
|
|
changed |= MOD_AMNT;
|
|
break;
|
|
case 1: /* Modify the share price */
|
|
if (new_amount == 0)
|
|
break;
|
|
|
|
price = value/new_amount;
|
|
|
|
if (price < 0) {
|
|
price = -price;
|
|
xaccSetPriceCellValue(reg->valueCell, -value);
|
|
changed |= MOD_VALU;
|
|
}
|
|
xaccSetPriceCellValue(reg->priceCell, price);
|
|
changed |= MOD_PRIC;
|
|
break;
|
|
case 2: /* Modify total value */
|
|
value = price*new_amount;
|
|
|
|
xaccSetPriceCellValue(reg->valueCell, value);
|
|
changed |= MOD_VALU;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The AMNT and NAMNT updates only differ by sign. Basically,
|
|
* the split cursors show minus the quants that the single,
|
|
* double and transaction cursors show, and so when updates
|
|
* happen, the extra minus sign must also be handled. */
|
|
if ((MOD_AMNT | MOD_NAMNT) & changed) {
|
|
double new_amount;
|
|
double credit;
|
|
double debit;
|
|
|
|
if (MOD_AMNT & changed) {
|
|
credit = xaccGetPriceCellValue(reg->creditCell);
|
|
debit = xaccGetPriceCellValue(reg->debitCell);
|
|
new_amount = credit - debit;
|
|
} else {
|
|
credit = xaccGetPriceCellValue(reg->ncreditCell);
|
|
debit = xaccGetPriceCellValue(reg->ndebitCell);
|
|
new_amount = debit - credit;
|
|
}
|
|
|
|
DEBUG ("xaccSRSaveRegEntry(): MOD_AMNT: %f\n", new_amount);
|
|
|
|
if ((EQUITY_REGISTER == (reg->type & REG_TYPE_MASK)) ||
|
|
(STOCK_REGISTER == (reg->type & REG_TYPE_MASK)) ||
|
|
(CURRENCY_REGISTER == (reg->type & REG_TYPE_MASK)) ||
|
|
(PORTFOLIO_LEDGER == (reg->type & REG_TYPE_MASK)))
|
|
xaccSplitSetShareAmount (split, new_amount);
|
|
else
|
|
xaccSplitSetValue (split, new_amount);
|
|
}
|
|
|
|
if (MOD_PRIC & changed) {
|
|
double price;
|
|
|
|
price = xaccGetPriceCellValue(reg->priceCell);
|
|
|
|
DEBUG ("xaccSRSaveRegEntry(): MOD_PRIC: %f\n", price);
|
|
|
|
xaccSplitSetSharePrice (split, price);
|
|
}
|
|
|
|
if (MOD_VALU & changed) {
|
|
double value = xaccGetPriceCellValue(reg->valueCell);
|
|
|
|
DEBUG ("xaccSRSaveRegEntry(): MOD_VALU: %f\n", value);
|
|
|
|
xaccSplitSetValue (split, value);
|
|
}
|
|
|
|
PINFO ("xaccSRSaveRegEntry(): finished saving split %s of trans %s \n",
|
|
xaccSplitGetMemo(split), xaccTransGetDescription(trans));
|
|
|
|
/* 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. */
|
|
split = xaccTransGetSplit (trans, 0);
|
|
if (split == info->blank_split)
|
|
{
|
|
info->blank_split = NULL;
|
|
info->last_date_entered = xaccTransGetDate(trans);
|
|
}
|
|
|
|
/* If the new transaction is different from the current,
|
|
* commit the current and set the pending transaction to NULL. */
|
|
if (trans != new_trans) {
|
|
xaccTransCommitEdit (trans);
|
|
info->pending_trans = NULL;
|
|
}
|
|
|
|
xaccSplitRegisterClearChangeFlag(reg);
|
|
|
|
if (account_refresh[0] != NULL) {
|
|
gnc_account_list_ui_refresh(account_refresh);
|
|
gnc_refresh_main_window();
|
|
}
|
|
|
|
return GNC_T;
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
static void
|
|
xaccSRLoadRegEntry (SplitRegister *reg, Split *split)
|
|
{
|
|
SRInfo *info = xaccSRGetInfo(reg);
|
|
int typo = reg->type & REG_TYPE_MASK;
|
|
char buff[2];
|
|
double baln;
|
|
|
|
/* don't even bother doing a load if there is no current cursor */
|
|
if (!(reg->table->current_cursor)) return;
|
|
|
|
ENTER ("SRLoadTransEntry(): s=%p\n", split);
|
|
|
|
if (!split) {
|
|
/* we interpret a NULL split as a blank split */
|
|
xaccSetDateCellValueSecs (reg->dateCell, 0);
|
|
xaccSetNumCellValue (reg->numCell, "");
|
|
xaccSetQuickFillCellValue (reg->descCell, "");
|
|
xaccSetBasicCellValue (reg->recnCell, "");
|
|
xaccSetPriceCellValue (reg->shrsCell, 0.0);
|
|
xaccSetPriceCellValue (reg->balanceCell, 0.0);
|
|
|
|
xaccSetComboCellValue (reg->actionCell, "");
|
|
xaccSetQuickFillCellValue (reg->memoCell, "");
|
|
xaccSetComboCellValue (reg->xfrmCell, "");
|
|
xaccSetComboCellValue (reg->mxfrmCell, "");
|
|
xaccSetComboCellValue (reg->xtoCell, "");
|
|
xaccSetDebCredCellValue (reg->debitCell,
|
|
reg->creditCell, 0.0);
|
|
xaccSetDebCredCellValue (reg->ndebitCell,
|
|
reg->ncreditCell, 0.0);
|
|
xaccSetPriceCellValue (reg->priceCell, 0.0);
|
|
xaccSetPriceCellValue (reg->valueCell, 0.0);
|
|
|
|
} else {
|
|
long long secs;
|
|
double amt;
|
|
char * accname=NULL;
|
|
Transaction *trans = xaccSplitGetParent (split);
|
|
|
|
secs = xaccTransGetDateL (trans);
|
|
xaccSetDateCellValueSecsL (reg->dateCell, secs);
|
|
|
|
xaccSetNumCellValue (reg->numCell, xaccTransGetNum (trans));
|
|
xaccSetQuickFillCellValue (reg->descCell,
|
|
xaccTransGetDescription (trans));
|
|
|
|
buff[0] = xaccSplitGetReconcile (split);
|
|
buff[1] = 0x0;
|
|
xaccSetBasicCellValue (reg->recnCell, buff);
|
|
|
|
/* If the reverse_balance callback is present use that.
|
|
* Otherwise, reverse income and expense by default. */
|
|
baln = xaccSplitGetBalance (split);
|
|
if (reverse_balance != NULL) {
|
|
Account *account;
|
|
|
|
account = xaccSplitGetAccount(split);
|
|
if (account == NULL)
|
|
account = info->default_source_account;
|
|
|
|
if (reverse_balance(account))
|
|
baln = -baln;
|
|
}
|
|
else if ((INCOME_REGISTER == typo) ||
|
|
(EXPENSE_REGISTER == typo)) {
|
|
baln = -baln;
|
|
}
|
|
xaccSetPriceCellValue (reg->balanceCell, baln);
|
|
|
|
xaccSetPriceCellValue (reg->shrsCell, xaccSplitGetShareBalance (split));
|
|
|
|
xaccSetComboCellValue (reg->actionCell, xaccSplitGetAction (split));
|
|
|
|
/* Show the transfer-from account name.
|
|
* What gets displayed depends on the display format.
|
|
* For a multi-line display, show the account for each member split.
|
|
* For a one or two-line display, show the other account, but only
|
|
* if there are exactly two splits.
|
|
*
|
|
* xfrm is the "straight" display, "mxfrm" is the "mirrored" display.
|
|
* xto is the "transfer to" display in single or double mode, or
|
|
* on the transaction cursor in an expanded mode. If we have a
|
|
* default source account, auto-fill the xto field with it.
|
|
*/
|
|
accname = xaccAccountGetFullName (xaccSplitGetAccount (split),
|
|
account_separator);
|
|
xaccSetComboCellValue (reg->xfrmCell, accname);
|
|
if ((safe_strcmp(accname, "") == 0) &&
|
|
(info->default_source_account != NULL)) {
|
|
char * xtoname;
|
|
|
|
xtoname = xaccAccountGetFullName(info->default_source_account,
|
|
account_separator);
|
|
xaccSetComboCellValue (reg->xtoCell, xtoname);
|
|
free(xtoname);
|
|
}
|
|
else
|
|
xaccSetComboCellValue (reg->xtoCell, accname);
|
|
free(accname);
|
|
|
|
{
|
|
Split *s = xaccGetOtherSplit (split);
|
|
gncBoolean need_to_free = GNC_F;
|
|
|
|
if (s) {
|
|
accname = xaccAccountGetFullName (xaccSplitGetAccount (s),
|
|
account_separator);
|
|
need_to_free = GNC_T;
|
|
} else {
|
|
/* determine whether s is null because threre are three
|
|
* or more splits, or whether there is only one ... */
|
|
s = xaccTransGetSplit (xaccSplitGetParent(split), 1);
|
|
if (s) {
|
|
accname = SPLIT_STR; /* three or more .. */
|
|
} else {
|
|
accname = ""; /* none ... */
|
|
}
|
|
}
|
|
xaccSetComboCellValue (reg->mxfrmCell, accname);
|
|
if (need_to_free)
|
|
free(accname);
|
|
}
|
|
|
|
xaccSetQuickFillCellValue (reg->memoCell, xaccSplitGetMemo (split));
|
|
|
|
buff[0] = xaccSplitGetReconcile (split);
|
|
buff[1] = 0x0;
|
|
xaccSetBasicCellValue (reg->recnCell, buff);
|
|
|
|
if ((EQUITY_REGISTER == typo) ||
|
|
(STOCK_REGISTER == typo) ||
|
|
(CURRENCY_REGISTER == typo) ||
|
|
(PORTFOLIO_LEDGER == typo))
|
|
{
|
|
amt = xaccSplitGetShareAmount (split);
|
|
} else {
|
|
amt = xaccSplitGetValue (split);
|
|
}
|
|
|
|
xaccSetDebCredCellValue (reg->debitCell, reg->creditCell, amt);
|
|
xaccSetDebCredCellValue (reg->ndebitCell, reg->ncreditCell, -amt);
|
|
xaccSetPriceCellValue (reg->priceCell, xaccSplitGetSharePrice (split));
|
|
xaccSetPriceCellValue (reg->valueCell, xaccSplitGetValue (split));
|
|
}
|
|
|
|
reg->table->current_cursor->user_data = (void *) split;
|
|
|
|
/* copy cursor contents into the table */
|
|
xaccCommitCursor (reg->table);
|
|
|
|
LEAVE("SRLoadTransEntry()\n");
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
static void
|
|
xaccSRCountRows (SplitRegister *reg, Split **slist,
|
|
Transaction *find_trans, Split *find_split,
|
|
gncBoolean *ext_found_trans,
|
|
gncBoolean *ext_found_split)
|
|
{
|
|
SRInfo *info = xaccSRGetInfo(reg);
|
|
CellBlock *lead_cursor;
|
|
Split *split = NULL;
|
|
Locator *locator;
|
|
Table *table;
|
|
|
|
gncBoolean found_split = GNC_F;
|
|
gncBoolean found_trans = GNC_F;
|
|
gncBoolean multi_line;
|
|
gncBoolean dynamic;
|
|
|
|
int save_cursor_phys_row;
|
|
int save_cursor_virt_row;
|
|
int save_cell_row;
|
|
int num_phys_rows;
|
|
int num_virt_rows;
|
|
int style;
|
|
int i;
|
|
|
|
table = reg->table;
|
|
style = (reg->type) & REG_STYLE_MASK;
|
|
multi_line = (REG_MULTI_LINE == style);
|
|
dynamic = ((REG_SINGLE_DYNAMIC == style) || (REG_DOUBLE_DYNAMIC == style));
|
|
if ((REG_SINGLE_LINE == style) ||
|
|
(REG_SINGLE_DYNAMIC == style)) {
|
|
lead_cursor = reg->single_cursor;
|
|
} else {
|
|
lead_cursor = reg->double_cursor;
|
|
}
|
|
|
|
/* save the current cursor location; if we can't find the
|
|
* requested transaction/split pair, we restore the
|
|
* cursor to this location when we are done. */
|
|
save_cursor_phys_row = reg->cursor_phys_row;
|
|
save_cursor_virt_row = reg->cursor_virt_row;
|
|
|
|
/* save the current cell row offset */
|
|
locator = table->locators[reg->table->current_cursor_phys_row]
|
|
[reg->table->current_cursor_phys_col];
|
|
save_cell_row = locator->phys_row_offset;
|
|
if (save_cell_row < 0)
|
|
save_cell_row = 0;
|
|
|
|
/* num_phys_rows is the number of rows in all the cursors.
|
|
* num_virt_rows is the number of cursors (including the header).
|
|
* Count the number of rows needed.
|
|
* the phys row count will be equal to
|
|
* +1 for the header
|
|
* +n that is, one (transaction) row for each split passed in,
|
|
* +n one blank edit row for each transaction
|
|
* +p where p is the sum total of all the splits in the transaction
|
|
* +2 an editable transaction and split at the end.
|
|
*/
|
|
num_phys_rows = reg->header->numRows;
|
|
num_virt_rows = 1;
|
|
|
|
i=0;
|
|
if (slist) {
|
|
split = slist[0];
|
|
} else {
|
|
split = NULL;
|
|
}
|
|
while (split) {
|
|
/* do not count the blank split */
|
|
if (split != info->blank_split) {
|
|
Transaction *trans;
|
|
gncBoolean do_expand;
|
|
|
|
/* lets determine where to locate the cursor ... */
|
|
if (!found_split) {
|
|
/* Check to see if we find a perfect match */
|
|
if (split == find_split) {
|
|
save_cursor_phys_row = num_phys_rows;
|
|
save_cursor_virt_row = num_virt_rows;
|
|
found_split = GNC_T;
|
|
found_trans = GNC_T;
|
|
}
|
|
/* Otherwise, check for a close match. This could happen
|
|
* if we are collapsing from multi-line to single, e.g. */
|
|
else if (xaccSplitGetParent(split) == find_trans) {
|
|
save_cursor_phys_row = num_phys_rows;
|
|
save_cursor_virt_row = num_virt_rows;
|
|
found_trans = GNC_T;
|
|
}
|
|
}
|
|
|
|
/* if multi-line, then show all splits. If dynamic then
|
|
* show all splits only if this is the hot split. */
|
|
do_expand = multi_line;
|
|
do_expand = do_expand ||
|
|
(dynamic && xaccIsPeerSplit(split, find_split));
|
|
if (dynamic && (NULL == find_split)) {
|
|
trans = xaccSplitGetParent (split);
|
|
do_expand = do_expand || (trans == find_trans);
|
|
}
|
|
|
|
if (do_expand)
|
|
{
|
|
Split * secondary;
|
|
int j = 0;
|
|
|
|
/* add one row for a transaction */
|
|
num_virt_rows ++;
|
|
num_phys_rows += reg->trans_cursor->numRows;
|
|
|
|
/* Add a row for each split, minus one, plus one.
|
|
* Essentially, do the following:
|
|
* j = xaccTransCountSplits (trans);
|
|
* num_virt_rows += j;
|
|
* num_phys_rows += j * reg->split_cursor->numRows;
|
|
* except that we also have to find teh saved cursor row,
|
|
* Thus, we need a real looop over the splits.
|
|
* The do..while will automaticaly put a blank (null)
|
|
* split at the end
|
|
*/
|
|
trans = xaccSplitGetParent (split);
|
|
j = 0;
|
|
do {
|
|
secondary = xaccTransGetSplit (trans, j);
|
|
if (secondary != split) {
|
|
|
|
/* lets determine where to locate the cursor ... */
|
|
if (!found_split) {
|
|
/* Check if we find a perfect match. We have to check
|
|
* the transaction in case the split is NULL (blank). */
|
|
if ((secondary == find_split) &&
|
|
(trans == find_trans)) {
|
|
save_cursor_phys_row = num_phys_rows;
|
|
save_cursor_virt_row = num_virt_rows;
|
|
found_split = GNC_T;
|
|
found_trans = GNC_T;
|
|
}
|
|
}
|
|
|
|
num_virt_rows ++;
|
|
num_phys_rows += reg->split_cursor->numRows;
|
|
}
|
|
j++;
|
|
} while (secondary);
|
|
} else {
|
|
/* Try to get as close as possible to the original cell row. */
|
|
if (found_split && (split == find_split) &&
|
|
(save_cell_row < lead_cursor->numRows))
|
|
save_cursor_phys_row += save_cell_row;
|
|
|
|
/* the simple case ... add one row for a transaction */
|
|
num_virt_rows ++;
|
|
num_phys_rows += lead_cursor->numRows;
|
|
}
|
|
}
|
|
i++;
|
|
split = slist[i];
|
|
}
|
|
|
|
/* ---------------------------------------------------------- */
|
|
/* the "blank split", if it exists, is at the end */
|
|
if (info->blank_split != NULL) {
|
|
/* lets determine where to locate the cursor ... */
|
|
if (!found_split && info->blank_split == find_split) {
|
|
save_cursor_phys_row = num_phys_rows;
|
|
save_cursor_virt_row = num_virt_rows;
|
|
found_split = GNC_T;
|
|
found_trans = GNC_T;
|
|
}
|
|
else if (!found_split &&
|
|
(xaccSplitGetParent(info->blank_split) == find_trans)) {
|
|
save_cursor_phys_row = num_phys_rows;
|
|
save_cursor_virt_row = num_virt_rows;
|
|
found_trans = GNC_T;
|
|
}
|
|
}
|
|
|
|
if (multi_line) {
|
|
if (!found_split && (find_split == NULL) &&
|
|
(xaccSplitGetParent(info->blank_split) == find_trans)) {
|
|
save_cursor_phys_row = num_phys_rows + 1;
|
|
save_cursor_virt_row = num_virt_rows + 1;
|
|
found_split = GNC_T;
|
|
found_trans = GNC_T;
|
|
}
|
|
num_virt_rows += 2;
|
|
num_phys_rows += reg->trans_cursor->numRows;
|
|
num_phys_rows += reg->split_cursor->numRows;
|
|
} else {
|
|
num_virt_rows += 1;
|
|
num_phys_rows += lead_cursor->numRows;
|
|
}
|
|
|
|
/* check to make sure we got a good cursor position */
|
|
if ((num_phys_rows <= save_cursor_phys_row) ||
|
|
(num_virt_rows <= save_cursor_virt_row)) {
|
|
save_cursor_phys_row = num_phys_rows - reg->split_cursor->numRows;
|
|
save_cursor_virt_row = num_virt_rows;
|
|
}
|
|
|
|
if ((save_cursor_phys_row < (reg->header->numRows)) ||
|
|
(save_cursor_virt_row < 1)) {
|
|
save_cursor_phys_row = reg->header->numRows;
|
|
save_cursor_virt_row = 1;
|
|
}
|
|
|
|
/* finally, record the values */
|
|
reg->num_phys_rows = num_phys_rows;
|
|
reg->num_virt_rows = num_virt_rows;
|
|
reg->cursor_phys_row = save_cursor_phys_row;
|
|
reg->cursor_virt_row = save_cursor_virt_row;
|
|
|
|
if (ext_found_split != NULL)
|
|
*ext_found_split = found_split;
|
|
if (ext_found_trans != NULL)
|
|
*ext_found_trans = found_trans;
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
void
|
|
xaccSRLoadRegister (SplitRegister *reg, Split **slist,
|
|
Account *default_source_acc)
|
|
{
|
|
SRInfo *info = xaccSRGetInfo(reg);
|
|
SplitRegisterBuffer *reg_buffer;
|
|
CellBlock *lead_cursor;
|
|
Transaction *find_trans;
|
|
Split *last_split = NULL;
|
|
Split *find_split;
|
|
Split *split;
|
|
Table *table;
|
|
|
|
gncBoolean found_pending = GNC_F;
|
|
gncBoolean found_split = GNC_F;
|
|
gncBoolean found_trans = GNC_F;
|
|
gncBoolean multi_line;
|
|
gncBoolean dynamic;
|
|
|
|
unsigned int changed;
|
|
int save_phys_col;
|
|
int type, style;
|
|
int phys_row;
|
|
int vrow;
|
|
int i;
|
|
|
|
xaccSplitRegisterConfigColors (reg);
|
|
|
|
info->default_source_account = default_source_acc;
|
|
|
|
table = reg->table;
|
|
type = (reg->type) & REG_TYPE_MASK;
|
|
style = (reg->type) & REG_STYLE_MASK;
|
|
multi_line = (REG_MULTI_LINE == style);
|
|
dynamic = ((REG_SINGLE_DYNAMIC == style) || (REG_DOUBLE_DYNAMIC == style));
|
|
if ((REG_SINGLE_LINE == style) ||
|
|
(REG_SINGLE_DYNAMIC == style)) {
|
|
lead_cursor = reg->single_cursor;
|
|
} else {
|
|
lead_cursor = reg->double_cursor;
|
|
}
|
|
|
|
/* figure out where we are going to. */
|
|
if (info->cursor_hint_trans != NULL) {
|
|
find_trans = info->cursor_hint_trans;
|
|
find_split = info->cursor_hint_split;
|
|
save_phys_col = info->cursor_hint_phys_col;
|
|
}
|
|
else {
|
|
find_trans = xaccSRGetCurrentTrans (reg);
|
|
find_split = xaccSRGetCurrentSplit (reg);
|
|
save_phys_col = table->current_cursor_phys_col;
|
|
}
|
|
|
|
/* paranoia */
|
|
if (save_phys_col < 0)
|
|
save_phys_col = 0;
|
|
if (save_phys_col >= table->num_phys_cols)
|
|
save_phys_col = table->num_phys_cols - 1;
|
|
|
|
/* count the number of rows, looking for the place we want to go. */
|
|
xaccSRCountRows (reg, slist,
|
|
find_trans, find_split,
|
|
&found_trans, &found_split);
|
|
|
|
/* If the current cursor has changed, and the 'current split'
|
|
* is still among the living, we save the values for later
|
|
* restoration. */
|
|
changed = xaccSplitRegisterGetChangeFlag(reg);
|
|
if (found_split && 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;
|
|
xaccMoveCursorGUI (table, -1, -1);
|
|
|
|
/* resize the table to the sizes we just counted above */
|
|
/* num_virt_cols is always one. */
|
|
xaccSetTableSize (table, reg->num_phys_rows, reg->num_cols,
|
|
reg->num_virt_rows, 1);
|
|
|
|
/* make sure that the header is loaded */
|
|
xaccSetCursor (table, reg->header, 0, 0, 0, 0);
|
|
|
|
PINFO ("xaccSRLoadRegister(): "
|
|
"load register of %d phys rows ----------- \n", reg->num_phys_rows);
|
|
|
|
/* populate the table */
|
|
i=0;
|
|
vrow = 1; /* header is vrow zero */
|
|
phys_row = reg->header->numRows;
|
|
if (slist) {
|
|
split = slist[0];
|
|
} else {
|
|
split = NULL;
|
|
}
|
|
while (split) {
|
|
|
|
if (info->pending_trans == xaccSplitGetParent (split))
|
|
found_pending = GNC_T;
|
|
|
|
/* do not load the blank split */
|
|
if (split != info->blank_split) {
|
|
Transaction *trans;
|
|
gncBoolean do_expand;
|
|
|
|
PINFO ("xaccSRLoadRegister(): "
|
|
"load trans %d at phys row %d \n", i, phys_row);
|
|
|
|
/* if multi-line, then show all splits. If dynamic then
|
|
* show all splits only if this is the hot split. */
|
|
do_expand = multi_line;
|
|
do_expand = do_expand ||
|
|
(dynamic && xaccIsPeerSplit(split, find_split));
|
|
if (dynamic && (NULL == find_split)) {
|
|
trans = xaccSplitGetParent (split);
|
|
do_expand = do_expand || (trans == find_trans);
|
|
}
|
|
if (dynamic && !found_trans && (vrow == reg->cursor_virt_row)) {
|
|
reg->cursor_phys_row = phys_row;
|
|
do_expand = GNC_T;
|
|
}
|
|
|
|
if (do_expand)
|
|
{
|
|
Split * secondary;
|
|
int j = 0;
|
|
|
|
xaccSetCursor (table, reg->trans_cursor, phys_row, 0, vrow, 0);
|
|
xaccMoveCursor (table, phys_row, 0);
|
|
xaccSRLoadRegEntry (reg, split);
|
|
vrow ++;
|
|
phys_row += reg->trans_cursor->numRows;
|
|
|
|
/* loop over all of the splits in the transaction. The
|
|
* do..while will automatically put a blank (null) split
|
|
* at the end. */
|
|
trans = xaccSplitGetParent (split);
|
|
j = 0;
|
|
do {
|
|
secondary = xaccTransGetSplit (trans, j);
|
|
|
|
if (secondary != split) {
|
|
xaccSetCursor (table, reg->split_cursor,
|
|
phys_row, 0, vrow, 0);
|
|
xaccMoveCursor (table, phys_row, 0);
|
|
xaccSRLoadRegEntry (reg, secondary);
|
|
PINFO ("xaccSRLoadRegister(): "
|
|
"load split %d at phys row %d addr=%p \n",
|
|
j, phys_row, secondary);
|
|
vrow ++;
|
|
phys_row += reg->split_cursor->numRows;
|
|
}
|
|
|
|
j++;
|
|
} while (secondary);
|
|
|
|
} else {
|
|
/* the simple case ... */
|
|
xaccSetCursor (table, lead_cursor, phys_row, 0, vrow, 0);
|
|
xaccMoveCursor (table, phys_row, 0);
|
|
xaccSRLoadRegEntry (reg, split);
|
|
vrow ++;
|
|
phys_row += lead_cursor->numRows;
|
|
}
|
|
}
|
|
else {
|
|
PINFO ("xaccSRLoadRegister(): skip trans %d (blank split) \n", i);
|
|
}
|
|
|
|
last_split = split;
|
|
i++;
|
|
split = slist[i];
|
|
}
|
|
|
|
/* add the "blank split" at the end. We use either the blank
|
|
* split or we create a new one, as needed. */
|
|
if (info->blank_split != NULL) {
|
|
split = info->blank_split;
|
|
if (info->pending_trans == xaccSplitGetParent(split))
|
|
found_pending = GNC_T;
|
|
} else {
|
|
Transaction *trans;
|
|
|
|
trans = xaccMallocTransaction ();
|
|
|
|
xaccTransBeginEdit (trans, 1);
|
|
xaccTransSetDateSecs(trans, info->last_date_entered);
|
|
xaccTransCommitEdit (trans);
|
|
|
|
split = xaccTransGetSplit (trans, 0);
|
|
info->blank_split = split;
|
|
|
|
reg->destroy = LedgerDestroy;
|
|
}
|
|
|
|
/* do the split row of the blank split */
|
|
if (multi_line) {
|
|
Transaction *trans;
|
|
|
|
/* do the transaction row of the blank split */
|
|
xaccSetCursor (table, reg->trans_cursor, phys_row, 0, vrow, 0);
|
|
xaccMoveCursor (table, phys_row, 0);
|
|
xaccSRLoadRegEntry (reg, split);
|
|
vrow ++;
|
|
phys_row += reg->trans_cursor->numRows;
|
|
|
|
trans = xaccSplitGetParent (split);
|
|
split = xaccTransGetSplit (trans, 1);
|
|
xaccSetCursor (table, reg->split_cursor, phys_row, 0, vrow, 0);
|
|
xaccMoveCursor (table, phys_row, 0);
|
|
xaccSRLoadRegEntry (reg, split);
|
|
vrow ++;
|
|
phys_row += reg->split_cursor->numRows;
|
|
} else {
|
|
xaccSetCursor (table, lead_cursor, phys_row, 0, vrow, 0);
|
|
xaccMoveCursor (table, phys_row, 0);
|
|
xaccSRLoadRegEntry (reg, split);
|
|
vrow ++;
|
|
phys_row += lead_cursor->numRows;
|
|
}
|
|
|
|
/* restore the cursor to its rightful position */
|
|
{
|
|
int row = reg->cursor_phys_row;
|
|
int col = save_phys_col;
|
|
|
|
if (gnc_table_find_valid_cell_horiz(table, &row, &col, GNC_F))
|
|
{
|
|
xaccMoveCursorGUI(table, row, col);
|
|
reg->cursor_phys_row = row;
|
|
|
|
if (reg_buffer != NULL)
|
|
{
|
|
xaccSplitRegisterRestoreCursorChanged(reg, reg_buffer);
|
|
xaccCommitCursor (table);
|
|
}
|
|
}
|
|
|
|
if (reg_buffer != NULL)
|
|
{
|
|
xaccDestroySplitRegisterBuffer(reg_buffer);
|
|
reg_buffer = NULL;
|
|
}
|
|
}
|
|
|
|
/* If we didn't find the pending transaction, it was removed
|
|
* from the account. It might not even exist any more.
|
|
* Make sure we don't access it. */
|
|
if (!found_pending)
|
|
info->pending_trans = NULL;
|
|
|
|
/* clear out the hint transaction and split. We want
|
|
* to know if it has been set from the move callback. */
|
|
info->cursor_hint_trans = NULL;
|
|
info->cursor_hint_split = NULL;
|
|
|
|
xaccRefreshTableGUI (table);
|
|
|
|
/* set the completion character for the xfer cells */
|
|
xaccComboCellSetCompleteChar (reg->mxfrmCell, account_separator);
|
|
xaccComboCellSetCompleteChar (reg->xfrmCell, account_separator);
|
|
|
|
/* enable callback for cursor user-driven moves */
|
|
table->move_cursor = LedgerMoveCursor;
|
|
table->traverse = LedgerTraverse;
|
|
table->set_help = LedgerSetHelp;
|
|
table->client_data = (void *) reg;
|
|
}
|
|
|
|
/* ======================================================== */
|
|
/* walk account tree recursively, pulling out all the names */
|
|
|
|
static void
|
|
LoadXferCell (ComboCell *cell,
|
|
AccountGroup *grp,
|
|
const char *base_currency,
|
|
const char *base_security)
|
|
{
|
|
gncBoolean load_everything;
|
|
Account * acc;
|
|
char *name;
|
|
int n;
|
|
|
|
ENTER ("LoadXferCell()\n");
|
|
|
|
if (!grp) return;
|
|
|
|
load_everything = ((base_security == 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.
|
|
*/
|
|
n = 0;
|
|
acc = xaccGroupGetAccount (grp, n);
|
|
while (acc) {
|
|
const char *curr, *secu;
|
|
|
|
curr = xaccAccountGetCurrency (acc);
|
|
secu = xaccAccountGetSecurity (acc);
|
|
if (secu && (0x0 == secu[0])) secu = 0x0;
|
|
|
|
DEBUG ("LoadXferCell(): curr=%s secu=%s acct=%s\n",
|
|
curr, secu, xaccAccountGetName (acc));
|
|
|
|
if ( load_everything ||
|
|
(!safe_strcmp(curr,base_currency)) ||
|
|
(!safe_strcmp(curr,base_security)) ||
|
|
(secu && (!safe_strcmp(secu,base_currency))) ||
|
|
(secu && (!safe_strcmp(secu,base_security))) )
|
|
{
|
|
name = xaccAccountGetFullName (acc, account_separator);
|
|
if (name != NULL)
|
|
{
|
|
xaccAddComboCellMenuItem (cell, name);
|
|
free(name);
|
|
}
|
|
}
|
|
LoadXferCell (cell, xaccAccountGetChildren (acc),
|
|
base_currency, base_security);
|
|
n++;
|
|
acc = xaccGroupGetAccount (grp, n);
|
|
}
|
|
|
|
LEAVE ("LoadXferCell()\n");
|
|
}
|
|
|
|
/* ======================================================== */
|
|
|
|
static void
|
|
xaccLoadXferCell (ComboCell *cell,
|
|
AccountGroup *grp,
|
|
Account *base_account)
|
|
{
|
|
const char *curr, *secu;
|
|
|
|
curr = xaccAccountGetCurrency (base_account);
|
|
secu = xaccAccountGetSecurity (base_account);
|
|
|
|
if ((secu != NULL) && (secu[0] == 0))
|
|
secu = NULL;
|
|
|
|
xaccClearComboCellMenu (cell);
|
|
xaccAddComboCellMenuItem (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);
|
|
xaccLoadXferCell(reg->xtoCell, group, base_account);
|
|
}
|
|
|
|
/* ======================= end of file =================== */
|